Makefile効果に注意
(blog.yossarian.net)- Makefile効果とは、複雑だったりなじみがなかったりするツールを一から使うのではなく、以前うまく動いた設定をコピーし、新しい状況に合わせて直すパターンを指す
- 問題解決の段階では、検証済みの例を再利用するため、バグのリスクを減らせる合理的なエンジニアリング上の対応になり得る
- しかし設計の観点では、ツールが最初から使いにくい、あるいは面倒で、コピーに変更が積み重なり続けるというユーザビリティ上のシグナルと見なせる
- Makeだけでなく、GitHub Actions、GitLab CI/CD、リンター・フォーマッター設定、ビルドシステムでも同様のコピー・修正の流れが見られる
- ツール設計者は、設定や独自文法が本当に必要か、なじみのあるCLIの慣用表現を再利用できるか、ユーザーがコピー&ペーストに依存するようになっていないかを確認すべきである
Makefile効果とは何か
- Makefile効果とは、一定の複雑さやなじみのなさを持つツールを新しく書くのではなく、過去の「うまく動いた」例をコピーして修正する現象である
- 典型的な流れは次の通り
- よくある形の作業を完了する必要があり、非常によく似た、または同一の作業が以前すでに実行されたことがある
- Makeや類似のツールが、その作業に適したツールとして選ばれる
- エンジニアは新しい
Makefileを書く代わりに、以前の作業のMakefileをコピーし、新しい環境で動くまで修正する
- Makeは代表例にすぎず、チームや個人によって習熟しているツールは異なるため、ある種のツール群はこの効果の影響を受けやすく、別のツール群は相対的に受けにくい
- 「heritage
Makefile」という冗談は、シニアエンジニアや教授などから受け継いだMakefileが、小さな修正だけを経ながら受け継がれ続ける状況を指す
繰り返しコピーが示すツール設計上の問題
- 目の前の問題を解決しなければならない時点では、動く例の再利用は簡潔な選択になり得る
- 作業の大部分は変わらないため、理論上は新しいバグを生みにくい
- Makefile効果は本質的に悪い、あるいは非効率な現象ではない
- 設計の時点では、ツールやツールの適用方法が複雑すぎる、または面倒すぎるというシグナルになる
- ユーザーは問題を一から解くより、既存の解法を繰り返しコピーする
- 単純な適用例でこの効果が現れるなら、その用途に対してツールが過度に複雑である可能性がある
- このパターンは、Make以外の設定中心のツールにも現れる
- CI/CD設定では、GitHub ActionsやGitLab CI/CDのYAMLを最後に動いた設定からコピーし、繰り返し実行しながら直す
- リンターやフォーマッターの設定は、基本ルールのまとまりをプロジェクト間でコピーし、ローカル条件に応じて強めたり緩めたりする
- ビルドシステムの自明でない構成は、以前のビルドシステムに似ていきやすい
学習、デバッグ、セキュリティに生じるコスト
- Makefile効果が強いツールは、広い学習を妨げる可能性がある
- 少数の熟練者がツールを設定し、残りの人は必要な部分だけを理解したままコピーして修正する
- ビルドシステムの依存関係グラフは本質的な複雑さだが、Makeで
$<と$^の違いを覚えなければならないことはそうではない
- 診断とデバッグ支援が不足していると、ユーザーはツールを何度も実行し、わずかな手がかりだけを得る方法に頼るようになる
- CI/CDでは、ネットワークと中間VMのオーケストレーション層を経由して、print式のデバッグをすることになる
- セキュリティ面でもリスクが大きくなり得る
- セキュリティ対策では、動作の「なぜ」を深く理解しなければならない場合が多い
- Makefile効果が現れるシステムは、コードとデータの混同、または一般的な**インバンドシグナリング(in-band signaling)**の混同を許している場合が多い
- GitHub Actionsのtemplate injectionが代表例である
ツール設計者が確認すべき質問
- 新しいツールを作るときは、ユーザーが一から書けるのか、それとも「動く例」を探してコピーしなければならないのかを見極めるべきである
- 確認すべき質問は次の通り
- 設定可能性は本当に必要か
- 独自文法は本当に必要か
- 他のツールやCLIのなじみのある文法や慣用表現を再利用できるか
- 作った本人も使い方をコピー&ペーストすることになるのか。そうなら、他のユーザーもそうする可能性があるのではないか
- 「cargo cult」という用語については、Ken Shirriffの記事が、不適切な専門用語であるという問題を扱っている
1件のコメント
Hacker News の意見
「動作する複雑なシステムは、例外なく、動作していた単純なシステムから進化してきたものだと分かる。最初から設計された複雑なシステムは決して動作せず、修正して動作させることもできない。動作する単純なシステムからやり直さなければならない」
— John Gall (1975), Systemantics: How Systems Really Work and How They Fail
https://en.wikipedia.org/wiki/John_Gall_(author)#Gall's_law
たいてい特殊ケースを処理するために継ぎはぎのような機能が付き始め、それは今後もそうした例外が際限なく増える兆候に見えます
「経験を適用すること」と「個人的に気に入らない一点を中心に、まったく新しいシステムを設計すること」の間には、微妙ですが重要な違いがあります
別の仮説があります。開発者のおよそ**10%は実際に原理を理解していて、何かをゼロから始められます。さらに40%**はローカルのコード、Stack Overflow、GitHub、LLM からコピー&ペーストしながら、おおよそ何が起きているかは分かったうえで日常業務をこなせます
残りの50%は LeetCode のパズルをいくつか解く以上のことはよく分かっておらず、自分がコピー&ペーストしているものについての実際の理解も不足しています
こうした分布なら、Makefile の半分以上は、なんとなく動くコピー&ペーストの断片である可能性が高いでしょう。すでに動いていた場所から持ってきたなら、とりあえず仕事は終わり、次のチケットへ進みます
ツール自体を責めているわけではありません。Makefile はよく知られていて、小さなプロジェクトでは冗長でもありません。10,000ファイル規模の怪物には悪い選択かもしれませんが、巨大なプロジェクトでもきれいな Makefile を見たことはあります。個人的には第一候補ではないでしょうが、Makefile は好きですし、30年以上断続的に使ってきました
たとえば C++ プログラマーがチケットを処理するとき、スタックの何層目まで知っている必要があるのでしょうか。CPU レジスタ名まで知る必要があるのでしょうか。Jupyter しか使わない AI 研究者はどこまで知るべきなのでしょうか。ツールやスタックをできるだけ学ぶことは推奨されますが、時間は限られています
Makefile で単一ファイルを1つ作るための個別ルールを見かけたら、ほぼ常に間違いだと思います
通常は、特定タイプのすべてのファイルをビルドするために使う汎用ビルドルールだけがあるべきです
Makefile にはソースファイル一覧や依存関係一覧はほとんど入れるべきではなく、ソースファイルがあるディレクトリ一覧だけがあれば十分です
Make がソースディレクトリを検索してファイルを見つけ、タイプ別に分類し、依存関係一覧を作り、適切なビルドルールを呼び出すべきです。少なくとも GNU make では非常に簡単で、ユーザーマニュアルにも載っています
こう書けば、プロジェクトが1ファイルでも10,000ファイルでも、Makefile を作成・修正する労力は同じように無視できる程度です。ソースファイルが作成・リネーム・移動・削除されるたびに Makefile を更新する必要もありません
言語やフレームワークごとにビルドの「手順」が違い、全部は覚えられないからです。でも
$ makeは簡単です多くの生産的なアプリ開発者が新規プロジェクトをゼロから設定できないかもしれない、という点には同意しますが、それは深い理解というより、複数のツールをうまく噛み合わせるための正しい魔法の規則や呪文を知っているかどうかの問題であることが多いです
職場で見たもう一つの要因は、開発者がいつかは扱わなければならないが、普段から学ぶ価値は低いと感じているツールやシステムである
例えばビルドシステムや CI 設定だ。必須ではあるが、開発者はそれを毎日扱うべきものだとは考えていない。CI は「設定して忘れる」システムのように見なされ、アプリをビルドするためにこれを全部学ばなければならないのか、という反応が出る
開発者は「ただ動く」ことを期待し、複雑さがあるなら別のチーム、つまり私のような役割の人が処理するものだと見なす。だから開発者がこのシステムに触るたびに、最後に動いていた設定をコピーして受け流し、「本当の」仕事に戻ろうとする動機が強い
最善の解決策は、開発者と中間地点で折り合うことだ。作業に合った単純または複雑なツールを提供し、ドキュメントを整え、「魔法」だという思い込みを最小限にする必要がある。Make のようなツールは複雑すぎ、ブラックボックスのように感じられるため、この点で失敗しがちだ
そのためフィードバックループが非常に長くなり、作業そのものが大きな苦痛になる
開発者が「CI/CD」作業の一部または全部をローカルで実行できれば、見て、制御し、理解できる。「CI/CD は別世界のもの」という感覚ではなく、所有感と安心感を与えるうえで大いに役立つ
いつもうまくいくわけではない。2人チームが薄いラッパーの CI/CD を持っていたにもかかわらず、異星のプロセスであるかのように扱って触るのを拒んだこともあった
同様に、CI には所有権が必要であり、最後に CI を使おうとした開発者に責任が降りかかるやり方は効率的な作業方法ではない
解決策は、大企業が使っているという理由だけで大企業の使うツールを持ち込まないことだ。不人気な意見だろうが、CI/CD は使われている大半の場所では不要だと思う
手順が少し増えたとしても、可動部品を絶対的に最小限にしてビルドとデプロイを行う方法を見つけるべきだ。そのうえで、どの部分を自動化するかのコストを慎重に見積もる必要がある。単純なことをするために大きなシステムを導入するのは、長期的にはたいてい価値がない
本当に CI/CD が必要なら、痛点があるので分かる。そのシステムが開発者に苦痛を与えるなら、それは適切なツールではない
makeが「複雑すぎてブラックボックスのようだ」と感じられるなら、cmakeはまだ見たことがないということだMake と Makefile は、autoconf が自動生成したものでない場合には非常に単純だ。autoconf が作ったものなら修正すべきではなく、それはビルド成果物である。可能なら autoconf も捨てたほうがよい
より広く見ると、この効果は実際に存在する。それにやられることもできるし、活用することもできる。活用する方法はこうだ。コードを少し書くか、どこかからコピー&ペーストし、プロジェクトで使ってみて、必要に応じて整える。次のプロジェクトを始めるとき、そのコードを再びコピーして入れ、2つ目のプロジェクトに合わせて修正する。変更を元のプロジェクトにも戻して適用できるか確認する。両方が動き、同期できたら、そのコードを抽出してライブラリにする。時にはライブラリらしい形に洗練するまでに、さらに多くのプロジェクトが必要になる。最良の場合はオープンソースとして公開し、他の人にも使ってもらう
Git は内部的にタイムスタンプを記録しないため、内容が同じでもファイルの mtime が更新されることがあり、実際によくそうなって不要な再ビルドを引き起こす
入力や出力がディレクトリ全体である現代的なツール、あるいは実行前には出力名が分からないツールも、まったく適切に扱えない
make は好きだし、欠点があってもうまく使ってきた。回避策と、その回避策の回避策まで知っている。だがツールが make の期待と完全には合わなくなり始めると、正しい Makefile を作る難易度と複雑さは急速に跳ね上がる
冗談ではない。
make cleanという概念が存在すること自体が決定的な証拠だMakefile は、おそらく不適切な比喩かもしれない。ほとんどの人が Makefile で直面する問題は、書く頻度が低すぎることにある。make が何をするかについての一般的な感覚はあるが、ゼロから書く筋肉記憶はない
それでも要点は合っている。Web からコピーしてきたコードに、実際には使われていないデッドコードが大量に残っているのをよく見る。良い習慣は、壊れるまで削り続け、壊れたその部分だけを戻すことだ。できる限り多く削るべきであり、現在使っていないものは確実にすべて削るべきだ
これまでの解決策は、こうしたツール、ライブラリ、フレームワークに関するメモとシェルスクリプトのサンプルプログラムを集めた別リポジトリを作ることだった
ツールを再び使う必要があるたびにメモを参照して記憶を呼び戻し、新しく学んだことがあればメモを更新する。今では LLM にそのリポジトリを示して質問することもできる
そのリポジトリは個人用で、公開されて入手可能なツールに関する情報を含む。組織固有の知識は似ているが別のリポジトリに保管し、顧客や雇用主との契約が終わったら破棄する
これを最もよく表す言葉はカーゴカルト開発だと思う。カーゴカルトは第二次世界大戦中の太平洋で生まれたもので、先住民の島民たちが、食料や酒、物資を積んでやって来る奇跡のような飛行機を見て、兵士たちの行動をまねし、竹の飛行機やココナッツ製の装置で神々を感動させ、物資が再び来るよう祈ったことに由来する。
問題は、島民たちが飛行機、無線機、銃のようなものの科学を理解していなかった点にある。
同じように、カーゴカルト的な開発者は起こり得る結果は見るが第一原理は理解せず、技術の高位聖職者たちがする行動をまねれば成功をコピーできると望む。
そのため、コピー、貼り付け、試行、いじり回し、検索、pull、push、調整を繰り返し、今度こそ火曜の夕方の特定データだけでも何となく動いてほしい、ということになる。
実際には、それ自体では効果のない動作や儀式を繰り返し、同じ儀式を行った別の会社が得た結果を望むものだ。
元記事で言っていることは違う。コピーされる対象であるコードは、それ自体に効果がある。テストして、動くかどうか判断できる。
この区別は重要だ。元記事の Makefile 効果とカーゴカルトの症状は異なるので、両者を同一視すると診断が難しくなる。カーゴカルトでは、迷信のせいで実際には機能しないことに時間を浪費する。Makefile 効果では、動くことは証明されているが、痕跡器官のような断片がコピーされ、徐々に保守しにくくなっていく。
具体的には、これがカーゴカルトとまったく同じだとは思っていない。カーゴカルトは理解不足を含意する。しかし、あるシステムをよく理解していても、そのシステムの内在的な複雑さがコピー&ペーストを誘発するため、主にそのようにして生き延びることはあり得る。それがこの記事の核心だった。
たいてい内部で何が起きているかは理解しているが、その「モード」へ切り替えること自体が認知的に重いので、できれば避ける。
ここに入るツールは CI/CD、k8s、Docker のように複雑さは非常に大きいのに、自分が解いている中核問題ではない運用系ツールが多い。Make は特にコンテキストスイッチを避けにくいので、最近は単に避けるようにしている。
魔法の呪文とは関係なく、自分がしているトレードオフは分かっている。それでも Makefile 効果に変わる危険はある。
「Makefile 効果は、カーゴカルト、逸脱の正常化、『書き込み専用言語』など、ほかの現象に似ている。本記事では、これが本質的に非効率または悪であるわけではなく、特定の設計の結果を扱う点でそれぞれと少し異なる、と主張する」
では、誰かが本当に「島を脱出して飛行機を操縦」したいなら、結局、大学が最も「本物」に近い道だということなのだろうか。
絶対的には言いにくく、例外も多く、人それぞれ「自分にはそれはできない」という正当化がさまざまにあることは分かっている。ここでは最適理論の観点で尋ねている。カルト的な行動を避ける最適な経路は全体を理解することであり、その「全体」は高等教育から来るのではないか。
論理的には、本で真面目に勉強して大学の「完全性」に追いつく、あるいはそれを超えようとしても、知識ある教育者と教室で過ごす時間に比べれば、ある面では不足するように見える。同じ人がどちらでも同じように熱心に取り組むと仮定してもそうだ。
もう一つの要因は使用頻度だ。LaTeX で大きな文章を書くのは年に一度以下だ。
自分が使うレベルの LaTeX は難しいツールではないが、年に数週間しか使わないと使い方の細部を決して覚えていられないので、たいてい以前の文書をコピー&ペーストして新しい文書を始める。
しかし LaTeX は以前かなりたくさん使っていたにもかかわらず、いつもテンプレートからコピー&ペーストしていた。beamer の発表や TikZ の図はさらにひどく、テンプレートよりも以前の発表や図からコピーしていた。
TikZ はツール自体が本質的に複雑で、自分が十分に学んでいないのだと思う。
LaTeX は十分に学んだと思っているので、別の理由があり得る。
自分の考えでは、まともなデフォルトの問題かもしれない。良いツールは良いデフォルトを提供すべきだ。しかし LaTeX はこの基準では良いツールではない。自分の文書はだいたい次のように始まる。 大半は、母語に必要な基本的な非 ASCII サポートを得たり、A4 用紙、microtype のようなまともなデフォルトを有効にしたりするためのものだ。pandoc/Markdown のような現代的なツールなら不要かもしれない。
だから、こうしたものをコピー&ペーストする目的は、より良いツールなら最初から提供していたはずの良いデフォルトを得ることにある場合が多い。
数式を書かない、または英語のプレーンテキストだけを書くのでないなら、LaTeX は「電池は付属していません」に近い。
これはコピペ主導開発であり [0]、Makefile だけに関する話でもありません。業界全体が、自分が何をコピーしているのかも分からないまま、あちこちにコードをコピーしていることに関係しています。
正直なところ、Copilot はこれをさらに悪化させたと思います。コードベースにコードの塊を目をつぶって受け入れているからです。
[0] https://andrew.grahamyooll.com/blog/copy-pasta-driven-develo...
makeの専門家になろうと、おそらく少なくとも7回は続けて挑戦しましたが、完全に覚えるまで毎日触る時間を与えられたことはありませんでした。ある時点で、ただ諦めました。筋肉記憶を作れず、必要になるたびに最初から学び直さなければならない難解な秘伝知識になってしまったのです。そこで、より単純なツールへ移りました。
深い作業の喪失は、優秀なプログラマーのせいではなく、ビジネス側の人たちのせいです。
他のプロジェクトから Makefile をそのままコピーしたあと、「動く」ように修正しながら、まったく関係のない不要なビルド手順やターゲットをそのまま残している例を、あまりにも多く見てきました。
個人的にはかなり気になる点です。
試行錯誤で? では、楽しんでください :p
必要以上に複雑なツールが多いのは確かですが、Make 自体はその例として適切ではないと思います。
現代のツールでは、複雑さの問題が、不十分なドキュメント、レシピ集のようなドキュメント、ツールがどのように動作するかを推論するために必要な概念モデルを説明しないドキュメント、十分に詳細で正確な仕様がないドキュメントによって、さらに悪化することがよくあります。
Make はそうではありません。ドキュメントはよく整備されており、実際にそれを読む時間さえ取れば、理解するのは難しくありません。
上のようなレシピ集的なアプローチは、自分が使うツールを学び理解すること、そしてそれを可能にする徹底したドキュメントの重要性を軽視する文化につながります。あるいは因果関係は逆かもしれません。
いずれにせよ問題は、包括的で最新のツール文書を作ることにかける時間が少なすぎること、そもそもそうした文書化が可能になるようにツールを設計することも不十分であること、そして必要なツールを教え学ぶためのリソースの割り当てが少なすぎることにあると思います。