これまでの読書感想はこちら:
第4章 美しさ
本章の内容
この章の核心は、フォーマットの一貫性は「絶対に正しい書き方」を追うことより大事、という点だ。見た目を整えるのは見栄えのためではなく、読む負担を下げて、ぱっと見ただけでコードの構造が分かるようにするためである。
1. 似たコードは見た目の輪郭をそろえる
複数のコードブロックが同じ種類の処理をしているなら、形と改行の仕方を統一する。まず悪い例。
void AddUser(int id, string name);
void RemoveUser(int id);
void UpdateUserInfo(int id, string name, int age, string address);
3つの関数はどれもユーザー操作だが、関数名の長さがばらばらで、引数の開始位置もそろっていない。さっと流し読みしても規則性をつかみにくい。そろえるとこうなる。
void AddUser (int id, string name);
void RemoveUser (int id);
void UpdateUserInfo(int id, string name, int age, string address);
人間の脳は、文字を読むより形を認識するほうがずっと速い。輪郭がそろっているだけで、流し読みの速度は大きく上がる。
2. 列をそろえる効果と代償
同種の情報を縦にそろえると、目に手すりをつけたような効果がある。
string name = "田中太郎";
int age = 28;
double salary = 15000.0;
bool isActive = true;
変数宣言をまとめて眺めるとき、スペルミスや型の違いにすぐ気づける。一方で、変数名を1つ変えるだけで他の行の空白も直す必要が出てくるし、コミットの diff も汚れる。本の結論は、使ってもいいが無理にそろえず、メンテナンスの負担が大きいなら諦める、というものだ。
3. 意味のある一貫した順序にする
変数、フィールド、引数の順序は動作に影響しないが、読みやすさには影響する。本の悪い例では、ヘッダファイルのフィールドが name、id、address の順なのに、実装ファイルの Save 関数では address、id、name の順に並んでいる。重要度順、アルファベット順、機能グループ順、フォームの順など、ルールを1つ決めて最後まで守る。読み手は一度順序を覚えれば、他の場所でも予測できる。
4. 空行で論理的な段落に分ける
文章に段落があるのと同じで、コードもかたまりで詰め込むと読むのがつらい。手順ごとに空行で区切り、概要コメントを1行添えると、関数が何段階で動くかひと目で分かる。
def generate_report(user_id):
# データを読み込む
user = db.get_user(user_id)
orders = db.get_orders(user_id)
# 合計を計算する
total = sum(o.amount for o in orders)
# レポートを作って送信する
report = format_report(user, orders, total)
send_email(user.email, report)
# ログを記録する
log_audit(user_id, "report_sent")
5. 一貫性はスタイルの正しさに勝る
波括弧を改行するか、インデントにスペースとタブのどちらを使うか、関数名をパスカルケースにするかスネークケースにするか。この手の問題に唯一の正解はない。プロジェクトの規約が間違っていると思っても、既存の規約に従うこと。最悪なのはどのスタイルを選んだかではなく、同じコードの中に複数のスタイルが混ざっていることだ。
感想
読みながら最初に思ったのは、この章の価値は大規模言語モデルの進歩で下がっていくのではないか、ということだ。すでにAIに古いコードをリファクタリングさせる会社が出てきている。古いコードにはどうしてもスパゲッティコード(いわゆる糞コード)が混ざるが、AIがそれを理解して、より整った、より速い書き方に置き換える作業は、これからどんどん簡単になるだろう。この流れなら、人間が1行ずつ手で見た目を整える重要性は下がると思う。
ただ、だから見た目が大事ではなくなる、という話にはならない。AIが書いたコードも、最後は人がレビューする。整ったコードのほうが、レビューする人の負担は明らかに小さい。そこで私の考えは、人が手で美しいコードを書く必要性は下がる一方で、AIの出力にフォーマットやコーディング規約の制約をかける必要性は変わらない、というものだ。その規約は、あとからコードを読む人を楽にするためにある。
第5章 コメントすべきことを知る
本章の内容
この章の核心は、コメントの目的は作者の意図を読み手に伝えることであり、コードを読めば分かる内容を繰り返すことではない、という点だ。
1. コードの言い換えにすぎないコメントは書かない
コードの文法を日本語に訳しただけのコメントは、画面の場所を無駄にしているだけだ。たとえば class Account に「Accountクラスの定義」、コンストラクタに「コンストラクタ」と書いても、誰が見ても分かる。
境界線上のケースもある。新しい情報はないように見えて、頭を使わずに済むコメントだ。
# 2つ目の「*」以降をすべて削除する
name = '*'.join(line.split('*')[:2])
コードを読めば分かるが、このコメントは高レベルの意図をそのまま言葉にしてくれる。文字列スライスを自分で解読しなくて済むので、価値のあるコメントといえる。
2. ひどいコードはコメントではなくリファクタリングで直す
関数はコードのあちこちから呼ばれ、呼び出し側からは関数の上にあるコメントが見えない。たとえば DeleteRegistry という関数に「ハンドルを解放するだけで、実際のレジストリは削除しない」というコメントがついているとする。Delete という単語は破壊的な意味を持つので、名前だけ見るとデータを消すと誤解してしまい、その曖昧さをコメントで補うことになる。ReleaseRegistryHandle に改名すれば、意味が名前の中に入る。本の言葉を借りると、優れたコードは、ひどいコードにどれだけ良いコメントを足したものよりも上だ。
3. 考えたことを記録する、監督の解説のように
コードを書いているときに頭にあった重要な情報を書き残す。なぜそうしたか、何にハマったか、実測値はどうだったか。本の例は「このデータセットでは、二分木のほうがハッシュテーブルより40%速い。ハッシュの計算コストがノード比較より高いため」というものだ。コードを見ただけでは絶対に分からない情報で、あとから保守する人が、速いと思い込んだ書き方に「最適化」してしまう無駄を防げる。
4. コードの欠陥を隠さず書く
コードを改良していく途中で、不完全な部分が出てくるのは避けられない。恥ずかしがらずに書いておけばいい。本では、よく使うマーカーがいくつか紹介されている。
| マーカー | 意味 |
|---|---|
TODO: |
あとで対応する、改善する |
FIXME: |
既知の欠陥がある |
HACK: |
一時しのぎの方法で、美しくない |
XXX: |
警告、ここに重大な問題がある |
「このクラスは肥大化して混乱している。ResourceNode サブクラスを切り出してリファクタリングするとよい」のように、現状と改善の方向をそのまま書いてもいい。問題を示して考え方も添えておけば、あとから来た人がひどいコードを前に手を出せなくなることがない。
5. 定数には値の背景を書く
定数を定義するとき大事なのは、その定数が何という名前かではなく、なぜその値にしたかだ。たとえば NUM_THREADS = 8 の後ろに「CPUコア数の2倍に設定。これ以上増やすとコンテキストスイッチでかえって遅くなる」と添えれば、なぜ1でも50でもなく8なのかが分かる。SECONDS_PER_DAY = 86400 のように自明な定数に、蛇足のコメントを足す必要はない。
6. 読み手の立場で疑問を先回りする
プロジェクトに詳しくない新人が「あれ、なぜこう書いているの?」と思う場所に、コメントをつける。1つ目は、あまり知られていないテクニックだ。
// vector の内部メモリを解放する(STL の swap テクニック)
// clear() は要素を消すだけで、メモリは返さない
vector<float>().swap(data);
多くのC++開発者はこの細かい仕様を知らないので、コメントがないと「なぜ clear() を使わないのか」と疑問を持たれる。2つ目は、隠れたリスクだ。たとえば SendEmail という関数は、名前を見ても、外部のメールサービスを呼び出して最大1分間ブロックすることが分からない。コメントがないと、HTTPリクエストの中で直接呼んでサービスを固めてしまう人が出るかもしれない。
7. 全体像のコメントと要約コメント
コメントは1行のコードの上だけでなく、もっと大きな構造の説明にも使える。ファイルやクラスのレベルのコメントでは、全体の役割を書く。たとえば「このファイルはファイルシステム関連の補助インターフェースを提供し、権限やパスの結合などを扱う」と書いておけば、新人はファイルを開いた瞬間に、何のファイルか分かる。コードブロックの要約コメントは、長い処理の目的をひとことでまとめる。
# 顧客本人が購入した商品を絞り込む
for customer_id in all_customers:
for sale in all_sales[customer_id].sales:
if sale.recipient == customer_id:
...
ループを1行ずつ読まなくても、このロジックが何のためにあるかを先に知れるので、細部で迷子にならずに済む。
8. コメントを書く心理的ハードルを越える
最初から完璧なコメントを書こうとして、結局書かない人は多い。本が勧めるのは3ステップだ。まず、頭にある言葉をそのまま書く。口語でも雑でもいい。次に、読み返して曖昧なところや説明が足りないところを探す。最後に、より正確な表現に磨く。コードを書きながらコメントも書き、最後にまとめて補おうとしないこと。早く書くほどコストは低く、情報も正確になる。
感想
この章は、AI時代にはむしろ重要度が上がると思っている。
AIがコードファイルを読むとき、コメントも一緒に読み込む。第4章で書いた流れが続いて、AIがコードを直接理解する力がどんどん上がるなら、「この部分は何を意味するか」「この関数は何を表すか」のようなコメントは、AIにとって新しい情報がほぼない。モデルが賢くなるほど、そういうコメントの価値は下がる。
では何を書くか。本の答えは、考えた過程を書くことだ。なぜそう選んだか、何にハマったか、実測値はどうだったか、そしてコードにすでにある既知の欠陥。たとえば「このデータセットでは二分木のほうがハッシュテーブルより40%速い」という情報は、コードを眺めても絶対に出てこない。読み手の立場で考えると、なぜそう書いたかという理由こそ、残すべきものだ。
これはAIにも当てはまる。AIがこのようなコメントを読めば、この書き方は意図的なものだと分かるので、ついでにハッシュテーブルへ「最適化」してしまうことはない。その部分に対して、より的を射たリファクタリングの提案をしてくれたり、こちらの元の考えをもう一歩先まで進めてくれたりもするだろう。
2つの章を並べて見ると、コードの「どうやるか」は、人にもAIにもどんどん読みやすくなる。読み取れないのは「なぜそうしたか」だ。これが、AI時代にこの2章の重みが違ってくる理由だと思う。