LinqのCount()拡張メソッドと向き合う #1
LinqのCount()拡張メソッドと向き合う
Collection Object色々
C#には、Collectionを扱う型がいくつもあります。IEnumerable<T>
や、List<T>
等が代表選手です。
Collectionの要素数を取得する方法として、System.Linq名前空間に用意されている拡張メソッドに、.Count()メソッドがあります。
Microsoft Docsを読んでみる
docs.microsoft.com リンク先のDocsを読んでいると
source の型が
ICollection<T>
を実装している場合は、その実装を使用して要素の数を取得します。 それ以外の場合、このメソッドはカウントを決定します。
という記述があります。
ICollection<T>
を実装している場合、Count()メソッドの中で、Countプロパティを参照し、要素数を返します。
実装を見て、この最適化について考える🤔
githubのCoreFx(.NET CoreのBCLに相当するアレ)のRepoだと以下の部分です。
https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Count.cs#L19github.com
CoreFxなこともあって、C#7の、[ is演算子の拡張] の際に入った型パターンで実装されていますが、実装仕様はC#7以前でも変わりはありません。
型パターンなので、
IEnumerable<string> hoges = new List<string> { "hoge", "fuga" };
で、List<T>
型を、IEnumerable<T>
型で受けたところで、このhogesに対して
if (hoges is ICollection<string>collectionHoges ){ //・・・ }
のisで検査した場合、結果はtrueになります。(List
.Count()って書いても大体の業務コードでパフォーマンスペナルティがあまり問題にならないのは、この最適化を、Count()側で入れてくれているからです。
あくまでパフォーマンスペナルティが「小さくなるよう」最適化がかかっているだけで、isでの検査はノーコストではないので、可能な場合は.Count()メソッドより、Countプロパティを直接参照してもらったほうがペナルティーは少ないです。
そして、ICollection<T>
を実装していない場合に.Count()メソッドを使うと、Collectionのカウントを一生懸命数えることを知っている人は、IEnumerable<T>
の件数を複数回数える場合は.ToList()などで変換します。
その結果、List<T>
のInstance生成コストが余分にかかります。
余談:個人的なオススメ
List<T>
にキャストできるならキャストし、そうでなければ.ToList()でList<T>
のInstance生成して積み替えることです。
IEnumerable<T>
の存在意義
yield returnを含んだイテレーターブロックを使用しているなら、IEnumerable<T>
で返すのが気楽ですし、IEnumerable<T>
&Linqの特徴である遅延評価は、パフォーマンス面でのメリットとして存在しています。
後続処理で、Where等の拡張メソッドを重ねていく場合には、IEnumerable<T>
で返してあげたほうがペナルティーが少なくなることも多いです。
.Countメソッドも、条件を受け取るオーバーロードのほうを利用する場合は、IEnumerable
逆に、お仕事でしばしば散見されるのは、IEnumerable<T>
に対して、Linqで同じ条件で何度もFilterしたりCountしたりするコードです。
「取り合えずIEnumerable<T>
で返すべし」と教えて、遅延評価に関する説明が相手に伝わっていないと大体こうなるので、自分含めて、「なぜ」この型で取り扱うべきなのかというのは、知らない人にわかりやすく説明するべきなのだろうなぁと感じます。
※ 遅延評価で特に怖いのはやっぱり遅延実行のObject周辺。
「Count()って軽い気持ちで書いただけなのに、実はCollection作成するところも毎回走ってるから、受けるパフォーマンスペナルティーが目も当てられない」なんてことになることも。
が、これはCount()だけの話というよりはLinq全般の注意点なので今回は割愛します。
おまけ:IReadOnlyList<T>
とかの、ICollection<T>
は実装していないんだけど、Countプロパティを持っているObjectの処遇
IReadOnlyList<T>
では.Count()メソッドを呼び出しても、型パターン使ってCountプロパティを見てくれる最適化は、行われません。
Github を見る感じ、最適化入れても効果が非常に限定的だから見送られたように読めます。
なのでIReadOnlyList<T>
の類を使う場合には、Count()メソッドではなく、Countプロパティを参照しないとパフォーマンスペナルティを強く受けることになります。
visualstudio.gitignoreと付き合う
.gitignoreを自動生成したいだけの人生だった
VS2017時代はソリューション作成時に「新しい Git リポジトリの作成」というPropがありました。
有効にすると、[ソリューションの作成], [git リポジトリの作成], [Visual Studio用 .gitignoreの生成]まで全部やってくれる仕様でした。(Microsoftさんっぽくて素敵です。)
VS2019ではソリューションの作成時にはこのPropがなくなっています😨
同じチームの方から、 「dotnet new gitignore に、vscodeのgitignoreをMergeして使ったらええんちゃうんちゃうん」という有難いアドバイスを頂き、SDK 3になったから dotnet new gitignore使えるようになったことに気づきました☺
(こういうところ自分は頭固いので、チーム内にサジェストしてくれる方がいらっしゃるのは本当に助かります。尊敬してます。)
まずはdotnet コマンドで.gitignoreを作る
dotnet new gitignore
こんな感じで無事生成されます。
Latest Versionの取得
Latest Versionは github.com からゲットしてね。
ってことだったので、上記githubからvisualstudio.gitignoreの設定を取得しました。
visualstudiocode.ignoreの取得
github.com からvisualstudiocode.gitignoreの設定をパクってきました。
PPAP(死語)する
githubからLatest Versionを取得したvisualstudio.gitignoreと、visualstudiocode.gitignoreをPPAPし、bin/ obj/ が含まれていなかったのでこれもPPAPして使い始めました。 github.com
visualstudio.gitignore、Dotnet Commandの時には居たJetBrains関連のignore項目がgithub側から取得する際に抜け落ちる。
Rider使ってる方、ごめんなさい🙇 以後気を付けます。
おまけ:Visual Studio2019上で.gitignoreを生成するには?
チームエクスプローラーのリポジトリの設定を開いて無視ファイルの追加ボタンを押せばOKです。 ここでもdotnet new gitignore コマンドが発火するのでしょうね。作成される.gitignoreの内容は全く同じものでした。(まあそらそうだよねというお気持ち。)
GUIからのほうが良い!という場合にはこちらから生成してもらうのが良いかな。
Property違いの同じ型を複数DI設定してみる。 その1
Propertyの違う、同一Objectを複数DI設定したいだけの人生だった
IEnumerableでInjectionする
DIでper RequestでInjectionしてほしいクラス
public class PiyoService { public string TagName { get; } public PiyoService(string tagName) { TagName = tagName; } }
DI設定(ServiceDescriptorの設定)
services .AddScoped(serviceProvider => new PiyoService("Hoge")) .AddScoped(serviceProvider => new PiyoService("Fuga"));
Constructor Injection
public ProductController(ProductService productService, IEnumerable<PiyoService> piyoServices) { ProductService = productService; PiyoServices = piyoServices; }
当たり前ですがこのInjectionの方法だと、IEnumerable
尚、プロパティ違いで2つのObjectをServiceDescriptorに設定している状態で、IEnumerable
Factoryクラスを作成してInjectionする
DIでper RequestでInjectionしてほしいクラス
using Dapper; using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Threading.Tasks; namespace MicroORMWrapper { public class SqlManager : IAsyncDisposable { private DbConnection DbConnection { get; set; } public bool IsOpenedConnection => DbConnection.State == ConnectionState.Open; public string ConnectionName { get; } public SqlManager(DbConnection dbConnection, string connectionName) { DbConnection = dbConnection; ConnectionName = connectionName; OpenConnection(); } public void OpenConnection() { if (IsOpenedConnection) { return; } DbConnection.Open(); } public async ValueTask CloseConnectionAsync() { if (!IsOpenedConnection) { return; } DbConnection.CloseAsync(); } public IEnumerable<TResult> Select<TResult>(string query) where TResult : class? { return DbConnection.Query<TResult>(query); } public IEnumerable<TResult> Select<TResult>(string query, object prameters) where TResult : class? { return DbConnection.Query<TResult>(query, prameters); } public IEnumerable<TResult> Select<TResult, TInclude1>(string query, Func<TResult, TInclude1, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? { return DbConnection.Query(query, includeFunc, prameters, null, true, splitOn); } public IEnumerable<TResult> Select<TResult, TInclude1, TInclude2>(string query, Func<TResult, TInclude1, TInclude2, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? where TInclude2 : class? { return DbConnection.Query(query, includeFunc, prameters, null, true, splitOn); } public Task<IEnumerable<TResult>> SelectAsync<TResult>(string query) where TResult : class? { return DbConnection.QueryAsync<TResult>(query); } public Task<IEnumerable<TResult>> SelectAsync<TResult>(string query, object prameters) where TResult : class? { return DbConnection.QueryAsync<TResult>(query, prameters); } public Task<IEnumerable<TResult>> SelectAsync<TResult, TInclude1>(string query, Func<TResult, TInclude1, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? { return DbConnection.QueryAsync(query, includeFunc, prameters, null, true, splitOn); } public Task<IEnumerable<TResult>> SelectAsync<TResult, TInclude1, TInclude2>(string query, Func<TResult, TInclude1, TInclude2, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? where TInclude2 : class? { return DbConnection.QueryAsync(query, includeFunc, prameters, null, true, splitOn); } public List<TResult> SelectAsList<TResult>(string query) where TResult : class? { return DbConnection.Query<TResult>(query).AsList(); } public List<TResult> SelectAsList<TResult>(string query, object prameters) where TResult : class? { return DbConnection.Query<TResult>(query, prameters).AsList(); } public List<TResult> SelectAsList<TResult, TInclude1>(string query, Func<TResult, TInclude1, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? { return DbConnection.Query(query, includeFunc, prameters, null, true, splitOn).AsList(); } public List<TResult> SelectAsList<TResult, TInclude1, TInclude2>(string query, Func<TResult, TInclude1, TInclude2, TResult> includeFunc, object prameters, string splitOn = "Id") where TResult : class? where TInclude1 : class? where TInclude2 : class? { return DbConnection.Query(query, includeFunc, prameters, null, true, splitOn).AsList(); } public BuiltInType GetValue<BuiltInType>(string query) { return DbConnection.ExecuteScalar<BuiltInType>(query); } public BuiltInType GetValue<BuiltInType>(string query, object prameters) { return DbConnection.ExecuteScalar<BuiltInType>(query, prameters); } public Task<BuiltInType> GetValueAsync<BuiltInType>(string query) { return DbConnection.ExecuteScalarAsync<BuiltInType>(query); } public Task<BuiltInType> GetValueAsync<BuiltInType>(string query, object prameters) { return DbConnection.ExecuteScalarAsync<BuiltInType>(query, prameters); } public int Execute(string query) { return DbConnection.Execute(query); } public int Execute(string query, object prameters) { return DbConnection.Execute(query, prameters); } public Task<int> ExecuteAsync(string query) { return DbConnection.ExecuteAsync(query); } public Task<int> ExecuteAsync(string query, object prameters) { return DbConnection.ExecuteAsync(query, prameters); } public async ValueTask DisposeAsync() { await CloseConnectionAsync(); GC.SuppressFinalize(this); } } }
InjectionしてほしいFactoryクラス
using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; namespace MicroORMWrapper.Extensions.DependencyInjection { public interface ISqlManagerFactory { SqlManager GetByName(string name); } public class SqlManagerFactory : ISqlManagerFactory { private readonly IServiceProvider _serviceProvider; public SqlManagerFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; public SqlManager GetByName(string name) { return _serviceProvider.GetServices<SqlManager>().Single(o => o.ConnectionName == name); } } }
DI設定(ServiceDescriptorの設定)
public static IServiceCollection AddSqlManagers(this IServiceCollection serviceDescriptors, IEnumerable<(string connectionName, string connectionString)> connectionSettings) { foreach (var (connectionName, connectionString) in connectionSettings) { serviceDescriptors .AddScoped<DbConnection>(serviceProvider => new SqlConnection(connectionString)) .AddScoped(serviceProvider => new SqlManager(serviceProvider.GetServices<DbConnection>().Single(o => o.ConnectionString == connectionString), connectionName)); } return serviceDescriptors .AddScoped<ISqlManagerFactory, SqlManagerFactory>(); }
Constructor Injection
public ProductRepository(ISqlManagerFactory sqlManagerFactory) { SqlManager = sqlManagerFactory.GetByName(DatabaseNameResource.KashilogDatabase); }
結果から言うと、SqlManagerFactory.GetByNameメソッド内で_serviceProvider.GetServicesを呼び出す時にSqlManagerに関連するObjectのInstanceがすべて生成されるため、IEnumerable
長くなってきたので次回に続きます。
EV2730Qを配備しました&日々のPC作業環境について
自宅にEV2730Qを配備したので作業環境について書いてみる
何年か前に話題になった1:1のスクエアモニター[ EV2730Q ] を配備しました。 www.eizo.co.jp コード書いたり、ドキュメント読んだりするときに便利かなと思っての購入でしたが、今のところ良さそうです。
何万番煎じくらいだと思いますが、自分の作業環境の話が誰かの助けになることもあるかな?ないかな?という思いで今回は作業環境の話を書いていきます。
ワークチェア
エルゴヒューマンプロのKM12を使っています。
長時間のデスクワークの強い味方です。
アーロンチェアや、コンテッサには手が出ないけど、ワークチェアを使いたい人は是非使ってみてください。
肩や腰の疲労が随分軽減されます。
ノートPC
HP Spectre x360 13の旧式(2018年版)のパフォーマンスモデルを使っています。
ノートPCはramがまだ16GBが主流なので、少々しんどい場面もありますが、PCを持ち歩くこともあるので、軽量ノートPCを選択しています。
ram32GB積めるモデルだとDellのxpsが一応あるんですが、30万なのでちょっと手が出ないです。
PC台、モニター台
PC台は特にこだわりはなくて、13インチのノートPCが乗せられればなんでもいいです。
大体、デスクトップ周りは、キーボードやマウス、書籍や、その他サプライ品を置く場所が欲しくなるので、ノートPCやモニター台を使うことで台の下を有効に使えるようになります。
モニター台のほうは、画面の高さを上げることが出来るので、目線が上がって肩こりが軽減される効果もあるので1粒で二度おいしい感じです。
まずはモニター台から導入すると良いです。 item.rakuten.co.jp item.rakuten.co.jp
モニター
EV2730Qは↑でリンク貼りましたが、メインモニターのほうはDELLのU2719Dです。
DPが使えて、デイジーチェーンが可能な機種です。
今となってはモニターはUSB-C(PD)で給電も同時にできるものがベストだったのですが、購入当初は自宅で使う予定がなかったため、少々物足りない性能になっています。
DellのU3219Qにデイジーチェーン機能が付いた製品が出るのを心待ちにしています。
モニターケーブル
U2719DがUSB-Cに対応していないので、USB-C to DPというちょっとアレなケーブルが必要になっているため使っています。
メインモニターを変更するタイミングでUSB-C to USB-Cで統一出来る(はず)
デイジーチェーン用のケーブルはDP1.4ケーブルを使用しています。
これは、単純に、今の規格上、一番上のケーブル刺しとけば、何台かつなぎたくなった時にも頑張れるだろうっていう浅いノリです。
https://www.amazon.co.jp/dp/B08182WTRF/ https://www.amazon.co.jp/dp/B07GJNVK38/
マウス / キーボード
ロジクールのMX ANYWHERE 2SとMX KEYSを使用しています。
マウスの高速スクロールは必需品ですし、キーボードもワイヤレス&パンタグラフが良かったのでこの構成になっています。
USBドングルを1つで済ませたいので、ロジクールで固めたかったっていうのも大きいです。
www.logicool.co.jp www.logicool.co.jp
まとめ
予算は出来る範囲で、使いやすいツールをすごい悩みながら買い足していっている感じなので、課題に感じているメインモニターも実際に変更するのは来年の後半でしょうし、ノートPCの新調に至っては2021年になると思います。
悩みながら道具を選んでいくと愛着も出ますし、気分も上がるのでオススメです。
是非、自分なりの環境を整えてみてください。
グレープシティ ECHO Tokyo 2019に行ってきた
SPA開発を知りたいだけの人生だった
Microsoft 井上章さんのセッションも含め、Angular,Reactの事例紹介を聞きに行ってまいりました。 grapecity-dev-tools.connpass.com
レガシー Web からの脱却 ~ 開発者が次に目指すべき Web アプリの姿とは?
Microsoft井上章さんのセッション。 レガシーの定義は人それぞれだと思います。
Technologyの古さ(言語、Framework等)や、運用の古さ、アーキテクチャ構造の古さ等々があげられますが、今回はモノリスなアプリケーションについてと、運用面の辛みというカットで説明をされていました。
モノリスだと、デプロイ辛い。
モノリスでは、
- デプロイ辛い。
- 言語、Frameworkのバージョンアップ辛い。
- Container化しにくい。
っていう、保守開発&運用に問題を抱えます。
他にもレガシーあるあるだと
- テストコードがない。
- CI / CDがない。
とかですかね。よくあることだし、この辺は後半にレガシー脱却のためのチャレンジとして話題に出ていました。うんうんというお気持ち。
井上章さんのセッションでは、UXとしてのSPAFrameworkにも焦点を当てていましたが、DXとしてのSPAFrameworkにも十分焦点が当たっていたように思います。
SPAは、オーバーエンジニアリングになりがちな領域なので、UXだけじゃなく、DXの面、とりわけ、チームのスケールにも焦点が当たってるのは非常に良いことですね。
Angularは大規模開発向け
Angularは日本国内だと大規模向けというのは改めて聞くとなるほどというお気持ち。
確かに、他の2つの代表的なSPAFrameworkに比べて重厚なので、大規模じゃないとコストが合わない感じはします。
個人的にはReactを選定することが多いものの、SPAFrameworkは本当に適材適所で使い分けという印象。
VSCodeでもVS2019と同じReactプロジェクトテンプレート利用可能
Create React Appを自動で叩いてくれるTemplateが利用可能。 この辺は、WebPack力がなくてもReact First Stepを踏み出せるのは良いことですね。
(本格的に開発するときはどのみちWebPack力必要だし、JavaScript力も必要なんですけど。)
Blazorデモ
WASM版のBlazorについては2020年5月予定。(この辺はMicrosoft Buildの兼ね合いっぽい。)
すべてC#で完結できるというのは、JavaScriptが苦手な子にもこの辺の開発を任せられるので、管理画面等のそれほどパフォーマンスを要求されない場面ではよいのかもなーというお気持ち。
とはいえ、来年の5月まではお預けなので、しばらくはSPAFrameworkはReact採用に舵を切るのが現実的な印象でした。
wasmのほうは、サンドボックスで駆動するのが最大のメリットだからブラウザの外でも使えるようにしようっていうのがMozillaさんとかFastlyさんとかで先日お話がありましたが、Blazorでもエッジコンピューティング等で使えるようになっていくのでしょうか?
C#使ってIoT出来るようになるのか、それとも、エッジコンピューティングはAzureにお任せなのか。
良くも悪くもドラクエの勇者的な言語なので、出来てほしい感はありつつ、ちょっとキメラすぎるかもなあという気持ちもありつつ。(個人的にはC#のなんでもできる感は割と好きなんですよね。)
マイクロサービスアーキテクチャの文脈でのgRPC
この辺は、セッションの後に井上章さんに
マイクロサービスアーキテクチャの文脈だと、まだまだREST優勢なんだけど、.NET Core 3.0でgRPCがサポートされた。 gRPC For Webも存在しているが、Microsoftとして、gRPCはServer間通信を主目的としているのか?それともclientからのRequestもgRPCの利用を想定しているのか?
という、なんともめんどくさいお話をさせて頂いたのですが、嫌な顔ひとつせずお話を聞かせて頂いた井上章さんには感謝しかありません。
お話を聞いていて、改めて理解したのは、
やはりまだまだREST優勢
gRPCはServer間通信での利用が主用途(MSが公開しているSampleCodeもServer間通信がメイン)で、clientのほうは事例ベースでも、ゲーム等のApplication等がほとんどで、純粋なWebアプリでの事例があまり出てきていない
ということでした。
HTTP 2は1回のRequest本数めっちゃ多いと1.1より遅くなるかもっていう話がちょうど職場で出ていたタイミングだったので、この辺、用途によりけりで武器にも弱点にもなるのかな?といったお気持ち。
先日LINE LIVEさんではgRPC For Webを利用しているという事例を聞いてきましたが、LINE LIVEさんだと、トラフィックの物量はめちゃ多いですが、アプリからの大量の並列Requestを一つに束ねるとかではないので、用途としてはマッチしているので問題にはならない可能性があるのかなーとふと思いました。
この辺は予測よりも計測が超大事なので後日改めて計測が必要ですね。
Angular関連
AngularJS、2021年7月EOS
https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c
JBSさんのセッションで超大事な連絡をしていただいたの本当に好感度高いです。
また、AngularがフルスタックのSPAFramework(個人的にはClientSide MVC Frameworkという印象がすごい強い)だよ!っていう話が強調されていたのもよかったです。
重厚さは特徴なので、知らずに使って大変じゃん!ってならないように啓蒙していくのはとても大事。
React関連
プロテック 疋田さんのセッション Reactの選定理由からReactの基礎の説明になだれ込みましたが、このReactの説明が非常にお上手でした。 初学者は是非一度聞いたほうがいいという内容。
そして、Reactは大規模だとやっぱりまだまだRedux使うんだなあという感想。
React Hooksがきて、Redux使わなくてもStateは関数チェインの時に伝播させていくノリだと思ってたんですが、やっぱりHooksでもReducer使うし、チーム開発だとそうじゃなきゃ状態管理に辛みがあるっていうのがすごい伝わってきてよかったです。
全体通して感じたこと
チーム戦だとコード解析必須!静的型付最高!っていうのがすごい伝わってきました。
実際の開発だと、SPAと一緒にTypeScript使いたい欲が強いというのはチーム戦が主流な現代開発ならではだなーといったお気持ち。
UX目的にせよDX目的にせよ、オーバーエンジニアリングにならないよう、適材適所に投入していきたいところです。(小並感)
IAsyncDisposable、パターンマッチだから、別にIAsyncDisposableインターフェースを実装してなくてもいい説、半分正解で半分誤りでした。
以前書いた記事
IAsyncDisposable、パターンマッチだから、別にIAsyncDisposableインターフェースを実装してなくてもいい説、半分正解で半分誤りでした。
結論から言うと、
await using var productService = new ProductService();
みたいに、普通にawait using でInstance生成する場合にはパターンマッチングが機能します。
なので、クラスに、
public async ValueTask DisposeAsync() {
がいれば、await usingって書けます。
逆にASP.NET CoreのDIContainerに登録して使う場合は、パターンマッチングだとだめで、IAsyncDisposableを実装している必要があります。(2019年12月3日時点)
なので、DIから挿してもらう場合はIAsyncDisposableの実装が現状必要です。 ASP.NET CoreはDI管理が前提で作られてるWeb Frameworkのはずなのでこの辺パターンマッチングでどうにかなりませんかね?
インターフェースを付けないほうが処理上のペナルティーもないはずなので何卒対応頂けると嬉しいというお気持ち。
LINE Developer Day 2019 Day2 レポーティング(と言う名の自分的メモ)
front-endと、Product Managementのことを知りたいだけの人生だった
Motivation
参加のMotivationは上の見出しの通りです。 ビジネス上の課題を技術で解決するお仕事をしているので、二日目のproductionというテーマのほうも多くの学びがありました。
Opening
元Livedoorの池邉さんが担当。(途中、各担当者にswitch) 池邉さん、ライブドア事件の時からいらっしゃる方だった気がするので、人材の流動性の高い業界でも長く留まって頑張っていらっしゃったんだなとしみじみ。
LINE Style
会社の価値基準に関するご紹介 linecorp.com
LINEさんの組織規模で、選択と集中という基礎の基礎の基礎の言葉が社是に記載されているのは素晴らしいというお気持ち。 Day1,Day2を通じて感じたのは、飢餓感のある方が割合多めな点でした。 採用のほうも、採用力も高く保つ努力をされているだろうし、プレイヤーのほうも手本になる人が周囲にいて、無力感や不足感が強い環境なんだろうなーと思いながら見ていました。
コードレビューを通してないコードはprodには一行もないというのは、死ぬまでに一度言ってみたい言葉ですね。(目暗でのPull Request承認してる可能性は?とか野暮は言いっこなしです。責任の頭割りも大事です。)
障害についての振り返りの文化についてもお話がありました。 自己組織化ということで、スクラムをアレンジしての組織醸成が行われている印象です。 失敗はプレイヤーを育てるエネルギーだし、見ないふりをしないのはとても素敵な文化。
チャレンジを楽しめ、というのは、コンテキストによってはブラックだと言われたりしますが、個人的には、チャレンジを楽しむ人と仕事したいです。(小声)
LINE-like Product Management of Smart Channel
LINEのプロダクトマネージャー / プロジェクトマネージャー 朝井さんのセッション LINEのトークリストの上のほうに表示されるレコメンドのことを、Smart Channelという機能らしいんですが、そのSmart Channelのプロジェクトのお話でした。
Smart Channel好きな人5%
セッションの会場でSmart Channelが好きな人、大体5%程度でした。 朝井さんの奥さんも激おこだったっていういい話もありました。 なお、このSmart Channelはトークリスト以外にも差し込まれていくらしいです。 このへんは非常にプラガブルに設計されているってことでしょうか。見習いたい。
チームリソースの確保
Smart Channelの開発リソースはプレイヤー100人くらい。 各プロダクトから人を確保して緩くチームを作る。動物園みたいな感じ。 とのことでした。 リソースは奪い合いというのは常日頃から感じるので、緩いジョインでも人を確保できるのは羨ましい。
なんかこの機能嫌だから、早期失敗に持っていこう期
どう考えてもSmart Channelうざいから、アンケートとって、早期にProjectをクローズさせよう作戦を行った結果、4割くらいポジティブな意見が返ってきて、根拠としては微妙な感じになってアレ?ってなった話でした。 この辺、すごい大事で、Product作ってるときって、「え?これ本当に作るの?正気なの?」みたいなときはやっぱりあります。 基本的に、「これをやると何がおいしいのか、何がうれしいのか」を第一に考えている人だから選択するアプローチだなーというお気持ち。
蛇足ですが、個人的には、「言われたから」ダメそうでも作る人はあまり好ましいとは思っていません。 大事なのは、それを作ることでどんな価値を提供できるか。 提供する価値が投下するリソースに対して見合っていないのであれば、うるさいと思われても具申する。 そして、それがProductとオーナーのためになると信じています。
叩き潰せないから価値を上げるために頑張る期
プロジェクトを叩き潰せないなら、今度はどうプロジェクトを成功に導くか、マインドチェンジしたよ! これは、流石の一言。
コンテンツ毎のレコメンドの受け入れられ方の情報を地道に収集する。 ポジが4割しかないなら、これをどうやって最大化するかの分析を正道でやろうという姿勢、すごく偉い。 ユーザーへの限定公開からのKPI観測、SNS観測、ユーザーリサーチ、どれも地道だけどすごく大事。
ProductManagementで大事にしていること
常にデータ駆動 機能に価値があるかを聴講せずに、まずGAして、フィードバックを基に改善する。 オープンコミュニケーション(なにがおいしいかを全員で考え、アクションする。)
LINE Styleでも書いてあることですが、事業に誠実に奉仕するっていうことなので、当たり前で且つ、大変なことなので尊いなというお気持ち。
What’s New in LINE Front-end Framework
LINE フロントエンドエンジニア / ProductManagerの清水さんのセッション LINEはProductManagerがフロントのプレイヤーを兼ねるんだなあというのが開始10秒の感想。
LIFFに関する、説明が色々あったんですが、一番大事なのは、LIFFが、LINE内のWebApplicationを作成するためのfront-endFrameworkという点です。 LINE内で利用するWebApplicationのため、自分の主戦場のC#であればBlazor wasm版とか使っての開発が可能です。 github.com github.com リッチメニューをxamlで作成するVisualStudio拡張も作成している方もいらっしゃるようで、C#の器用さを感じる瞬間でした。
2Daysを通しての感想
LINEさんの取り組みに関しては、AIカンパニーということと、AIを利用するために、そもそもの品質レベルが高くないとAIでの押し込みも有効に作用しないため、各プレイヤーが積極的に、事業にコミットするために技術を磨いているという印象でした。 飛び道具でどうにかするというよりは、コツコツと正道で道を切り開いているのが意外だったのと、その正道でどうにかできるプレイヤーの技量とマインドは素晴らしい。
LINEを通じたSolutionも、JAVAも主戦場ではない自分でも学ぶことが大量にあったので、技術スタックがマッチする方だと、更に刺激になるイベントだったことでしょう。
採用力の強化も含めてのイベントだとは思いますが、そういう話を置いておいたとしても、事例の公開は、感謝しかありません。 このへんは、.NET / C#は露出が極めて残念なのでどうにかしていかなきゃいけない部分です。(これは、我々プレイヤーが貢献できることなので、我々も少しずつでもどうにかしていかなきゃいけないという意味です。)
自分が技術に寄っている人間なので、技術側の話に寄りがちですが、どちらかというと、組織で権限を持っている人も参加すべきイベントに感じました。 このレベルのイベントだと運営も超大変だと思います。LINEさん、イベント運営関係者の方々には感謝申し上げます。