https://sizu.me/podhmo/posts/tokfh2r77htd の第2版
日々の開発業務や個人プロジェクトの中で、「この繰り返し作業、何とかならないか」「この情報を手軽に確認できるコマンドがあれば便利なのに」と感じることは、多くの開発者にとって共通の経験ではないでしょうか。そうした小さな課題意識は、しばしば自作のコマンドやスクリプトを作成する動機となります。本稿では、そうした「ちょっとしたツール」が、開発者の思考や技術の進化に伴い、どのような変遷を辿るのか、そして現代の技術がそのプロセスにどのような影響を与えているのかを、ある架空の開発者の内省を追体験する形で探求していきます。
かつて、そうした「ちょっとした自動化」の欲求を満たす主要な手段は、シェルスクリプトでした。grep
、awk
、sed
、find
といったUnixコマンド群は、まるで魔法の杖のように機能し、それらをパイプラインで繋ぎ合わせることで、驚くほど多くの処理を実現できました。特に一行で完結する「ワンライナー」は、その簡潔さと即時性から、多くの開発者に愛用されたものです。
このワンライナーという形式は、単に効率的であるだけでなく、コミュニケーションツールとしても機能しました。GitHubのIssueやチーム内のチャットで「このコマンドで解決できるよ」と共有する際、コピペしてすぐに試せる手軽さは、技術的なアイデアの伝達を円滑にしました。そこには、Unix哲学の「小さく、鋭く、うまく協調する道具を作る」という思想が色濃く反映されていたように思います。
しかし、ワンライナーの魔法も万能ではありませんでした。処理が少し複雑になると、一行に収めるための技巧が可読性を著しく損ねたり、そもそも一行では表現しきれなくなったりします。そうなると自然と、複数行にわたるシェルスクリプトを書くことになります。
さらに、データ構造の扱いや、より高度な制御フロー、エラーハンドリングなどを考えると、シェルスクリプトだけでは力不足を感じる場面も増えてきます。この段階で多くの開発者が次に目を向けたのは、Pythonのような汎用スクリプト言語だったのではないでしょうか。Pythonであれば、豊富な標準ライブラリや扱いやすいデータ構造、そして比較的平易な文法で、シェルスクリプトでは煩雑だった処理もすっきりと記述できます。こうして、特定の目的を持った「1ファイルのPythonスクリプト」が、手元のツールボックスに増えていくことになります。
これらの1ファイルスクリプトは、特定の作業を自動化する上で非常に有用でした。しかし、それらが増えてきたり、少し凝った機能を追加しようとしたりすると、新たな課題も見えてきました。例えば、外部ライブラリへの依存をどう管理するか、異なる環境でも同じように動作させるにはどうするか、といった問題です。
そんな折、Denoのような新しいJavaScript/TypeScriptランタイムが登場しました。これは、従来のNode.jsエコシステムが抱えていたいくつかの課題に対する一つの答えを提示しているように見えました。TypeScriptを標準でサポートすることによる型安全性の向上、デフォルトでセキュアな権限モデル(ファイルアクセスやネットワークアクセスに明示的な許可が必要)、そしてURL経由で直接スクリプトを実行・インストールできる (deno install
) という手軽さは、特に小さなコマンドラインツールを作る上で、非常に魅力的に映ったのです。
かつてシェルスクリプトで四苦八苦しながら書いていた処理、あるいはPythonで書いていたものの依存管理や配布に少し手間を感じていたようなツール、例えば「gitのコミット履歴から最新の更新日時を取得し、特定のディレクトリ構造をtree
コマンドのように表示する」といった類のコマンドを、Denoで書けばもっと楽に、そして堅牢に作れるのではないか。そんな期待感が、新しいツールへの移行を後押ししました。
もちろん、Pythonも依然として強力な選択肢です。しかし、Denoの持つ、権限を明示的に絞れるというセキュリティ思想や、ビルドプロセスなしにTypeScriptを直接実行できるシンプルさは、日々のちょっとしたツール開発におけるフットワークの軽さに繋がりそうだと感じられました。
Denoを使い始めると、その快適さの一方で、ある種の「こだわり」が再び頭をもたげてきました。それは、「可能な限り1ファイルで完結させたい」という、かつてPythonスクリプトでも抱いていた思いの延長線上にあるものでした。
ここで言う「こういうやつ」とは、やはり汎用的なライブラリや大規模アプリケーションではなく、あくまで「使い捨て」に近い感覚の、しかし手元にあると日々の作業が捗るような、比較的小さなコマンド群です。なぜ1ファイルにこだわるのか。それは、シンプルさを極限まで追求したいからです。ファイルが分割されると、それだけで管理の対象が増え、全体像の把握が少し煩雑になります。小さなツールであればあるほど、その構成要素は少ない方が良い、という考えです。
このこだわりは、依存関係の扱いにも強く影響します。理想は、依存するパッケージは外部からURLで直接、かつ個別に指定し、それ以外の暗黙的な依存は極力持ち込まない、という状態です。特に、他のライブラリが間接的に引き込んでくる依存(いわゆる推移的依存)には敏感になりがちです。意図しない依存が紛れ込むと、まるで清流に濁りが生じたかのような不快感を覚えることすらありました。package-lock.json
や yarn.lock
が肥大化していくのを見ると、少なからず憂鬱な気分になった経験は、多くの開発者にあるのではないでしょうか。
この段階では、deno.json
のようなプロジェクト設定ファイルすら、可能な限り作りたくないという気持ちがありました。スクリプトファイルそのものが全ての情報を内包し、それ以外の付随物はない、というミニマルな状態が理想だったのです。
しかし、1ファイル主義も絶対ではありません。スクリプトが少しずつ機能を追加し、「成長」してくると、どうしても1ファイルでは見通しが悪くなってきます。ロジックが複雑化し、コードの行数が増えれば、可読性や保守性の観点から、ファイルを分割したり、より体系的なプロジェクト構造を導入したりする必要性が出てきます。
この段階で、改めて言語やツールの選択について再考することになります。Denoのままでももちろんツールを成長させることは可能ですが、例えば「複数ファイルで構成される、もう少し本格的なスタンドアロンツール」という段階になった時、Go言語のような選択肢が俄然魅力的に見えてきます。Goの強みは、何と言ってもシングルバイナリとしてコンパイル・配布できる手軽さ、比較的シンプルな依存管理 (go.mod
)、そして強力な標準ライブラリです。npm
エコシステムのような複雑な依存の連鎖や、それに伴うnode_modules
ディレクトリの肥大化といった悩みから解放される可能性は、大きな誘因となります。
かつては、一度選んだ言語でじっくりとツールを育てていくのが正道だと考えていたかもしれません。しかし、最近では「状況やツールの特性に応じて、より適した別の言語に書き換える」という選択も、現実的な視野に入ってくるようになりました。それぞれの言語やエコシステムには、固有の強みと弱みがあります。ツールの成長フェーズや目指すゴールに応じて、最適な道具を選び直す柔軟性は、現代の開発においてますます重要になっていると感じます。OCamlのような、よりアカデミックな背景を持つ言語に惹かれる時期もありましたが、この種のコマンドラインツールにおいては、開発環境の構築やインストールの手軽さが、実用面で大きなウェイトを占めることを痛感させられました。
この「別言語への書き換え」に対する心理的なハードルや、そもそも新しいスクリプトを一から書き始める際の初期コストを劇的に下げた要因の一つとして、生成AIの進化は見逃せません。以前は、異なる言語へのコード移植は相応の時間と専門知識を要する骨の折れる作業でしたが、現在ではAIに既存のコードと移植先の言語を指示するだけで、驚くほど質の高い翻訳結果を得られるようになってきました。例えば、Pythonで書かれたプロトタイプをDeno/TypeScriptへ、あるいは古いシェルスクリプトをモダンなGoのコードへと変換する作業が、以前とは比較にならないほど効率化されています。
さらに、開発の初期段階、つまり「こんな感じのスクリプトが欲しい」というアイデアを具体的なコードに落とし込むプロセスにおいても、生成AIは強力な助っ人となります。かつてホワイトボードにラフなスケッチを描くように、大まかな仕様や期待する動作をAIに伝えるだけで、DenoやPythonの1ファイルスクリプトの雛形がものの数分で生成されることも珍しくありません。冒頭で触れたような「gitの更新日時とtreeを組み合わせたコマンド」といったものも、まさにAIに大まかな要件を伝えて初期バージョンを生成させ、そこから手直しを加えていく、という開発スタイルが現実のものとなっています。
この変化は、「とりあえずワンライナーで試してみる」というフェーズから、「まずはAIに叩き台となる1ファイルスクリプトを作らせてみる」というフェーズへの移行を加速させています。アイデアを素早く形にし、実際に動かして試行錯誤するサイクルが短縮されることで、より本質的なロジックの検討や、ツールの使い勝手の改善に注力できる時間が増えたと言えるでしょう。
ツールの開発において、依存関係の管理は永遠のテーマの一つです。特にJavaScript/TypeScriptのエコシステムでは、npm
を介して膨大な数のパッケージが利用可能である一方、その利便性と引き換えに、意図せず依存関係が複雑化・肥大化しやすいという課題も抱えています。ほんの小さなユーティリティを試しただけなのに、package-lock.json
が数千行、時には1万行を超え、node_modules
フォルダがギガバイト級のサイズに膨れ上がるといった状況は、多くの開発者が一度は頭を抱えた経験があるのではないでしょうか。
「依存関係は lock
ファイルでバージョンを固定すれば再現性は担保できる」というのはその通りです。しかし、そもそもそんな巨大な依存ツリーを持ち込みたくない、というのが偽らざる心情です。依存が増えれば増えるほど、潜在的なセキュリティリスクの調査範囲は広がり、ビルド時間や最終的な成果物のサイズにも影響が出ます。依存関係を小さく、クリーンに保つ努力は重要ですが、そのためにはエコシステム全体に対する深い理解や、個々のライブラリが抱える推移的依存に関する知識が求められることもあり、その学習コストに二の足を踏むこともあります。
Denoの jsr:
スキームによるバージョン管理された標準モジュールや、URLインポートによる依存の明示性は、この問題に対する有望なアプローチの一つです。また、Goのモジュールシステム (go.mod
) も、比較的シンプルで予測可能な依存管理を提供しており、意図しない依存の爆発を避けやすい設計になっています。こうした「依存の複雑さ」をコントロールしやすい仕組みは、ツール選定において非常に重要な判断基準となります。
これまでの思索の旅路を振り返ると、ある開発者が手元で作成するコマンドラインツールが辿るかもしれない、典型的な進化のステップが見えてきます。
-
アイデアの萌芽とワンライナーでの試行:
- 目的: 特定の繰り返し作業の効率化、即時的な情報確認、アイデアの検証。
- 手段: シェルコマンド (
grep
,awk
,find
,git
など) をパイプラインで接続。 - 特徴: GitHub Issueやチャットでの共有が容易。その場限りの使い捨てが多い。
- AI活用: 「こういうことをしたい」という自然言語での指示から、ワンライナーの候補を生成。
-
1ファイルスクリプトへの昇華:
- 目的: ワンライナーでは複雑すぎる処理の構造化、定型作業のより堅牢な自動化。
- 手段: Python、Ruby、あるいはシェルスクリプト(複数行)。
- 特徴: 単一ファイルで完結。設定ファイルや複雑な依存は避ける傾向。
- AI活用: ワンライナーや簡単な仕様記述から、基本的な1ファイルスクリプトを生成。
-
モダンなランタイムへの移行と洗練:
- 目的: 型安全性、セキュリティ、より良い開発体験、依存管理の改善。
- 手段: Deno (TypeScript)、あるいはGoの初期検討。
- 特徴: ここでも1ファイル主義や依存最小化へのこだわりが強く出る。
deno.json
やgo.mod
はまだ導入しないか、最小限に留める。 - AI活用: 既存のPythonスクリプト等をDeno/TypeScriptへリファクタリング、あるいは新規生成。
-
本格的なツールとしての構造化:
- 目的: 機能追加に伴う複雑性の増大への対応、再利用性、保守性の向上、テストの導入。
- 手段: Denoプロジェクト (with
deno.json
)、Goプロジェクト (withgo.mod
)。 - 特徴: ファイル分割、ディレクトリ構造の導入。依存管理ファイルを積極的に活用。ユニットテストやインテグレーションテストを記述。
- AI活用: プロジェクト構造の提案、boilerplateコードの生成、テストケースの雛形作成、既存コードのリファクタリング支援。
-
共有と配布のための整備:
- 目的: 他者との共有、チームでの利用、バージョン管理、継続的な改善サイクルの確立。
- 手段: GitHubなどのバージョン管理システムへの登録。
- 特徴:
deno install
、go install
、あるいはクロスコンパイルされたシングルバイナリの配布など、他者が手軽にインストール・利用できる手順を提供。READMEや利用方法のドキュメントを整備。 - AI活用: READMEやAPIドキュメントの雛形生成、リリースノート作成支援、インストールスクリプトの提案。
このステップは必ずしも線形に進むわけではなく、ツールの性質や開発者の好みによって、途中のステップが省略されたり、順番が入れ替わったりすることもあるでしょう。
小さなシェルスクリプトから始まる道具作りの旅は、おそらく終わりがありません。日々の業務で直面する新たな課題、利用可能になる新しい技術、そして開発者自身のスキルセットや設計思想の変化に応じて、作り出すツールの形も、その開発プロセスも、絶えず変化し続けます。
Unix哲学が説く「一つのことをうまくやる、小さく鋭い道具を作り、それらを組み合わせてより大きな問題を解決する」という思想は、形を変えながらも現代のソフトウェア開発において依然として強力な指針を与えてくれます。その普遍的な知恵を胸に刻みつつも、DenoやGoのような現代的なツールが提供する新しい可能性、そして生成AIのような革新的な技術がもたらす開発効率の飛躍を、恐れることなく柔軟に取り入れていく。そうすることで、私たちはより快適に、より創造的に、日々の課題解決という名の冒険に挑むことができるはずです。
結局のところ、最も大切なのは、自分自身にとって最も生産性が高く、そして何よりも「作っていて楽しい」と感じられる開発スタイルを見つけ出し、それを磨き続けていくことなのかもしれません。この、ある開発者の内省を辿る旅が、読者の皆様自身の道具作りや技術選択における、何らかの気づきや共感、あるいは新たな視点を発見する一助となれば、これに勝る喜びはありません。
うーん、前のほうが良かったかな。まだ。意図にそぐわない全体的な編集が加わってる。導入も嬉しくない。