Makefileを便利コマンドメモとして使うことに対する違和感

Table of Contents

追記

この記事に対するyasunoriの補足記事はこちら。

https://blog.yasunori0418.dev/p/appendix-makefile/

Introduction

yasunoriがZennに書いた「職場のプロジェクトに必ず配置しちゃうMakefileの話」というのがバズっています。 「コマンドが覚え辛い」という、どのプロジェクトにもあるような問題を「Makefileをコマンドメモとして整える」ことによって解決するという記事でした。 SNSやvim-jp slackを見ていると賛同の声が多い印象を受けています。

https://b.hatena.ne.jp/entry/s/zenn.dev/loglass/articles/0016-make-makefile

私個人も過去に同様のMakefileを作ったことがあるし、現職でもMakefileが入っているプロジェクトがあるくらいにはよくある手法です。

しかし、この記事を読んだ時に自分としてはこの手法がイマイチだと感じました。 モヤモヤをちゃんと言語化した方がいいだろうということでこの記事を書いています。

決して元記事を批判するつもりはなく、こういう視点もあるよというくらいの温度感で捉えてほしいです。

Makefileをプロジェクトに入れたい時の前提整理

そもそも「誰のために」「どのプロジェクト規模で」「どういう問題を解決したくて」Makefileをプロジェクトに追加するものなのか整理する必要があります。

今回のユースケースでは、古き良きC言語をBuildする時の make && make install のようなタスクランナー的な使い方ではなく、元記事と同様プロジェクトのコマンドメモとしての使い方について言及しています。

Makefileの想定利用者

想定利用者のペルソナ像は主に4種類います。

1つめは、デザインや文言を修正したい非エンジニア寄りのデザイナーです。

役割はUI/UXデザイナー、またはマークアップ担当者で、技術スキルは低〜中程度を想定しています。 主な目的は、文言・デザインの軽微な修正を手元で確認し、FigmaやXDのようなデザインツール上ではなく「実際の画面」で表示を見ることです。 行動特性としては、GUIでの操作が望ましく、ターミナル操作も最低限であれば対応可能ですが、何度も環境構築の手順を覚えるのは非効率だと感じています。 主な課題として、環境構築に時間がかかること、依存関係が多すぎて途中で躓いてしまうこと、Node.js, Ruby, Pythonといった技術スタックの違いが分からない、といった点が挙げられます。

2つめは、そのプロジェクトに精通していないプログラマです。

役割は外部の協力会社のメンバー、派遣や業務委託のエンジニア、またはプロジェクトに参加したばかりの新人エンジニアを想定しています。技術スキルは中〜高程度です。 主な目的は、限られた期間や担当範囲の中で、与えられたチケットの実装や修正を効率的に行うことであり、プロジェクト全体の詳細な把握は必要最低限で済ませたいと考えています。 行動特性としては、ローカル環境の立ち上げに時間をかけたくないと感じており、覚えるべき新規のコマンドが多すぎると混乱してしまいます。 主な課題として、「.envファイルはどこにあるのか?」「どの順番で起動すればいいのか?」「フロントエンドだけでなくバックエンドも動かす必要があるのか?」といった疑問が次々と湧いてくる点が挙げられます。

3つめは、そのプロジェクトに精通しているプログラマです。

役割はリードエンジニア、メンテナー、あるいは主要な開発者であり、技術スキルは高いレベルにあります。 主な目的は、長期的な保守や機能の拡張や新機能の実装や大規模な構成変更など、プロジェクトの根幹に関わる作業することです。 行動特性としては、再利用性や抽象化といった概念に関心があり、開発フローの自動化や最適化にも積極的です。 主な課題として、コマンドが乱立して管理が煩雑になること、特定の担当者にしか分からない作業(属人化)が増えること、そしてドキュメントが古くなって陳腐化しがちである点が挙げられます。

4つめは、AI Coding Agentです。

