Chestertonの中指
(arp242.net)- Chesterton’s fence は、理由の分からないコードをむやみに変えるなという助言だが、理由を残していないコードとコミット履歴は後任の開発者に同じ負担を押しつける
- このリポジトリの過去 13年間のコミット本文は、コマンドで数えると 295 行しかなく、dependabot・revert・typo 関連の本文を除くと 167 行まで減る
- コミットタイトルは大きな変更でも「fix page A」のように文脈を与えられず、別ドキュメントやコードコメントもほとんどないため、変更理由を追跡しにくい
- コードには、終わっていないリファクタリング、削除された機能の残骸、追加されたが接続も利用もされていない機能、誰も使っていないように見える機能が残っている
- コミットメッセージとドキュメントには、「何を変えるのか、なぜ変えるのか、なぜ良い解決策なのか」を最低限でも残すべきであり、何も残さなければ後に来る開発者のコストが大きくなる
理由のないコードが残す負担
- Chesterton’s fence は、何かがなぜそのようになっているのか理解していないなら、むやみに変えるなという比喩
- プログラミングでも、奇妙に見えるコードを「直した」あとで、その奇妙さには理由があったと分かることがある
- この事例では逆側の問題が表れている
- 奇妙なコードや判断は多いのに、なぜそうなったのか分かる記録がない
- 以前の開発者たちは皆去っており、雇われたあとに尋ねる相手もいない
- リポジトリのコミット履歴は、実質的な文脈をほとんど提供していない
- 過去 13年間のコミット本文は合計 295 行
- dependabot, 「revert commit」, 「fix typo」コミット本文を手作業で除くと 167 行
- おおよそ 1 か月に 1 行の水準
- コミットタイトルもたいてい「fix page A」のように、大きな変更を説明するには不十分
- 別ドキュメントはなく、コードコメントもほとんどない
- こうした状況は、「私たちは奇妙なことをたくさんしたが、なぜそうしたかは言わない」という Chesterton’s middle finger に近い
開発者が残すべき最低限の記録
- コードベースにはさまざまな種類の未完了・残余コードが残っている
- 終わっていない リファクタリング
- 削除された機能の残骸
- 追加されたが接続も利用もされていない機能
- 誰にも使われていないように見える機能
- 全体としては Chesterton’s gap の問題も深刻に見える
- 「まだ柵がないのだから作ろう」というように、そもそも本当に柵が必要なのかを問わない状況に近い
- 良い文章を書くのは難しいが、とりあえず通用するレベルの説明を残すことは難しくない
- 変更記録は基本的に 3 つの質問に答えるべき
- 何を変えるのか
- なぜ変えるのか
- なぜこの解決策が良いのか
- 「Implement new feature X」だけで十分な場合もあるが、たいていは機能がなぜ、またはどのようにその方式で追加されたのか書くべき内容がある
- バグ修正、リファクタリング、実質的な変更では、通常少なくとも 1〜2 段落ほどで変更内容と理由を残せる
- こうした記録は選択事項ではなく、ソフトウェア開発者の仕事に含まれる
- 流麗である必要はない
- 完璧な英語である必要もない
- 大げさなエッセイである必要もない
- 書き漏らしがあっても、何もないよりはるかにまし
- 何も残さなければ、その後のすべての人に問題を押しつけることになる
1件のコメント
Lobste.rs の意見
後になって何が重要になるかは、その時点では明確でないことがある。コミットに至るまでの過程がすべて公開で記録されていれば大いに助かるが、関係者は皆すでに多くの文脈を共有しているため、「あまりに自明だ」と見なされて抜け落ちる内容も出てくる。
議論が記録されていないと、意思決定の過程をデジタル考古学のように掘り起こすのははるかに難しくなる。結局、なぜその柵があるのかわからない状態を扱わなければならないことがあり、現在の文脈と今いる人たちのシステム知識をもとに評価するしかない。
プロジェクトに長く関わり、システム全体への理解と勘を身につけた人がいると非常に助かる。開発者を交換可能な歯車のように扱う組織では、誰も十分に長く残らないため、同じ失敗を繰り返し、車輪の再発明をすることになる。
次にそのコードを見る人が、なぜこうなっているのか理解できる可能性がゼロではなくなる。
コミットメッセージにただ「fix」や「WIP commit」のようなことしか書かない開発者を理解したことがない。おそらく本格的なコード考古学をしたことがないか、そもそもそんなことができると考えたことすらなかったのだと思う。
自分は常に、情報が多すぎる側に倒れるようにしている。そうすれば未来の自分や後任者、あるいは障害発生時に呼び出される気の毒な人が、何かが壊れたときにせめてその理由を突き止められる可能性が生まれるからだ。
そういう場合、各チェックポイントの後で何が変わったのかを頭の中で追跡したり、意味のある説明を付けたりするのは難しく、コミットをよく定義された作業単位に分ける手段というより、ビデオゲームの「セーブ」ボタンのように扱っている状態に近い。
逆に、大きな API リファクタリングの結果として大きなコミットを作るなら、設計文書に準ずる説明でなければ不十分である可能性が高く、それをしないのであれば長いメッセージの意味も曖昧になる。ただ、一部の人はこれを勲章のように受け取り、リリースノートに「バグ修正および機能改善」のような文言しか書かなくなったりするが、それは明らかに誤った結論だ。
変更の背景を読んで理解しなければならない「他人」が、未来の自分自身かもしれないという事実を、多くの開発者はしばしば忘れる。コードブロックを見て頭をかきながら
git blameをしたら、自分の名前が staring back してくる状況には、誰もが覚えがあるだろう。良いコミットメッセージのおかげで、イシューやメール、チャットログを掘り返してなぜを探す時間が、何時間も、ひどいときには何日分も減ったことが数え切れないほどある。自分なら当然すぐ答えられるはずのケースですらそうだった。
未来の自分には親切であるべきだ。知っていたこと、考えていたこと、議論されたことをすべて git ログに流し込んでおくほうがいい。5年後にも何が依然として自明かなど、決して明らかではない。
git blameで自分の名前を見て面食らうようなことは、少なくとも理由があった変更についてはあまり起きない。相手の奇妙な挙動のようにランダムなものは、その側のプロセスを見られなければ有用な説明をするのが難しいが、かつて自分には筋が通っていたことなら、読み返せばたいていまた理解できる。自分の学び方は、溶岩の層のように積み重なるやり方に近い気がする。高校以来、大きく変わるというより、知っていることと使えるやり方をずっと積み増してきた。
最近、コミットメッセージが1文を超えたらたいてい時間の無駄だと主張している人がいたが、強く反論したかったものの、その逆を証明するのは思ったよりうまくできなかった。
ひとつの問いは、どんな情報をコミットメッセージに入れるべきで、どんな情報をインラインコメントやADR、あるいは他の長文形式のドキュメントに入れるべきかということ。
いまでも良いコミットメッセージを書こうとはしているが、職場ではもう見込みがなく、個人の遊びプロジェクトでも一貫してできてはいない。
ただし最終的なコードは、コミットメッセージを読まなくても理解できるべき。新しいコードの中で根拠が必要な部分があるなら、それはコメントに入るべき。
言い換えると、コミットメッセージはなぜこの変更を今行ったのかを説明し、コメントは変更が完了した時点のコードがなぜその状態なのかを説明する。
より大きな変更、特に新機能については、どこかに設計文書があるべき。レビューが必要なら、リポジトリ内の実際の文書かもしれないし、課題追跡システムにあるかもしれない。
コミットメッセージはその文書を指し示すべきであり、設計がコードに落とし込まれる中で生じた詳細も説明する必要があるかもしれない。可能なら設計文書の短い要約も含めるとよい。レビュー候補が複数いるときに、誰が最も関心を持つべきかを示すシグナルになり、設計段階でフィードバックすべきだった人が抜けている場合を拾ううえで特に重要。
git logやjj logで見るのではなく、ほぼ常に行コメント表示を通して見るという点。タイトル行は、さらに掘り下げる必要があるか判断するのに、広くとても役に立つ。本文に変更がなぜ行われたのかという情報があれば、その理由が直感的でないときに助かる。
たとえば、かなり多くのコードが入っていても、「admin: add impersonation」だけで十分なことが多い。だが「auth: shorten JWT timeouts」なら、なぜタイムアウトを短くする必要があったのか、1〜2文ほどは見たい。
本当に長文のコミットメッセージは、実際のところかなり役に立たないと考えている。記事で指摘されていた理由ともおおむね同じ。ああいう形式は、コミットメッセージがそのままPR説明になるワークフロー、たとえばメールベースのフローやGerritなどから来たのだと思う。そういう場合でも有害ではないが、必ずしも価値を加えるとは言い切れない。
私も似たような状況で、職場のより広いグループの中で詳細なコミットメッセージを書く人は、私ともう1人しかいない。
柵がなぜ作られたのかを知っていても、なぜ今そこにあるのかは分からないことがある。Chestertonの柵を作った当人であっても、それを取り壊してよいかは分からないかもしれない
システムが作られた当時に意図された依存関係ツリーは、ある時点での実際の依存関係ツリーの部分集合にすぎない。だから、完璧な開発者がその柵をなぜ作ったのかをすべて教えてくれたとしても、その有用性は限られている
彼らが知っているのは、なぜ作ったかであって、「この柵を取り除くと何が壊れるのか?」への答えではない。https://xkcd.com/1172/ を見ればよい。笑えるユースケースの中には重要でないと見なせるものもあるが、元の開発者自身にも分からなかった正当な利用法は常に存在する
元の開発者が何を考えていたか、あるいは何を吸っていたかを知るのは悪くないが、その情報は不完全と無関係の間のどこかにある
作り話の例として、Chestertonの柵がもともと両側に農場があった時代に、水たまりから子どもを遠ざけるために建てられたとしよう。今では高速道路ができて、その柵が偶然にもシカと車の衝突による大規模な動物と人の死亡を防ぐ唯一の仕組みになっているかもしれない
誰もその事実を知らない。高速道路+柵なしという組み合わせはテストされていないだけでなく、そもそも存在したこともなく、高速道路の建設者も自然資源局も、なぜその道路でロードキルが少ないのか分からない。数年後に農場がすべて住宅に変わり、主要な動物の移動経路でなくなれば、その柵は無用になるかもしれないし、ならないかもしれない
これがこじつけっぽいとか、自分には当てはまらないと感じるならうらやましい。私の経験では、ある程度の規模があり、しかも極端に新しくない会社を除けば、たいてい物事はこう回っている
真実は、私たちが行うすべてのことが生態系の一部であり、明示的に相互作用することに合意したこともないものに依存し、また依存されているということだ。APIの表面積を減らし、実装の詳細がすべて隣人の仕事にならないようにはできるが、意図しない結合はエントロピーの増大と同じくらい避けがたい宇宙の法則に近い
これを虚無的で敗北主義的に聞く人もいる。エントロピーと戦うべきではないのか、と言うかもしれない。しかし、時間の使い方と投資対効果を最良にするには、これは根本的に戦う対象ではなく、管理する対象だと認めることから始まると思う
いつでも世界の状態を把握できると仮定するのは、失敗と自己非難をあらかじめ予約するようなものだ。100%の稼働率が存在せず、しかもほとんどの対象にとって間違った目標であるのと同じだ
ある程度のハイゼンベルク的な不確実性を持つ過程を管理しているのだと認めれば、1日に与えられた限られた時間を効果的に使って、より良い結果を生む方法を選べるようになる。特に、事前対応と事後対応の賢いトレードオフが可能になり、事後対応をゼロにはできないこと、そして時には1日で済む事後対応を避けるために1年がかりの事前対応をするのは筋が通らないことも理解できる
では、コミットにはどれだけ文書を書くべきなのだろうか。設計文書やテスト計画はいくつ必要なのだろうか。私にも分からない。ひとつ選択肢を挙げるなら、すべての文書は読者のために書かれる
コードベースを変更するなら、現在のチームメンバーや新しく入る人も含めて、調査によってその変更が何をし、なぜそうしたのかを理解できるべきであり、危険な足場や荷重を支えるバグについてのいくつかの警告もあるべきだ
これは長々とした散文ではなく、舞台設定をしてくれる追加コンテキストへのポインタの形であることがたいてい適切だ。たとえば「この段階で認証を要求したのは、すべての変更に複数者承認を必要とするポリシーの一部である。see: go/multiparty」のような感じだ
人間を相手にしながら完全性を追求するシステムは本当に使いづらい。DRM、trusted computing、remote attestation、Faro Plague、smart contracts のようなものだ
サービスモードで再起動して修正できるシステムのほうがはるかによい。今後ソフトウェアが人々の役に立つためにどの方向へ進化すべきかを予測することはできないからだ。100%堅固にロックするより、修正しやすくしておくほうがよい
私たちはコミット本文をほとんど書かないが、タイトルはかなりうまく書くほうだ。それが測定基準だとするなら、何を測るための基準なのかはよく分からない
大きなコードベースでは、明確なコードと十分なテストカバレッジのほうが文書よりはるかに有用なことが多い
完全に妥当な変更が行われ、テストもそれに合わせて更新されたが、後から見ると、なぜその変更が必要だったのかがまったく不明瞭な場合がある。特に、変更された行が本番環境で予期しない動作や追加の動作を引き起こすならなおさらだ
単純なリバートが望ましくないこともあり、このとき変更がなぜ行われたのかという全履歴があると本当に助かる
アイデア自体は正しかったが予期しない結果が出た、というケースを私は何度も見てきた。意図が分かれば、新しい問題も修正しつつ、元の変更理由も保った本当に正しい変更を導ける
コミット1行にこだわるなら、少なくともチケット番号を入れて、そこから履歴を読めるようにすべきだ
こういうコードベースを復旧しに5年間エキゾチックな地域を渡り歩いて、かなり良い金を稼いだ。@arp242、常に料金を上げて、https://archive.org/details/working-effectively-with-legacy-code を枕元に置いて寝るべきだ
幸い、AIジャンク生成器は巨大なコミットメッセージを書いてくれる。実際の変更とある程度関係があることも多いので、少なくともその部分は解決したことになる