3 ポイント 投稿者 GN⁺ 2024-09-12 | 1件のコメント | WhatsAppで共有
  • コメントでは、識別子より表現力の高い人間の言語を使えるため、コードにはない選択肢や放棄した代替案を残すのに適している
  • 「comment the why, not the what」は、情報をできるだけ識別子に込めようとするアプローチだが、最近では理由までも長い関数名やテスト名に移そうとする流れがある
  • Logic for Programmers の epub ビルドでは、16個の数学記号を文字列ごとに順次置換する遅い実装を使っていたが、現在の数学文字列は25個しかないため十分に速い
  • このようなコメントは、後で数学文字列が数百個に増えてビルドのボトルネックになったときに修正すべき箇所を示し、遅いコードが意識的なトレードオフだったことを残す
  • 関数名やテストは、コードが実際に行うことに付けられるものなので、選ばなかった代替案や行わないことを含む否定的情報を自己文書化するのは難しい

コメントのほうがコードより含めやすい情報

  • コードは構造化された機械語であり、コメントは表現力のある人間の言語で書かれる
  • 「comment the why, not the what」は、できるだけ多くの情報を識別子に入れよ、という意味に解釈できる
  • 識別子はコードの中に含まれる限定的な人間の言語に近く、すべての「what」を含められるわけではないが、多くの場合は可能である
  • 最近では、「why」もコメントではなく、長い関数名やテストケース名に入れられるという見方が増えている
  • 自己文書化されたコードベースは、たいてい識別子の追加によって文書化を増やす
    • 例外的に、コメントをロギングに変えることでコードをより自己文書化するという事例もある

「Why Not」コメントが扱うもの

  • コードで表現しにくい情報は否定的情報である
  • 否定的情報は、システムに何がないのか、なぜ特定の代替案が選ばれなかったのかに注意を向けさせる
  • 「why not」は、コードがしていることを説明するというより、コードが選ばなかったこととその理由を残す

epub の数学記号置換の事例

  • Logic for Programmers の epub ビルドでは、技術的な理由により \forall のような数学表記が のような記号に変換されなかった
  • これを解決するため、数学文字列内のトークンを対応する Unicode 記号に直接置き換えるスクリプトを書いた
  • 最も簡単な実装は、必要な16個の数学記号それぞれについて string = string.replace(old, new) を呼び出す方式である
  • この方式は各文字列を16回走査するため非効率だが、1回の走査ですべての16個の置換を処理する方式はより複雑である
  • 残したコメントの要旨は次のとおり
    • 各文字列を16回走査する
    • 本には現在、数学文字列が25個しかなく、そのほとんどは5文字未満である
    • そのため、依然として十分に速い
  • このコメントは「なぜ遅いコードを使うのか」だけでなく、「なぜ速いコードを使わないのか」まで説明している

後の読者のための標識

  • 遅いコードが今すぐ問題を起こしていなくても、後で問題になる可能性はある
  • Logic for Programmers の将来のバージョンで数学文字列が数百個に増えれば、そのビルド段階がビルド全体のボトルネックになる可能性がある
  • 今のうちに標識を残しておけば、後でどの部分を直すべきかすぐに分かる
  • コードがその後も問題なく動き続けたとしても、コメントは書き手がトレードオフを認識していたという事実を保持する
  • 2年後に epub_math_fixer.py を再び開いたとき、遅いコードが未熟さ、時間不足、ミスのせいだったのかを調べ直す必要が減る
  • 否定的コメントは、遅い実装を認識しており、代替案を検討し、最適化しないと決めたという情報を残す