役割はVibe Coding、その技術スキルは正確かつ高速な処理能力にあります。 主な目的は、指定された手順を正確に実行し、ソースコードのビルド、テスト、静的チェックといった成果物の検証作業を滞りなく行うことです。 行動特性としては、曖昧な指示に弱く、処理の中間ステップが明示的に定義されていないと失敗する傾向があります。また、コマンドの標準出力(STDOUT)が構造化されていることを期待する場合もあります。 主な課題として、コマンドの実行に失敗した場合でも、その背景にある詳細な文脈を読み解いてリトライしたり、原因を推測ができない点が挙げられます。

想定しているプロジェクト規模

当記事で想定しているプロジェクトは主に3種類あります。

プロジェクト規模コード行数特徴
小規模プロジェクト1万行程度数ページ程度のちょっとしたWebアプリケーション、単一言語、依存ツールチェインが少ない
中規模プロジェクト10万行程度複数機能があるWebアプリケーション、複数言語、依存ツールチェインや開発用コマンドがそれなりに多い
大規模プロジェクト100万程度大規模なWebアプリケーション、複数言語、依存ツールチェインや開発用コマンドが多い

Makefileで実行するコマンド

今まで見てきたコマンドは主に3種類です。

コマンド種別
マシン環境に依存するコマンドbrew install, npm install -g, docker compose up
プロジェクト環境に依存するコマンドnpm install, composer install
プロジェクト開発時に使うコマンドvendor/bin/phpstan, ./gradlew build

元記事の状況整理

元記事から次のような前提があるという認識を持っています。

  • 想定利用者
    • そのプロジェクトに精通していないプログラマ
  • Makefileで実行するコマンド
    • マシン環境に依存するコマンド
    • プロジェクト環境に依存するコマンド
    • プロジェクト開発時に使うコマンド
  • 想定しているプロジェクト規模
    • 中規模プロジェクト
    • 大規模プロジェクト

次の一文からも分かるように実際の利用者の職場の人は満足しているようです。

こういう改善をやると開発チームのSlackチャンネルでは、「これはアプノマ(Update Normal)だ!」と言ってもらえる素敵な環境です。

個人的な見解

まず大前提として、「実際の利用者が満足している」状態をどう作るかというのがもっとも重要であり、Makefileを入れることによって職場の人が満足しているのであればそれでいいし、素晴しいことだと思っています。

その上で、環境構築オタクとして「そもそもMakefileを作るのはイマイチである」と思っている点をひとつずつ整理していきました。 元記事の内容だけでなく、自分が過去に関わったプロジェクトのMakefileについても書いているのでそのあたりもご了承ください。

本来はREADMEを充実させるべき

規模や人にかかわらずすべてのユースケースでもっとも重要なことでここにもっとも力を入れるべきです。 何のミドルウェアに依存していて、何をどう実行するかを明文化してMarkdownで伝えることができます。

チーム全体での技術的共通認識が形成され、ブラックボックス化を防げるので属人化防止に役立ちます。 長期保守性・引き継ぎのしやすさが飛躍的に上がるのもいいですね。

Makefileに寄せてしまうとMakefileは文書に向いていないのでそもそもイマイチだし、READMEとMakefileで二重管理になるのもイマイチです。 特に元記事ではコマンドを @echo で出力しているので素直にREADMEに書けば良さそうに感じています。

「デザインや文言を修正したい非エンジニア寄りのデザイナー」に向けてならしょうがなく作る

Terminalに不慣れな人向けに make updocker compose up --build が動くようにすることは時々あります。

そもそも docker compose up --build などの複雑な文字入力をさせるのは困難な場合があるので、そういう時はしぶしぶMakefileを作って make up だけ叩いてくださいというのが早くて正確なのでしょうがなく作っています。 そういう人向けには可能な限りTargetを減らして最低限で提供したい所です。

「そのプロジェクトに精通しているプログラマ」はそもそもMakefileを使わない

大体の「そのプロジェクトに精通しているプログラマ」は自分の意図したコマンドを意識して細かく実行するので、変に抽象化されたMakefileは逆に足枷になりがちな印象があります。 Makefileのメンテナンスがおざなりになり、ちゃんと動くことを保証されなくなりがちです。

