コードベースを完全に理解していなくても問題ない理由
(seangoedecke.com)- 数千万行に及ぶ大規模システムは、誰も全体を頭の中に収めることはできないため、エンジニアは 部分的に正確な理解 だけでも効果的に作業できる必要がある
- Peter Naur の Programming as Theory Building は、既存チームの理解が失われたらプログラムを捨てて作り直すほうがよいと見るが、ユーザーと無数の例外が絡む大規模システムは最初から再構築しにくい
- 担当者が全員去ったコードベースでも、1つの処理フローを最後まで把握したうえで慎重に変更範囲を広げれば蘇らせることができ、大規模組織ではこうした 理解の再構築 が繰り返し起こる
- LLM は詳細なメンタルモデルの形成を妨げる一方で、部分的な理解をすばやく作って活用する助けにもなり、協業・法的要件・セキュリティ更新・依存関係の導入も コード理解とは別の価値とのトレードオフ を求める
- 正確なメンタルモデルは楽しく安定した開発に役立つが絶対的な基準ではなく、実務では速度・法令順守・組織的要請のために 完全な理解を手放す必要 もある
完全な理解と部分的な理解
- 小さく、メンバーの入れ替わりが少ないコードベースでは、全体を理解しなければよい仕事はできないと考えやすい
- Redis や The Witness) のようなプロジェクトがこれに当たる
- Google のウェブ検索バックエンドや GitHub のように大きく、メンバーの入れ替わりが激しいコードベースでは、全体を理解することはできないため、それぞれが担当する 局所領域 を最大限把握する形で作業する
- 2つの環境では開発手法や慣行、文化が大きく異なるが、オンラインのソフトウェアエンジニアリング議論では、完全な理解を重視する最初の文化が過剰に代表されている
- オープンソースのエンジニアは作業を文章で共有しようとする動機が強く、大規模なプロプライエタリシステムよりも純粋なエンジニアリング作業が目立ちやすい
- プロプライエタリシステムは法的理由で公開しにくく、公開できたとしても、大規模コードベースを説明するにはあまりに多くの具体的文脈が必要になる
- 多くのソフトウェア環境で 部分的理解 は誤った状態ではなく、大規模システムでは現実的に到達できる最善である
- こうした違いは、Pure and impure software engineering で区別された完全な理解の文化と部分的な理解の文化が衝突する問題につながる
Programming as Theory Building の主張
- Peter Naur の論文 Programming as Theory Building は、プログラマが作る主たる成果物をコードではなく プログラムに関する理論 だとみなす
- この理論は、何がなぜ起きるのかについての直感的な理解から成る
- コードや文書はその理解を部分的にしか表せない
- コードを失っても理論を持つチームはプログラムを書き直せるが、チームが全面的に入れ替わって理解を失うと既存コードを把握しにくくなる
- Naur によれば、文書やコードだけで既存の理論を再構築することはできないため、元のプログラムを破棄し、新しいチームが問題を最初から解き直すべきだとされる
- Naur が引用した Gilbert Ryle の The Concept of Mind は、理論構築の範囲をより広く捉えている
- 実際に何かを行う過程で、理論やノウハウは自然に形作られうる
- したがって、コードそのものを探りながら既存コードベースを理解していく過程も、このアプローチと両立する
大規模システムをゼロから書き直せない理由
- ユーザーを抱える十分に大きなシステムには、再実装しにくい何千もの 例外ケースや特異な挙動 が蓄積している
- システムをよく知るチームでさえ、すべての詳細を同時に考慮することはできず、全体を一度に書き直すのは難しい
- 成功するリライトは、既存コードベースを小さく隔離された部分に分けて 1部分ずつ置き換える 形で進む
- 結局のところ、リライトも既存システムに一連の変更を加える作業である
- 既存システムを変更できないなら、新システムで完全に置き換えるのはなおさら難しい
放置されたコードベースを復旧する方法
- 数億行のコードと数千人のエンジニアを抱えるテック企業では、特定のコードベースを知る人が誰も残っていないことは珍しくない
- 担当者が数人、悪いタイミングで退職したり、コードベースが1年間保守されなかったりするだけで、この状態に至りうる
- Naur は、既存理論を少しでも知る人がいない完全に死んだプログラムの復旧を新しいプログラマに任せることはほとんどないと見ていたが、大規模組織では実際に起こる
- 放置されたコードベースでも、時間をかければ新たな理解を構築し、効果的に作業できる状態へ戻せる
- まず1つの処理フローを 最初から最後まで 理解する
- 慎重に変更しながら、そのフローから周辺領域へと理解の範囲を広げていく
誰もが不完全な理論で作業している
- 現代の大規模ソフトウェアは、個人はもちろんチーム全体でもすべての挙動を頭の中に保持できないほど大きい
- 十分に大きなコードベースでは、誰もがプログラムについてある程度 不正確な理論 を持ったまま作業することになる
- 効果的なエンジニアは、完璧な理解を持つ誰かが答えを出すまで待つのではなく、今ある情報で最も根拠のある判断を下し、その結果に対応する
- こうした作業には、不確実な状況でも 立場を定める能力 と 自信 が必要になる
- 大規模プロダクト全体の挙動を誰も完全には知らないという現実は、Nobody knows how software products work で扱われた状況と同じである
Naur 時代と現代のコード規模
- Naur が論文を書いた 1985年 には、平均的なプログラム規模は今日より何桁も小さかった可能性がある
- Naur が挙げた最初の大規模プログラムの例は20万行規模の産業用監視プログラムで、2つ目の例はコンパイラだった
- GCC の最初のバージョンは 1987年 に約 10万行 だったが、2015年には 1,400万行以上 にまで増えた
- 既存テストを再利用できるなら、10万〜20万行規模のプログラムは比較的容易に書き直せるが、100万〜200万行を超えるシステムに同じ判断を当てはめるのは難しい
LLM と理論構築の両面性
- LLM は一般的な 理論構築のプロセス を妨げるという理由で、よくないツールだと評価されがちである
- しかし、他のソフトウェアツールと同じく LLM にも両面性がある
- ソフトウェアに関する詳細なメンタルモデルを作るのは、より難しくなるかもしれない
- 部分的な理論は素早く構築できる
- 不完全な理解を土台にしても、より効果的に作業できるよう支援できる
- LLM とコード理解の関係は、単純な長所短所の区別では整理できず、依然として判断を要する複雑なトレードオフである
コード理解を難しくしても必要な選択
- コードベースの正確な理論を維持しにくくする要因は、LLM 以外にもさまざまある
- 他人が同じコードベースにコードを書くことを許可すること
- アクセシビリティやデータ保護のような 法的に求められる機能 を実装すること
- 同僚が退職したりチームを移ったりできるようにすること
- セキュリティパッチのためにソフトウェアのバージョンをアップグレードすること
- ライブラリやその他の依存関係を導入すること
- 理論構築を妨げるという理由だけで、ツールや慣行が悪いと判断することはできない
- コードベース理解は、可読性・保守性・正確性と同様に、複数あるエンジニアリング価値の1つにすぎない
- 状況によっては、他の価値を犠牲にしてでも正確な理解を優先できる
- 逆に、速度、法令順守、組織内の政治的理由のためにコード理解を譲ることもある
- コード理解が他のすべての価値を実現するための中核だという反論は、可読性・保守性・正確性にも同じように当てはまり、実務ではこうした中核的価値ですら継続的にトレードオフされる
個人的な好みと仕事上の責任
- とくに 純粋なエンジニア は、正確なメンタルモデルを保ちながら作業することを好む
- 開発がより楽しく、ストレスも少ない
- 自分が考える本来のエンジニアリングにより近く感じられる
- 多くのエンジニアが余暇に1人で小さなオープンソースプロジェクトを作る理由も、コードベースに関する正確な Naur 的理論 を保ちながら作業できるからである
- 仕事では、個人のエンジニアリング価値観よりも、組織がコストを払って求める価値の集合に従う必要がある
- パフォーマンスを重視していても、納期に合わせたり厳しい要件を受け入れたりするために遅いコードを書くことはある
- コードベースの完全な理解も、常に守るべき絶対基準ではなく、業務目的に応じて他の価値と交換しうる選択肢である
2件のコメント
だから抽象化にこだわるんですよね
Lobste.rsの意見
原文のタイトルは残念ながらやや釣り気味で、実際の内容は 大規模コードベースを部分的にしか理解していない状態でも前進できるべきだ という趣旨に近い。
理論構築の観点でも、理論には広さと深さの両方があると考えれば、文章の要点と矛盾しない。広さとは、システムのどの範囲まで十分に理解して質問に答え、適切に変更できるかを意味し、深さとは、特定の部分についてどれだけ複雑な質問に答え、整合性を保ったまま複雑な変更を行えるかを意味する。
コードをモジュールごとに書き直しつつ移行期間を設けるなら、そのモジュールについて狭いが深い理論を構築し、それに従って作業していることになる。小さな関数なら引数や戻り値を1つ追加してもよいが、規模が大きくなると、少なくとも特定モジュールだけでも クリーンな状態からやり直し、他の部分が新しい実装を呼び出すようにつなぐ方が合理的だ。根本的にバグの多い実装の正確性を段階的に改善しようとするやり方はうまく機能しないことが多く、これは性能のような性質にもよく当てはまる。
ただし、Naurが「プログラムを蘇生させるより、既存のプログラムテキストを破棄し、新しいプログラマチームに与えられた問題を最初から解かせるべきだ」と述べたとき、それが書き換えを始める前に既存コードを先に削除せよという意味なのか、それとも新しいプログラムが適切な代替物だと確認してから破棄してよいという意味なのかは議論の余地がある。
しかも現実には、その 与えられた問題自体が与えられていない。Naurは元の問題定義が残っていると仮定しているが、大規模コードベースではそうでないことが多い。最も近いものは通常テストスイートなので、テストを通し続けながら構成要素を置き換えていくことができる。
当時は継続的デプロイもなかった。今日ではサービスを動かし続けながらオンコールを担当し、問い合わせに答え、緊急バグを修正しなければならないため、書き換えに関する損得勘定も変わってくる。
この反論にはうまく表現されている部分もあるが、大半は自明に感じられる。十分に大きなシステムでは 部分的な理解が唯一の状態 だが、組織は常により深い理解を目指すべきだ。
「仕事をするために給料をもらっている」という理屈とは別に、大規模ソフトウェアはしばしばユーザーを満足させられなかったり、事業目標を達成できなかったりする。WindowsやOS Xも、蓄積した複雑性によって劣化していると見なせる。
経営陣も、魔法のように複雑性を消せるならそうするだろう。ただ、これはいまだ未解決のソフトウェア工学上の問題であり、経営陣が悪いコードを出荷したがっているというよりは、現在の条件下でそうしたトレードオフを受け入れているに近い。
「独占なら十分だから、ひどくてバグの多いソフトウェアを出してもよい」と考えることもできるが、それはあまりに虚無的だ。独占ではない、あるいはユーザーを囲い込めないソフトウェア会社も多いので、そういう場所では品質が実際に重要になる。
この解釈では、理解という概念も一種の門番の役割に似て見える。
記事で説明されているアプローチは、コードの一部を 局所的に推論する能力 と密接に関係している。
周辺すべてを理解せずとも、大規模コードベースの一部分を理解する局所的推論は、コンピュータ科学が当初から追求してきた中核概念だ。
構造化プログラミング の主な利点は局所的推論が可能になることだったし、グローバル変数を避けるべきだという勧告もおおむね同じ目的から出ている。関数型プログラミングは副作用を取り除くことで局所的推論を可能にし、オブジェクト指向プログラミングの原則の1つであるデータ構造とそれを扱うコードを結びつけるやり方も、局所的に推論するための別の手段を提供する。
こうした基本概念の真の力は、望ましい状況ではないにせよ、コードベース全体を理解しないまま小さな部分を推論し、作業できるようにしてくれる点にある。
「コードベースのすべての行を理解しなければならない」という助言に対する興味深い反論として読めたし、記事の中心的主張が開発の 実務プラクティス と接しているため
#practicesタグを付けた。このコミュニティがLLMを好まないのは分かるが、この記事が本当に
#vibecodingに当たるのかは疑問だ。vibecodingに分類しており、私も同意はしないが、それが慣例になっている。この記事の中心的な問いも LLM補助コーディング と密接に関係している。ただし
vibecodingだけが唯一のタグであるべきではない、という点には同意する。記事は最後になってようやくLLMを明示的に扱っており、中核の論点はLLM以前の時代でも興味深く、議論する価値がある。したがってpracticesタグを復活させる根拠は十分あると考え、実際に自分で復活させたし、他の人にも同じ対応を勧める。LLMの利用がコードベースの完全な理解を妨げうる世界においても関連性がある。
タグが「バイブコーディングに関心のある人に関係しうる」という意味なら、たしかに適切だ。逆に「バイブコーディングに関心のある人以外には無関係だ」という意味なら適切ではない。他のタグはどれも前者の意味で使われているが、Lobstersのユーザーのかなり多くはこのタグだけを後者の意味で使っているので、結局彼らはこの記事を見落とすことになる。
1985年と異なる現代開発のもう1つの特徴は、30万行のコードベース の専門家になったとしても、翌週にはまったく別の30万行のコードベースで働いているかもしれないことだ。
経験ある現役プログラマは、いつでも未知の領域に投入されうることに慣れており、共通のイディオム(Google C++ Style Guide)、APIを誤用するとエラーを出してくれるコンパイラのような自動化ツール、大規模プログラムが「どう構成されるべきか」に関する直感の組み合わせに頼っている。
BunのLLM書き換えのように「もはやコードベース全体の動作を理解している人がいないのに、どうやって開発を続けられるのか?」という議論も見たが、それは作家でなければどうやって小説を理解できるのかと問うのに似ている。結局はコードなのだから、読めばよい。ある関数が理解できなければ、いくつかの断片に分けたり、テストを書いてみたり、制御フローを紙に書き出したり、何でも試せる。
プロジェクトを定期的に渡り歩くことになるので、昔のコードベースへの理解を維持し続ける理由もない。ある時点ですべてが似たように混ざり合う。会社が買収したスタートアップのScalaコードが、カスタムThriftベースのRPCプロトコルを通じて、Goで書き直し中のJSONベースのRubyサービスと通信できるよう手助けしなければならないとしても、1時間ほど
Scala syntax referenceとThrift wire encodingを検索すれば始められる。専門家になる必要すらない。1か月後には、LinuxカーネルのGo実装で、JavaScript向けのOCamlベース型チェッカがなぜクラッシュするのかをデバッグしているかもしれないからだ。結局は全部コードにすぎない。