関数名やテストで代替しにくい理由

  • RunFewerTimesSlowerAndSimplerAlgorithmAfterConsideringTradeOffs のような関数名は長すぎ、実際のトレードオフを十分には説明できない
  • 後でコードを最適化すると、その名前を複数箇所で変更しなければならない可能性がある
  • より大きな問題は、このような名前では関数が実際に何をするのかが分からず、自己文書化をむしろ弱めてしまう点である
  • 関数名や変数名のような識別子は、1つの節の情報しか含められない
  • 1つの識別子に「関数がすること」と「関数が受け入れるトレードオフ」を同時に含めるのは難しい

テストでも残しにくい否定的情報

  • 本から数学ブロックを grep して数が80個を超えたら失敗するテストを作ることはできる
  • しかし、そのようなテストは EpubMathFixer 自体を直接テストしない
  • 関数内部には、そのテストが引っかけられるポイントがない
  • 自己文書化は、書かれたコードに付随してコードが行うことを説明する
  • 否定的情報はコードが行わないことを扱うため、自己文書化の方式とは根本的に合わない

より広い問い

  • 「why not」コメントは反事実(counterfactual)の一例と見ることができる
  • 人間のコミュニケーションにおける抽象的要素が、一般に自己文書化できるのかという問いが残る
  • 比喩、不確実性、倫理的主張のような情報も自己文書化できるのかは、依然として疑問として残っている

