C# の LINQ を日本語変数で書いたら、クエリが仕様書みたいになった話
業務計算ツールで日本語変数名を使い始めてから、LINQ のラムダ式にも日本語を使うようになった。最初は「どうせ慣れてる英語のほうが書きやすい」と思っていたんだけど、やってみたら予想外に読みやすかった。仕様書に書いてあった条件式がそのままコードになる感覚があって、これはなかなかいい発見だった。
英語変数名の LINQ と比べてみる
まず典型的な業務クエリを英語で書いた場合。
// 英語変数名
var result = employees
.Where(e => e.Department == "営業" && e.IsActive)
.OrderBy(e => e.HireDate)
.Select(e => new { e.Name, e.HireDate });
e が何を指しているか、最初の一行を読んで確認しないとわからない。複数の LINQ チェーンが絡むと、どの e がどのコレクションの要素なのかを都度追いかける必要がある。
同じクエリを日本語で書くとこうなる。
// 日本語変数名
var 結果 = 社員一覧
.Where(社員 => 社員.部署 == "営業" && 社員.在籍中)
.OrderBy(社員 => 社員.入社日)
.Select(社員 => new { 社員.氏名, 社員.入社日 });
ラムダ変数が 社員 になっただけで、文章として読める密度が上がる。「社員一覧から、営業部署に在籍中の社員を、入社日順に並べて、氏名と入社日を取り出す」というビジネスルールが、そのままコードになっている感じだ。
クエリ構文(query syntax)だとさらに読みやすい
LINQ には where・select・orderby を使う SQL ライクなクエリ構文もある。日本語変数名と組み合わせると、仕様書に近い表現になる。
var 結果 = from 社員 in 社員一覧
where 社員.部署 == "営業" && 社員.在籍中
orderby 社員.入社日
select new { 社員.氏名, 社員.入社日 };
「社員 in 社員一覧」「社員.部署 == 営業」という部分は、ほぼ日本語の箇条書きと同じ構造になっている。仕様書に「社員一覧の中で営業部に在籍中のものを…」と書いてあれば、それを見ながらそのまま書き写せる感覚があった。
クエリ構文とメソッド構文、どちらで使うか
クエリ構文は可読性が高いけど、Aggregate・Skip・Take・Distinct のような操作はクエリ構文に対応するキーワードがなく、メソッド構文でしか書けない。実際の業務コードでは「シンプルなフィルタリング・ソートはクエリ構文、ページングや重複除去はメソッド構文」という使い分けが多くなった。なお GroupJoin はクエリ構文の join ... into として書けるので、グループ結合もクエリ構文で表現できる。
集計・グループ化でも効果が出る
単純なフィルタリングだけじゃなく、集計処理でも日本語ラムダ変数は読みやすさを上げてくれる。
// 部署ごとの残業時間合計
var 部署別残業集計 = 勤怠一覧
.GroupBy(勤怠 => 勤怠.部署)
.Select(グループ => new
{
部署名 = グループ.Key,
残業時間合計 = グループ.Sum(勤怠 => 勤怠.残業時間),
対象人数 = グループ.Count()
})
.OrderByDescending(集計 => 集計.残業時間合計);
グループ・集計・勤怠 と変数名が変わるたびに「何を操作しているか」が明示される。英語だと g や x のような1文字変数になりがちで、複数のラムダが重なると何が何だかわからなくなる。
Join でも意味が通りやすくなる
複数テーブルの結合になると、英語変数名では特に読みにくくなる。
// 英語
var result = orders
.Join(customers,
o => o.CustomerId,
c => c.Id,
(o, c) => new { o.OrderDate, c.Name, o.Amount });
// 日本語
var 注文明細 = 注文一覧
.Join(顧客一覧,
注文 => 注文.顧客ID,
顧客 => 顧客.ID,
(注文, 顧客) => new { 注文.注文日, 顧客.氏名, 注文.金額 });
o と c が何を表すか覚えておかなくていい。注文 と 顧客 という名前が都度文脈を説明してくれる。
気になった点と対処法
IME の干渉
LINQ の式を書いているとき、=> の直後に日本語入力が始まってしまうことがある。Visual Studio の IME 連携で、ラムダの矢印を打った後に日本語入力に切り替わってしまうケースだ。自分の場合は「=> を打ったら手動で IME をオンにする」という習慣にしたら気にならなくなった。
チェーンが長くなると行が長くなる
英語の1文字変数(e、x)は短いので、横に長い式でも収まりやすい。日本語変数名は文字幅があるため、チェーンが長くなると1行が長くなりがちだ。.Select() の前で改行を入れるなど、縦に展開する書き方を意識するようになった。
// 改行を入れて整理する
var 対象社員 = 社員一覧
.Where(社員 => 社員.在籍中)
.Where(社員 => 社員.入社日 < 基準日)
.Select(社員 => 社員.氏名)
.ToList();
汎用メソッドへの切り出し時は英語に戻す
ドメイン固有の LINQ クエリは日本語変数で書くけど、複数の場所から呼ばれる汎用的なフィルタ関数を切り出すときは英語に戻している。日本語変数名は「この文脈でこの操作をする」が明確な場所にほど効果が出やすい。
まとめ
- LINQ のラムダ変数を日本語にすると「何のコレクションの要素を操作しているか」がコードから直接読める
- クエリ構文(
from 社員 in 社員一覧 where ...)は仕様書の条件式に近い構造になり、照合コストが下がる - Join・GroupBy など複数コレクションが絡む処理で特に可読性向上の恩恵が出やすい
- IME の干渉と行長には慣れが必要。縦展開する書き方を意識するといい
日本語変数名を業務計算ロジックに使い始めた経緯についてはこちらの記事、コメントと変数名の非対称についての考察はこちらの記事もあわせてどうぞ。