Copyright (C) 2025 Takym.
(この記事は osdev-jp の Discord で筆者が投稿した内容を大幅加筆編集して公開したものです。)
概要
「明らかに美しいコード」は人によって判断が別れるでしょうし、「誰が見ても美しいコード」というものは恐らく存在しないでしょう。しかし、「明らかに汚いコード」「誰が見ても汚いコード」と評価され得るコードは実際に存在しています。筆者はこの対称性の崩れを不思議に思いました。直感的に考えると、「美しいコード」の定義次第では「汚いコード」の範囲も変動するのではないでしょうか。例えば、「美しいコード」の価値判断が別れる理由を単純に「好みの違い」と仮定してみましょう。この場合においては、「汚いコード」の価値判断も好き嫌いで別れる気がします。しかし、実際には「美しいコード」を作成するより、「汚いコード」を作成する方が容易であるのです。経験的にもコード整形に時間を取られる事はよくあります。コードの美しさの維持は本質的に大変なのでしょう。この記事では、コード整形の難易度の対称性の崩れを考察し、また、美しく整形する為の考え方も提示します。コーディング規制や命名規則の策定の参考にして頂ければ幸いです。尚、サンプルコードには C# を用いておりますが、他の言語にも応用できると思います。
ここでは、「美しいコード」を次の要件・性質を全て満たすコードと定義してみましょう:
- 一貫性:汎ゆる場所で同じ規則を適用しているかどうか。
- 視認性:目視で瞬時に内容を判別または把握できるかどうか。
- 解読性:データ構造やアルゴリズム、その他の意図等を説明無しに理解できるかどうか。
- 合理性:その様に記述する必然的な理由を論理的に説明できるかどうか。
- 文化性:見慣れているかどうか。慣例に則っているかどうか。
また、「汚いコード」は何れも満たさない場合のコードを指すものとします。この定義では、「美しくも汚くもないコード」というものも存在しています。「好みの違い」と比べると曖昧性は低減されていますが、それでも厳密とは言えません。しかし、この記事には特定のコーディング規制を支持する目的はありませんので、これ以上の厳密性は追求しません。
普通は「悪くしよう」とするよりも「良くしよう」としているものと仮定します。また、「好みの違い」との仮定は、より厳密な「価値観や感覚、需要等によって、美しいコードの要件の閾値は揺らぐ事があり、重視する性質も異なる」に置き換えます。仮定の証明は省きます。この前提の下で、「誰が見ても汚いコード」の作成を試みてみます。
一貫性
先ずは、一貫性のあるコードと一貫性のないコードを読み比べてみましょう。
「一貫性」の定義を再掲します:
汎ゆる場所で同じ規則を適用しているかどうか。
例えば、次のコードでは、if-else 文における波括弧の位置には一貫性があると言えるでしょうか。
if (条件文1) {
/* 処理1...... */
} else if (条件文2) {
/* 処理2...... */
} else if (条件文3) {
/* 処理3...... */
} else if (条件文4) {
/* 処理4...... */
} else {
/* 処理5...... */
}
開始波括弧 { は必ず条件文の後ろに置かれています。条件に一致した場合の処理は、字下げされています。終了波括弧 } は最後の処理の次の行に字上げされて配置されています。また、else 文が設置される場合は、必ず終了波括弧 } の後ろに配置されています。「汎ゆる場所で同じ規則を適用している」と言えるでしょう。この書き方に関しても好き嫌いが別れるのでしょうが、ここでは問題にしません。
一貫性を崩すには、ブロック毎に異なる規則を適用すれば良いのです。
if (条件文1) {
/* 処理1...... */
} else if (条件文2) {
/* 処理2...... */
}
else if (条件文3)
{
/* 処理3...... */
} else if (条件文4)
{
/* 処理4...... */
}
else {
/* 処理5...... */
}
違和感のあるコードになったと思います。
次の様に更に一貫性を失したコードを作る事もできるでしょう:
if (条件文1) {
/* 処理1...... */
} else if (条件文2) {
/* 処理2...... */
}
else if (条件文3)
{
/* 処理3...... */
} else
if (条件文4) {
/* 処理4...... */
} else
{
/* 処理5...... */ }
波括弧の位置だけではなく字下げに関しても一貫性を崩してみました。
美しいコードを書く為には、改行位置や字下げなどの細かい部分でも一貫性を維持する必要があります。
視認性
「視認性」の定義を再掲します:
目視で瞬時に内容を判別または把握できるかどうか。
次の変数定義を見比べてみてください。
// コード A
int num = 123;
string message = "Hello, World!!";
var someObject = new SomeObject();
bool cond = someObject.CheckMessage(message);
decimal numIdx = someObject.GetIndex(num);
// コード B
int num = 123;
string message = "Hello, World!!";
var someObject = new SomeObject();
bool cond = someObject.CheckMessage(message);
decimal numIdx = someObject.GetIndex (num );
コード A は一貫性はあるかもしれません。しかし、コード B と比べると視認性は低いと言えます。コード B では、変数型、変数名、初期値が同じ列に並んでおり、瞬時に判別する事ができます。関数名と引数についても同様の工夫が施されています。また、コード B にも一貫性があると言えるでしょう。
解読性
「解読性」の定義を再掲します:
データ構造やアルゴリズム、その他の意図等を説明無しに理解できるかどうか。
視認性と解読性は似ている概念ではあります。視認性が時間効率を重視しているのに対し、解読性は背景情報を知らない人でも容易に理解できる事を重視します。
次のコードは、円の面積公式の様に見えますが、本当に面積を計算しているのかは不明です。
double value = 3.14 * 25 * 25;
円の面積を求める意図を強調するには次の様に書くべきでしょう:
double radius = 25;
double circleArea = Math.PI * Math.Pow(radius, 2);
行数は増えてしまいましたが、コードを解読する上ではこの様に記述する方が有用でしょう。尚、美しいコードの要件とは無関係ですが、演算結果の精度を高めるには 3.14 ではなく Math.PI を用いるべきです。
もしかしたら、円の面積を求める意図ではなかったのかもしれません:
double rate = 3.14;
double cost = 25;
double amount = 25;
double price = rate * cost * amount;
コードの意図が分からなければ、誤解したまま別の処理を意図するコードに修正されてしまう懸念があります。解読性は、コードを美しく整形するだけではなく、プログラムの保守にとっても重要な概念です。
合理性
「合理性」の定義を再掲します:
その様に記述する必然的な理由を論理的に説明できるかどうか。
視認性で用いた例をここでも用います。
// コード A
int num = 123;
string message = "Hello, World!!";
var someObject = new SomeObject();
bool cond = someObject.CheckMessage(message);
decimal numIdx = someObject.GetIndex(num);
// コード B
int num = 123;
string message = "Hello, World!!";
var someObject = new SomeObject();
bool cond = someObject.CheckMessage(message);
decimal numIdx = someObject.GetIndex (num );
先程はコード A にもコード B にも一貫性があると説明しました。コード A には、打鍵数を少なくできるという利点があります。つまり、プログラマの労力を削減できるという事です。コード B は前述の通り視認性が高いのです。どちらも合理的であると言えるでしょう。
しかし、次の場合はどうでしょうか?
// コード C
int num = 123;
string message="Hello, World!!" ;
var someObject =new SomeObject( );
bool cond= someObject . CheckMessage ( message ) ;
decimal numIdx =
someObject.GetIndex (num);
少なくとも筆者には一つも利点を挙げる事はできませんでした。一貫性も視認性も無いという欠点のみが残っています。
見た目が綺麗でも合理的とは言えない例もあります。
if (条件文1) {
/* 処理1...... */
} else if (条件文2) {
/* 処理2...... */
} else if (条件文3) {
/* 処理3...... */
} else if (条件文4) {
/* 処理4...... */
} else {
/* 処理5...... */
}
芸術的には興味深いコードでも、残念ながら実務ではあまり役に立ちません。字下げの深さが原因で、各処理の比較が困難となり、視認性が悪化していると言えます。そもそも、閲覧環境に依っては表示崩れを引き起こしているかもしれません。一方で、もしかしたら、オープンソースである場合や一般大衆にコードを書いて見せたりする場合は、この表現を選ぶ合理性が認められる事も有り得るでしょう。
次のコードは一見すると一貫性がありません。
public static void Method()
{
if (条件文) {
// 任意の処理
}
}
筆者はこの書き方を好むのですが、これには理由があります。クラスや関数とコードブロックは別の概念である訳ですが、波括弧の改行位置を別ける事で、概念の相違を強調しています。
蛇足ですが、ラムダ式はコードブロック内における「式」として認識し、局所関数(ローカル関数)は「関数定義」として認識しており、次の様に記述しています:
public static void Method()
{
Action lambda = () => {
// 任意の処理
}
static void LocalFunc()
{
// 任意の処理
}
}
// 実は、委譲型(デリゲート)を返す場合において、一貫性があります。
// 通常の関数との違いは「=> () =>」が付くかどうかです。
public static Action GetAction()
=> () => {
// 任意の処理
};
しかし、異なる考え方によっては「合理的ではない」と捉えられているかもしれません。
文化性
「文化性」の定義を再掲します:
見慣れているかどうか。慣例に則っているかどうか。
クラスや関数とコードブロック内の文において、波括弧の改行位置の取り扱いを変えている事を説明しました。
これには次の様な異なる考え方もります:
// 波括弧は全て独立した行に置く。
public static void Method()
{
if (条件文)
{
// 任意の処理
}
}
// 開始波括弧は独立した行に置かない。
public static void Method() {
if (条件文) {
// 任意の処理
}
}
// クラス・関数とコードブロック内の文の取り扱いが筆者とは逆。
public static void Method() {
if (条件文)
{
// 任意の処理
}
}
これらは、一貫性、視認性、解読性、合理性においては対等な関係にあります。誰しも自分自身が慣れているコードに最も強い親しみを感じるでしょう。「美しいコード」の判断基準を別ける最も大きな原因は文化性である事は間違いないでしょう。しかし、「悪くしよう」とするよりも「良くしよう」とする事が多いという仮定により、何時何処の状況に限らず文化性の無い書き方も存在しているでしょう。
最高に汚いコード
前述までの汚いコードの例を組み合わせてみます。
public
static
int GetValue (
) {int num = 123;
string message="Hello, World!!" ;
var someObject =new SomeObject( );
bool cond= someObject . CheckMessage ( message ) ;
decimal numIdx =
someObject.GetIndex (num);
if (cond) {
return numIdx
+ 123 * 456;
} else if (numIdx < 00100) {
num*= someObject.
GetInt(); return num-5;
}
else if (numIdx >
111222)
{
return 15;
} else
if ( someObject . GetCurrentState ( ) ) {
return 16;
} else
{
return 17; }
}
このコードが汚い事には異論は無いでしょう。
しかし、次のコードが美しいかは、要件の解釈違い等で見解の別れる所でしょう。
public static int GetValue()
{
int num = 123;
int num2 = 456;
int offset = 15;
int magicA = 100;
int magicB = 111222;
string message = "Hello, World!!";
var someObject = new SomeObject();
bool cond = someObject.CheckMessage(message);
decimal numIdx = someObject.GetIndex (num );
if (cond) {
return numIdx + (num * num2);
} else if (numIdx < magicA) {
num *= someObject.GetInt();
return num - (offset / 3);
} else if (numIdx > magicB) {
return offset;
} else if (someObject.GetCurrentState()) {
return offset + 1;
} else {
return offset + 2;
}
}
改善の余地は幾らでも見付ける事ができます。例えば、筆者の感覚では、現状の変数名は分かり難いですし、一部の定数は関数の外に分離させるべきでしょう(サンプルコードなので深い意図がある訳ではありませんが)。
最後に
「美しいコード」の基準としては再考の余地があるのかもしれません。一方で、「誰が見ても汚いコード」が存在する事は証明できたと思います。