1件のコメント

 
GN⁺ 2024-09-12
Hacker News の意見
  • 数年前に Twitter で見たようなジョークを思い出した。「ジュニアエンジニアはコードが何をしているかを説明するコメントを書き、中級エンジニアはコードがなぜそうしているかを説明するコメントを書き、シニアエンジニアはなぜコードが別のやり方で書かれていないのかを説明するコメントを書く」といった内容だった

    • 本当にその通り。コードそのものより5倍長いコメントを付けなければならなかったこともあるが、それはコードやドメイン、大きな動的スコープを扱う際の注意点を知らない人が、善意でリファクタリングしてしまうのを防ぐためだった
      逆に、関数名と変数名だけで意味が明確にできるなら、かなり大きな関数でもコメントなしで書いたことがある
    • 3つともやる。要約コメントは非常に有用で、実証研究でも検証されているのに、多くの人が Clean Code 的な考え方に深くはまり込みすぎていて、救いようがないように見える
    • ジュニアプログラマーはたいてい何も文書化しないか、すべてを文書化する。経験を積むと、特異なものだけを文書化すればよいと分かるようになり、さらに経験を積むと特異なものがだんだん減って、コメントも少なくなる
      だからコメントが少ない側が勝つことにはなるが、コメントのないコードベースを見たとき、それが美しく磨き上げられた傑作なのか、初心者たちが作った不安定なコードなのかは、実際に調べてみないと区別できない
    • 明らかに馬鹿げて見える動作を、なぜ再現し続けなければならないのかを説明するコメントもある。何か別のものがその馬鹿げた動作に依存しているからだ
    • Cレベルのエンジニアなら、「このコードをリファクタリングするのに X 時間が無駄になりました。先人と同じ轍を踏むことにしたなら、このカウンターを増やしてください」のようなコメントを残す
      完全な応急処置に見えても、時にはそれが実際に得られる最善であることもある
  • 1年後にまたコードを見る自分にとって有用そうなら、すべてコメントとして残す。たいていはなぜなぜ駄目なのかで、コードが複雑なときは流れを見やすくするために短く「何を」も書く
    有用でないのは義務的なコメントだ。公開 API は十分に文書化すべきだが、組織によっては private 関数まで含めてすべての関数にコメントを強制し、目的があまりに明白で関数名を言い直すだけのものになることもある。これは時間の無駄であるだけでなく、コメントに鈍感にさせ、無視する習慣を教えてしまう
    ツールが追加する無駄なコメントも嫌いだ。すべてのループに //for//try を付けるようなものは特に好きではない

    • 不思議なことに、多くのシンタックスハイライトの配色はコメントを薄くしてコントラストを下げている。おそらく義務的なコメントや生成されたコメントの情報量が少ないからだろう
      義務的なコメントや生成されたコメントをなくし、ダークテーマではコメントを明るいネオン色に変えて目立たせるほうがよい。コメントが付いているなら、それは重要だという意味であるべきだ
    • 以前は「すべてのコメントはコードスメルだ」という立場で、今もかなりの部分でそう思っているが、数百人が活発に開発・保守している非常に大きなコードベースで働く中で、少し考えが和らいだ
      タイトなスケジュールのビジネス環境で古い大規模システムを維持するには、妙なことをしなければならない場合があり、そのときはなぜそうしているのかを説明する必要がある
      コメントに反対する大きな理由は、コメントもコードの一部になり、保守が必要になるからだ。しかし多くの人は詰まったときにだけコメントを読み、エディタもコメントを灰色に薄くして心理的に見えにくくする。そのためコメントは簡単に古びる
      「未来の自分」のための文脈が、多くの開発者が触る共有コードベースでも有用なのか、それとも少人数だけがコードを触る場合にうまく機能するやり方なのか気になる
    • 完全に同意。コメントが多すぎると、クラスや関数の中に何があるのか見づらくなる。本来1画面に収まるクラスや関数がコメントのせいで1画面を超えると、可読性のコストが発生する
    • 昨日、個人プロジェクトで「これは本当に有用なのか?」というコメントが付いた行を見つけ、簡単に削除できそうに見えた。新しくきれいなクラスを使ってみようとしたが、特定の古いやり方が実際には必要だった
      そこで既存のコメントに「=> yes!」を付け足し、過去の自分がその疑問を文書化しておいてくれたことに感謝した。仕事では特にバグ修正のとき、明確でない変更の上にチケット番号とともに1〜2行のコメントをよく残す
  • コードに残したコメントの中で一番好きな形式は、このテンプレートだ。「DEAR MAINTAINER: このコードは[理由]のためにこうなっています。これを『直そう』としてひどい間違いだったと気づいたら、次の人への警告として total_hours_wasted_here = n カウンターを増やしてください」
    原作者ではないが、1〜2回ありがたく使ったことがあり、カウンターだけを増やす1行のコミットを見ると面白かった

    • 数年前にこれがあればよかった。要件を満たすには再帰をかなり自由に使う必要がある SQL 生成コードを作ったことがあり、相互再帰が多くてコードは汚かったが、必要悪だった
      よりシニアなエンジニアがコードベースを引き継ぎ、すべてをループ方式に「直し」、メールで再帰がなぜ悪いのか説教しようとしたが、彼のコードは実際の要件をすべて満たせず、結局私が再帰でやっていたことを実質的に作り直した
      後で彼はいくつかの発言について謝ったが、冒頭にこういうコメントを付けておけば、一連のことを避けられたかもしれない
  • タイトルが曖昧だという点には同意するし、だからこそ記事を読んだ。個人的には全体としてコメントを少なめにするほうを好むが、記事に出てくる説明コメントには確かに価値がある。なぜそうしたのか、なぜ別の方法ではないのかを説明せよ、というよいリマインダーだ
    とくに5年、10年、15年後にも保守しなければならない自分のコードに当てはまる。最近、同僚の新しいコードをレビューしていて「なぜこうしたんだろう?」と思ったが、10行上に8年前の自分が同じようにしていた理由があった。その同僚は保守の重要なルール、つまり既存コードのように見せることに従っていたのだ

    • 古いコードベースを保守するとき、既存コードのように見せることはあまりにも過小評価されている。後から来る人たちの精神衛生のために、どうか既存コードのように見せてほしい
  • より広く従うべき原則の特殊なケースにすぎない:コードを読むときに驚くようなことにはコメントを付ける、というもの
    コードを書くとき、頭の中で常に「あとでこのコードを理解できるだろうか?」と問い、そのたび本能的に「できる」と答える人は傲慢で、しばしば間違っている。答えが「確信はない」なら、次の問いは自然に「なぜ?」になり、その答えこそコメントに書くべき内容である
    ときには答えが「読む人が、なぜ別の方法で書かなかったのか不思議に思うかもしれないから」であり、この記事が扱う特殊なケースである。しかし、ときには「どう動くのか、なぜ正しいのかが明確でないから」であり、その場合は別の種類のコメントが必要になる

    • 最初にあるやり方でコードを書いてみたが動かず、2つ目のアプローチが必要になったなら、その場所にはコメントが必要だという強いサインである。作成中に驚いたのなら、1年後にはその驚きを忘れ、コードを読むときにまた驚く可能性が高い
    • 似た原則として、「これが期待どおりに動かなかったら、どこを見るべきか?」を基準にする。答えがドキュメントにない、または wiki → パッケージ/モジュール → ファイル → クラス → 関数/メソッドという経路で10行以上離れているなら、インラインコメントを付けるかドキュメントを更新する
      主に文字列を切り出したり、特殊なデータ構造を中間段階として探索したりするときにこうしたことが起きる
  • 識別子だけでもかなり遠くまで行けるが、最後まで行けるわけではない。個人的には、公開メソッドや変数、フィールド、パラメータには jsdoc/xmldoc のようなドキュメントコメントを求めるやり方が好きだ
    メソッド名をうまく付けることも重要だが、何をするのかを短く書いてみるとさらに明確になり、とくに明白な欠陥が見えてくる。最初の一文を書いた瞬間に、より良い名前が思い浮かぶことも多く、説明に「そして」が入り始めたら、そのメソッドが多くのことをしすぎていて、より論理的に分割できるというサインである
    プロパティは明確すぎてドキュメントは不要だと思いがちだが、/** The API key */ string ApiKey; のようなものでは抜けている情報が多すぎる。このキーがどこから来るのか、内部専用なのか外部システムとやり取りするものなのか、必須なのか、null や空値があり得るのか、最大長があるのか、不正な値だと何が起きるのか、さらに読むべきコードやドキュメントがあるのかが分からない
    元の作者なら1〜2分で書ける情報だが、新しく来た人が修正したり使ったり、数年後にバグ修正のために投入されたりしたとき、それを突き止めるのに数時間かかることがある

  • コードレビューで過度に細かく突っ込むレビュアーが何と言いそうか予想できるときは、「Y のため X はしなかった」のようなコメントをよく書く。面倒な往復の議論を減らすのが目的である

    • 経験上、往復の議論量はそのままだが、「コメントに書いてあるように」と何度か書くことはできる
    • そうした内容は PR に先回りして追加するが、コード内に残す価値が大きいかどうかはよく分からない
  • 「各文字列を16回走査するが、本に出てくる数式文字列は今のところ25個だけで、ほとんどが5文字未満なので十分速い」といったコメントには、別のバリエーションもある
    当初の設計上の制約より入力がはるかに大きくなったときに発動するデバッグログを入れることだ。将来の開発者にほぼ同じメッセージを伝えられるが、より早く発見できるため、診断とデバッグの時間をさらに減らせる

    • 多くの場合、良いアイデアである。今は動くが非常に遅いループを使うことがあり、タイマーを置いて「X秒以上かかったら警告ログを残す」ことができそうだ
      理想的なロギングとオブザーバビリティのシステムなら、アプリのすべてのコンポーネントの時間を計測し、デバッグ情報を頻繁に残すだろうが、そんな完璧なシステムを実際に使っている人がどれだけいるだろうか。あとで性能が悪化し得る部分や、最適化する時間がなかった部分に、具体的にログを入れる努力のほうが重要である
      振り返れば明白なアイデアだが、これまではあとで見直すべき場所にコメントを残し、状況が悪化したときにそのコメントを思い出してくれることを期待する、というやり方だった
    • bazel のような善意のツールは、エラーでない出力を隠すべきノイズとして扱うことが多い。こうしたメッセージを最寄りのゴミ箱に送ってしまいがちなので、一部の領域ではログが制約を伝える手段として非常に不安定になる
  • 誰が何と言おうと、コードのあちこちにコメントやドキュメントコメントをたくさん書く。ただし逆からアプローチして、まずアプリケーションのステップ一覧をコメントで大まかに書き、その後開発しながら大きなステップを小さなステップに分割し、元のコメントを消したり残したりしつつ、ほぼ完成したアルゴリズムになるまでコメントを細分化していく
    たいてい外側から内側へコーディングするので、コメントを分割している間にコードも一緒に書く。たまにまとめてコーディングしたあと、後から大半の人が面倒がるくらいコメントを付けることもある。すべての関数と変数に説明を付け、deg_to_rad 関数にも """Converts degrees to radians.""" を付ける。ストレージは安いので
    多くの人が好まないのは分かっているが、それで構わない。見たくなければスクリプトで削除するか、コードレビューで削ればいい。それでもコメントのない他人のコードより、自分の古いコードを読むほうがずっと楽しい。Pythonでは Flask API のような単純なボイラープレートコードはたいてい自己文書化されているが、むしろそうしたボイラープレートは変更が多く、重要なコメントが付くことがある。業界ではアルゴリズム周りは丸ごと書き直されることのほうが多い
    これからもコメントとドキュメントコメントを好み続けると思う

    • 似たようなことはするが、主に最上位の構成要素だけに行う。そういう場所でコメントが最も役に立つからだ
      頭の中で全体を概念化するのが好きで、初期段階で複数の設計案を実際に書いて試すのは遅いと感じる。だから設計上の選択が決まったら、必ず文書化しなければならない。他の人はまだ自分の頭の中にアクセスできないからだ
      細部は、他の人も概念化を終えればより明確になるはずだと期待している。ただし何らかの理由でその概念化ができないと読みづらくなる可能性があるので、最低限の基本的な可読性を保つよう追加で整える
    • コメントはきちんと保守されていると素晴らしい。しかし、読む人に対してコメントを管理する人が非常に多いオープンソースのような場合でなければ、結局みんな忘れるか面倒になって保守しなくなる
      コードを直すのと同じくらい、コメントの更新も作業になることが多い。だから現実的には、コメントはたいてい嘘になるのを待っているものだ。結局コメントとコードが食い違い、それはコメントのないコードより悪い場合もある
      それなら意図を示す自動テストのほうがよい。テストは基本的に嘘をつけないし、そうでなければマージしなかったはずだからだ。コードがどう使われることを意図しているのかを示す、まともなテスト群があるなら、それは説明的でありながら、ほぼ真実であることが保証されているので見たい
    • 「気に入らなければ自分のバージョンでスクリプトで消すか、コードレビューで消せばいい」というところまではよかったが、ここからはチームメンバーとして大きな危険信号に見える
    • 自分も似ている。半分くらいは、まずやろうとしていることを説明するコメントを書き、その後、予想どおりにいかなかった点や、実際に動くようにするために必要だったことを付け加える
      5週間後に見返さなければならないとき、ものすごく助かる
    • すべての場合にドキュメントコメントが必要だとは思わないし、すべての関数のすべての引数と戻り値を文書化すべきだとも思わない。ただし複雑な APIでは確実に有用だ
  • 「コメントは未来の自分に送る謝罪」という考え方に従っている
    コードが奇妙だったり遅かったり、誰かに説明するときに「ちょっと雑なんだけど」と言うことになりそうな部分には、たいていコメントを残す。特に以前に変更してみたことがあるなら、動作しなかったケースや修正内容などを文書化する
    この基準で取り組むと不要なコメントは自然に消え、たいてい本当に必要なときだけなぜを文書化するようになる。自分のコードベースで1か月ほど試してみれば感覚が分かる