
2026/09/04 8:47
シンプルとは小さなことではありません
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
本議論は、難解な 9 ヶ月のバグに関するコードカバレッジの話を始め点とし、友人が「そのような壮大なデバッグのエピソードを避けるツールの構築方法を」と質問することから始まりました。当初は「単純さを最優先する」という見解を示しましたが、Unix パイプラインが本当に単純なのかという問いかけを行った後、この見解を見直しました。著者は、小さなが相互依存した Bash パイプライン(
cat ... | sort | uniq --count)に対し、(slurp)、(re-seq)、(frequencies) を使用してデータフローを自然に処理し、不透明な正規表現や一時ファイルを必要としない Clojure のソリューションと比較しました。これにより、Bash は集約と順序付けをしばしば「編み込む」一方、Clojure はそれらを分離することが示されました。著者は Rich Hickey による「sim-plex」(単一の依存関係チェーンまたは編み込み)と「com-plex」との区別を参照し、真の単純さはコードサイズの縮小ではなく、構造的な分離から生じるものであると論じました。このアーキテクチャ的な明確さは、SQLite、Blink、Google Drive for Desktop といった大規模でありながら単純なシステムで特徴が緩く結合されている点に表れています。Clojure に特化して言えば、これは Malli などのライブラリを介した可能性も含め、マップを用いたランタイム注釈を通じて型定義をデータ構造と分離することによって実現されます。これに対し、Rust では固定された struct 表現が用いられます。最終的に著者は、プログラムは常に非結合的な仕様を通じて単純でなければならないと主張し、今後の議論ではアーキテクチャ的な嗜好の醸成、分離を伴う垂直統合、複雑性を再導入せずにシステムをスケールさせることについて扱います。本文
ボタンが多い UI は混乱を招く ~Unix 的な「単純さ」の再考~
記事の背景:クロスデバイス同期とデバッグ物語
議論のきっかけ
- UI の複雑さ: ボタンが多すぎる UI は、ユーザーに混乱と不安を与えます。
- クロスデバイスの必要性: 単なる単純化ではなく、クロスデバイス間の真正な同期を動作させる必要があります。
- 実際の課題:
- 著者が行なった講演では、「正確で信頼性の高いコードカバレッジ」という複雑なテーマを扱いました。
- そのシステムはデバッグに9 か月も要するほど複雑でした。
- 友人から「もっとシンプルで大規模なデバッグが不要なツールができないか?」との問いかけがありました。
最初の回答と違和感
- 著者の結論: 「私たちは単純化を最優先すべきです。」
- 現状の問題点:
- カバレッジパイプラインには多くのノードがあり、ツールが過剰に複雑になっています。
- **「計算のあり方」**を見直す必要があります。
- 違和感: その答えに満足することはできませんでした。
Unix パイプラインは単純ではありません
単語頻度カウントの例
ファイル内の単語頻度を計算する 2 つのアプローチを比較しました。
1. Bash のアプローチ(複雑に見えるが簡潔)
以下のコマンドは「
README.md を読み込み、各単語の境界を改行に変換し、大文字を小文字に変換、出現回数をカウント、頻度順に表示」します。
cat README.md \ | tr --complement --squeeze-repeats '[:alpha:]' '\n' \ | tr A-Z a-z \ | sort \ | uniq --count \ | sort --reverse --numeric-sort
- 見かけ上の特徴: プログラムが小さく、アドホックに連結されているように見え、簡潔です。
2. Clojure のアプローチ(名前の付け加えと Higher-order 関数)
同じ処理を行う Clojure スクリプトでは、より抽象的な記述が可能になります。
(->> (slurp "README.md") (re-seq #"[a-zA-Z]+") (map str/lower-case) frequencies (sort-by val >) ; 各ペアについて出力する (run! (fn [[word count]] (println count word))))
- 順序維持の要件: 元のファイル順序で出力したい場合、Clojure では
を使ってシーケンスを保存し、マップから頻度を検索して処理できます。distinct- 一時ファイルを多用した Bash のバリエーションに比べて論理的です。
- Bash では一連の一時ファイルと醜い正規表現、複雑なソート・ジョイントが必要になります。
なぜ Unix パイプラインは「単純」ではないのか?
元のプログラムは小さくても、実は結合(Coupled)が強く存在します。
の制約:sort | uniq --count
マニュアルには「入力の隣接していない行の重複は検出しないため、事前にソートが必要」とあります。uniq- 頻度を表すネイティブなコマンドが存在せず、この組み合わせが最適解です。
- 結合の結果:
- パフォーマンス低下(入力全体をメモリに収集)。
- 集約処理と順序付けが密接に結びついています。
- これにより、「順序付けから集約処理を分離する」ことが難しくなります。
- 結果として、テキストファイルを介したテーブル・ジョイントのような奇妙な操作が必要になります。
Unix 哲学の真実
「Unix システムに関しては『一つの目的しか持ちず、それを良く行う』プログラムを書くべきだ」というUnix 哲学は一般的に「単純さ」に関連づけられますが、実際にはサイズの問題です。
- 結論: Unix ツールは小さくはありますが、単純ではありません。
「Simple」とは何か?
Rich Hickey の定義
- 『シンプル・イズ・イージィ』(転記版)において、「simple」はラテン語のsim-plex(一つの編み目のみ)から由来します。
- これに対し、複数のものを編み合わせたものはcom-plex(複雑)と呼ばれます。
- 本記事では、曖昧さを避けるために**「複雑(complex)」の代わりに「結合された(coupled)」**という用語を使用します。
シンプルさの本質
- 小ささ ≠ 単純さ: Bash パイプラインは小さいですが、
などにより強く結合されています。sort | uniq --count - 結合の問題点:
- プログラムの一部に結合を導入するメリットはほとんどありません。
- 開発者にとっては維持が困難になります。
- ユーザーにとっては柔軟性が低下します。
大きさは結合されていることと同じではありません
Google Drive for Desktop の例
- 規模: 非常に大規模なプログラムです(ファイルウォッチャー、Google3 コア、ネットワーククライアント、競合解決ロジック)。
- ユーザー視点:
- インストールし、監視フォルダを指定するだけ。
- ローカル保存かクラウド保存かの選択のみ。
- その他はすべて自動的に処理される。
- 結論: 規模が大きいにもかかわらず、ユーザー体験としては非常にシンプルに感じられます。
結合の緩和(Decoupling)への道
複雑さを減らすには、機能を無理に結びつけず、独立させることが重要です。
言語による違い:Rust vs Clojure
値の管理方法が「結合度」にどう影響するかを比較します。
Rust の場合(構造体とマップの二律背反)
struct HttpResponse { status: u16, } let strukt = HttpResponse { status: 200 }; let mut map = HashMap::new(); map.insert("status", 200); println!("map: {}", map.get("status").unwrap()); // unwrap() が必要(型チェック器がキーを知らないため) println!("struct: {}", strukt.status); // 構造体なら直接アクセス可能
- 問題点:
- 構造体は既知のフィールドを持ちますが、実行時情報を失います。
- マップを反復処理は簡単(
)だが、構造体を反復処理するには追加の実装(プロシージャルマクロなど)が必要です。for (key, val) in map
- 根本原因: Rust は構造体(型チェックと固定された表現)とマップを分離できません。
Clojure の場合(データ表現と型チェックの分離)
; 名前 `http-response` をリスト形式で定義 (def http-response [:map [:status :int]]) ; 関数 `print-resp` にメタデータとしてスキーマを注釈付け (defn ^{:malli/schema [:=> [:cat http-response] :nil]} print-resp [map] (println "status:" (:status map)))
- 利点:
- Clojure では構造体もマップであり、データ表現と型チェックを分離できます。
- ランタイム(またはライブラリ Malli)で型チェックを行うため、コンパイラに依存しません。
- マップの値に対する型安全性、反射、反復処理を犠牲にせず実現可能です。
小さいままでいることが有用なのはいつか?
適切な「小ささ」のケース
- リソース制限: メンテナとして多くのリソースを割く余裕がない場合。
- 制約環境: PDP-11 など古いハードウェアでの動作、あるいは月に数時間しかかけられないオープンソースプロジェクトなど。
- 戦略: できることに集中し、一部機能のみを実現する環境。
「小ささ」と「単純さ」の違い
- **「小さい」**ことは正当な理由ですが、「単純(結合がない)」こととは異なります。
- 「いつシンプルであるべきか」: いつもです。
- 結合を導入することには利点がありません。
- プログラムの一部だけを「小さく保つ」のではなく、全体的に結合を減めることが重要です。
どのようにしてシンプルなプログラムを作成するか?
難問へのアプローチ
- 必要条件:
- プログラムに対する良いメンタルモデル。
- 良い味付け(判断力)。
- これは教えることが難しいですが、感覚で養う必要があります。
- 苦しい選択:
- **「我慢して困難なことを書く(Suck It Up And Write The Hard Thing)」**ことも必要な場合があります。
結合のない技術の難しさ
- 宣言的な記述:
- CSS や SQL は結合が少ないです。「何を行うべきか」を宣言し、エンジンが実装方法を任されます。
- これを実現するには、ブラウザエンジンやデータベースランタイムが長年の開発で達成したものです(SQLite: 何世紀にもわたる人年、Chrome の Blink: 数万の人年)。
- 現実的な制約:
- 結合のないプログラムを書くためにこれほど時間がかかる分野もあります。
- 必ずしも長期の開発が合理的とは限りません。
- 「mothlamp problem」:
- コードの「あり方」や「あるべき姿」に夢中になる特定のタイプの作業です。
- 道具を置いて仮眠をとることも、機能する場合があります。
カバレッジパイプラインへの示唆
- 著者が修正後のカバレッジパイプラインは、サイズ的には大きくなりました。
- しかし、隠れた依存関係が減り、システム全体として単純化(結合の緩和)されたのです。
次に何が起こるべきか?
この投稿では、単に「小さく」するのではなく、「結合のない(Decoupled)」ツールを見つけるよう提案しました。
今後の議論の方向性:
- 味付けの感覚: そのような判断力をどのように養うか。
- 垂直統合と非結合: プログラムが垂直統合されていても結合されていない方法。
- 大規模システム: 規模が大きくなった際に、どのようにして複雑さを回避するか。