業務計算ツールの変数名をぜんぶ日本語にしたら、思いのほかよかった話 — C# WinForms 給与計算アプリ
C# で社内向けの業務計算ツール(給与・手当計算のWinFormsアプリ)を作るとき、変数名・メソッド名をすべて日本語の漢字にしてみた。「さすがにそれはないでしょ」と思われそうだけど、やってみたら想像以上に機能した。この記事では、なぜそうしたか、実際どうだったか、C#で日本語識別子を使ううえで気をつけた点をまとめる。
なぜ英語でもローマ字でもなく漢字にしたか
業務系のドメインは独特で、用語を英語に直訳するのが思いのほか難しい。「残業手当」を英語にすると allowanceForOvertimeWork になるんだけど、これ、長すぎて読む気が失せる。略して overtimeAllowance にすると今度は「割増賃金」なのか「手当」なのかが曖昧になってくる。
ローマ字も試してみたことがある。zangyoTeate とか shouhizeiGaku みたいな感じ。書き慣れてくるとそれなりに読めるようにはなるんだけど、初見の人には厳しいし、仕様書と見比べるときに一段階変換が入る感覚がずっとある。「仕様書の『消費税額』= コードの『shouhizeiGaku』」という対応関係を頭の中で維持し続ける必要がある。
そこで、思い切って漢字にしてみた。仕様書に「残業手当 = 基本給 × 残業係数」と書いてあったら、コードにもそのまま書く、というやつだ。
C# での日本語識別子 — 仕様と実態
言語仕様としては完全に合法
C# の識別子はUnicode文字をサポートしているので、漢字・ひらがな・カタカナをそのまま変数名・メソッド名・クラス名に使える。Visual Studio 2022(バージョン17.x)での動作確認済みで、インテリセンスも普通に効く。補完候補に漢字が出てきて最初はちょっと笑ってしまったけど、慣れると快適だった。
// 給与計算クラスの例
public class 給与計算
{
public decimal 基本給 { get; set; }
public decimal 残業係数 { get; set; } = 1.25m;
public int 残業時間 { get; set; }
public decimal 通勤手当 { get; set; }
public decimal 残業手当を計算する()
{
return 基本給 / 160m * 残業時間 * 残業係数;
}
public decimal 支給合計を計算する()
{
return 基本給 + 残業手当を計算する() + 通勤手当;
}
}
// 呼び出し側
var 今月分 = new 給与計算
{
基本給 = 300000m,
残業時間 = 20,
通勤手当 = 15000m
};
decimal 支給額 = 今月分.支給合計を計算する();
Console.WriteLine($"支給合計: {支給額:C}");
コードとしてはこんな感じ。仕様書の計算式とほぼ1対1に対応している。
Visual Studio での挙動
インテリセンス・リファクタリング(Rename)・Find All References はすべて正常に動作した。日本語識別子だからといって特別扱いが必要な場面は、少なくとも今回の規模では出てこなかった。
ただ、ReSharperを入れている場合は「命名規則違反」の警告が出ることがある。デフォルトではpublicプロパティにはPascalCase英語名を期待しているので、それに反するということになってしまう。プロジェクト設定で日本語識別子のパターンを追加するか、警告を無効化するかで対応した。Riderでも同様の警告が出ると思う。
実際に使ってみた結果
よかったこと
一番効いたのは仕様書との照合コストがほぼゼロになったことだ。Excelで書かれた計算仕様と画面を並べて確認するとき、式が1:1で対応しているのでミスを発見しやすかった。「このコードはこっちの仕様の何行目に対応してるんだっけ」という頭の中の翻訳作業がなくなる。
もうひとつ想定外によかったのが、業務担当者(非エンジニア)が直接コードを確認できたこと。「この計算式、合ってますか?」とコードを見せたら、向こうも「あ、これ仕様書と同じだ」と言ってくれた。型とかクラスの概念は理解できなくても、計算ロジックの正しさは確認できる。
引き継ぎのコストも下がった感覚はある。後から入ったメンバーに「この変数何ですか?」と聞かれる回数が明らかに減った。英語命名のコードだと「overtimeAllowance は残業手当のことで……」という説明が毎回必要になるけど、漢字で書いてあればそのまま読める。
気になった点
正直に言うと、コードの見た目がカッコ悪い。英数字と漢字が混在するので、慣れた目には違和感がある。これは純粋に美観の話で、動作や保守性には関係ないんだけど、「こういうコードを書いてる人です」と他のエンジニアに見せるのが少し躊躇われる気持ちは正直あった。
あと、git の diff が少し見づらい。日本語テキストの変更がパッと視認しにくいのは確かで、コードレビューを GitHub 上でやる場合には若干の慣れが必要かもしれない。
向いている用途・向いていない用途
| 向いている | 向いていない |
|---|---|
| 業務計算ロジック(給与・税務・在庫など) | 汎用ライブラリ・OSS公開するコード |
| 仕様書と1対1対応するコード | 国際チームでの開発 |
| 非エンジニアとのレビューが発生する開発 | フレームワークの命名規則に従う必要がある箇所 |
| 社内ツール・内製システム | 英語圏のエコシステムに乗り入れるコード |
ポイントは「誰が読むコードか」と「どのドメインか」の組み合わせだと思う。社内の業務担当者と一緒に仕様を確認しながら作る内製ツールなら、日本語識別子は十分に合理的な選択肢になる。逆に、NuGetで公開するライブラリや英語圏の開発者が触るプロジェクトには全く向かない。
C# 特有の注意点まとめ
namespaceとclassは英語のままのほうが無難
プロパティやメソッドは漢字にしても問題なかったけど、`namespace` や外部公開する型名は英語にしておくほうがいい。NuGetパッケージとして配布しないにしても、設定ファイルやリフレクションで型名を文字列として扱う場面では日本語の型名が混乱のもとになることがある。今回は計算ロジック側のクラス内部(プロパティ・メソッド・ローカル変数)だけ漢字にして、`namespace` や外側のクラス名は英語にした。
テストコードも日本語にすると読みやすい
xUnitでテストを書くとき、テストメソッド名も日本語にしてみた。残業時間が0のとき残業手当は0になること() みたいな感じ。テスト結果の出力にそのまま日本語のメソッド名が出てくるので、何が通って何が落ちているかが一目でわかってよかった。
[Fact]
public void 残業時間が0のとき残業手当は0になること()
{
var 計算 = new 給与計算 { 基本給 = 300000m, 残業時間 = 0 };
Assert.Equal(0m, 計算.残業手当を計算する());
}
[Fact]
public void 基本給と残業手当と通勤手当の合算が支給合計になること()
{
var 計算 = new 給与計算
{
基本給 = 300000m,
残業時間 = 20,
通勤手当 = 15000m
};
// 残業手当 = 300000 / 160 * 20 * 1.25 = 46875
Assert.Equal(361875m, 計算.支給合計を計算する());
}
テストが失敗したときに「残業時間が0のとき残業手当は0になること — Failed」と表示されるので、何が壊れているかの把握がすごく楽だった。英語でテスト名を書くより、業務担当者に見せたときの反応もよかった。
まとめ
- C# は Unicode 識別子をサポートしており、漢字・ひらがな・カタカナの変数名・メソッド名が問題なく使える
- 業務計算ロジックでは、仕様書との1対1対応・非エンジニアとのレビュー・引き継ぎコスト削減という実益があった
- 気になる点は「カッコ悪い」という美観の問題だけで、動作・保守性には影響しなかった
- ReSharper の命名規則警告には注意。namespace や公開型名は英語のままにするのが無難
- テストメソッド名も日本語にすると、テスト結果が読みやすくなる副次効果があった
英語命名にこだわることで生まれる「翻訳コスト」と「読み違えリスク」を考えると、対象ドメインと読み手次第では日本語識別子は十分に機能する。「カッコいいコード」より「チームが読めるコード」を優先した結果として、今のところ後悔はない。