はじめに
「UNIX 哲学 (Unix philosophy)」とは、一つの大きなシステムを、独立した小さなソフトウェアの集まりとして作るという考え方です。UNIX のように大きく複雑なものをシンプルに作るための考え方で、技術的な用語で説明するならば、大きなシステムをモジュール化された構成可能なプログラム設計で開発するということです。
UNIX 哲学に公式の定義は存在しません。ケン・トンプソンを始めとする UNIX の創始者が UNIX の開発を通して示したソフトウェア開発の考え方が UNIX 哲学と言われるようになり、それを他の人が独自に解釈して解説したものが UNIX 哲学として知られています。UNIX 哲学と呼ばれているものが複数あって、それぞれで異なっているのはそのためです。UNIX 哲学の本質的な考え方は今も通じるものですが、これまでの UNIX 哲学の解説の多くは古い技術を元に解説されており誤解を招くものになってしまっています。この記事は古い UNIX 哲学の解説を更新し、現代のコンピュータ技術、ソフトウェア技術に対応させ 1980 年代の古い時代遅れの UNIX 哲学を現代に蘇らせる試みです。
UNIX 哲学としてよく知られているものには、古い順に「マキルロイの UNIX 哲学 (1978)」「ガンカーズの UNIX 哲学 (1994)」「レイモンドの UNIX 哲学 (2003)」の 3 つがあります。これらは UNIX の別名とも言えるソフトウェアツールを例に UNIX 哲学の考え方を解説したものです。この記事では過去の解説を踏まえて 3 つの UNIX 哲学を「ソフトウェアツールの思想」「ソフトウェアツールの教義」「ソフトウェアツールの開発原則」と読み替えて、よく知られている UNIX 哲学の古くて誤解されそうな短い説明文を新しい言葉で置き換え、修正しなければならなかった理由や多少の解説を付け加えて、UNIX 哲学とはどういうものであるかを解説しています。特に問題が多い「ガンカーズの UNIX 哲学」は大きく修正しています。原典とも言える「マキルロイの UNIX 哲学」のようなシンプルさがなく「レイモンドの UNIX 哲学」のような現代的なソフトウェア工学に基づいてもおらず、1980 年代当時の DEC 社の独自 UNIX 開発者であるガンカーズの偏った考えが大きく反映されたものだからです。「シェルスクリプトの移植性が高い」や「効率を重視すると移植性が下がる」などという今を基準にすると間違いとも言えるぐらい内容が古くそのままでは全く参考にできません。(参考 名著「UNIXという考え方 - UNIX哲学」は本当に名著なのか? 〜 著者のガンカーズは何者なのかとことん調べてみた)
念の為ですが、この記事の解説が唯一の正しい UNIX 哲学だと言うつもりはありません。そもそも 3 つの UNIX 哲学もその人による解釈でしかなく、そこに私が 4 つ目を新たに追加したと考えてください。正しいかどうかは別として少なくとも古い時代に書かれた UNIX 哲学の解説よりは現代的なものにしたつもりです。あといつも記事が長いと好評なので先に言っておきますと、この記事はたった 7 万字程度しかありません。本にしたら 100 ページちょっとぐらいの薄い本程度しかないですよ?
UNIX 哲学について
UNIX 哲学は具体的な設計方針ではなく考え方です。UNIX 哲学は UNIX の開発の中で生まれ、そして UNIX は UNIX 哲学を実践した最初のシステムです。UNIX は「プログラマのためのソフトウェアツールが詰まった道具箱」という考え方があります(参考 GNU CoreUtils - ソフトウェアの道具箱)。つまり UNIX 哲学の実践例の一つが UNIX = ソフトウェアツールというわけです。それは組み合わせて使えるソフトウェアの道具を作り、そして組み合わせて使うという考え方です。普段からさまざまなソフトウェアを使い、そして時に自ら作っているプログラマにとって、UNIX 哲学は切っても切り離せないものなのですが、その価値が正しく理解されていないように思います。
UNIX 哲学は「UNIX で生まれた」哲学であって「UNIX 専用の」哲学ではありません。しかし UNIX という名前に引きずられてか UNIX だけに関係するものと思われていたり、また UNIX 哲学を説明したものが 1980 年代の UNIX の世界を例にしているものが多いためか、古い時代の UNIX の使い方、例えば C 言語プログラミングやシェルスクリプティング、UNIX シェルや awk や UNIX コマンドを使いこなすテクニックが UNIX 哲学だと誤解されている気がしますが、それは違います。例えば Linux + fish シェルスクリプト + Python コマンド + MySQL サーバーという組み合わせでも UNIX 哲学は成り立ちます。これは単に組み合わせて使う道具を入れ替えたと言うだけです。
UNIX 哲学を実践しているものは UNIX だけではありません。わかりやすい例だと、独立した Docker コンテナを Docker Compose でまとめてシステムを作ることや、マイクロサービスアーキテクチャは、(意識しているかどうかは別として)UNIX 哲学を実践したものです。他にも Apache Kafka(Java と Scala で書かれた分散ストリーミングメッセージングシステム)の中にも UNIX 哲学が息づいていることが「Apache Kafka, Samza, and the Unix Philosophy of Distributed Data」を読むとわかります。UNIX 哲学の内容は他のよく知られたソフトウェア開発の原則から学ぶことが出来るので、そういう人にとっては UNIX 哲学は馴染みがある概念を別の言葉で表現したものにすぎず、新たに学ぶようなことは少ないかもしれません。しかし UNIX 哲学が重要な考え方であることには変わりありません。
UNIX 哲学の応用範囲はコンピュータ技術やソフトウェア技術の進化と共に広がっています。UNIX の進化と共に歩んできた UNIX 哲学は変化することを許容しています。昔の UNIX にはなかった道具、さまざまな言語や多くのコマンドを使うことは UNIX 哲学の考え方です。UNIX 時代と同じやり方を続けることは UNIX 哲学ではありません。UNIX の創始者であるケン・トンプソンは、現在 Go 言語の開発をしているという事実からも、そのことは明らかです。
これまでの UNIX 哲学と「新しい UNIX 哲学」の比較
細かい解説は抜きにさくっと違いを知りたい人のために、違いを簡単にまとめました。変更した理由などについては後半の解説を参照してください。
マキルロイの UNIX 哲学
マキルロイの UNIX 哲学は、UNIX 哲学として一番最初に AT&T ベル研究所で文書化された原典とも言えるものです。重要なポイントとして(オリジナルの時点で)「小さなプログラムにする」「効率よりも移植性が重要」「シェルスクリプトを使う」などという定理はないことに注意してください。C 言語を使えともパイプを使えとも言っていないのです。
(i)
-個々のプログラムは1つのことをしっかりやるようにせよ。新しい仕事をするなら、
+個々のプログラムは与えられた役割をしっかりこなすようにせよ。異なる役割の仕事をするなら、
新しい機能を追加して古いプログラムを複雑にするのではなく、まったく新しいプログラムを作れ。
(ii)
すべてのプログラムの出力は、他の未知のプログラムの入力になるのだということを予想せよ。
余分な情報で出力をごちゃごちゃにしてはならない。
-縦方向の並びを厳密に揃えたり、バイナリ入力形式を使ったりすることを避けよ。
+入出力には行指向の単純なテキスト形式、または標準的なデータ形式を使用せよ。
対話的な入力にこだわるな。
(iii)
オペレーティングシステムであっても、
-ソフトウェアは早く(できれば数週間以内に)試せるように設計、構築せよ。
+ソフトウェアは素早く(できれば数週間単位で)試せる仕組みを構築せよ。
+要件定義、設計、開発、構築、テスト、試用など全行程を何度も繰り返せ。
まずい部分は捨てて作り直すことを躊躇するな。
(iv)
技能の低い者に手伝ってもらうくらいなら、
-プログラミングの仕事を明確にするためにツールを使え。
+ソフトウェア開発の仕事を楽にするためにツールを使い、可能な限り自動化せよ。
ツールを作るために遠回りしなければならない場合でも、
使ったあとで一部のツールを捨てることになることがわかっている場合でも、ツールを使え。
ガンカーズの UNIX 哲学 - 9 の教義
旧 教義 | 新 教義 |
---|---|
スモール・イズ・ビューティフル | シンプル・イズ・ベスト |
一つのプログラムには一つのことを うまくやらせる |
一つのプログラムには本当にやるべきことを うまくやらせろ |
できるだけ早く試作を作成する | できるだけ素早く何度も試作できる仕組みを作れ |
効率より移植性 | 要件を満たす品質と高い生産性を両立させよ |
数値データはASCIIフラットファイルに 保存する |
ファイルには標準的な形式か テキストベースの単純な形式を使用せよ |
ソフトウェアの梃子を有効に活用する | 多くのソフトウェアを活用して生産性を高めよ |
シェルスクリプトを使うことで 梃子の効果と移植性を高める |
コマンドをシンプルに組み合わせるために シェルスクリプトを活用せよ |
過度の対話的インターフェースを避ける | 自動処理のためにコマンドには非対話的な インターフェースを持たせろ |
すべてのプログラムをフィルタにする | データを処理するプログラムは可能な限り フィルタにせよ |
ガンカーズの UNIX 哲学 - 重要度の低い 10 の準教義
旧 準教義 | 新 準教義 |
---|---|
好みに応じて自分で環境を 調整できるようにする |
プログラマ向けの環境や道具はカスタマイズ性を 高くせよ |
オペレーティングシステムの カーネルを小さく軽くする |
カーネルは小さくし、ユーザーランドのプログラムを 豊富にせよ |
小文字を使い、短く | 可読性のために長い名前を使い、短い名前は 楽したい時に使え |
森林を守る | 森林や環境を守り、電力使用量やCO₂排出量を減らせ |
沈黙は金 | データは出力すべき時に、一貫した形式でデータを 出力せよ |
並行して考える | 並行処理・並列処理を行うプログラムを作れ |
部分の総和は全体よりも大きい | 大きなシステムは複数のプログラムを組み合わせて作れ |
90 パーセントの解を目指す | 本当にやるべきことに集中し、やらないことも決めよ |
劣るほうが優れている | 時間をかけて完成させるより、素早くプロトタイプを 作れ |
階層的に考える | 目的に応じて最適なデータ構造を使用せよ |
レイモンドの UNIX 哲学
注意 説明の内容に変更はありません
原則 | 説明 |
---|---|
モジュール化の原則 | クリーンなインターフェイスで結合される単純な部品を作れ |
明確性の原則 | 巧妙になるより明確であれ |
組み立て部品の原則 | 他のプログラムと組み合わせられるように作れ |
分離の原則 | メカニズムからポリシーを切り離せ。エンジンから インターフェイスを切り離せ |
単純性の原則 | 単純になるように設計せよ。複雑な部分を追加するのは、 どうしても必要なときだけに制限せよ |
倹約の原則 | 他のものでは代えられないことが明確に実証されない限り、 大きなプログラムを書くな |
透明性の原則 | デバッグや調査が簡単になるように、わかりやすさを目指して 設計せよ |
安定性の原則 | 安定性は、透明性と単純性から生まれる |
表現性の原則 | 知識をデータのなかに固め、プログラムロジックが楽で 安定したものになるようにせよ |
驚き最小の原則 | インターフェイスは、驚きが最小になるように設計せよ |
沈黙の原則 | どうしてもいわなければならない想定外なことがないのなら、 プログラムは何もいうな |
修復の原則 | エラーを起こさなければならないときには、できる限り早い段階で けたたましくエラーを起こせ |
経済性の原則 | プログラマの時間は高価だ。マシンの時間よりもプログラマの 時間を節約せよ |
生成の原則 | 手作業のハックを避けよ。可能なら、プログラムを書くための プログラムを書け |
最適化の原則 | 磨く前にプロトタイプを作れ。最適化する前にプロトタイプが 動くようにせよ |
多様性の原則 | 「唯一の正しい方法」とするすべての主張を信用するな |
拡張性の原則 | 未来は予想外に早くやってくる。未来を見すえて設計せよ |
基礎知識
UNIX 哲学はソフトウェアツールに限定されない
ソフトウェアツールはソフトウェアの道具を組み合わせて使うという考え方ですが、UNIX 哲学はソフトウェアツールに限定されたものではありません。あらゆるソフトウェアの開発に応用できる考え方です。npm の創設者である Isaac Z. Schlueter は「Unix Philosophy and Node.js」で以下のように述べています。
All too often, people get hung up on the wrong aspects of the Unix Philosophy, and miss the forest for the trees. The Unix Philosophy is not about a specific implementation, or anything that is necessarily unique to any Unix operating system or program. It’s not about file descriptors, pipes, sockets, or signals. Those sorts of complaints are like saying that someone is not a buddhist unless they speak Pali.
Unix Philosophy is an outlook for software development, not any specific technical development in software. It is an ideal to reach for, and perhaps ironically, it is an ideal that instructs us to occasionally eschew idealism in favor of practicality.
翻訳
あまりに頻繁に、人々は Unix 哲学の間違った側面にとらわれ、木を見て森を見ずになっています。Unix 哲学は特定の実装や、いずれかの Unix オペレーティングシステムやプログラムに特有のものではありません。それはファイルディスクリプタ、パイプ、ソケット、シグナルといったものではありません。そのような文句は「パーリ語を話せなければ仏教徒ではない」と言っているようなものです。
Unix 哲学はソフトウェア開発の展望であって、ソフトウェアにおける特定の技術的開発ではありません。それは到達すべき理想であり、おそらく皮肉にも、時には理想主義を捨てて実用性を優先するように指示する理想なのです。
翻訳ここまで
他にも「"Unix Philosophy" + 特定の言語」などで検索すれば、UNIX 哲学を理解したものたちによる、UNIX を超えたさまざまな分野への応用が見つかることでしょう。
UNIX 哲学が UNIX やシェル環境に紐付いているように見えるのは、単に UNIX 哲学が UNIX で生まれ、当時のコンピュータの使い方がシェル環境しかなかったからです。UNIX が誕生した頃は人がコンピュータの前に座って対話的に使うという、今では当たり前の使い方はまだ一般的ではなく、C 言語も誕生しておらず、多くのソフトウェアやライブラリなどもありません。そのような時代に、具体的な例で分かりやすく説明すると UNIX とシェル環境を使った説明になってしまうのは仕方ありません。GUI を誰も使ってないような時代に、GUI 環境を例に UNIX 哲学を説明したって伝わるはずがありませんよね?
UNIX 哲学は他の環境や言語に応用することも可能ですし実際に応用されています。しかし UNIX 哲学はソフトウェアツールの考え方として UNIX と共に発展したという経緯があります。将来はシェルとは異なるソフトウェアの道具を組み合わせて使うためのより優れた環境が登場するかもしれませんが、少なくとも今は、UNIX 哲学はシェル環境に限定されたものではないものの、シェル環境やシェルスクリプトからコマンドを組み合わせて使うことが UNIX 哲学を実践するのに最も適した環境であると言うことは出来ます。
「道具を使う」と「道具を作る」の違い
UNIX 哲学には二つの自由があります。「道具を使う自由」と「道具を作る自由」です。道具とは全てのソフトウェアの事を意味しています。誰かが作ったソフトウェアを使うのも良し、自分でソフトウェアを作るのも良しです。「道具を使う」と「道具を作る」は表裏一体です。道具を作らなければ道具は使えず、道具を使う時のことを考えなければ良い道具は作れません。どちらか一方だけでは成り立ちません。UNIX 哲学は「道具を使う」と「道具を作る」の二つを合わせた考え方です。
「道具を使う」というのはわかりやすい例で言えば、シェルからコマンドを実行することですが、シェルスクリプトを書くということも道具を使う行為の一つです。より正確に言うなら「道具を使う手順書をあらかじめ書くこと」がシェルスクリプトを書くということです。一方で「道具を作る」というのは、伝統的な UNIX の流儀で言うならば、C 言語でコマンドを作ることです。もちろん今は C 言語だけではなく任意の言語を使って構いません。道具を作らずとも既存のコマンドだけで十分と思うかもしれませんが、既存のコマンドは汎用的なコマンドばかりでなので、それだけでは不十分なことが多々あります。そういう時に既存のコマンドだけで頑張るのではなく道具を自分で作る必要があります。「シェルスクリプトを書く事」と「C 言語のコードを書く事」は、それぞれ「道具を使う事」と「道具を作る事」に対応し、実はこの二つは別の行為です。ただし後述しますがこの二つの境界線は曖昧です。とりあえず、この瞬間は別の行為だと考えてください。
「道具を使う」をもう少し詳しく言うと「複数の道具を組み合わせて使う」ということです。道具を組み合わせて使うというのは、コマンドをパイプでつなげることだけではありません。例えば上から順番に一連のコマンドを実行していくことも組み合わせて使うことの一つです。シェルから一つのコマンドを実行するだけの場合、それは一つの道具を使うだけで作業が完了したということです。一つの道具を使うだけでは作業が終わらない場合は複数の道具を使うことになります。複数の道具を組み合わせた一連の作業を手順化して何度も同じことを繰り返したい時に、あらかじめ書いておくのがシェルスクリプトというわけです。作業そのものはシェル上で手作業でコマンドを実行しているのと同じなので、シェルスクリプトを書くというのは「道具を使う」ことと同義です。
「道具を作る」をもう少し詳しく言うと「部品を組み込んで道具を作る」ということです。新しく「部品」という用語が登場しましたが、部品とは関数やライブラリ、オブジェクト指向言語ならクラスライブラリも含まれます。道具も部品も(汎用的に作られているものは)再利用可能なコードなので似たようなものに見えるかもしれませんが、その違いは構成方法にあります。道具は複数の部品から構成されています。それらの部品はさらに小さな部品を使って構成されています。そしてさらに小さい部品をと、部品は階層的な構造で構成されています。例えばデジタル式目覚まし時計という道具は、LSI や LED やスイッチや電池ボックスやブザーなどと言った小さい部品で構成されていますが、それぞれの部品はさらに小さな電子素子や金属パーツなどから構成されています。これは C 言語で作られたコマンドが関数を組み合わせて作られており、それらの関数は内部でさらに小さな関数を呼び出しているのと同じ構造です。
階層構造で部品を作るのは、小さすぎる部品を直接使って大きなものを作ると複雑になるからです。適度な大きさの部品にし、その部品の中身、実装の詳細を知らずに使えるようにすることで、過度の複雑性から逃れることが出来ます。このような考え方を抽象化といいます。道具の信頼性は信頼性が高い部品を使うことで担保します。もちろん部品の信頼性もさらに小さい部品の信頼性で担保しています。この信頼性の担保の階層構造によって大きな道具を作ることを可能にしています。一方で「道具を使う」シェルスクリプトは基本的にフラットな構造で構成されています。フラットな構造にしたい場合は全体として何をしているのかを把握したいからです。ただしフラットな構造だと信頼性を担保には組み合わせた全体でテストするしか無くなってしまうので、シェルスクリプトで大きなものを作るのは困難です。大きめのシェルスクリプトを作る場合は、関数を使ってある程度の抽象化や構造化を行うことをおすすめします。
全体として何をするかを把握するためにフラットな構造を使い、それぞれの工程の詳細を気にしないために階層構造を使う。このような構造の違いから、シェルスクリプトは簡単なタスクを実行するのに適しており、大きく複雑なものを作る時には他の言語の方が適しているという違いが生まれています。
このように「道具を使う」と「道具を作る」は全く別のものなのですが、境界線が曖昧だと言ったのは、シェルスクリプトで道具を作ることもできるし、任意の言語から道具を使うこともできるからです。私はよくシェルがどの環境にもインストールされているスクリプト言語であるという事実を利用し、シェルスクリプトで汎用的に使える道具を作っています。しかしシェルスクリプトでよく見かけるような書き方では大きく複雑なものを作るのは大変なため、一般的な言語の手法を使っています。例えば移植性を確保するために、各環境での互換性問題を吸収するためのラッパーを取り入れたりしています。これも実装の詳細から逃れるための抽象化のテクニックです。逆に一般的な言語でコマンドを呼び出すこともできます。スクリプト言語を使って簡単にコマンドを実行するライブラリやフレームワークもあります。「道具を使う」と「道具を作る」の違いは使用する言語で決まるのではなく考え方の違いです。どの言語を使おうが道具を使う場合はフラットな構造が適していますし、道具を作る場合は階層構造が適しています。そしてソフトウェアの種類よってはこの二つが混ざってしまうこともあります。それらの違いを詳しく例示しながら説明したい所ですが、そうするとこの記事の分量が倍増するのが目に見えているので、非常に残念ですが割愛させてください。
とりあえずここで言いたいことは「道具を使う」と「道具を作る」ことは別のことであり UNIX 哲学を理解するには、どちらも知る必要があるということです。道具の使い方を知らないと良い道具を作ることは出来ませんし、道具を作らないといつまでも貧弱な UNIX コマンドを使った曲芸めいたシェルスクリプトを書き続ける羽目になります。「道具を使う」と「道具を作る」の両方を合わせて UNIX 哲学です。ちなみに UNIX 哲学の源流の一冊である「ソフトウェア作法」(原著は Software Tools (1976) 著 P.J. Kernighan, Brian W. Plauger)は「道具を作る」人のための本で、道具の作り方が(Ratforという珍しい言語で)書かれています。この本には「道具を使う」技術であるパイプの話はでてきますがシェルスクリプトの話はほとんどでてきません。Bourne シェルより前の Thompson シェルの時代でスクリプト言語能力が少なく、書くようなことがないというのも理由の一つでしょうが、シェルスクリプトの使い方だけではなく、道具を作ることも UNIX 哲学の考え方に含まれていることがわかると思います。
しばしば、シェルスクリプトを使うのはやめて Python を使おうなどいう話を聞きますが、シェルスクリプトと Python はどちらかを使うかではなく組み合わせて使うものです。bash 後継を目指した新しいシェルである Oil シェルの開発者は 1000 行の Python スクリプトを書くのではなく、200 行の Python スクリプトと 200 行のシェルスクリプトを書く方が良いと語っています。「道具を使う(シェルスクリプトを書く)」と「道具を作る(C 言語や Python などでプログラムを書く)」が違うという考え方から、シェルスクリプトと他の言語との使い分けも理解できるのではないかと思います。また「道具を作る」言語がどれだけあっても「道具を使う」シェルスクリプトがなくならない理由はそこにあります。
UNIX が誕生したときから C 言語と シェルスクリプトは両方とも存在していました。この二つは好みで選ぶことができる二つの言語を作ったのではなく両方とも必要だったから作った言語です。道具を作る C 言語はその他のコンパイル言語やスクリプト言語で代替可能ですが、道具を使うシェルスクリプトの代替は(fish など別の文法の)シェルスクリプトしかありません。スクリプト言語を使ってシェルスクリプトと同じようなことをやろうとしているライブラリやフレームワークがありますが、使いやすくすればするほどシェルスクリプトと似た文法の DSL が使えるシェルスクリプトの薄いラッパーになっていくだけです。シェルスクリプト風ライブラリの使い方を覚えるぐらいなら、よりシンプルなシェルスクリプトを使ったほうが良いと思いませんか?
「UNIX 哲学 - べからず集」と変化し続ける道具
少なくない人が UNIX 哲学(ソフトウェアツール)を間違って理解しているため、よくある間違いを「べからず集」としてまとめました。
-
UNIX にこだわるな。
- それは UNIX 哲学が生まれた一つの OS にすぎない。
-
UNIX コマンドにこだわるな。
- それは UNIX 開発初期に作られた、わずかなコマンドセットにすぎない。
-
POSIX コマンドにこだわるな。
- それは各 UNIX で互換性があった UNIX コマンドのサブセットだ。
-
自分でコマンドを作ることにこだわるな。
- 使えるコマンドを探せ。世界には多くのコマンドがある。
-
既存のコマンドにこだわるな。
- 探しても使えるコマンドがなかった時、自分自身でコマンドを作れ。
-
C 言語にこだわるな。
- それはコマンドを作るための言語の一つにすぎない。適材適所で言語を選べ。
-
Bourne シェルにこだわるな。
- それは UNIX 哲学を実践するための多くのシェルの一つすぎない。
-
標準インストールにこだわるな。
- 簡単なインストール作業を行うだけで、どの言語もどのコマンドも動く。
単純な話です。UNX 哲学は UNIX の将来の発展を潰すような哲学ではありません。古い UNIX コマンドに固執するということは、UNIX の未来で誕生していたかもしれない新しいコマンドを否定することになります。本家 UNIX の開発が終了してしまったため、UNIX コマンドが増えることはありませんでしたが、別の未来では新しい UNIX コマンドが誕生していたかもしれないわけです。UNIX コマンドは UNIX の能力の全てを引き出せるわけではないので、どうしても新しいコマンドが必要になります。それを禁止するはずがありません。UNIX 哲学は新しい道具を作ることに制限を与えることはありません。新しい道具をより良い形で作る考え方を述べているだけです。
UNIX 哲学と同様によく勘違いされているのが POSIX および POSIX コマンドです。UNIX は POSIX に従って作られた OS ではありません。UNIX の誕生は 1969 年、POSIX の誕生は 1988 年です。本家 UNIX から分岐し、いくつもの独自 UNIX が誕生し、移植性が高い(どの UNIX でも動く)アプリケーションを作るのが大変になったから作られたのが POSIX です。したがって多くの種類の UNIX が作られたのが先で、UNIX ベンダーは UNIX の一部として POSIX を採用したという形です。さらに言うなら POSIX は OS に必要とされる機能の全てを標準化しているわけではありません。例えばパッケージ管理システムは POSIX 標準化の範囲外なので OS ごとに異なっています。
そして POSIX は拡張機能を実装することを禁止していません。むしろ拡張機能の実装を妨げないように注意して標準規格が策定されています。POSIX コマンドは POSIX が移植性が高いと認めたコマンドおよびオプションの一覧にすぎず、これ以外のコマンドやオプションを実装(拡張)しても POSIX 違反にはなりません。POSIX は UNIX 哲学と同様に、新しい道具を作ることに制限を与えたりしていません。例えば bash は POSIX に準拠してない機能を持ったシェルではなく、POSIX シェルの標準規格に準拠し、なおかつ POSIX で実装を許可された拡張機能を持っているシェルというのが正しい解釈です。
どこでも動くソフトウェアは OS 標準のシェルやコマンドだけではないという事実を無視しないでください。多くの言語やコマンドはインストールするだけで動きます。POSIX とはそもそも「インストールすれば動く」を実現するために作られたようなものです。そのために C コンパイラ(c99
コマンド)が標準化されているのです。C コンパイラが標準化されているのは POSIX で使用を許可された言語は C 言語だけという意味ではありません。C コンパイラさえあれば、どのような言語もコマンドも芋づる式にコンパイルして使えるようになるという意味です。インストール作業は昔と違って自分でビルドする必要もなくパッケージから追加するだけです。UNIX 開発の黎明期に AT&T の外のさまざまな場所で多くのソフトウェアが作られて便利になっていったというのに、最近のソフトウェアの使用を封印するとか一体何を考えてるんだ?という話です。
UNIX コマンドや POSIX コマンドだけを使っていこうという考えは、UNIX 哲学や POSIX の方針と無関係というか、むしろ正反対の考え方です。そのことはちゃんと調べてとわかることです。UNIX 哲学にも POSIX の標準規格にも、UNIX コマンドだけ、POSIX コマンドだけしか使ってはいけないという決まりはありません。したがって、世の中にあるすべての言語、すべてのコマンド、すべてのミドルウェア、すべてのソフトウェアを使うことは UNIX 哲学や POSIX と矛盾するものではなく、むしろソフトウェア技術を発展させるために使っていくべきものです。
UNIX 哲学者たちの今の考えを知る
UNIX の創始者たちが、何をやろうとしていたか、今何をしているのか、を調べることも UNIX 哲学の理解の助けになります。UNIX の創始者たちは、Plan 9 という新しい OS を開発していました。政治的な理由もしくは現実的な理由で、そのプロジェクトはほぼ終わった状態ですが(一応まだ動きはある)、Plan 9 は UNIX ベースではなくゼロから新しく設計したもので、シェルにも rc シェル という、よりシンプルなシェルが開発されています。もし Plan 9 の開発が成功していれば、UNIX の世界の道具は大きく変わっていた可能性がありました。
UNIX の創始者のケン・トンプソンとロブ・パイクは、現在 Google で Go 言語の開発をしています(念の為ですが Google に脅されてイヤイヤ Go を開発しているのではありません)。Go はクラウドやネットワークサービスに適しており、コマンドラインツールの作成もできます。ドキュメントや FAQ から、Go は並行処理、マルチコアを使った並列処理に強く、ネットワーク化されたマシンを最大限に活用でき、プログラマの生産性を高める言語であることがわかります。言い換えると、それまでの UNIX にはなかった、もしくは足りなかったものを補う言語です。シェルスクリプトや UNIX コマンドではマルチコアの性能を引き出せず、ネットワークにも十分対応することができないから Go が必要になったということです。
UNIX 哲学に従った道具は変わり続けています。Go は新しい時代に対応した UNIX 哲学のための新しい道具(言語)です。
用途 | UNIX 時代 | ネット黎明期以降 | 現代 (クラウド以降) |
---|---|---|---|
システム記述言語 | C 言語 | C 言語 | C 言語, Rust |
コマンド制御言語 | Bourne, ksh | Bourne, rc, dash, ksh, bash, zsh | dash, bash, ksh, zsh, fish |
コマンド開発言語 | C 言語 | C 言語, C++, Perl, Python | C 言語, Python, Rust, Go |
ウェブシステム開発言語 | - | Perl, Python, PHP, Ruby | Python, PHP, Ruby, Go |
データ処理言語 | AWK | AWK, Perl, Python, SQL | Python, SQL, R |
注意 上記の分類は独断と偏見によるイメージです。異論は認めます。特定の分野は省略しました。
Go には昔の UNIX 時代にはなかった特徴をいくつか備えています。例えば go
コマンドはサブコマンドを利用しています。オプション解析の標準モジュールの flag
はロングオプションをサポートしています。マルチコアの性能を引き出す文法を持っています。ネットワークの機能に対応しています。RDBMS を利用するための標準モジュールがあります。互換性を高めるために静的ビルドを行い大きなバイナリを生成します。これらは UNIX 時代に不便だったものであり、UNIX 哲学に従ってケン・トンプソンらが改善したものなのです。UNIX 哲学を理解するものならば Go を積極的に使っていくべきでしょう。もちろん他の言語でも構いません。私は Go はアプリケーション開発用で、C 言語の代わりの言語は Rust の方が適切だろうと思っています。
Go が開発された理由の一つはマルチコアの性能を簡単に引き出せるようにするためです。少し前にシェルスクリプト(のパイプライン部分)を自動的に並列化するという謎技術が話題になりましたが、はっきり言うとあれは使い物になりません。一通り仕組みは把握しているつもりですが、オーバーヘッドが大きいためほとんどのケースで逆に遅くなり「自動的に並列化させるために必要な定義を手動で書く」のが面倒なだけです。シェルスクリプトでマルチコアの性能を効率良く引き出せるのは xargs -P
やバックグラウンドプロセス実行 (&
) による並列化だけです。パイプラインを並列化するというアイデアは限界がすぐに来るため、都合良く作られたケース以外でマルチコアの性能を引き出すことは出来ません。だからこそケン・トンプソンらは新たに Go という言語を開発したわけです。
UNIX 哲学を一番良く知る人物たちは、現実を生き、未来を見ています。UNIX コマンドに固執している人は、UNIX 哲学の本質を理解しておらず、過去にとらわれているだけです。
UNIX 哲学者たちが今どのようなことを考えているかは、彼らの発言からうかがい知るができるでしょう。例えばケン・トンプソンは今(2009 年のインタビュー) Linux を使っています。
「UNIXはただ死んだだけでなく、本当にひどい臭いを放ち始めている」
死んだのは UNIX であって UNIX 哲学ではありません。原文は「Not only is UNIX dead, it's starting to smell really bad.」です。この言葉を言ったロブ・パイクは AT&T ベル研究所のメンバーの一人です。UNIX の開発者ではありません(「Rob Pike Responds」でそのように語っています)が、1983 年の論文「Program Design in the UNIX Environment 」(共同著者はブライアン・カーニハン)で BSD Unix の cat -v
や ls
コマンドのカラム表示が UNIX 哲学的ではないとして批判した人物で、UNIX 哲学を広めるための「UNIXプログラミング環境」の執筆者(共同著者はブライアン・カーニハン)でもあり、UNIX 創始者に近い立場から UNIX 哲学を理解している人物であると言えます。現在はケン・トンプソンと共に Go の開発をしています。
「UNIXはただ死んだだけでなく〜」の発言は 1991 年頃だと言われてるようです。1991 年というのは AT&T が商用路線に進み System V R4 がリリースされ、SunOS、AIX、HP-UX といった大手商用 UNIX が登場し、本家の UNIX が多数の UNIX に分岐した時期です。商用ではない元の Research Unix は、その最終版である Version 10 Unix (1989) が開発され、UNIX の後継を目指していた Plan 9 (1992) の開発が行われていました。Plan 9 の初期のメンバーはケン・トンプソンやロブ・パイクたちです。
この発言の文脈はよくわかりませんでしたが、このような状況を踏まえると、UNIX の設計はもう古い。AT&T の System V 系 UNIX 開発に未来を感じられなくなった。BSD Unix で取り入れられたネットワークの機能は「すべてがファイル」を満たしておらず UNIX 哲学を実践できない。同様にグラフィックもファイルではなく UNIX 哲学を実践できない。真に UNIX 哲学を実践できる OS は Plan 9 であるというの考えからの発言ではないかと思っています。Unix の真の後継 OS である Plan 9 の開発者であれば UNIX が古いと考えるのは当然です。むしろ明らかな問題点が判明している(当時の時点で)20 年も前の UNIX が最先端の OS であると考えるほうがどうかしています。UNIX 哲学を実践する OS としては能力不足なわけで、UNIX 哲学を実践するためにその先の未来へ進まなければいけません。
ロブ・パイクは UNIX が嫌いになったから「UNIXはただ死んだだけでなく、本当にひどい臭いを放ち始めている」と言ったのではありません。UNIX を称賛しつつも批判すべきところは批判するという、技術者としてあるべきことを言っているだけです。UNIX は完成された OS ではありません。だからこそ Plan 9 を開発していたわけです。UNIX を批判したから、もうロブ・パイクの発言は認めないというのであれば、それは何も考えていないただの古い実装の UNIX の信者でしか無く、UNIX も UNIX 哲学も理解してないと言わざるを得ません。批判など聞きたくないと耳をふさぎ、批判の内容ではなく批判する人に文句をいうだけで、批判から学ぼうとすることができない人に成長はありません。
良い道具の見つけ方と道具の開発方針や将来性
シェルスクリプトを使うと素早く仕事を完了させることが出来ます。これはシェルスクリプトが優れているのではなく、便利な道具(コマンド)があらかじめ用意されているからです。シェルスクリプトはコマンドをつなげることに優れていますが、生産性が高いのはシェルスクリプトの功績ではなく道具の功績です。道具は多くの言語によって作られています。
UNIX に付属していたものが道具の全てだった時代とは違い、今はたくさんの道具にあふれています。しかし誰もが簡単に道具を開発できるようになった今、全てが良い道具であるとは限りません。良いものもあれば悪いものもある、まさに玉石混交の状態です。良い道具は大きな価値をもたらしてくれますが、悪い道具を選んでしまうと逆に道具に振り回されてしまいます。
良い道具を見つける鍵は UNIX 哲学の考え方を理解することです。この記事は UNIX 哲学を理解する助けとなると信じています。それでも UNIX 哲学が理解できない場合、多くの人に長く使われている道具を選ぶと良いでしょう。多くの人が使っている道具は OS のパッケージ管理システムに登録されていたり、多くの解説記事が存在したり、GitHub のスターがものすごく多かったりします。もちろんこれは自分で判断できないときの最終手段です。パッケージ管理システムに登録されていたとしてもメンテナンスされておらず削除寸前かもしれませんし、記事が多くとも古い記事ばかりで今は使われてないかもしれませんし、GitHub のスターも話題性があれば爆発的に伸びることがあります。最終的には自分で使って評価するしかありません。道具を使いながらで良いのでゆっくりでも UNIX 哲学を理解する必要があります。
プロジェクトの性質の違いも注意する必要があります。例えば GNU プロジェクトは安定性や互換性を重視しており、安心して長く使い続けることが出来ますが、新機能の追加や改善はなかなか行われません。逆に新しい機能の追加や改善に積極的なプロジェクトでは、たびたび互換性が保たれずバージョンアップしたときの対応が大変になる場合があります。商用でプロプライエタリ(独占的)なソフトウェアは使用するのにコストがかかったりが契約終了で使えなくなる場合があります。
もう一つの注意点は、その道具がどれだけ汎用的かどうかです。汎用的かどうかを見分けるコツは、他の場所や他の道具と組み合わせて使えるかどうかを考えればわかります。例えば今のプロジェクトを抜けた時や他の会社に移った時、あなたはその道具を別の場所で使うでしょうか?逆に他の場所から来た人が同じ道具を使っている可能性があるでしょうか?その道具を全く別の誰かが作った道具と組み合わせて使えるでしょうか?これらの質問にイエスであれば汎用性が高い道具と言えます。汎用性が低い道具はどれだけ優れていても寿命が短くなってしまいます。ただし汎用性が低い道具は使ってはダメだということではありません。少なくとも今の業務を続けている限りは価値があるでしょう。
どれが良いとか悪いとかではなく、自分のプロジェクトにあったものを選ぶことが大事です。いずれにしてもやってはいけないのは、運悪く自分にあってない道具を選んでしまい、嫌になって他の人が作った道具を使うことをやめてしまうことです。それは選んだ道具が悪いのであって、すべての道具が悪いということではありません。道具の評価は道具ごとにする必要があります。道具を使うのをやめてしまうと、自分の力で出来ることが自分の限界になってしまいます。一人で出来ることは多くはありません。全て自分の力でやっていたらいくら時間があっても足りません。
「新しいUNIX哲学」による三つの大きな修正点
よく知られている UNIX 哲学では「一つのことだけをしろ」「テキスト形式を使え」「効率が下がってでも移植性」のように言われていましたが、私の UNIX 哲学の解釈にはこのような原則はありません。完全に間違っているというつもりはありませんが、本質を捉えきれておらず矛盾点が存在しています。したがって私はそれらを修正しているわけですが「UNIX 哲学ではこう述べられている。お前は UNIX 哲学を分かっていない。」と言われるのは癪なので、特に重要だと考えられるこの三つを、UNIX 哲学を分かった上で意図的に修正したとその理由を明確にしたいと思います。
小さなプログラムを組み合わせるのは難しく複雑になりやすい
道具(コマンド)がシンプルであることと、道具の使いこなし(自分で書いたシェルスクリプト)がシンプルであるかは完全に別問題です。多数のコマンドを組み合わせたシェルスクリプトは複雑になりがちです。同じ処理をする時に、使う道具が小さくシンプルだと、道具を使ったコードは逆に大きく複雑になります。例えるなら歯車はシンプルな道具ですが、多数の歯車が組み合わさったからくり人形は複雑です。全体を考えた場合、小さい道具が必ずしもベストだとは限りません。物事をシンプルにするには組み合わせる道具の数を減らすことです。例えば sort
コマンドと uniq
コマンドを組み合わせるより sort -u
を使った方がシンプルです。(2022-09-01 補足 sort -u
は Research Unix の Version 7 Unix (1979) で初めて実装されたようです。Version 6 Unix では usort という別のコマンドだったものが統合されたようです)
小さな道具が嬉しいのは「道具を作る側」 です。UNIX 哲学は UNIX 開発のための哲学なので、UNIX コマンドをシンプルにすると良いというのは「道具を作る側」の論理なのです。小さな道具を組み合わせて使うというのは専門的で難しい行為です。UNIX はプログラマのための OS であるため、難しい道具の組み合わせぐらい出来て当然という考え方ですが、「道具を使う側」からすれば便利な機能を持つコマンドを使う方が楽です。大きなシェルスクリプトをシンプルに書くのであれば、それなりの技術が必要です。マイクロサービスの例でも小さいサービス自体はシンプルであっても、それを組み合わせた場合に複雑になるというのはデメリットとしてよく知られています。小さなプログラムに速かったり効率がよかったりとかいうメリットはありません。
道具を使って楽をしたいと言うのに、その道具の使いこなしに訓練や教育が必要では本末転倒です。大きく複雑なシェルスクリプトはテストも必要になってきます。それよりもテスト済みの大きなプログラムを使って、短いシェルスクリプトを書く方が信頼性も生産性も高くなります。
このような問題があるのに、なぜ「小さいプログラムにせよ」という考え方が UNIX 哲学として広まったのでしょうか?私が見つけた中で UNIX と小さいプログラムのつながりは UNIX の最初の論文 1974 年の The UNIX TimeSharing System にあります。この中でケン・トンプソンは UNIX の設計の考慮次項として次のようなものがあったと述べています。
- プログラムを書き、テストし、実行することが容易なインタラクティブシステム
- システムおよびソフトウェアのサイズの制約にうながされた経済的でエレガントな設計
- UNIX 自身で UNIX をメンテナスできること
この 2 の「サイズの制約にうながされた経済的でエレガントな設計」が小さいプログラムに関係しています。「経済的」というのはシステムリソースを食わないという意味だと思われますが、私はソフトウェアの設計事項の話で「経済的 (economy)」という用語を使っていることに違和感を感じました。その理由は 1978 年の論文 UNIX Time-Sharing System: Forward に書かれたスモール・イズ・ビューティフルと経済学者シューマッハーとのつながりから明らかになりました。どうやら 1973 年に経済学者 エルンスト・フリードリッヒ・シューマッハー が出版した Small Is Beautiful: A Study of Economics as if People mattered と 1973 年に発生した第一次オイルショックで「経済的」は当時注目を浴びていた用語だったようです。
つまりスモール・イズ・ビューティフルの考え方は、当時の流行り言葉に当てはめただけで、最初から UNIX 哲学を表す適切な表現ではなかったのだと思います。当時のコンピュータの性能は低く UNIX 哲学の本質ではないながらも小さいプログラムにせざるを得ません。そこから、小さいプログラムを書くことが UNIX 哲学だという考えが広まったのでしょう。今となっては小さいプログラムにメリットはありません。ケン・トンプソンが設計した Go は小さいプログラムを生成しません。サイズの制約により無駄のないシンプルでエレガントな設計が生まれたのは事実だと思いますが UNIX 哲学との直接の関係はないと思われます。
そもそも UNIX 創始者の一人であるマキルロイの UNIX 哲学では「一つのプログラムは一つのことをせよ」とは言っていますが「小さなプログラムにしろ」とは言っていません。小さなプログラムにしろと言っているのはガンカーズの UNIX 哲学です。一つのことをすることと、小さなプログラムにすることは意味が全く違います。
では「一つのこと」とは一体どのようなものでしょうか? 一つのことだけをやっていない UNIX コマンドはいくつもあります。例えば sed
コマンドは、文字列の追加(a
)や置換(s
)や削除(d
)といった複数のことをしています。rm
コマンドは通常ディレクトリを削除できないのに、-r
オプションをつけた場合はディレクトリを含めてすべてのファイルを削除します。修正日時を更新する touch
コマンドはファイルが存在しない時にエラーにならず作成します。ls
コマンドもソート機能がありますし find
コマンドも多くのことをしています。test
([ ]
) コマンドでさえ、ファイルの存在チェックや数値や文字列の比較といった複数のことをしています。「一つのこと」が何を意味するのかわからないまま「一つのこと」をしっかりやろうと見当違いのことを考えていても時間を無駄に浪費するだけです。
したがって事実上意味のないものとなっているこの言葉を修正します。といってもむやみに大きな物を作れという意味ではありません。「一つのこと」という誰も守っていない曖昧な基準や「小さなプログラム」という何のメリットももたらさない基準をやめるということです。UNIX 哲学で重要なのはモジュール化と構成可能性です。それが実現できている限りプログラムは大きくても良いということです。プログラムをシンプルにするのであれば、プログラムの内部をモジュール化すれば十分です。今はプログラムを使う側がシンプルに使えるにはどうするか?を考えるべきです。
なぜこれを明示的に修正すると言っているのかというと、悪用する人がいるからです。今では Systemd は有益なものとして広く採用されていますが、当初は UNIX 哲学に反するとして大きな論争になりました。私もどちらかと言えば Systemd は少し大きすぎで、もっとシンプルにできるのではないかと思っているのですが、Systemd に反対する理由としてダメなのは「一つことをしていないから UNIX 哲学に反しており間違っている」という論理です。「一つのこと」をしてない有益なコマンドは UNIX 自体にもあるわけで理由にはなりません。もし「一つのこと」が正しい方法であれば UNIX 哲学という権威に頼らずとも、そうするメリットが言えるはずです。「一つのことに」にメリットはなかったから結局 Systemd は広く普及しました。
シンプルで小さな道具は使うのが簡単ですが、その道具を使って作ったものがシンプルになるとは限りません。簡単な道具でも使うと複雑になることがあるということ知っておく必要があります。シェルスクリプトであって大きなソフトウェアをシンプルに作るためには、それなりの技術が必要です。シェルスクリプトは大きくしたくないので、道具を使う側(シェルスクリプト)を小さくするために、道具の方(コマンド)を大きくする必要があります。余談ですがシンプルと簡単は異なる意味を持つ言葉です。この記事では詳しく解説しませんが、重要な考え方なので「シンプルさの必要性」などで詳細を確認してください。
バイナリベースのデータ形式を禁止するのは現実的ではない
一般的な原則としてテキスト形式を優先した方が良いのは変わりません。しかし zip 形式や gif 形式すら存在していなかった 1980 年代とは違い、今はバイナリベースであっても数多くの標準化された形式やデファクトスタンダードになった形式があります。テキスト形式だけではなくそれらのバイナリ形式も使用してよいと明確にします。実際これらのバイナリ形式は広く使われており移植性が高く、コマンドやライブラリが充実しているため、バイナリ形式というだけで一律で拒否するのは現実的ではありません。
テキスト形式は人間が読み書きしやすい形式だとよく言われますが、実際の所、人間がテキスト形式を簡単に読み書きできるのテキストエディタがあるからにすぎません。テキスト形式だから簡単なのではなく単にテキスト形式に対応した道具が多いと言うだけです。標準的な画像形式はグラフィックソフトがあれば簡単に読み書きすることができます。バイナリ形式でもそれを扱う適切な道具があれば簡単に読み書きできます。昔はソフトウェア専用に独自のバイナリ形式を作るしかありませんでしたが、今は逆で、標準的なバイナリ形式があって、その形式にソフトウェアが対応しています。
どのような場合にテキスト形式が適切で、どのような場合にバイナリ形式が適しているのか基準が必要かもしれません。例えばコンピュータだけではなく人間がテキストエディタで読み書きしたくなるようなデータはテキストベースの形式が良いでしょう。画像データ、動画データ、(楽譜では表せない)音声データなどは、テキストエディタで読み書きしたいとは思わないのでテキスト形式にする必要はありません。Markdown のようなシンプルなドキュメントであればテキスト形式が良いでしょう。リッチテキスト形式ぐらいならば、まだ人間が直接テキストエディタで読み書きしたくなるかもしれません。しかし MS Word のようなものになれば、それがたとえテキスト形式(XML)だとしても人間がテキストエディタで読み書きしたくなるようなものではありません。XML タグの意味を理解して頭の中で表示結果を想像できる人はいません。
データベースのようなものは一見テキスト形式だと良さそうに思えるかもしれません。例えばテキストベース Recfiles 形式のファイルを使ってデータベースを扱う GNU Recutils というプロジェクトがあります。単純な例であれば確かにテキストエディタを使って読み書きできるのですが、完全な仕様を把握して間違えることなく手作業で書き換えるのは少し注意が必要になるでしょう。Recfiles はテキストファイルですが一般的な UNIX コマンドを使って正しく編集するのは大変です。ちょっと間違えると簡単にデータベースを壊してしまいます。結局、道具としての利便性を求めると Recfiles 専用のコマンドが必要となります。読み書きに専用のコマンドが必要なのであればテキスト形式を選ぶ理由はありません。データベースでテキスト形式であってほしいのは「データ」であって「データベースファイル」ではないということです。要するにテキスト形式のインポートとエクスポートの機能さえあればデータベースファイルはバイナリ形式で問題ないということです。実際、ロックやインデックスや信頼性のことを考えるとデータベースファイルはバイナリのほうが適切です。
マキルロイの UNIX 哲学とガンカーズの UNIX 哲学は、両方ともテキスト形式やバイナリ形式に言及しているところがありますが、実は大きな違いがあります。マキルロイの UNIX 哲学ではプログラムの入出力にテキストベースのインターフェースを使用せよと言ってるのに対して、ガンカーズはテキストベースのファイルを使用せよと言っている所です。マキルロイの UNIX 哲学ではプログラムの構成可能性の話をしており、ガンカーズはデータの移植性の話をしているので全く別の話です。元々 UNIX は移植性がなかった OS で移植性は後付です。したがって移植性の高さはソフトウェアにとって重要ですが、移植性は UNIX 哲学とは無関係なのです。バイナリベースのファイルを使っていたとしても、その入出力がテキストベースであれば、他のプログラムとの連携のしやすさは変わりません。SQLite はバイナリベースのファイルを使うデータベースソフトですが、その入出力はテキストベースなの構成可能性を十分に満たしています。
ただしシェルスクリプト及び UNIX コマンドが扱う形式はテキスト形式ベースのものにすると良いでしょう。なぜならシェルや UNIX コマンドはバイナリ(正確には \0
)を扱いないことがあるからです。実際の所これは単にシェルスクリプトや UNIX コマンドの欠点です。もっと重要な問題はテキスト形式のフォーマットが厳密に定義されていないことです。例えばデータの中に改行文字が含まれている場合はどうすればよいでしょうか? それらの文字をエスケープするという回避策はすぐに思いつきます。例えば Oil シェル プロジェクトでは「QSN: A Familiar String Interchange Format」という文字リテラルを提案しています。しかし現在のシェルや UNIX コマンドは QSN のようなものを適切にサポートしていません。最初にエスケープのルールがきちんと決まっていればよかったと思いますが、残念なことにそのようなルールを定義するとなく UNIX コマンドは開発されました。これがシェルスクリプトの罠につながっています。これは解決していかなければいけないシェルスクリプティングの課題なのですが、その話をここに書くには狭すぎます。
この話題は尽きることがありませんが、ひとまずこの記事ではテキストベースの形式を優先するものの、標準的なバイナリ形式も使用して良いと明確にしておきます。
高性能なハードウェアを使っても高い移植性や高寿命が実現した
「効率より移植性」、おそらくこれを言っていたのはガンカーズだけだと思うのですが、これは「効率(性能)が高いシステムは移植性が低くなる」と間違って解釈されかねない教義です。これは当時の時代背景に完全に依存した教義で、現代には全く当てはまっていないので修正します。
移植性を重視しろと言っている理由は「効率が良いシステムは移植性が低くから」ではなく「ハードウェアの特殊機能を使って効率を上げると移植性が低くなるから」です。効率がよいかどうかは論点ではなく「ハードウェアの特殊機能を使って」がポイントです。しかしその意味であったとしても現代には当てはまりません。その理由はさまざまな抽象化レイヤーによって実装の詳細、つまりどのようなハードウェアを使っているかを気にしなくて良くなったからです。抽象化レイヤーとは、具体的には OS の一部として機能するドライバ、ライブラリ(OpenGL など)、言語、ラッパーなどと呼ばれる、何かの間に入っているソフトウェアの層のことで、移植性や互換性などの問題を解決するためによく使われています。現在の OS の高い移植性や互換性の実現はこの抽象化レイヤーによるものがほぼ全てです。
ガンカーズ(X Window System の開発チームに属していた)が気にしていた性能とは主にグラフィックアクセラレータに関するもの(だけ?)です。1980 年代はまだ OpenGL(1992 誕生) すら登場してない時代で、高い描画性能を出すために、OS がサポートしてないインターフェースから、ハードウェアの特殊機能を直接使用していたため、移植性が低くなった。というのが「効率より移植性」の真実です。グラフィックアクセラレータというか GPU 分野は現在も比較的大きく変化している分野ですが、特に 1980 年代から 2000 年代ぐらいまでの間は、直線や矩形の描画などの単純な 2D グラフィック機能から、ポリゴンやテクスチャマッピングなどの 3D グラフィック機能まで、短期間で大きく進化した時代でした。OS はその急速な変化に対応しきれておらず、グラフィックに関する OS の標準インターフェースはまだ十分に確立していませんでした。OpenGL が誕生してない 1980 年代はなおさらです。
OS の完成度が高くなった今では、ほとんどのハードウェアは抽象化されており、効率より移植性は過去のものとなっています。抽象化というのは具体的は実装を気にしなくていいということです。言い換えると「高性能なハードウェアが搭載されていればその性能を活かし、そうでない場合でもそれなりの性能が動く、アプリケーションはどんなハードウェアを使っているか気にしなくて良い」ということです。また、抽象化レイヤーという仕組みがなと、ハードウェアの性能を引き出せません。なぜなら、どれだけ高性能なハードウェアを搭載していたとしても、どのハードウェアでも動く共通の機能しか使えないということになってしまうからです。このようなソフトウェアの層はコンピュータの性能を引き出すための仕組みであるとも言えます。
抽象化の概念はハードウェアに関するものだけではありません。言語、ライブラリ、ラッパーによって、Windows と Linux の違いを吸収していたり、MySQL や PostgreSQL と言ったデータベースの違いを吸収していたりと、さまざまな環境の違いを吸収し、単一のソースコードでそのまま別の環境でアプリケーションが動くようになっています。このようなものがなければ特定のソフトウェアや技術に過度に依存してしまいベンダーロックインとなってしまいます。もし既存のソフトウェアで環境の違いを吸収しきれていないのであれば、自分でその違いを吸収すると良いでしょう。例えばシェルスクリプトでもインターネットからファイルを取得する時に、wget
コマンドと curl
コマンドのどちらでも動くような get
関数というラッパーを作ることで、動作する環境を増やし、シェルスクリプトの移植性や寿命を改善することが出来ます。
このように抽象化レイヤーは大きなメリットがある仕組みですが、1980 年代にあまり用いられていなかった理由の一つはラッパーが入ることによる速度低下です。当時は間にこの程度のソフトウェアの層が入るだけでも効率の低下を気にしていた時代でした。コンピュータの性能が向上した現代では、この程度期にする必要はありません。その意味で「(ラッパーによる)効率(低下)よりも移植性」を重視しろというのであれば今も当てはまります。抽象化を行わない場合、具体的なハードウェアや OS のインターフェース(API やコマンド)を直接使うことになるので、移植性が大きく低下してしまいます。
ソフトウェアツールの思想
以下の内容は「マキルロイの UNIX 哲学」を元に、より適切だと思われる説明に修正したものです。マキルロイは AT&T の UNIX の創設者のメンバーの一人なので UNIX 哲学の原典に最も近いものです。最も古い UNIX 哲学ですが、時代の流れに影響がでにくい抽象的な表現となっているため、元の意味と大きく異なるようなレベルの修正はありません。
i. プログラムは与えられた役割をしっかりこなせ
(i) 個々のプログラムは与えられた役割をしっかりこなすようにせよ。異なる役割の仕事をするなら、新しい機能を追加して古いプログラムを複雑にするのではなく、まったく新しいプログラムを作れ。
オリジナル
(i) 個々のプログラムは1つのことをしっかりやるようにせよ。新しい仕事をするなら、新しい機能を追加して古いプログラムを複雑にするのではなく、まったく新しいプログラムを作れ。
変更理由と解説
すでに書いたように「一つのこと」に明確な基準がない以上、それにこだわっても意味がありません。実際には 1 つことをしているコマンドはほとんどないのですから「与えられた役割」と言い換えた方が適切です。役割は名前によって与えられます。sed
は stream editor なのでストリームを編集することが与えられた役割です。awk
は awk programming language の実行が与えられた役割です。サブコマンドを使用しても UNIX 哲学とは矛盾しません。例えば git
は git を使ったバージョン管理のサブコマンドを取り扱うことが与えられた役割です。サブコマンドはその名前で与えられた役割をしっかりこなしています。
名前によって与えられた役割以外の異なる仕事をするのなら、全く新しいプログラムを作るべきです。例えば yes
コマンドは y を繰り返し出力するコマンドですが、yes n
と実行すると n をつぶやき続けるナンセンスなコマンドです。指定した文字列を繰り返し出力することは yes という名前で与えられた役割とは異なる仕事なので、新しいプログラムを作らねばいけなかった例です。yes
コマンドは確かに「一つのこと」をしているかもしれませんが「与えられた役割」以外のことをやっている不適切な名前のコマンドです。
与えられた役割に適合しているのであれば、別のプログラムとして実装可能であっても新しい機能を古いプログラムに追加すべきです。新しい機能を追加したとしてもプログラムの中身をきちんと抽象化や構造化していればソースコードをシンプルに保つことが出来ます。
ii. 他のプログラムと連携できるように作れ
(ii) すべてのプログラムの出力は、他の未知のプログラムの入力になるのだということを予想せよ。余分な情報で出力をごちゃごちゃにしてはならない。入出力には行指向の単純なテキスト形式、または標準的なデータ形式を使用せよ。対話的な入力にこだわるな。
オリジナル
(ii) すべてのプログラムの出力は、他の未知のプログラムの入力になるのだということを予想せよ。余分な情報で出力をごちゃごちゃにしてはならない。縦方向の並びを厳密に揃えたり、バイナリ入力形式を使ったりすることを避けよ。対話的な入力にこだわるな。
変更理由と解説
他のプログラムと連携するための本当の条件は標準的なデータ形式を採用することです。あるプログラムが PNG 形式でデータを出力し、別のプログラムが PNG 形式の入力に対応しているのであれば、それらのプログラムは連携させることが出来ます。標準的なデータ形式が存在しなかった時代は、データを解釈することが簡単な、単純なテキスト形式を使うのが他のプログラムとの連携手段として最適でしたが、今はバイナリ形式であっても標準化されたデータ形式はいくつもあり、ライブラリを使えばそのようなデータ形式でも簡単に扱うことができます。
縦方向の並びを厳密に揃えるというのは、おそらく見た目のためにスペースの数で位置調整をするなということだと思うのですが理由がはっきりしません。cut
コマンドは連続するスペースを一つとして解釈しないので、スペースの数で位置調整されると困りますが awk
コマンドやシェルの read
コマンドのように複数のスペースを一つとみなすため問題ない場合もあります。awk
や read
は「マキルロイの UNIX 哲学」が文書化された後に誕生した可能性があるため、当時は位置調整されたら困る cut
しかなかったからなのかもしれません。しかしながら ls
コマンドのように位置調整を行って出力するコマンドもあるため、縦方向の並びを厳密に揃えるのを避けよというのは、かなり古い時代の話ではないかと推測していますが、はっきりしたことはわからず、この説明を残していても混乱するだけなので削除しました。
この思想はデータを保存するファイル形式について述べたものではないことに注意してください。ストリーム(標準入出力)に限った話です。プログラムが内部でどのようなファイル形式を使っているかは、プログラムの入出力データの形式とは関係ありません。たとえデータを独自形式のデータベースファイルに保存していたとしても、プログラムから単純なテキスト形式で出力する方法があるのなら、別のテキスト形式に対応したプログラムと連携させることができます。重要なポイントは他のプログラムと連携できるように作るということだけです。
iii. 試作を素早く何度も繰り返せ
(iii) ソフトウェアは素早く(できれば数週間単位で)試せる仕組みを構築せよ。要件定義、設計、開発、構築、テスト、試用など全行程を何度も繰り返せ。まずい部分は捨てて作り直すことを躊躇するな。
オリジナル
(iii) オペレーティングシステムであっても、ソフトウェアは早く(できれば数週間以内に)試せるように設計、構築せよ。まずい部分は捨てて作り直すことを躊躇するな。
変更理由と解説
「早く」だけではなく何度も繰り返す必要があるので「素早く」に変更しました。繰り返す内容は全行程です。そして素早く試すには、それが可能な仕組みを構築する必要があります。精神論だけでは解決しません。
補足ですが「まずい部分は捨てて作り直すことを躊躇するな」というのは、良い考えなのですが、修正のたびに、すべてを捨てて作り直すのはよくありません。捨てる部分は「まずい部分」だけなので簡単な修正でもファイル全体を作り直すようであれば、基本的な設計がまずいということです。
iv. 捨てることになろうともツールを使え
(iv) 技能の低い者に手伝ってもらうくらいなら、ソフトウェア開発の仕事を楽にするためにツールを使い、可能な限り自動化せよ。ツールを作るために遠回りしなければならない場合でも、使ったあとで一部のツールを捨てることになることがわかっている場合でも、ツールを使え。
オリジナル
(iv) 技能の低い者に手伝ってもらうくらいなら、プログラミングの仕事を明確にするためにツールを使え。ツールを作るために遠回りしなければならない場合でも、使ったあとで一部のツールを捨てることになることがわかっている場合でも、ツールを使え。
変更理由と解説
ソフトウェア開発の仕事はプログラミングだけではありません。仕事を楽にするためにツールを使うのは当然ですが、高価な人間の時間を節約するために、可能な限り自動化することが重要です。ツールはより便利なものに置き換わり、古いものは捨てられることがあります。それでも「捨てることがになることがわかっている場合でも、ツールを使え」です。
ソフトウェア開発に求められることはますます多くなっており、もはや自分一人の力だけやるには時間が圧倒的に足りなくなりました。さまざまなツールを駆使して仕事をする必要があります。「技能の低い者の手伝い」はツールによって置き換えられてしまいます。ツールに置き換えられることを人手でやってはいけません。
ソフトウェアツールの教義
以下の内容は「ガンカーズの UNIX 哲学」の教義を元に、古い説明を現在に通用するように改善したものです。参考として元のガンカーズの教義を「旧」として書いています。「UNIX という考え方」は主に 1980 年代から 1990 年代初めの、オープンソースもインターネットも Linux もなく、これから UNIX の世界が到来すると夢見ていた時代が舞台であり、内容が古い上に初学者向けのわかりやすく具体的な文章が仇となって、勘違いを引き起こしやすい問題が多い教義となってしまっています。そのため全面的に修正と解説を行っています。
ガンカーズは UNIX の開発で使われたミニコンピュータ(PDP シリーズ)を販売していた DEC という会社(今はもうありません)の社員でした。ガンカーズと UNIX 創始者たちと直接のつながりはないと思いますが、DEC は 1980 年代に BSD Unix をベースにした自社のコンピュータ用の独自 UNIX を開発するという形で関わってきます。その UNIX の開発を通じてガンカーズが理解した UNIX 哲学をまとめのが「ガンカーズの UNIX 哲学」です。
おそらくですが、他の UNIX 哲学と異なり、ガンカーズの UNIX 哲学は読者に対しての「このようにしろ」というアドバイスではなく「私たち DEC の UNIX 開発チームはこのように考えている。このような目標を実現するために頑張るぞ!」という自分たちの考えや決意を伝えているものだったのではないかと思っています。その根拠は「旧準教義2: オペレーティングシステムのカーネルを小さく軽くする」は、(OS 開発者ではない)私たちにそんな事言われてもどうしようもないという教義だからです。したがって例えば「テキストファイルを使う」の真の意味は「UNIX 開発者である私たちは OS の設定ファイルなどにテキストファイルを使用する!(みなさんはご自由にどうぞ)」という宣言で「すべてのプログラムをフィルタにする」というのは「(今の UNIX では不可能だけど)それが実現できるように UNIX を開発する!」という到達すべき理想だったのかもしれません。要するに元の教義は私たちが従うための教義として作られていない可能性があると思っているのですが、この記事では他の UNIX 哲学に合わせて、文章を書き換えて、私たちが従うべき教義に変更しています。
ガンカーズの UNIX 哲学は 1994 年(日本語訳は 2001 年)に出版された「UNIX という考え方」で定義されました。2003 年には改訂版「Linux and the Unix Philosophy」が出版されているのですが、こちらは日本語に翻訳されていません。ちなみに「UNIX という考え方」の日本語訳では「教義 (Tenets)」の事を「定理」と書いています。しかし(DEC を除く?)UNIX 界隈で広く受け入れられていたとも思えないので、本来の意味である「教義」に変更しています。「UNIXという考え方」の P9 には教義(定理)に関して以下のように書かれていますが、
ここまでの項目は、UNIX の開発者たちにとって、その教義にも匹敵するものだ。他の UNIX に関する書籍にも取り上げられているだろうし、UNIX の基礎をなす考え方として広く受け入れられている。これらの定理を身に着けていれば「UNIX 人」として認められるだろう。
この教義そのものがガンカーズの手によって考え出されたものでガンカーズの主張にすぎず、それが事実であることを示す証拠はありません。鵜呑みにしないようにしてください。
教義1: シンプル・イズ・ベスト
- 旧: スモール・イズ・ビューティフル(小さいものは美しい)
コンピュータの性能が低いときは、プログラムの実行イメージが小さい(= メモリ使用量が少ない)ことが、スワップやページングを減らしパフォーマンスの向上にも繋がりました。しかし大量のメモリを搭載している今はプログラムの実行イメージを小さくするメリットは少なく、逆に使用メモリ量や使用ディスク量が大きい方がパフォーマンスや移植性の点で優れている場合すらあります。例えば巨大なデータベースや動画編集ソフト、アルゴリズムによってはメモリを大きく使うことで高速化が可能ですし、仮想マシンイメージを使ったりコンテナを使うとディスク使用量は大きくなりますが移植性や可搬性が高くなります。
ソースコードを小さくするメリットはサイズではなく複雑さを「小さく」することであり、それはすなわちシンプルにするということです。プログラムの実行イメージを小さくすることにメリットがなくなった今、スモール・イズ・ビューティフルというよりもシンプル・イズ・ベストと言ったほうが適切です。今必要とされている道具というのは、「小さな道具」ではなく「シンプルを実現してくれる道具」 です。道具自体が大きくてもそれによって、シンプルを実現してくれるのであれば、それはベストな道具です。
シンプルの正しい意味は「簡単」ではなく「絡ませない」ことです。例えば一つの処理の流れに、その本質とは関係ない処理が絡んでいるようなものを複雑と言います。例えばシェルスクリプトで、本質的な処理と環境毎のコマンドの違いを吸収するようなコードが「絡んでいる」ようなものを複雑と言います。長過ぎるパイプラインも異なる処理が「絡んでいる」事があるため複雑になりがちです。絡んでいるコードは取り除くのが難しくなります。複雑性から逃れるためのキーワードは抽象化です。抽象化とは具体的な実装(環境ごとのコマンドや処理)を隠蔽することで、その実現方法としてよく使われるのが関数や外部コマンドに分離することです。シンプルと簡単は異なる意味を持つ言葉です。「シンプル」「複雑」は客観的な概念で、人によって感じ方が違うものは「簡単」「難しい」という概念です。詳細は「シンプルさの必要性」を参照してください。
教義2: 一つのプログラムには本当にやるべきことをうまくやらせろ
- 旧: 一つのプログラムには一つのことをうまくやらせる
変更した理由は「マキルロイの UNIX 哲学」の i. と同じです。「一つのこと」というのは定義が曖昧で、実際には一つのことをしているコマンドはほとんどありません。一つのプログラムを一つのことにすることにこだわると、逆に使う側が面倒になります。何を一つとみなすか?という無駄な議論に終始するだけで何も生まれません。それならば「本当にやるべきこと」はなにか?を考えた方がはるかに価値があります。本当にやるべきことはプログラムの役割で決めるべきことです。それをみんな分かっているから「一つのこと」と言っていたとしても守っていないわけです。
もちろん「しのびよる多機能主義」には注意すべきです。それはほとんど必要とされないような機能を作らないということです。別のコマンドとして作った場合、それは結局の所しのびよる多機能主義の犠牲になっています。単に組み合わせないで使えれば便利なものを、組み合わせなければ使えないように不便な形で作っただけです。UNIX 哲学は「組み合わせて使うことができる」道具にすることで「組み合わせて使わなければいけない」道具にすることではありません。
皮肉なことに組み合わせて使えるようにした道具は多機能になりがちです。なぜならあらゆる道具と組み合わせることができるように機能が追加されていくからです。ls
コマンドは端末に出力した時に自動的に複数カラムで表示されます。ls
コマンドから複数カラム表示機能をなくし ls | column
を使うようにした場合、その column
コマンドはあらゆるコマンドと組み合わせれるように区切り機能を変更できるなど複数のオプションを備えています。複数カラム表示機能を ls
コマンドに内蔵させるか?別のコマンドに実装するか?ではなく両方もたせた方が便利です。ls
コマンドに内蔵されているものは簡易版、もっと便利なことがしたいなら column
コマンドと組み合わせるという考え方です。大抵の場合は簡易版で十分なのでシンプルに保つことが出来ます。
一つのプログラムで多くのことをするのが嫌な人もいるでしょう。「一つのこと」にしたいならそのように作るのは自由です。ただしよく考えてみてください。多くの機能を実装することで困るのは実装が大変になるかもしれない実装者だけだということです。利用者は不要な機能があったとしても使わないだけなのでたいして困りません。つまり何が言いたいかと言うと、他人に「◯◯の機能が欲しいです」と言われたら実装するのは面倒だなと感じるかもしれませんが、実装者が自分でそれを実装したいと思ったのなら実装して良いということです。実装者には自分のプログラムを自由に作る権利があります。誰かが言った「一つのこと」に自由を奪ばれる謂れはありません。
教義3: できるだけ素早く何度も試作できる仕組みを作れ
- 旧: できるだけ早く試作を作成する
変更した理由は「マキルロイの UNIX 哲学」の ii. と同じです。この考えはアジャイルにも通じる考え方です。早い段階で一度だけ試作するのではなく、素早く何度も試作を繰り返すことが重要です。システムを高速に開発するには仕組みを作らなければいけません。仕組みを作るというのは例えばビルドやテストの自動化を意味します。ウェブアプリであればサーバーへのデプロイも含まれ、そのために git などのバージョン管理ツールや CI サービスとの連携も必要となってくるでしょう。これらの仕組みを提供する「ソフトウェアという道具」を組み合わせることは、まさに UNIX 哲学の考え方です。仕組みなしでは何度も試作するのが面倒になりウォーターフォールの考え方に戻ってしまうことでしょう。
余談ですが「アジャイルソフトウェア開発宣言」には「道具を使わない」とか「ドキュメントを作らない」とか「計画しない」とは書いていません。アジャイルは道具を使うし、ドキュメントを作るし、計画します。勘違いしないよう「アジャイルソフトウェア開発宣言の読みとき方 」の『「アジャイルソフトウェア開発宣言」に対する誤解と真意』をよく読んでください。
教義4: 要件を満たす品質と高い生産性を両立させよ
- 旧: 効率より移植性
ハードウェアの特殊機能を使い効率を上げると移植性が下がると言い出したのはガンカーズですが、これは 1980 年代の古い考え方です。現代の考え方はドライバやラッパーなどでハードウェアや環境の違いを吸収し、効率と移植性を両立させるという考え方が主流です。この考え方はあらゆる場所で使用されています。ハードウェアの特殊機能が使える場合は使い、そういう機能がなくても動くため効率と移植性が両立します。ラッパーはわずかにオーバヘッドがありますが、もし「効率より移植性」という言葉を現代に当てはめるならば「ラッパーをなくして効率を求めるより、ラッパーを使って移植性を優先しよう」と言うべきです。
重要な注意点としてガンカーズが言っている「移植性」とは、異なるアーキテクチャのコンピュータへの移植の話であって、異なる OS への移植の話では無いということです。1980 年代は今よりも多くのコンピュータ会社が覇権を握ろうと争っており、ソフトウェア会社はいくつかのコンピュータ専用にソフトウェアを移植していました。しかし現在はハードウェアの違いは OS によって吸収されており、私たちは特定の OS 用にソフトウェアを書けば良くなりました。さらにウェブの普及で一つの OS 専用にソフトウェアを開発したとしても、多くの種類のコンピュータの利用者を相手にビジネスが出来るようになりました。移植性は今も重要ですが、その価値は昔ほど大きくはありません。特定の OS しか対応していなくても今すぐ使える方が役に立ちます。
また、一般的なソフトウェアであれば、適切な言語やライブラリを選ぶだけで移植性はただで手に入るものになっています。移植性を気にせずに単一のソースコードを書くだけで誰でも簡単に最低限度の移植性を手に入れることができるようになったため、移植性の高さは技術力の差では無くなってしまいました。今の時代に移植性が低い言語というのは、自分で互換性問題を解決しながらプログラミングしなければいけない言語のことで、シェルスクリプトは移植性が低い言語の一つとなっています。
ガンカーズの「効率」という用語の使い方にも注意が必要です。「効率が良い」には CPU、メモリ、ディスクのリソース使用量が少ないという意味がありますが、ガンカーズは「効率 (efficiency) が良い」を「性能 (performance) が良い」の意味で使っていることがあります。「効率が良い」とは単に「リソース使用量が少ないこと」ではなく「ある条件下において無駄が少ないこと」です。例えば高速に処理するために多くのメモリを使用するアルゴリズムがあったとして、その性能が達成できるという条件の元でメモリの使用量が少なければ効率が良い言われますが、性能が達成できないのであればいくらメモリの使用量が少なくても効率が良いとは言われません。コンピュータの搭載メモリが少ない時代は、多くのメモリを使用するとスワップやページングによって速度が遅いディスクへの読み書きが発生し性能が低下したため、「効率がよい」=「メモリ使用量が少ない」=「(スワップやページングが発生しないため)性能が良い」が成り立っていましたが、現在は「効率がよい」と「性能が良い」はイコールではありません。
注意すべきことは「ハードウェアの特殊機能を使う = 性能が良い = 移植性は低い」という話は効率とは無関係の話だということです。ここで性能と効率と同一視してしまうと「効率が良い = 移植性が低い」という間違った式が生まれます。元の教義の「効率より移植性」はこの間違った式が元になっています。効率(性能)が良い方法は、必ずしも移植性が低くなるわけではありません。今は効率と移植性を両立させようとするので 「効率(性能)を犠牲にしてでも、移植性が高い方法を選べ」 という話は時代遅れの考え方です。
ガンカーズの移植性の話の真実は、
- 1980 年代まではコンピュータの性能が低かったので効率(性能)がとても重要だった。性能を上げるにはハードウェアの特殊機能を使うのが効果的だったが OS によってハードウェアの違いが吸収されていないため、自分たちでハードウェアの違いを吸収するラッパーを書かなければならなかった。しかもラッパー自体が効率の低下をまねくほどコンピュータの性能は低かった。さらにハードウェアの性能が短期間で大きく向上した時代でもあり、次から次へと高性能だが互換性のない新しいハードウェアが開発された。この時代では効率と移植性を両立させるのは困難で開発コストを考えると割に合わず、効率よりも移植性の方が重要だという考え方が生まれた。
ということなのです。
現在はその状況は全く変わっています。一部の例外を除きソフトウェアはどのようなハードウェアを使っているかを気にすることはなく開発することができるようになりました。OS がハードウェアの特殊機能があればそれを使い、そうでなければソフトウェアでエミュレートをするなどして移植性を気にすることなくコンピュータの性能を引き出すことができるようになっています。したがって現代で効率よりも移植性が重要だからハードウェアの特殊機能を使わないようにしようと言われても時代錯誤でしかありません。今でもハードウェアの特殊機能が重要な分野(例えば高度な 3D ゲームや機械学習やビッグデータ関連など)は残っています。そういった分野ではハードウェアの特殊機能が極めて重要な意味を持つため、移植性が大事と言われても性能を諦めたら実用レベルの要件を満たせません。ソフトウェアエミュレーションでは実用レベルの性能が出せないから、ハードウェアの特殊機能を使うしかないという分野も一部にまだ残っています。
さてソフトウェアに必要なものは効率や移植性だけではありません。効率や移植性は今は品質の一部として扱われています。品質は以下のように「機能適合性」「性能効率性」「互換性」「使用性」「信頼性」「セキュリティ」「保守性」「移植性」に細分化されています。(参考 IPA「つながる世界のソフトウェア品質ガイド」)
このような品質の考え方は 1990 年代頃より確立されていったようです。つまり「ガンカーズの UNIX 哲学」(1994 年出版だが、ほとんどは 1980 年代の話)は品質の考え方がまだ十分に発展していない時代だったということです。当時のコンピュータの性能は低いため、効率や移植性以外のことを考える余裕はなかったでしょうから仕方のない話です。なお品質に関する歴史は「ソフトウェア品質技術の歴史を振り返る - ソフトウェア品質測定を中心に -」で分かりやすくまとめられていました。
ソフトウェアに求められる品質のレベルはプロジェクトによって異なりますが、いずれであってもソフトウェアに求められる品質は高くなる一方で、高い品質を実現するにはどうしても時間がかかってしまいます。生産性が不要というプロジェクトはまず無いでしょうから、高い品質と生産性を両立させるなければなりません。そのためには適切な道具を組み合わせて使うことが重要です。素晴らしいことに現代では品質の高いソフトウェアがしかもオープンソースで数多く提供されるようになりました。それらの言語やライブラリ、コマンドやミドルウェアを組み合わせて使うことこそが UNIX 哲学の考え方に通じる考え方です。
現代において移植性を高める技術はドライバやラッパーによる抽象化です。ラッパーの存在によって効率が下がると思うかもしれませんが、現代ではその効率の低下は無視できるレベルで、それを取り除いてまで性能を重視する意味はありません。そういう意味で「効率より移植性」という言葉はある意味正しいのかもしれません。Go などの適切な言語やライブラリを選べば、ラッパーに相当する処理が(必要ならば)組み込まれているので何もする必要がありません。つまり最初から「効率よりも移植性」が実現されている上に生産性も高いです。移植性は最優先事項ではなく品質の要件の一つでしかありません。したがって他の環境への対応はビジネス戦略上の理由で必要だと思うのなら対応し、そうでなければ対応しなくても良い、もしくは必要になってから対応すれば良いといった程度のものです。その他にも考えなければいけないことは山程あります。「ソフトウェアに必要なものは効率か?移植性か?」という二択だけを考えればよかった時代はとっくに終わっています。
教義5: ファイルには標準的な形式かテキストベースの単純な形式を使用せよ
- 旧(初版): 数値データはASCIIフラットファイルに保存する
- 旧(改訂版): データはフラットテキストファイルに保存する
この教義はマキルロイの UNIX 哲学にある他のプログラムと連携できるようにテキストベースのインターフェースにする話とは似て非なるものです。他のプログラムと連携するために必要なのはインターフェース(標準入出力)であって、ファイル形式は関係ありません。したがって「データはフラットファイルの保存する」というのはガンカーズの UNIX 哲学の独自の考え方です。
ファイルに移植性があるということは、未知のプログラムからでもそのファイルの読み書きができるということです。そのために必要な条件は標準的なファイル形式を使用することです。テキストにするかバイナリにするかは関係ありません。テキストの方が取り扱いに便利だとは思いますが、ファイルの移植性の話とは関係ないですし、バイナリを使用禁止にするほどのことではありません。標準的なファイル形式を使用しているならば、知らない人が作ったプログラムとも連携させることが出来ます。標準的ではないファイル形式を使う場合、そのファイル形式を扱うための専用ツールができあがってしまいます。他のプログラムはその特殊なファイル形式に対応しなければいけなくなるでしょう。
zip 形式や gif 形式すら存在していなかった 1980 年代とは違い、今はバイナリベースであっても数多くの標準化された形式、デファクトスタンダードになった形式があります。特定の企業が独占しているものではなく、仕様がオープンで厳密に決まっており、世の中で広くサポートされているファイル形式であれば、移植性が高いファイル形式と言えます。バイナリでありながら移植性が高い形式には例えば SQLite のデータベース形式や Excel ファイル形式があります。(旧 xls 形式も仕様は公開されています, Microsoft Office Excel 97-2007 Binary File Format (.xls) Specification - This specification is provided under the Microsoft Open Specification Promise.)
逆にテキストベースのものでも、明確なファイル形式の仕様がなく、特殊なデータ構造やディレクトリ構造を持っていたり、ファイルの配置やアクセス方法に特別なルールがあったり、逆にルールが全くなく気まま勝手に仕様が作られていたりするものは、移植性が低いと言えます。なぜなら既存のプログラムで対応しているものはなく、複雑な形式であれば正しく扱うのは困難になるからです。またテキストベースのファイル形式であっても企業が独占しているプロプライエタリな形式はあるので注意してください。一例として FBX 形式を紹介しておきます。テキストベースの形式だからといって必ずしも移植性が高いわけではありません。
もし標準化されたデータ形式で適切なものがなければ、できるだけ単純な行指向のテキスト形式にしておくのが無難です。単純な行指向のテキスト形式とは、一行がそのまま一データになっている形式や、一行に空白などの文字で区切られてデータが並んでいるような形式(いわゆる SSV 形式)です。単純なテキスト形式であれば対応するのもそんなに大変ではないでしょう。可能な限り単純にしておかなければ他のプログラムから読み書きするのは困難になります。しかしこの場合、データに空白や改行などの特殊文字が含まれる場合の標準的な決まりがないためデータの性質によっては問題が発生する場合があります。シェルスクリプトや UNIX コマンドは一部を除き単純な行指向のテキスト形式しか基本的には扱えず、特殊な文字を扱うための標準的な決まりがありません。それが今後改善していかなければならないシェルスクリプトの問題点の一つです。
しばしば間違って解釈されていますが「UNIX コマンドはテキストベースの形式なら何でも扱える」わけではありません。例えば JSON 形式はテキストベースの形式ですが、その構造を理解してないので「構造を持たないテキスト形式」としてしか扱うことが出来ません。構造の意味を理解してなくても一応扱えるじゃないかという理屈は、バイナリエディタを使えばすべてのバイナリファイルを読み書きできるからバイナリは移植性が高いと言ってるのと同じことです。構造(または半構造)のテキストベースのデータ形式が誕生したのも UNIX の時代より後です。つまり UNIX コマンドが扱えないテキストベースの形式が新たに誕生してしまったわけです。UNIX コマンドの正しい解釈は「単純な行指向のテキスト形式であれば UNIX コマンドでも扱える」という程度のものです。それ以外の形式を扱うには jq
コマンドのような UNIX コマンド以外のコマンドが必要です。
どうしても独自の形式が必要な場合は、独自形式はテキストベースにしておいた方が良いとは思いますが、データの移植性が高い基準はテキストベースかバイナリベースかではなく標準的なファイル形式を使うことです。もしどうしても標準的なファイル形式を使うことが出来ず、単純な形式にも出来ないのであれば、新たにファイル形式の仕様を定義して公開したり、簡単に読み書きや標準的な形式に変換できるライブラリやコマンドを(可能であればオープンソースで)作ると良いでしょう。もしその形式が世の中に広まれば標準的なファイル形式の仲間入りです。もちろんプログラムが内部的に使うだけのファイル形式であれば独自形式で仕様を定義しなくても構いません。標準入出力のインターフェースさえわかりやすければそれで十分です。
教義6: 多くのソフトウェアを活用して生産性を高めよ
- 旧: ソフトウェアの梃子を有効に活用する
最初にこれを読んだ時「ソフトウェアの梃子」の意味がわかりませんでした。梃子(テコ)とはテコの原理のテコです。英語だとレバレッジです。レバレッジは投資用語としてはよく使われていますがテコでもレバレッジでもよくわかりませんでした。この文章の意味は「一人の力は小さく限界があるが、他の人が作ったソフトウェアをテコとして考えると、テコを使って小さい力を大きな力へと変換できるように、自分一人の力では出来ないことや作れないようなものを他人の力を借りて実現できる」と言う意味です。つまり「ソフトウェアの梃子を有効に活用する」とは 「既存のソフトウェア資産を利用して生産性を高めましょう」 という意味です。
ただしソフトウェア資産を利用するといってもコードのコピペ(既存のコードを切り貼りして使うこと)をしてはいけません。シェルスクリプトであっても著作権はありますし、既存のコードを(意味を理解せずに)切り貼りして使う行為は品質を落とすことにつながるのでダメだというのは、よく知られていると思います。これをわざわざ書くのはガンカーズがコピペを推奨しているような書き方をしているからです。最初にやるべきことは汎用的なライブラリを修正せずに使うことです。この当たり前の話が「UNIX という考え方」に登場してないのは、おそらく当時は「汎用的なライブラリ」という考え方が希薄だったと思われるからです。その根拠は「詳説 正規表現 第三版」の P86 に書かれている以下の文章です。
同じく 1986 年に初めて登場し、おそらく POSIX よりも重要なものが、Henry Spencer が C 言語で書いた正規表現パッケージである。このパッケージは、他のプログラマが自由に自分のプログラムに組み込めるようになっていたが、そのようなものは当時としては初めてだった。Henry のパッケージを使っていたプログラム(多数あったが)は、作者がわざわざ書き換えるようなことをしなければ、どれも一定の同じ動きをした。
この文章を初めて読んだ時、(OS の標準ライブラリ以外に)汎用的なライブラリがなかった時代があったことに、自分が気づいていないことに気が付きました。考えてみれば C 言語自体が UNIX の開発で生まれた言語なので、UNIX が世の中に発表された時に C 言語用の汎用ライブラリがないのは当たり前の話です。UNIX を世の中が使い始めた時、皆は全てのコードを自分で書いていたわけです。
汎用的なライブラリを設計するのは意外と難しいものです。汎用的なライブラリがない時代では、既存のコードの中から使えそうなコードを探し出してコピペするしかありません。そのため UNIX が提供する標準的な機能(C 言語の標準ライブラリやコマンド)のみを使い、あとはコピペして使うのが当時のよくある開発スタイルだったのだと思います。まあ、それも昔の話です。今は高品質な汎用ライブラリがどの言語にもあります。(シェルスクリプトは少ないのですが・・・)
ソフトウェアの梃子は UNIX に最初から備わってる基本機能だけのことではありません。C 言語やシェルスクリプトだけではなく、あらゆる言語、あらゆるライブラリ、あらゆるフレームワーク、あらゆるコマンド、あらゆるミドルウェア、すべてのソフトウェアが梃子です。今のプログラムはとても大きくなり、また OS が提供してない画像処理や音声処理など高度で難しいアルゴリズムを必要とするものも多いため、一人の力で価値のあるソフトウェアを作るのは難しくなってしまいました。価値の高いライブラリというのは何千行、何万行もあり、同じようなものを作るとしたら膨大な時間がかかってしまいます。
すでにあるソフトウェアを活用するのは今では当たり前の考え方です。今ではさまざまなソフトウェアがオープンソースかつ無料で使えるようになりました。逆に簡単すぎることまで過剰にライブラリに依存するのはやめようと警告しなければいけないぐらいです。ソフトウェアの品質は依存しているソフトウェアに依存するので、品質の高いものを選ばなければいけません。そういった品質が高いソフトウェアを見抜くのも技術力の一つです。
教義7: コマンドをシンプルに組み合わせるためにシェルスクリプトを活用せよ
- 旧: シェルスクリプトを使うことで梃子の効果と移植性を高める
まず最初に明らかにしておかなければいけないことが二つあります。一つはガンカーズは C 言語よりもシェルスクリプトの方が移植性が高いと言っているだけで、Rust や Go や Python や Perl などの他の新しい言語との比較はしていないということです。もう一つはシェルスクリプトを他の OS へ移植する場合は移植性は低いということです。梃子の効果を高めるのにシェルスクリプトである必要はありません。
一つ目についてですが、ガンカーズが他の言語と比較していない理由は、UNIX 全盛期の時代には、他の言語は、まだ誕生していないか普及していない段階であり、UNIX 開発者であるガンカーズにとって UNIX の一部ではない言語は選択肢にならなかったからです。C 言語とシェルスクリプトは UNIX の開発の中で生まれた言語で UNIX の一部ですが、その他の言語はそうではありません。補足として一応 awk も UNIX で生まれた言語ですし sed も言語に近い所がありますが、汎用性としてはシェルスクリプトよりも劣っています。
二つ目についてですが、ガンカーズがシェルスクリプトの移植性が高いと言ってるのは、あくまで自社の UNIX を新しいアーキテクチャのコンピュータに移植した時に、シェルスクリプトで書いていれば修正の必要がないという話です。移植した UNIX はこれまでの UNIX と同じ、もしくは同じ UNIX の後継バージョンなので、その場合は確かにシェルスクリプトの移植性は高いと言えます。しかし他社の UNIX へシェルスクリプトを移植する場合、環境依存が激しいためシェルスクリプトの移植性は低くなります。
ここで POSIX で UNIX の仕様が標準化されたのだから、POSIX に従っていればシェルスクリプトの移植性は高くなるのではないか?と思うかもしれませんが、そこに罠があります。実は POSIX は各 UNIX コマンドの実装の違いを完全になくすことを目的としていません。大雑把に言うならば「1. どの実装でも互換性がある部分」「2. 微妙に動きが異なる部分」「3. 互換性がない部分」に分類して、移植性が高いアプリケーションを開発する場合は 2 と 3 に気をつけながら開発してくださいというガイドラインを作っただけです。各 UNIX ベンダーは後方互換性を維持するために自分たちの実装の動きを変更せず、POSIX もそれで良し(POSIX 準拠と認める)としています。POSIX は実装ありきの考え方なので、多くの実装で互換性があると認められれば標準化しますが、自らが音頭を取って仕様を統一させたり新しい仕様を策定したりしません。基本的に UNIX ベンダー任せとなっており、UNIX ベンダーは後方互換性のために動作を変更しないため POSIX の改訂が少ない理由です(実装ありきなので改訂したくても UNIX ベンダーが変更するまでは改訂出来ない)。ただし C 言語の部分に関しては ANSI C で標準化されたものを採用するという方針であり、ANSI C は POSIX とは別の所で定期的に改善されています。つまりシェルスクリプトに関係する部分だけが移植性が低いまま取り残されているのが現状です。
突然ですがここでクイズです。次の 1、2 のうち、移植性が高いシェルスクリプトは一般的にどちらになるでしょうか?なおここでいう移植性とは他の UNIX または UNIX 系 OS への移植性のことです。
- 標準インストールされている UNIX (POSIX) コマンドだけを使うシェルスクリプト
- 標準インストールされていない 非 UNIX (POSIX) コマンドだけを使うシェルスクリプト
正解は 2 です。1 は各 UNIX ごとに微妙に互換性がないコマンドに依存しています。ブラウザや Java の実装が多くて互換性問題で苦しんでいたのと同じ話で UNIX コマンドの実装が多ければ多いほど互換性は低くなります。一方、2 の UNIX コマンド以外は基本的に開発元が一つで、そのコマンドをそのまま使っていることが多いため互換性問題はありません。もちろんそのコマンドがどの環境でも動き互換性を維持している場合に限られますが、ユーザーが多いコマンドは大抵その条件を満たしています。ここで 1 と答えた人は、おそらく 「OS の最小構成で動くこと」と「移植性」を混同しています。インストールされていないならインストールすれば良いだけです。1980 年代の「C 言語で作られたコマンドはビルドが難しくてインストールするのは大変」という考えは過去のものです。今はパッケージマネージャを使って簡単にコマンドのインストールやセキュリティ更新ができます。シェルや UNIX コマンドの互換性問題でさえ、GNU bash や GNU CoreUtils をインストールする程度で解決することが出来ます。サーバーソフトならいざしらず、ほとんどのコマンドは設定不要でインストールするだけで使えます。パッケージがない場合でも Go で作られたソフトウェアなどはスタティックビルド済みのものが配布されていたりするので、シェルスクリプトをコピーするように実行ファイルをコピーするだけで使えます。
さて、ここまででシェルスクリプトの移植性は低いと明らかにしました。後に誕生した新しい言語は C 言語やシェルスクリプトよりも優れています。ソフトウェアの梃子(コマンドやライブラリ)の効果なら他の言語でも活かすことが出来るのでシェルスクリプトにこだわる理由はありません。さらに他の言語は難しいことを考えずに高い移植性を実現することができます。シェルスクリプトでアプリケーションを開発するというのは、他に C 言語しかなくライブラリもなかった 1980 年代のアイデアです。当時は面白いアイデアだったかもしれませんが、もう 30 年も 40 年も昔のアイデアです。テキストベースのファイルを扱う場合でも他の言語の方がより高速で簡単です。
シェルスクリプトでのアプリケーション開発は、本質的にいくつもの問題を抱えています。コンパイルが不要という点は C 言語に対するメリットになりますが、他のスクリプト言語はそれも同じです。スクリプト言語の時点で高いパフォーマンスを実現することはできませんが、頻繁に外部コマンドを呼び出してはすぐに終了させるスタイルであるため、何度もコマンドの実行やデータの読み直しが発生しパフォーマンスの悪さに拍車をかけています。パイプによる大量のデータのプロセス間通信もパフォーマンスに影響を与え、パイプでつなげるコマンドが多いとその数だけ一時的な中間データをメモリ内に何度も作ることになるので効率も悪くなります。複雑なデータ構造に対応してないため、適切なデータ構造を直接扱えず変換を必要とし、OS が持っている API を直接呼び出すこともできず、さまざまな処理を他の言語のプログラム経由で実行しなければならないため、そこがオーバーヘッドになります。結局他の言語のプログラムを呼び出して処理するのであれば、最初からその言語でやったほうが効率も性能も高いということです。簡単なものならいざしらずシェルスクリプトの中でいろいろな処理をしようと思ったら多くのコマンドを組み合わせなければいけなくなるため、すぐに複雑になってしまいます。
簡潔に言うなら「高度なデータ処理を行うのであればシェルスクリプトを使うよりも他の言語を使ったほうが良い」ということです。他の言語には昔から awk や Perl が使われてきましたが、最近なら Python や Go などを使うと良いでしょう。もちろん単純なテキスト処理で十分な場合は sed
や grep
を使ってよいのですが、それらのコマンドをいくつも複雑に組み合わせてようやく「一つのデータ処理」をするぐらいなら、その部分をシンプルな「他の言語で作った一つのコマンド」で置き換えた方が優れています。だからといってシェルスクリプトに役目がないということではありません。シェルスクリプトの役目は、独立した複数のデータ処理を行う「コマンドを連携させること」です。
シェルスクリプトがコマンドを連携させるのに向いている理由は、コマンドを関数のように使えるように設計されているからです。他の言語ではその言語の関数を使って間接的にコマンドを呼び出す必要があるため面倒です。またコマンドは標準出力にデータを出力しますが、他の言語の関数は標準出力は基本的に使わず、戻り値でデータを返すため、関数とコマンドではインターフェースが全く異なっています。このような違いがあるため、他の言語はシェルスクリプトの代わりにはならず、したがってコマンドを連携させるスクリプト言語にはシェルスクリプトが最も適していると言えます。なおここでいうシェルスクリプトとは Bourne シェルや POSIX シェルに限定していません。対話的に使えるシェル(csh や fish など)で動作するシェルスクリプトすべてを含んでいます。(広義には Windows のバッチファイルも含まれます)
さて、少し長くなってしまったので軽くまとめておきましょう。梃子の効果と移植性を高めるためだけにシェルスクリプトを使う理由はありません。いくつも組み合わせて無理やり一つのデータ処理を行うぐらいなら、他の言語でデータ処理を行うコマンドを作りましょう。UNIX コマンドに固執したいなら awk を言語として使えますが、もっと適切なコマンドが使えないか探しましょう。シェルスクリプトはコマンドを連携させる時に使います。ただしシェルスクリプトや UNIX コマンドは移植性が低いので注意が必要です。移植性は必須ではないので諦めるのもありです。また GNU bash や GNU CoreUtils などをインストールして使えば移植性問題はある程度解決します。UNIX 哲学はいろんな道具を組み合わせて使うことなので、道具をインストールして問題を解決することはまさに UNIX 哲学の考え方です。
教義8: 自動処理のためにコマンドには非対話的なインターフェースを持たせろ
- 旧: 過度の対話的インターフェースを避ける
「過度の対話的インターフェースを避ける」と意味が大きく変わったわけではありませんが、過度と言われてもどのレベルが過度なのかわかりません。また、重要なことは対話的なインターフェースを避けることではなく、対話的で花インターフェースをもたせることです。したがって対話的インターフェースを実装しても良いけれども、自動処理ができるように非対話的に使えるインターフェースも持たせましょうという形に修正しています。
対話的インターフェースとは、例えば「実行しますか? (Y/N)」のように人間がコンピュータと話す(対話)ように操作することを前提としたインターフェースのことです。他にはカーソルキーでメニューを選択するようなインターフェースもそうです。操作だけではなく、ls
のカラム表示や出力に色を付けるような人間のための出力機能も対話的インターフェースと言えます。(シェルスクリプトではなく)人間がシェルを使っているときも対話的に使っていると言います。対話的な機能を実装している場合には、それを無効にするオプションや仕組み(出力先が端末ではない時に自動的に無効にするなど)を持たせなければなりません。
対話的インターフェースを避ける理由は、このようなものがあるとシェルスクリプトから自動処理を行うことが困難になるからです。(Y/N) のような質問であれば yes
コマンドを使って回避もできますが、それは行儀が悪いプログラムのための最終手段であって yes
コマンドを使うことがないようにしなければいけません。例えば全ての質問に自動的に yes を選択するオプションをコマンドに実装したりすることです。
CLI 環境で動くプログラムは CLI コマンドだけではないことに注意してください。TUI(テキストユーザインタフェース)と呼ばれるテキストベースのユーザーインターフェースを持ったプログラムもあります(ちなみに CUI は和製英語です)。例えば vim
や emacs
のようなもので、テキスト文字を使うけれども、画面全体を使って GUI のようなインターフェースを提供しているものです。このような TUI として使うのを前提としたプログラムにわざわざ非対話的なインターフェースを用意する必要はありません。TUI に CLI として使えるようなインターフェースを追加して複雑化させるよりも、別の CLI コマンドとして独立させた方が良いでしょう。別コマンドとして分離したとしても、その中で実行する処理をライブラリなどにすることで共通化することが出来るので重複コードが増えてしまうことはありません。
教義9: データを処理するプログラムは可能な限りフィルタにせよ
- 旧: すべてのプログラムをフィルタにする
UNIX コマンドの文脈でフィルタとはデータを標準入力から入力し標準出力にデータを出力するコマンドのことです。UNIX コマンドは必ずしもフィルタになっているわけではありません。厳密に数えてはいませんがおそらくフィルタとして機能するもの半数ほどしかないと思われます。
UNIX コマンドでフィルタになっているのは、テキスト処理やデータ処理のコマンドです。このようなコマンドには sed
, awk
, cut
, grep
などがあります。また diff
や join
のようにデータの一つは標準入力から受け取るもののもう一つはファイルから入力するという不完全なフィルタもあります。
一方でファイル管理やプロセス管理のコマンドはフィルタではありません。例えば df
, du
, ls
, ps
などです。これらは入力データを標準入力ではなくファイルシステムやプロセス情報から直接取得します。したがって全てのプログラムをフィルタにすることは出来ません。
データ入力を行わないコマンドも多数あります。例えば echo
, printf
, seq
, yes
, date
, dirname
, basename
などです。これらのコマンドはコマンドライン引数で挙動を変更します。コマンドライン引数を入力と考えることも出来ますが、標準入力からデータを入力するのではないため定義上はフィルタではありません。このようなプログラムは人間の指示(引数)で「プログラムがデータを作る」と考えることが出来ます。必ずしもプログラムから読み取り可能なデータが先にあるわけではないので、これもすべてのプログラムをフィルタには出来ない理由です。
標準入力も標準出力もないコマンドがあります。例えば chmod
, chown
, kill
, wait
などです。これらは引数で人間の指示を受け取りファイルシステムやプロセス情報に直接影響を与えます。
このように既存の UNIX コマンドにはフィルタではないものが多数あります。このような状態で「すべてのプログラムをフィルタにする」と言われても説得力はありません。無理やりフィルタのような形にしようと思えば出来るかもしれませんが、それでは UNIX として直感的なインターフェースとは言えなくなるでしょう。(個人的には無理やりフィルタの形にするという発想で面白いことが出来るのではないかと考えていますが、まだ考えはまとまっていません)
データとはなにか?おそらくそれは「ファイルの中身」として表現可能なものではないでしょうか?フィルタがない時代、つまりパイプが存在しない時代、同じようなことをするにはあるプログラムの出力をファイルに出力して、次のプログラムはそのファイルを処理していました。それを不要にしあるプログラムの出力を別のプログラムの入力に直接つなげることが出来るのがパイプです。つまりフィルタにすることが可能なのは「ファイルの中身」を扱うプログラムだけだということです。chmod
のようなものは「ファイルの中身」ではなく「ファイルのメタ情報」を扱うものなのでフィルタにできません。もしすべてのプログラムをフィルタにするならば、その前提としてすべての情報を「ファイルの中身」として表現する OS を作らねばならないでしょう。UNIX はそのような OS ではないので、すべてのプログラムをフィルタにすることはできません。
ソフトウェアツールの準教義
「UNIX という考え方」では 9 つの教義の他に「さらなる 10 の UNIX の考え方」 (Ten Lesser Tenets) として重要度の低い教義が紹介されています。この記事ではこれらを準教義と表現しています。ただし単に「UNIX という考え方」に対応させて準教義と書いているだけなので、この記事の説明には Lesser(劣る)というニュアンスは含まれていません。
ガンカーズは「UNIX という考え方」の P105 で、以下のように強い言葉で「教義」がどれほど重要であるかを主張しています。
ここまでの定理については、 UNIX 開発者は一歩も譲らない。攻撃されれば全力で立ち向かう。
しかしこれはあくまでガンカーズの主張であって、ここでいう UNIX 開発者というのはおそらく 「DEC の UNIX 開発チーム」 で、我々はそれぐらいの気持ちで UNIX を開発しているという程度の意味が妥当なのだろうと思っています。また、
以下で議論する考えは、「まあ、どちらかといえば俺も賛成だ」程度だ。」UNIX 陣営の全員が賛成しているわけではないが、コミュニティ全体としては支持していると言って良いだろう。
というのも 「DEC の UNIX 開発チームの間でも意見が分かれている」 という程度の意味なのだろうと思っています。ガンカーズの主張が間違っているという意味ではありませんが、原典の UNIX 哲学とはそんなに近いわけではない一個人の主張と捉えるべきで、それが唯一の正しい UNIX 哲学であるかのように解釈するのは間違いです。
準教義1: プログラマ向けの環境や道具はカスタマイズ性を高くせよ
- 旧: 好みに応じて自分で環境を調整できるようにする
カスタマイズ可能にすべきものは(GUI デスクトップ)環境だけではありません。シェルの実行環境やコマンドのオプションなどによるカスタマイズも同じです。この環境を調整できるようにするという考え方は「スモール・イズ・ビューティフル」や「一つのプログラムには一つのことをうまくやらせる」という古い教義と矛盾していることにお気づきでしょうか?環境を調整できるようにするということは、いろんなことができるようになり、それだけプログラムが大きくなるということだからです。しかし私は既に古い教義を小さくする必要はないと修正したので矛盾はありません。
道具に高いカスタマイズ性を持たせるという考え方は、コンピュータのユーザーではなく、プログラマにとっての価値がある考えだと理解しておく必要があります。プログラミングなどに興味がない一般のユーザーにとっては高いカスタマイズ性は逆に難しい道具に見えてしまうことがあります。Windows や macOS でもカスタマイズの範囲はあまり広くしようとせず、一部の機能は高度な方法(レジストリや CLI コマンド)からのみ設定可能になっています。UNIX 哲学はそもそもプログラマのための哲学です。
具体例をいくつか紹介します。
Windows のメモ帳はカスタマイズ性は乏しくエンドユーザー向けのアプリケーションであると考えられます。サクラエディタのようなものはある程度のカスタマイズ性を備えていますが、その範囲は小さく現在の基準ではプログラマ向けの道具として十分ではありません。VSCode は高いカスタマイズ性を備えておりプログラマの道具として適切であり、拡張機能で機能を追加できるというのは、道具を組み合わせて使うという UNIX 哲学にも通じています。今挙げた三つテキストエディタの中では、プログラマなら VSCode が最も適しており使い方は難しいかもしれませんが生産性は高くなります。しかし一般のユーザーにとってはメモ帳やサクラエディタの方が簡単でしょう。(もちろん vim や emacs もカスタマイ性が高くプログラマの道具です)
別の例として高いカスタマイズ性を備えたコマンドは、他の道具と組み合わせて使う能力が上がります。例えばあるコマンドに単純なテキスト形式だけではなく JSON 形式で出力するオプションがあれば JSON を扱う他のコマンドと組み合わせることが出来るようになります。(複雑さを乗り越えられるのであれば)オプションが多いことは良いことです。
シェル環境もカスタマイズできるようにしておく必要があります。例えばある人は macOS を使いつつも、Homebrew でインストールした bash と GNU CoreUtils を優先して使っている場合もあります。ユーザーがカスタマイズした環境を使いたいという気持ちを妨げてはいけません。移植性が高いシェルスクリプトはそのような環境(macOS なのに使うコマンドは GNU 版)でも動作するように作っておくと良いでしょう。
準教義2: カーネルは小さくし、ユーザーランドのプログラムを豊富にせよ
- 旧: オペレーティングシステムのカーネルを小さく軽くする
「オペレーティングシステムのカーネルを小さく軽くする」ことができるのは UNIX(カーネル)開発者だけなので、ここから「ガンカーズの UNIX 哲学」は「UNIX を開発している人が従うべき教義」であって UNIX 上でソフトウェアを開発している開発者が従う教義として作られたものではないのだろうと私は判断しました。
元の教義の時点から小さくすると言ってるのはカーネルだけです。カーネル以外、つまりユーザーランドのプログラムである UNIX コマンドやそれ以外の多数のコマンド以外を小さくするとは言っていません。修正後の説明では、カーネル以外のプログラムを小さくする必要はないと明確にするために「ユーザーランドを豊富にせよ」と付け加えています。ユーザーランドとはカーネルやドライバ以外の部分で、UNIX コマンドやほとんどのプログラムはユーザーランドで動作しています。
UNIX コマンドは OS の標準コマンドですが、それ以外のコマンドと区別がないことに注意してください。シェルでさえ他のコマンドと変わらず、ユーザーランドで動くプログラムでしかありません。これは UNIX コマンド(広義の OS のコマンド)だから速いというような性質はないということです。ユーザーランドのプログラムから、カーネル(狭義の OS)のコードが実行されるのはシステムコールが呼び出されるときだけです。システムコールの呼び出しが遅いというのはシェルスクリプトが遅くなる原因として知っておく必要があります。外部コマンドの呼び出しであれパイプによるプロセス間通信であれ、システムコールを使う処理は遅くなってしまいます。そのため速度が重要なら他の言語を使った方が速くなります。
この教義では UNIX コマンド、つまりユーザーランドにある OS の基本コマンド(シェルも含む)を小さくしろとは言っていませんが、実際には小さくすることの方が多いです。それは最小のコンピュータで動くようにするためです。OS はどのような環境で使われるかわかりません。マルチコアの CPU と豊富なメモリを備えたコンピュータで動かすかもしれませんが、IoT や組み込みのような貧弱な環境で動かすかももしれません。CPU の性能は低くても遅いだけでプログラムは実行可能ですが、メモリは足りなければ動きません。組み込み環境(特にリアルタイム制御が必要な場合)は仮想メモリが使えるとは限らないので OS は省メモリ優先であることが多いです。少ないメモリでも OS や基本的なコマンドは動くようにするべきですが、使用メモリが少ないソフトウェアは高いパフォーマンスを出せない可能性があります。したがって OS の基本機能は小さくし、高パフォーマンスのソフトウェアは追加パッケージのものを使うというのが理想的であると言えます。
Linux は OS を小さく保っている OS です。GNU コマンドは大きいと考えるかもしれませんが、Linux というカーネルにとって GNU コマンドはユーザーランドで動くコマンドセットの一つでしかありません。GNU コマンドが大きいなら別のコマンドセットを使えばよいのです。実際に GNU コマンドの代わりに小さなコマンドセットである BusyBox を使った Alpine Linux があります。Alpine Linux でも GNU コマンドはコマンド毎に提供されており必要ならば入れ替えることが可能です。余談ですがシェルやコマンドを含まない distroless という Linux コンテナイメージもあります。プログラムを実行するだけならシェルやコマンドすら不要です。
準教義3: 可読性のために長い名前を使い、短い名前は楽したい時に使え
- 旧: 小文字を使い、短く
大文字を使うかどうかは慣習に合わせてください。ファイル名は通常小文字がよく使われますが、README
などは慣習的に大文字です。一応 ASCII 文字コード順で並べた時に目立つ前の方に来るからという理由があったのですが、ロケールに en_US.UTF-8
などを使った場合はそうならないので、大文字を使うのはもはや慣習です。環境変数は大文字を使いシェル変数は小文字を使うのが慣習です。コマンド名、関数名、クラス名など言語によって慣習は違うので適切なものを使用してください。
昔はテレタイプからのキー入力も出力(印字)も遅く、プログラムを小さくするためにそうすることに意味がありましたが、今はそのような意味はありません。UNIX コマンドやそのオプションが短いからと何も考えずに真似をしないようにしてください。
省略された短すぎるコマンド名は覚えにくいだけでメリットはありません。何の前提知識も持ってない人でもわかるような名前の方が優れています。UNIX コマンドが短い名前を使っているから、短い名前にするのが UNIX 哲学なんだというのはただの勘違いです。短い名前にしているのは、単に短い名前の方がメリットがあると考えているだけです。どこでもよく使われる汎用的なコマンドは覚えづらくとも何度も使うので覚えてしまいます。覚えてしまったのであれば短いことにメリットがあります。しかしあまり使わないようなコマンドは覚えることはなく、単に分かりづらいだけです。
短いコマンド名が許されるのは、汎用的で何度も利用し、わかりづらくとも覚えることに価値があると自信を持っているようなものだけです。例えば jq
コマンドのようなものはそうでしょう。汎用的なコマンドはいろんなプロジェクトで広く使われているものです。まあ自信作を作ったプログラマはこれはすごい、これからよく使われるはずだ、短い名前だと入力が楽だ、と考えるのが普通なので短くしたくなる気持ちはわかります。でも短い名前は他とかぶりやすく貴重です。短い名前はプログラマにとっての共有財産なので大切にしてください。短く名前を変更するのは有名になった後からでも出来ますし、個人的に短くしたいだけなら alias
を使えば良いだけです。git
のように一連のコマンド群が必要ならサブコマンドの利用を検討してください。go
コマンドもサブコマンドを使っています。
コマンドのオプションには基本的に長いオプションを作ることを推奨します。1 文字オプションはどうせ数が足りなくなってしまいます。1 文字オプションは入力を楽にするために、シェルからよく使うようなオプションに割り当てると良いでしょう。シェルスクリプトからコマンドを呼び出す場合は長いオプションを使ったほうが可読性の点から良いでしょう。例外は POSIX コマンドのオプションです。移植性が必要なシェルスクリプトで OS に標準でインストールされているコマンドで動くようにしたい場合は短いオプションを使うしかありません。覚えてしまうほどよく使うオプションなら短いオプションを使っても良いと思いますが、基本は長いオプションです。Go は標準のオプション解析ライブラリでロングオプションをサポートしています。
その他、プログラミング言語ではスコープが大きいほど長い名前で、ローカル変数なら短くても良いなどのルールがありますが、それぞれの言語のコーディングスタイルを参考にしてください。
準教義4: 森林や環境を守り、電力使用量やCO₂排出量を減らせ
- 旧: 森林を守る
正直な所、これが UNIX 哲学に入ってる理由は謎です。紙を無駄にしていたことと UNIX 哲学は関係ないからです。当時のブームの一つ「ペーパーレス社会 (Paperless society)」にあやかってガンカーズは UNIX 哲学に入れたのだと思われます。最初は削除しようかと思っていたのですが、UNIX 哲学に結びつけることが出来たので残しています。
元々この教義は昔は長いソースコードを紙に印刷してデバッグしていたという話です。昔のディスプレイで表示できる範囲は小さく、おそらく小さな関数にするというスタイルもまだ十分普及していなかったため、長いコードを読むのが大変だったのでしょう。さらに昔はパンチカードによるデータ入力(紙のカードに穴をあけるので基本的に使い捨て)でさらに紙を無駄にしていました。これらの問題は大きなグラフィカルなディスプレイで解決しました。他にも様々なシステムがオンライン化し、ゆっくりとではありますが今もペーパーレス化は進んでいます。
環境で守るべきものは森林だけではありません。例えばエネルギー問題があります。これには仮想マシン技術やコンテナ技術、そしてクラウドという「道具を使う」という考えから UNIX 哲学に結びつけることが出来ます。コンピュータは省電力機能を備えていますが、何も処理をしていなくてもつけているだけで電気を消費します。コンピュータの能力が大きくなって一台のコンピュータでも能力があまるようになった今、仮想マシン技術やコンテナ技術という道具を使って、複数台のコンピュータを一台に集約することで電力を節約することが出来ます。
クラウド(データセンター)では、それと同じことを多数のユーザーで行うことが出来るため、より効率的にエネルギーを使うことが出来ます。手元(自社のコンピュータ室など)にコンピュータがあるのも遠隔地にコンピュータあるのも変わらないと思うかもしれませんが、データセンターでは多数のコンピュータをまとめて空調管理しており、また特別な電力システムを備えている場合があり、環境に優しいと言えます。
データセンターは全体で大きな電力を消費するために、逆にそれを少しでも減らそうと取り組んでいます。例えば冷却対策として涼しい地域にデータセンターを作ったり、海水で冷やしたり、高圧直流送電 (HVDC) を採用していたります(参考 直流時代の到来!<前編>さくらインターネットの直流給電システム(HVDC))。実際にその成果も出ているようです。「データセンターが世界の電力を使い果たす? そんな事態を避けるために取り組むべき課題」より
データセンターの作業負荷は2018年の時点で10年と比べて6倍以上に増えていたが、エネルギー消費量はほとんど変わらなかったというのだ。
こういった取り組みは大掛かりなデータセンターでもなければなかなか出来ません。なにがなんでもクラウドサービスを使うべきだというつもりはありませんが、クラウドだとコンピュータをいちいち購入する必要はなく、例えば開発の初期段階では小さく(安く)環境を構築して、必要になった時に必要なスペックのコンピュータに変更することも出来ます。サーバーの負荷に応じてピーク時は仮想マシンの台数を増やし、そうでない時は減らすという作業を自動化させればコストも手間も削減できます。以前のように高性能のコンピュータを買ったものの、ピーク時以外はほとんど何もしていないなんてことはありません。クラウド特有のトラブルはありますが、それを言ったらハードウェアも故障するわけで、それらに比べれば復旧作業は楽でしょう。仮想マシン技術、コンテナ技術、クラウド技術という道具を使うと環境を守りつつコストも削減できて一石二鳥というわけです。
準教義5: データは出力すべき時に、一貫した形式でデータを出力せよ
- 旧: 沈黙は金
「沈黙が金」を正しく説明すると処理が正常終了した時の「処理を完了しました」や、返すべきデータが無い時の「データが見つかりません」のような「状況を示すメッセージ」を出力するのではなく、何も出力しないしないようにしろ(沈黙せよ)という意味です。エラーが発生してるのに沈黙した方が良いという意味ではありません。エラーはちゃんと標準エラー出力に出力するようにしてください。
この教義は「教義8: 自動処理のためにコマンドには非対話的なインターフェースを持たせろ」の出力データがない場合に適用する特殊なルールといえます。コマンドの出力は、別のプログラムへの入力となる可能性があるので、別のプログラムで処理しやすいような形式で出力しましょうということです。余談ですが初期の UNIX には標準エラー出力がなく、エラーメッセージも標準出力に出力していたため、いろいろと面倒なことになっていたそうです。
状況を示すメッセージは人間が読むためのもので他のプログラムの入力となる出力データではありません。したがって出力する必要はありません。しかし絶対に表示してはいけないかと言われれれば、必ずしもそうではなく、出力が形式一貫していれば問題ありません。例えば、返すべきデータがなにもない時に限り「データが見つかりません」というメッセージを出力する仕様だとデータとして処理しづらいですが、代わりに「total 0」のように常に見つかった件数を出力するという仕様であれば、データとして使用可能になるため問題にはりません。実はこれは POSIX の ls -l
の仕様です。
データとして使用可能な情報が無いのであれば、状況を示すメッセージは不要で何も出力しなくてよいのですが、もしどうしても状況を示すメッセージを出力したい場合、それを抑制するオプションを付けたり、標準エラー出力に出力するといった方法もあります。
この教義の本当に重要なポイントは沈黙することではなく、データ処理可能な情報を一貫した形式で出力することです。
準教義6: 並行処理・並列処理を行うプログラムを作れ
- 旧: 並行して考える
この教義の古い説明は原著では「Think parallel」です。普通に訳したら「並行 (concurrent)」ではなく「並列して考える」だと思うのですが、なぜ並行に変えたのか意図がつかめませんでした。全て並行に変更しているのであればまだわかるのですが「並行主義」という言葉があったり「並列思想」という言葉があったりで、並行と並列が入り混じっています。おそらく原著はすべて parallel だと思います。中途半端に並行と並列が混じっているので混乱しますが、多分区別して使っていない気がします。
コンピュータ用語で「並行」とは複数の処理を同時に実行されているかのように実行することです。CPU が一つしか無い環境では CPU を細かい時間で切り替えながら複数の処理を実行しています。遅いディスク読み書きやネットワーク処理、人間の入力待ちなどによる CPU 待ち時間に他の処理をすることで、効率よく CPU を使うための方法です。一方で「並列」とは複数の CPU、または CPU コアを使って本当に同時に処理を行うことで、その前提として並行処理が可能な作りになっている必要があります。
「並行して考える」という説明の悪い所は、主語が書かれていない所です。コンピュータが並行で考えるのか、それとも人間が並行して考えるのか? 人間が並行して考える、つまり細かい時間で作業を区切りながらマルチタスクで複数の作業を同時進行したら効率が悪くなるのはよく知られています。人間は並行ではなく非同期で考えなければいけません。非同期で考えるとは、バックグラウンドでプログラムを動かし、処理が完了するまで忘れていられることです。
CLI 環境では、並行処理対応のプログラムを使う場合を除き、シェルでコマンドの最後に &
をつけて意識してバックグラウンドでプログラムを実行する必要があります。GUI デスクトップ環境では単にアプリケーションを切り替えるだけです。1980 年代ではまともにマルチタスクが行える OS は UNIX ぐらいしかありませんでした。今ではどの OS でもマルチタスクに対応しており GUI を使うことで自然にバックグラウンドでプログラムを動かすことができるので、意識せずとも誰もが「並行して考える」を実践しています。
「UNIX という考え方」の解説の内容は、コンピュータの CPU を忙しく働かせるという話をしていたり、バックグラウンドでプログラムを実行する話が書いてあったり、人間の話が主題なのかコンピュータの話が主題なのかよくわかりません。かなり悩んだのですが、コンピュータを休ませずに無駄なく使え、その方法は問わない、という話をしたいのだと解釈しました。
並列処理を行うにはいくつかの方法があります。(概念的に別に扱うべきものがごちゃまぜですが)
- マルチプロセスによる CPU の並列処理
- マルチスレッドによる CPU の並列処理
- GPU を使った並列処理
- Elixir や Erlang 等の軽量プロセスによる並列処理
- Haskel や Go 等の軽量スレッドによる並列処理
伝統的な UNIX では 1 しか使えません。シェルスクリプトのバックグラウンドプロセス起動や xargs -P
や make -j
などを使ってプロセスを複数起動することで並列処理を行う場合はこの方式となります。
2 のマルチスレッドによる並列処理は、マルチプロセスを使った並列処理とは異なり、一つのプロセスで複数の CPU を使う方法で、メモリを共有しているという特徴があります。例えばマルチプロセスでパイプを使ってプロセス間通信を行うとメモリのコピーが必要になりますが、スレッドを使うとこれを避けることが出来ます。そのためマルチスレッドの方がマルチプロセスよりも効率がよいのですが実装上の都合等で必ずしもマルチスレッドの方が速いとは限らないので注意が必要です。
3 の GPU を使った方法は最近トレンドの手法で機械学習やビッグデータ処理などで用いられています。並列処理が得意な GPU を利用して計算を行うため対応するハードウェアが必要です。CPU を使った並列処理とは全く使い方が異なるため、GPU 処理用のプログラムを書く必要があります。Python などがライブラリが充実しており便利です。
4 や 5 は一部の並列処理が得意と言われる言語で利用可能な方法です。OS のマルチプロセスやマルチスレッドの機能を直接使うのではなく、言語に備わっている並列処理の機能を使います。軽量という名の通り OS の機能では不可能な数百万、数千万といったプロセスやスレッドを作成することが可能です。
このようにUNIX 全盛期時代にはなかった並列処理の新しい手法が増えています。どの手法を選ぶかは場合によりますが、1 以外はシェルスクリプトや UNIX コマンドから直接使うことは出来ません。つまりシェルスクリプトや UNIX コマンドは並行処理・並列処理を使ってコンピュータの性能を引き出すにはあまり適していないということです。コンピュータの性能を引き出すには他の言語を使ったり、新しいコマンドを使う必要があります。ケン・トンプソンが Go を作った理由もそこにあります。とは言え並列処理は難しい分野でもあるため、プロセス単位での並列処理となりますが、並列処理を行わないコマンドを簡単に並列化できるシェルスクリプトにも手軽さというメリットがあります。シェルスクリプトに求めるべきものは性能ではなく手軽さです。
準教義7: 大きなシステムは複数のプログラムを組み合わせて作れ
- 旧: 部分の総和は全体よりも大きい
補足ですが「部分の総和は全体よりも大きい (The sum of the parts is greater than the whole)」とはアリストテレスの言葉「The whole is greater than the sum of the parts」が元ネタだと思われます。
この教義は大きなシステムを作る時、それを単一の大きなソフトウェアとして作るのではなく、複数のプログラムの集まりとして作るとメンテナンス性などが向上してより優れているという話です。例えば大きな統合アプリケーションを複数の部品の集まりとして構成することであり、大きなサーバーを複数のフロントエンドサーバーと一つのバックエンドサーバーで構成することであり、大きなサービスを複数のサービスの組み合わせで作るマイクロサービスの考え方などと同じです。このような考え方はソフトウェア開発のいたる所にあります。再利用可能なソフトウェアが増えた今、全てを一つのソフトウェアで作ることはあまりないでしょう。
大きなシステムを複数のプログラムの組み合わせにするというのは、いわゆる疎結合にするということです。このようにすることでプログラム単位で並行して開発やテストがしやすくなり、プログラム単位で入れ替えることが可能になります。この時に重要なのはインターフェースを適切に決めることです。適切に決めるというのは単にテキスト形式というだけでは不十分で、例えば JSON 形式ならどのようなキーが有るかなども含めてインターフェースです。今の用語で言うのなら Web API をきっちり定めるということです。プログラム間のデータの受け渡しにはパイプやソケット(やしばしばファイル)などのプロセス間通信が用いられます。
複数のプログラムの組み合わせで作ることにもデメリットはあります。それは組み合わせるプログラムの数が多くなると複雑化するということです。それぞれのプログラムは単体ではシンプルであっても、それを組み合わせた時にシンプルになるとは限りません。組み合わせるという行為は複雑性を増やす行為だからです。シェルスクリプトで多数のコマンドをパイプでつないだ時に複雑になるのと同じ話です。この複雑性を軽減するには組み合わせるプログラムの数を減らすことです。例えばアプリケーションコンテナ技術を使って複数のプログラムで構成された複雑なソフトウェアを、一つの単純なコンテナにすると複雑性が下がります。ソフトウェアを階層化して構成することで、組み合わせる数を減らすという考え方です。
準教義8: 本当にやるべきことに集中し、やらないことも決めよ
- 旧: 90 パーセントの解を目指す
「90 パーセントの解を目指す」というのは「何事も 90 パーセントぐらいが丁度いい」という意味ではありません。間違っても「完璧じゃないことが正しいんだ!」「完璧じゃないことを目指せ!」という意味には解釈しないでください。この教義の本当の意味は、完璧主義ではいけないという意味であり、完璧にしてはいけないという意味でもありません。何をどこまで作るかは重要度やプロジェクトの方針によって異なります。100% を目指さなければいけないソフトウェアは今では数多くあります。
この教義は YAGNI の原則としても知られています。例えばあなたの作ったソフトウェアはあらゆる UNIX 系 OS で動かすでしょうか? Linux だけの対応で十分ではないでしょうか?ということです。後で動かすかもしれないなどという予測で作っても実際には使われないことが多く、対応するのに費やした時間は無駄になります。そのような対応はコードを複雑にしてしまいます。それよも設計を単純にするほうが重要です。
「90 パーセントの解を目指す」をプログラムの不備やバグが発覚したときの免罪符にしてはいけません。不備やバグを指摘されて後出しで「90 パーセントの解を目指すだからバグがあるのはあたりまえ」と開き直るためのものではありません。合理的な理由を提示していないのであればバグと言われてもしょうがありません。バグはちゃんと対応する必要があります。対応するというのはバグを修正するという意味ではなく、バグと認めた上で修正するかどうかを検討して結論を出すということです。修正しないと決めた場合は、その事を理由とともに記録に残すことが対応するということです。やることだけではなく、やらないことも決める必要があります。
「90 パーセントの解を目指す」は誤解を与えかねない文章です。バグが発覚したときの言い訳として悪用されないためにも表現を変更します。
準教義9: 時間をかけて完成させるより、素早くプロトタイプを作れ
- 旧: 劣るほうが優れている
「劣るほうが優れている」のような読者の興味を惹かせるだけのキャッチーなフレーズを使ってはいけません。天の邪鬼な文章の意味を説明、または理解するのがメインテーマになって、本当に伝えるべきことが何も伝わりません。当然ですが劣っている方が優れていることなんかありえません。バグがあったり使いにくいかったり、劣っているというのは普通はそういう意味です。この教義は「劣っている方が優れている」という意味として悪用されます。
ソフトウェアの理想の完成形は誰にも開発者にも想像できません。ましてや今まで誰も作ったことのない独創的で画期的なソフトウェアであればなおさらです。正しい姿がわからないのであればプロトタイプを作りましょう。少数の開発者だけで悩んでいても適切な答えはでません。プロトタイプがあればより多くの意見を集めることが出来ます。
「劣るほうが優れている」とは、機能的が少なかったり理想に比べて未完成な状態でリリースしたけど、そこから学ぶことが出来たし、最終的にそれが優れたソフトウェアの開発につながったということです。劣っているのはプロトタイプ版の話で、最終的には優れたものが出来上がります。
「劣るほうが優れている」という言葉に惑わされると、ガンカーズのように「UNIX があらゆる面で本当に良くなったら、今度は死滅の危機を覚悟しなければならなくなるだろう。」とか意味不明なことを言い出すはめになります。「劣るほうが優れている (Worth is Better)」と言い出したのは Richard P. Gabriel です。ガンカーズもまた、このキャッチーなフレーズに惑わされた犠牲者です。(参考 "Worse is Better"コンセプトとアジャイル/リーン)
準教義10: 目的に応じて最適なデータ構造を使用せよ
- 旧: 階層的に考える
「UNIX という考え方」には階層的なものの例として、P11 より、ファイルシステム、ネットワークサービスでの命名法、ウインドウ管理、オブジェクト指向開発、P122 より、プロセスツリー、GUI インターフェースが挙げられています。他にも階層的なものには、サブコマンド、構造化プログラミング、関数の呼び出しツリー、ライブラリの名前空間、JSON、YAML、XML、いたるところに階層的なものはあります。しかし階層構造は必ずしも最適なデータ構造とは限りません。
階層構造ではない有名な例は、コマンドが扱うフラットファイルです。ということで、階層的に考えるというのは、ガンカーズの「データはフラットテキストファイルに保存する」という教義と矛盾する考え方です。もっとも私はその教義を「標準的な形式を使用せよ」と訂正したので矛盾しません。「階層的に考える」という教義と「フラットファイルに保存する」という教義が矛盾している所からもわかるように、場合によって階層構造が適切な場合も、フラット構造が適切な場合も、それ以外のデータ構造が適切な場合もあるということです。
歴史を遡るとフラットテキストファイルの元は、メインフレームのフラットデータベースです。さらに歴史を遡ると、それはタビュレーティングマシンで用いられた、パンチカードです。カード一枚が一行のデータに相当します。ではそのメインフレームがフラットデータベースだけで十分だったかと言うと、もちろんそんなことはなくデータベースの歴史 を紐解くと UNIX が誕生する 1969 年までに、構造化データベース、ネットワークデータベース、関係データベース (RDB) といった構造を持ったデータベースが誕生しています。
データベースの歴史から最適なデータ構造というのは一つではなくいろんな構造があることがわかるでしょう。結局の所、階層構造というのは、重要なデータ構造の一つですが唯一の正しいデータ構造ではありません。目的に応じて適切なデータ構造があります。ただしシェルスクリプトやUNIX コマンドが簡単に扱えるのはフラットデータだけなので、階層データなどを扱うために他の言語や UNIX コマンド以外を使う必要があります。
ソフトウェアツールの開発原則
主に道具(ソフトウェアツール)を作るときの開発原則です。「レイモンドの UNIX 哲学」をベースに修正・・・と言いたい所ですが、こちらは引用のみで修正はありません。その理由は、十分抽象化されており、原則は古くなっておらず、誤解しそうな文章ではないからです。それにもかかわらず、この記事に載せているのは、ソフトウェアツールを開発するためのより具体的な原則として必要なものだからです。「ソフトウェアツールの思想」と「ソフトウェアツールの教義」は漠然とした内容で、これだけでは道具を作る考え方を習得できるようなものではありません。それだけでは足りないという意味を込めて、この記事には「レイモンドの UNIX 哲学」を「ソフトウェアツールの開発原則」として引用しています。
ただし内容に異論がまったくないわけではありません。いくつか気になる点はあるのですが、少なくとも原則の文章から誤解される可能性は小さいのでそのまま放置でも大丈夫だろうということで編集していません。。あとはまあ細かい点を指摘できるほどまだ十分に読み込んでいません。ページ数も「UNIX という考え方」の 4 倍近くあり量も多いです。
「レイモンドの UNIX 哲学」の詳細については「The Art of UNIX Programming」を参照してください。英語版なら著者の公式ページから無料で読むことができます。他の文書に比べて新しい(と言っても 2003 年出版ですが)だけではなく UNIX の歴史の詳細や具体的なケーススタディ、他の OS との考え方の違い、UNIX 哲学に陶酔するのではなくメリット・デメリットなども書かれており、UNIX 哲学を学ぶのであれば「UNIX という考え方」よりも幅広い内容を扱っている 「The Art of UNIX Programming」の方がオススメ です。
ちなみに「The Art of UNIX Programming」の日本語訳では「Unix 思想」と書かれていますが Basics of the Unix Philosophy からわかるように、原著では「UNIX 哲学」です。
さいごに
本文を書き上げ、さて最後はどうやって締めようかと考えていた時、古い本を探している時に見つけた「ソフトウェア開発201の鉄則」をたまたま手にとっていました。この本の初版は 1996 年に出版されており、原書の「201 Principles of Software Development 」は 1995 年 3 月です。「UNIX という考え方」の出版は 1994 年 12 月 なのでわずか 4 ヶ月しか離れていません。本の趣旨が違うとは言え、読んだ時に感じる時代の空気がここまで違うものなのかと少し驚きました。やはり「UNIX という考え方」は 1980 年代が舞台です。
「ソフトウェア開発201の鉄則」の P2 にはこのように書かれています。
一組みの原理は、学問分野の成長につれて進化するものである。現存する原理は改定され、新たな原理が加えられ、そして古い原理は破棄される。(中略)もし、1964 年のソフトウェア工学の原理を調査したとすれば、これらの原理は現在ではひどくばかげたものに見えるだろう(例えば、常に短い変数名を使用せよ、とか、プログラムを短くするためにはどんなことでもせよ、といった原理である)。それと同じように、現在通用している原理は、30 年後にはばかげたものに見えることだろう。
この記事を執筆している 2022 年は、その 30 年後まであとわずか 3 年になってしまいました。その通りです。30 年もすれば当時の原理なんて変わってしまうものです。UNIX 哲学は変わらない?今もそのまま通用する?そんなわけありません。原理は改定しなければいけません。それをこの記事で行ったわけです。
私が UNIX 哲学の解説を修正したことが気に入らない人もいるのではないかと思います。今までの UNIX 哲学の解説が正しいと信じて疑わず、昔に覚えた知識だけあれば十分、新しいことを学ばなくていい、これからも変えなくていいと思っていた人たちです。さて、ページをパラパラとめくっていた所、P142「原理 129 読んだことのすべてを信じるな」で哲学について興味深いことが書かれていたので引用します。
一般的なルールとして、特定の哲学を信じる人々は、その哲学を支持するデーターを探し求め、支持しないデーターを捨てるものだ。他の立場を信じようとする人は、明らかにそれを支持しないデーターには興味を示さず、それを支持するデーターを用いる。「手法 x を使えば、93 % まで生産性(または品質)を改善できる」というせりふに出会ったとしよう。この場合、その手法は本当にそうした結果を達成したのかもしれないが、それは例外的なケースだろう、と思った方がよい。大部分のプロジェクトはドラマティックとはほど遠い結果を経験することだろう。これはごくありふれたことだ。さらに、あるプロジェクトでは、手法 x を使ったために、生産性が下がることだってあるのだ。
Ref: Fenton, N., "How Effective Are Software Engineering Methods?" Journal of Systems and Software, 22, 2 (August 1993), pp. 141-146.
201 の鉄則は古くなったと感じられるものがいくつかありますが、私の意見では、この「原理 129 読んだことのすべてを信じるな」は今も確実に通用しています。つまり「UNIX 哲学の読んだことのすべてを信じるな」です。
古い UNIX 哲学の説明を信じる人々は、それを支持しないこの記事の「新しい UNIX 哲学」の説明を(考慮することなく)捨てるに違いありません。ですが極めて変化が早いコンピュータとソフトウェアの分野で 30 年以上前の考え方が、そのままの形で今も通用するなんてありえないのです。UNIX 哲学は変わらないのではなく変えていないだけです。UNIX 哲学が変わらずとも世の中のソフトウェア技術は大きく変わっています。更新されない UNIX 哲学は、世の中から遅れている考え方ということになってしまいます。プログラマにとって重要な UNIX 哲学の考え方を時代遅れのままにしてはいけません。
最後に重要なことなのでもう一度説明しておきます。UNIX 哲学は大きなシステムをモジュール化された構成可能なプログラム設計で開発するという考え方です。その実践例の一つがソフトウェアツールです。UNIX シェルや UNIX コマンドだけではなく、さまざまなシェル、さまざまなコマンド、さまざまな言語、さまざまなミドルウェアなど、さまざまなソフトウェアを作り、そして組み合わせて使うという考え方です。UNIX 哲学はいつまでも変わらない古い道具を使い、UNIX 時代の開発手法を続けるという考え方ではありません。時代の変化に対応して新しい道具を作り出し、変化していくのが本来の UNIX 哲学です。