コードのシンプルさ論争に終止符?結局、大切なのは認知負荷だった!
引用元:https://news.ycombinator.com/item?id=45074248
プログラマーは認知負荷が低いシンプルな解決策を好むけど、何が”シンプル”かは意見が分かれるよね。慣れてるか、 mental model がコードと合うかで変わる。記事で推奨されてる早期リターンは goto と比較され、制御フローが分かりにくくなることもある。中間変数も”isSecure”みたいに一見分かりにくく、むしろ条件式の方が即座に理解できる場合も。HTTP コードは標準化されてて認知負荷が低いけど、カスタム JSON コードは違う。シンプルさは主観的で、誰にとってシンプルかに依るんだ。Kolmogorov complexity で測らない限り、客観的なシンプルさは存在しないんじゃないかな。
”isSecure”は調べたくなるけど、”condition4”や”condition5”は調べないってこと?記事のポイントは、”isSecure”ならどんな実装か推測できるけど、”condition4”だと何も分からないってことじゃないかな。
たとえば、isSecure = user.role == ’admin’ とか。
”is secure”みたいなコメントは中間変数にした方が良い。コメントは「なぜ?」を説明してくれないし、嘘をつくこともあるから、実行されるコードを信頼する方がいいよ。中間変数もざっと読むのに役立つしね。
”condition4”や”condition5”は例示用のプレースホルダーだと思ってたよ。”channel_encrypted”とか”checksum_verified”みたいな、もっと分かりやすい名前を想定してたんだ。
”isSecure”が嘘である可能性や、人によって解釈が違うって意見には賛成。チェックする手間や、変数の意味を覚えるのは大変だよね。これはよく議論されるテーマで、バランスが重要だ。結局、普遍的にシンプルと認められ、形式的に検証できるプラクティスやルールはないってのが僕の主張だよ。
経験的にもツール的にも、ネストされた if 文は認知負荷を上げるね。だから早期リターンの方が理解しやすいんだ。コードレビューでは命名についてよく議論になるけど、 juniors には”くだらない”って思われても、読みやすさやデバッグには超重要だよ。
あと、HTTP エラーコードについては君の意見を誤解してた、ごめん。あれはよく文書化されてるし、 HTTP 関連のコードを書くなら必須の知識だよ。
シンプルさは主観的だという意見に反論してる?それとも早期リターンみたいな例に言及してるだけ?僕はシンプルさは主観的だとすでに言ったから、早期リターンがなぜシンプルじゃないと思うかを説明するね。過去10年 Haskell や関数型言語をメインに使ってたから、 return や break がない制御フローに慣れてるんだ。例外も嫌い。命令型言語で早期リターンを使うこともあるけど、関数の一番最後に ret 変数を返すのが好き。これによりコード構造と制御フローの一貫性が保たれて、 Haskell や Scheme みたいにコードを読める。NASA の C style guide [0] も参照してて、これは goto と比較されることもある。これは唯一の正解じゃないけど、そういう考え方もあるんだ。 goto の賛否もシンプルさの視点の違いの例だよね。
URL: https://ntrs.nasa.gov/api/citations/19950022400/downloads/19…
”isSecure = user.role == ’admin’”についてだけど、僕は中間変数を、意図よりもステートメントに合うように命名する方が好きだな。これは一種のセマンティック圧縮だね。例えば”isAdminUser = user.role == ’admin’”だと、条件に必要なロールの使用を隠さないで済むけど、”isSecure”だと何でもあり得る。管理者ユーザーの概念は隠したくないけど、ロールを使う具体的な方法は隠したい、って感じかな。あくまで僕の意見だけどね。
GPじゃないけど、名前付けは難しいってよく言われるように、もっと良い名前はいつも見つかるよね。”isSecure”や”isAdminUser”と”condition4”の間には、認知負荷にかなりの差があると思うよ。昔、”if (temp2 && temp17) temp5 = 1;”みたいなコードをデバッグしたことがあるんだけど、結局諦めて再実装したんだ。当時はまだユニットテストが一般的じゃなかったから、大変だったよ。
これは Python でよく使われるパターンだよ。評価の前に変数を導入するんだ。例えば、”if (user.id < 4 and user.session): …”じゃなくて、”_user_has_access = (user.id < 4 and user.session); if _user_has_access: …”とか、 walrus 演算子を使って”if _user_has_access := (user.id < 4 and user.session): …”みたいにするんだ。同僚がこのパターンをすごく気に入ってて、彼のコードはいつも読みやすかったから、僕も今でもこれを使ってるよ!
認知負荷ってのは、その人のメンタルモデルによるよね。馴染みとシンプルさは違うのに、同じに感じちゃうんだ。賢いけど型にはまらないコードを使うと、周りが学ぶコストを払うことになる。一度学べば楽になるけどね。だから、慣れてるコードをシンプルにするのは難しいんだ。記事の後半でも言ってたけど、新人くんを呼んでコードをレビューさせるのがいいよ。
プログラマーはシンプルな解決策を好むけど、どれがシンプルかで意見が割れるって話だよね。そこで「エゴの低さ」が重要だって。コードベースの品質は一貫性とまとまりで決まるから、ベストなコードベースは、趣味が似てるか、既存の規約に従う低エゴな人たちによって作られるんだ。客観的に悪いパターンもあるけどね。低エゴだと既存規約に従う→慣れる→シンプルに見える、って流れだ。
この前のコメントは必ずしも正確じゃないと思うな。誰もちゃんと理解してないのに、悪い慣習をみんなが守り続けてるプロジェクトにたくさん入ってきたよ。深いネストや早期exitなし、深いオブジェクト継承とかね。これって、多くの開発者が波風を立てたくないし、問題を悪化させずに複雑さを解きほぐすスキルがないから起こるんだ。
残念ながら、シンプルさには明確な定義がないよね。開発者は「慣れてるもの」をシンプルって呼ぶだけで、「ブランチが少ない」とか「コード行数が少ない」みたいな客観的な尺度じゃないんだ。せいぜい「スクロールが一番少ないコード」くらいしか思いつかないけど、これも測定が難しいし、IDEで軽減できるしね。
同じように、短いチェックには三項演算子を好む人がいるよね。if/else/while/forの次に学ぶものだから賛成したい気持ちもあるけど、あれって略記だから、短くても必ずしも読みやすいとは限らないんだ。value = condition ? a : b
みたいな単発なら気にならないけど、複数行になったりネストしたら問題視しちゃうな。
副作用がなければ、三項演算子の方が好きだな。意味合いがより引き締まって読みやすくなると思うし、コンパイラーが最適に処理してくれると信じてる。こういうフォーマットがすごくいい感じなんだ:value = (condition)<br> ? foo<br> : bar;
副作用や制御フローを伴う三項演算子は特に嫌いだな。制御フローの場合は常にインデントされてないと見落とすことがあるから、if
文の方が断然良いと思う。
確かに、ある解決策をシンプルだと感じるかどうかの95%は、その馴染みやすさにあるんだよね。
時には、改善されたバージョンよりも確立されたパターンの方が理解しやすいこともあるよね。そういう場合は慣習に従う方が良いんだ。例えば、HTTPコードを名前でなく直接比較する方が、Web開発の経験がある人なら誰でも読みやすいからね。
ChatGPTが「エレガント」とか「シンプル」って言葉を軽々しく使うのにムカつくんだよね。そんな言葉、二択じゃないし、まるでやる気満々のインターンが先生に媚びてるみたいでさ。数々のコードを学習したデジタル脳の思考とは思えないんだよな。
あなたの怒り、もしかしてChatGPTが「エレガント」や「シンプル」について価値判断をしてるって思ってるから?ChatGPTがそういう概念をどこで学んだか、ちゃんと知ってるんだよね?
記事の「早期リターン」についてだけど、僕は複雑さを減らすと思うよ。でもOusterhoutは「複雑さ」って変更のしやすさだって言ってるんだ。もし言語に早期リターンしかないなら、後からコードを追加しにくいのが難点だね。良い言語なら「break」とかラベル付きブロックとか、cleanupセクションがあるべきだよ。Javaの例外処理とはちょっと違うけどね。
あなたの言う「良い言語」じゃなくてもさ、その「break」とかラベル付きブロックとか、全部関数でラップすれば実現できるじゃん。C言語でいつもやってるパターンだよ。
関数をラップするより、ブロックをラップする方が正直めんどくさいよ、特にC言語だと関数をネストできないし。すべてのプログラミング言語は本質的に同じだけど、記事のタイトルにもあるように「認知負荷」って視点だと、便利な言語とそうでない言語があるってことだよね。
C/C++の昔からの議論だよね、これ。Cの人たちはexitブロックを好むし、C++の人たちはデストラクタを使いたがる。僕はC++派だから、早期リターンを推すよ。だって、どの操作にフォールバックがないかすぐ分かるし、「return」を見れば「else」がないって一目で分かるから。それに、コードのインデントが深くならないのも良い点だね。
個人的には成功値を早期リターンするのには反対かな。僕は関数の最後に成功値を返すのが好きだよ。エラーとかnullなら早期リターンもアリだけど、成功値だとコードが読みにくく感じるんだ。パフォーマンスが必要な時以外は避けるべきだと思うね。例で示したように、stepsResult
を宣言して最後に返す方が、制御フローがずっと分かりやすくて、クリーンアップコードも置きやすいんだ。
僕は単一リターンと早期リターンの両方が好きだよ。例えばこんな感じだね: https://github.com/lelanthran/libds/blob/b5289f6437b30139d42…
これはJohn Ousterhoutの『A Philosophy Of Software Design』から僕が得た一番の学びだよ。みんなにこの本を勧めるね。要するに、ソフトウェア設計では複雑さを最小限にすべきで、複雑さってのは「変更のしやすさ」なんだ。そしてその「しやすさ」は、理解するのに必要な「認知負荷」の量で決まるってことさ。
ルールじゃ好みや経験は測れないし、結局アーキテクチャは人や文化次第。この記事は良いけど、本当に必要な人には響かないだろうな。Rob PikeがGoogleにいなきゃGoは生まれなかったってこと。(好き嫌いは別の話だけどね)
俺からするとDRYはアンチパターンだね。アプリがちゃんと理解されるまでは、むしろ同じことを繰り返せ!1バージョン目で問題、2バージョン目で解決策、3バージョン目になって初めてDRYを考えればいいんだよ。
DRYってのは「同じ処理を再実装するな」って意味じゃなくて、「コードをコピペするな」って話だぞ。最初は楽かもしれないけど、後で変更が超大変になるからやめとけ。
うちのチームで試してることなんだけど、関数のサイクロマティック複雑度は低めに、コメントとシグネチャはしっかり書くようにしてる。これで認知負荷が超減ったんだ。複雑なロジックはdo_thing_a(); do_thing_b(); do_thing_c(); みたいに分けちゃうけど、むしろそれがコードの「あらすじ」みたいになって、すごく読みやすいぞ。
もっとコメントを表示(1)
DRYはジュニア開発者がよくハマる罠だ。早すぎる最適化になることもあるし、重複を避けるために抽象化を追加したり、本来独立してるはずのコンポーネントを繋いじゃったりする。結果、後で変更が大変になったり、影響範囲がデカくなったりするんだ。
「どんなルールも何にでも使える」って言うけど、複雑さを排除するルールなら話は別だろ。俺のルールはこれだ。1. マルチスレッド禁止。(See Mozilla’s ”You must be this high” sign) 2. Visitorパターン禁止。(See grug oriented development) 3. Observerパターン禁止。(See django when signals need to run in a particular order) 4. カスタムDSL禁止。5. XML禁止。(XMLに関しては喧嘩売ってるぞ!)
客観的な数値目標で読みやすいコードは作れない。それより、コードレビューで同僚が困ったら、コードとコメントだけで説明できるように書き直せ!口で説明するんじゃなくてな。最高のリンターは、やっぱり同僚の主観的なレビューだよ。これ、チームが変わっても機能するんだ。
似てるコードを統合しないなら、その理由をコメントで書いとけ。コピペはダメだ。(言語/フレームワークのボイラープレートが多すぎて再利用が面倒な時以外は。)もし気づかずに同じこと書いてたら問題あり。リファクタリングした関数が要件変更で無理になったら、また別の似たバージョンを作ればいいさ。
コメント4のサイクロマティック複雑度についてはhttps://github.com/fzipp/gocyclo、コメントについてはhttps://github.com/mgechev/reviveを見てみろ。関数にコメント書けってのはアーキテクチャじゃないし、みんなサイクロマティック複雑度を誤解しすぎ。あるチームなんて’else’キーワード使うのを禁止してたぞ!
ヨーロッパ展開で、俺は既存のインフラを「Europe」ってフォルダに丸ごとコピペしたんだ。そしたら、ヨーロッパ限定の要件が来た時に、変更が超簡単だったんだよ。何十万行ものYAMLをコピペしたのが悪い選択だとは思わないし、俺には25年の経験があるんだぜ。
コードレビューする人にいつも伝えるのに苦労するのが、俺に理解させようとしないで、読者みんながわかるように自己説明的にしてほしいってことなんだ。(もちろん、明示的に頼むよ)俺はたまに、コードを行ったり来たりしてやっと理解できたことについて質問して、どうしたらもっと分かりやすくなるか考えてもらうこともあるんだ。残念ながら、ほとんどの人は最高の“先生”になろうと考えるより、自分の考え(不安?)を説明することに集中しちゃうんだよね。
Visitorパターンは、コンパイラ開発みたいな一部の分野ではめちゃくちゃ便利だよ。
俺は音楽家としてこの原則に慣れてるんだけど、ソフトウェアの世界に入ってからも通用するなんて面白いな。
Rule of Threeとすごく近いよね。一度の重複はOKだけど、3回目が必要ならリファクタリングを考えよう。
https://en.wikipedia.org/wiki/Rule_of_three_(computer_progra…
俺はこれ、かなり良い妥協点だと思うよ。昔はコードを全く重複させないようにしてたんだけど、結局は苦労のほうが大きかったな。2つの場所でコードが必要ならコピペを許して、3つ以上ならリファクタリングする、これがかなり良い経験則だね。
俺は複雑さの量に苦労してるんだ。経験の浅いSWEとして、関数呼び出しの数(A)+ソースコードファイルの数(B)がNに達すると、全部頭に入れるのが難しいんだ。特に、Bが3以上、Aが3以上だと、画面を切り替えずに全ファイルを見るにはB個の画面が必要だし、Aが増えると認知負荷が上がるんだ、特に“パターン”が絡んでくるとね。でも俺は経験不足だから、これが俺の経験不足のせいなのか、それとも本当に無駄な複雑さが原因なのか、判断できないんだ。
その逆で、DRY原則が足りなかったせいで、変更が必要な場所のうち一部しか変わらず、バグを招くこともあるんだ。俺からすると、具体的に同じ振る舞いをさせるつもりがあるものこそ、DRYに保つべきだね。
それはAlgebraic Data Typesとパターンマッチングがない言語でしか当てはまらない話だよね。最近じゃJavaだってこれらを持ってるのに。
「アーキテクチャの議論には勝てない」って心底共感するよ。でも最近はこれを理解して受け入れ始めたら、いつも俺と逆の立場を取るように見えるアーキテクトとの議論のイライラが減ってきたんだ。正しいとか間違ってるとかないんだ。頭の中で何を優先するかで、常に違うトレードオフがあるだけなんだよね。
「記事は良いけど、本当に必要な人には伝わらない」ってのは本当だね。一度読んだだけで考え方は変わらないし、メンターについても結果はイマイチ。エンジニアは完全に同意しても、真逆のことするんだから。過去の意思決定が何につながったかのフィードバックループを構築するのが一番難しいみたい。それが完了するのに数年かかるから、ほとんどの人は自分のアーキテクチャの決定が悲惨な結果を招いたことを忘れちゃうんだよね。あるいは自分とは関係ないって思っちゃう。
コードレビューでは、まず人間関係を築くのが大事だよ。僕はいつも、例示コードと「このバージョンどう思う?いくつか明確にしてみたんだけど」って質問のレビューコメントを残してる。何が明確になったか説明すると、たいてい報われるんだよね。
デフォルト設定とオーバーライドを使うのが普通じゃない?
あんたのアプローチ(完全コピー)だとさ、まず1. EuropeとUSAで同じ変更をする時、2箇所でやんなきゃいけない。2. 数千行のコードで1行だけ違う場合、どっちがどっちか分かりづらい。3. 将来Africaを追加したら、さらに問題が複雑になるよ。
完全コピーはif
文やfeature flagなしで段階的なロールアウトができるから、変更をずらせて逆にメリットなんだよ。インシデントの時はコードの読みやすさが超重要だし、オーバーライドは認知負荷を上げるからやらない。共通部分はimport
で対応する。1行の違いはdiff
で見れば一発だし、Africaが増えてもコピーするだけ。チームもインフラもこれならスケールするし、みんなの認知負荷も減るんだ。
「2. No visitor pattern. (See grug oriented development)」ってやつ、個人的にはすごく嫌なんだよね。自分が下手だからって思ってるけど。今度『grug』を読んでみるよ。あと、一行関数も大嫌いなんだ。
うちのチームは100%テストカバレッジ必須なんだけど、Foo, Bar, Bazってテーブルが80~90%共通ロジックを持ってても、共通の抽象コンポーネントにはしないんだ。3つのファイルとテストを維持してて、変更はコピペで済ませることが多いよ。共通化すると追加ファイルが増えて、変更が大変になったり、テストの連鎖更新が起きたりする可能性もあるからね。6年間動いてるコードベースを、ビジネス上の理由なしに書き換えるのは現実的じゃないってこと。
これ、マジで地獄みたいだね。
すべての関数がコメントを必要とするわけじゃないし、複雑さは発生した時に直せばいいんじゃない?複雑さを禁止するのはやめたほうがいいよ。
音楽やプログラミングって、自分が作ったものを「他人」の視点で見るのがマジで難しいんだよね。時間を置いて忘れるか、新鮮な視点をキープできるかが品質を上げるコツ。すぐに自分の作品に慣れちゃうと問題点が見えなくなるから。すぐに自分のコードを「新鮮な目」で読めるようになったら、それって最強のチートコードだよ。時間をかけて作ったものがダメで、即興が傑作になるのは、まさにその視点を維持できたからなんだ。
俺の若い同僚たちはみんな俺のキャッチフレーズを知ってるよ。「コピー・ペーストはタダ!抽象化は高くつく!」ってね。
Visitorパターンってさ、グラフの探索と処理を分けられるんだよ。パターンマッチングがある言語でもやっぱり必要になるんだ。
あと網羅性チェックも便利で、全部処理するか、興味ない部分はno-opにするか選べる。RustのコンパイラでもVisitorパターンが使われてる例がここにあるよ: https://github.com/rust-lang/rust/blob/64a99db105f45ea330473…
「if文の山」アーキテクチャって一見簡単でチケットも閉じやすいけど、本当はバグだらけでデータ漏洩のリスクもあるんだよね。
でも、企業ではビジネスロジックがコロコロ変わるから、ちゃんとした抽象化は難しい。結局、「if文の山」が最善策なのかな?って悩んでるよ。
ビジネスソフトウェアだと「if文の山」が結構最適解に近いと思うよ。
デカい変更より小さいルール変更が求められるしね。
古参のベテランが大事なコードをメンテするやり方、変に見えるけど、どこでもうまく機能してる。
最新システムへの刷新なんて、だいたい失敗してお金もかかるし。
PMが「複数住所配送できる?」って聞いたら、ベテランは「if文の山」だから2年以上かかるって。
PMは短い納期を迫って、ベテランはしぶしぶ応じて、結局プロジェクト途中で会社を辞めるんだ。
あるあるだよね。
ソフトウェアエンジニアリングのベストプラクティスって、ビジネスの無茶な要求の前には通用しないことが多いよ。
ビジネスのスピードと短期的な利益優先のせいで、まともな開発はほぼ無理で、if-elseのネストが必要になるんだ。
だから、ソフトウェアエンジニアリングとプロダクトエンジニアリングは別物として考えるべきだと思う。
「if文の山」の件なら「Big Ball of Mud」の論文[1]を読むといいよ。
システムって最初「Big Ball of Mud」から始まって、成長して、デザインも改善されるんだけど、変化のせいでまた壊れるんだ。
本番ソフトウェアは常に変わるから、ドメインモデリングと、そこそこの抽象化、建設的破壊で対応するのが仕事だよ。
詳しいブログ[2]もあるよ。
[1] https://laputan.org/mud/mud.html [2] https://swizec.com/blog/big-ball-of-mud-the-worlds-most-popu…
ビジネスロジックのオーナーがそれをケアしないって話だけど、実際は企業がそうさせてるんだよ。
エンジニアの在職期間は短いし、いろんなオーナーが変わる中でまともな引き継ぎもない。
数ヶ月前に来たばかりの君が、責任だけ押し付けられても何もできないよね。
同じ人が長く見るコードベースとは全然違うよ。
もっとコメントを表示(2)
「if文の山」って、まさにビジネスの取引そのものだよね。
変な条件付きの契約とか、全部if文の塊だよ。
ビジネスロジックのもう一つの良いモデルはスプレッドシートで、これはSQLがぴったりくる。
関数型とかOOPのモデルは、条件とかクエリを動かすための足場じゃないと、柔軟性がないんだよね。
この手の論文を書く人って、本当の「Big Ball of Mud」コードを現場で見たことないんじゃないかな?
「Big Ball of Mud」でいいって言う人は、ホントの泥沼を知らないんだよ。
俺が経験した真の泥団子は、全容解明だけで数ヶ月、改善にはチームで数年かかるレベルだった。
「if文の山」を理解するには真理値表を作るのがいいよ。
例えば5つのif文があるなら、5列32行の表で全パターンを洗い出して、どうなるべきか書き出すんだ。
if文が増えると組み合わせが爆発的に増えるけど、これなら全ケースをリストにできるから、コードの再構築もしやすくなるよ。
エンジニアの在職期間が短いのは、会社がクビにするんじゃなくて、社員が10%の昇給のために短いスパンでどんどん転職するからだよ。
この1週間Codex CLIで遊んでたんだけどさ、バグ修正するときに、そのバグ専用の特殊ケースをコードに追加しまくるんだよね。抽象化を促さないとパターンを認識してくれない。ただの“if”文を「ヒューリスティック」って言ってどんどん追加するだけ。10個のテストを直しても、同じバグの新しいテストは失敗するの。
多くのソフトウェアエンジニアリングの「ベストプラクティス」って、ビジネス要件にぶち当たると使い物にならないんだよね。現実では問題なんて最初から分かってないし、何を作るべきかも不明。ソフト開発は進化の過程だから、常に変化が必要なんだよ。自分のサイドプロジェクトでも、時間と共にアイデアや要件が変わるのを見ればわかるはず。
結局、問題の核心は現実世界とビジネスが本当にゴチャゴチャで、まさに“if”文の山だってことだよね。技術的な問題とか一般化できるなら抽象化で“if”文を減らせるけど、ドメイン自体が複雑で曖昧だと、抽象化もツールも柔軟性を組み込むことでしか助けにならない。だって矛盾が「バグ」じゃなくて「特徴」ってこともあるんだからさ。
無効なデータを表現できないようにすると、コードってすごくシンプルになるんだよ。最初に妥当性をチェックして、例えば5つのbool値が32通りのうち7通りの組み合わせしか有効じゃないなら、それを7要素のenumに変換しちゃう。そしたら後は網羅的な“switch”文で処理すれば、コンパイラが全部のケースをチェックしてくれるから、変更があっても安心だね。
良いソフトウェア設計ってのは、進化に対応しつつベストプラクティスを守ることなんだよ。でもさ、進化が無秩序で、一般化できない特殊ケースやルールが山ほど出てきたら、それが現実の「未知の未知」ってやつだね。そうなると、もう“if else”でパッチを当てるしかないんだ。
「泥だんごコード」が良いなんて思わないけど、それを管理したり、修正したり、拡張したりするのが仕事なんだよね。完璧なコードベースなんて理想論だし、現状動いてるものを動かし続けるのがエンジニアリング。サンフランシスコの水道管直すのに街全部作り直さないのと同じさ。ごちゃごちゃを管理可能に保つのが大事なんだよ。
「単一オーダーを複数アドレスに発送」みたいな要件変更って、別に問題じゃないんだよ。テーブルを追加してAPIを更新し、v2を公開すればいいだけ。問題なのは、みんなが安易な近道を選んで、ワークアラウンドだらけのシステムにしちゃうこと。抽象化は柔軟だから、必要に応じて変えればいい。変更が大変じゃないようにコードを設計するべきなんだ。
(前のコメントの例の続きだよ) PMが「2週間で複数アドレス配送、できる?」って言って、経験の浅い奴が「やります!」って答える状況ね。そいつは結局、古いコードに“if”文で新しいロジックをごちゃっと追加して、エラー処理も適当にしちゃうんだ。
“if”文だらけでもコードはシンプルにできるんだ。“if”文を上に持ち上げれば、可変性を一箇所にまとめて実装・文書化できるよ。入出力を正確にモデル化して、統一データ型はできるだけ後回しにしよう。クラスとかパターンなんて、本当に必要な時に使う最後の手段だね。管理が楽なら関数もファイルもいらないし、最近のフレームワークは無駄な分離が多いって思うな。
ビジネスソフトウェアの品質って、「泥だんごコード」かどうかじゃなくて、以下の3つで測られるんだ。
1. 次の要件変更にどれだけ早く対応できるか?
2. どれくらいバグが少ないか?
3. そのコードがどれだけ金を稼ぐか、節約するか。
良いアーキテクチャは1と2に影響するけど、LLMがコード書く時代になれば、その影響も薄れるだろうね。結局、一番大事なのは3なんだ。ソフト開発の「アート」なんて、ビジネスでは大して評価されないってことだよ。
新機能を追加するたびに、それまでの抽象化が台無しになるような機能の構築順序を慎重に選べるよ。
意図を明確にし、根底にあるドメインを説明することの方が、純粋な“抽象化”よりも価値があるのかもしれないね。
callNewModule
は奇妙なバグだらけ。特定の会社への発送でラベルが空になったり、請求書が複数生成されたり。セールスが売った機能が1年後に動かなくなり、未発送の在庫が増えまくる。Eager Beaverはもう会社を辞めてて、自分のコードは最高だったって自慢してるけど、火消ししてるのはGrey Beardだよ。PMに「こんな事態を防ぐためだった」と言っても信じてもらえないだろうね。
抽象化は、新しい目標や制約が入っても自然に進化するプログラムを作るツールになるよ。他のエンジニアもコードを上から下まで読まずに、高レベルで理解できるしね。コードが変わる可能性があるなら、抽象化なしで得た初期の成功はすぐに、利子付きで返済させられる。抽象化はシンプルさそのもの。経験豊富なプログラマーが最初から抽象化を用いるのは、それがメリットだと知ってるからだよ。専門家は、タスクごとに適切な抽象化のレベルを知っていて、いつ適用すべきかを理解している。だから、経験の浅いエンジニアには抽象化が不器用で遠回りに感じられることもあるんだ。
彼らはすごくヘッジして、一つのやり方を試してはエラーハンドラを用意し、また全然違うやり方を試すんだ。どれか一つが正しいはずなのに、気にしない。どのパスが実際に使われているかを確認して、他を削除するのに骨が折れるよ。こういう行動は、品質を無視して一発で修正できるSWEのベンチマーク最適化のせいだと確信してる。この文脈なら、こんなコードを書くのも納得だね。
「問題ないよ。注文に複数の住所を対応させたいだけなら、OrdersとShippingAddressesの間にリレーションシップテーブルを追加すればいい」って言うけどさ。各場所にどのアイテムがどれだけ出荷されるの?各サブ注文の履行状況は?実際にその住所に送るの?それともクロスドッキングのために箱を梱包してマーキングして、最終住所に近い地域DCに発送を分割するの?DBスキーマとコードで多くのことを更新する必要があるんだよ。これが良い例じゃないって思うなら、これは大手小売業者の実際の注文の例だからね。
OOPでもね…仮想関数は大量のif
文を引き継ぐから、if
文は仮想関数を持つクラスをインスタンス化する場所に移動するだけ。改善はあるけど、一つのクラスが多くの仮想関数を持てるから、同じ質問をする全てのif
文を、適切な仮想関数を持つクラスを作成する一つのif
文に置き換えられるんだ。クラスが複数の質問に対して仮想である必要がある場合は、もっと複雑になるけどね。
「不正なデータを表現できないようにすれば、多くのコードがシンプルになる」って言うけど、それは夢だよね。エラーハンドリングがそれを打ち砕くんだ :)