これはREADMEでも起こりがちな問題ですがREADMEを充実させる方が筋がよいだろう、というのが自分の意見です。

「マシン環境に依存するコマンド」をMakefileで実行することに対して違和感がある

次のようなコマンドをイメージしています。

install-tools: ## 開発ツールのインストール
        brew install postgresql redis minio awscli
        npm install -g @aws-amplify/cli

この手のMakefileを見るたびに次のようなことを考えています。

  • そもそもREADMEを充実させる方がよい
  • ワンショットで実行するものをいちいち載せたくない
  • マシンに依存するコマンドはプロジェクト固有のツールではない認識なので違和感を感じる
  • MiddlewareはDockerに寄せたい

Targetが多すぎる

複雑なコマンドの組み合わせを定義したいという動機は理解できるが、それが大量に存在している時点でプロジェクト構造やワークフロー自体に根本的な歪みがある可能性が高いように感じています。 「人が覚えきれないからMakefileに記述させる」というより、「本来、そんなに複雑であるべきではなかった設計をMakefileで補っているだけ」になっている危険性がありそうな印象です。

package固有script機能に寄せたい

npm scriptやcomposer scriptのように、現代のpackage managerにはscript機能が大体搭載されています。

ざっと次のようなメリットがあります。

  • 標準化されたスクリプト管理
    • すでに存在する package.json / composer.json に統合できる
  • 依存ツールとの連携がスムーズ
    • e.g.eslint, vitest, phpunit, phpstan
  • エコシステムとの親和性が高い
  • ドキュメントにせずとも npm run / composer run で一覧が見られる
    • シェル補完が効く
  • AIや新人でも認識しやすい

「小規模プロジェクト」ならpackage固有script機能に寄せられるはずです。

オレオレMakefileを書くと「大規模プロジェクト」のような多言語用のscriptを統合できるというメリットもあるが、基本的にはREADMEに書くだけでいいはずです。 必要に応じて、package固有scriptの組み合わせMakefileを作るのはいいでしょうし、そうなるとMakefileのTargetが最低限になるはずです。

正規表現はメンテナンス性が低い

Shell ScriptやAWKや正規表現は「書くのは速いが、読むのは苦痛」なものです。 最初からチームや将来の自分のために、npm/composerなどの構造化されたタスクランナーの標準に乗っかっておいた方が長期的にはずっと健全だと思っています。

AI Coding Agentフレンドリーじゃない

AI Coding Agentは構文解析と文脈理解に基づいてコードの提案・修正・生成していますが、これらのエージェントは主に構造化された明示的記法とツールチェインの規約・ドキュメントに依存します。 Makefileは伝統的なビルド定義ファイルであるが、DSLであり文脈が不透明になりやすく、推論には全体の文脈とMakefile知識が必要になります。

AIにとって解析しやすく、意図の推論や補完しやすい方がVibe Coding時代には合理的という認識です。

その他

org-modeで管理すればよいのでは?

「READMEを充実させる」というのが一番大事だと思っているので、READMEからコマンドを実行できること自体は理想だと思っています。 とはいえ、全員が全員Emacsを使っている訳ではないし、任意の環境で安全に実行できるのが理想なのでそこはorg-modeに寄せる必要はないですね。

Shell Scriptにした方がよいのでは?

前述のとおり、Shell Scirptは「書くのは速いが、読むのは苦痛」なものです。 しかも、テストも書きにくいのでメンテナンス性に難が有ります。

複雑なことをしないなら尚更READMEに書いておいた方がいいし、複雑なことをするならShell Scirptではなくフレームワークのコマンド作成や別の言語に寄せたいという感覚です。

まとめ

「本来はREADMEを充実させるべき」というのがAIにとっても新規開発者にとっても嬉しい施策だという理解です。 可能な限りREADMEなどのドキュメントをちゃんとメンテナンスしていきたいですね。