ディスカッション (11件)
UnixやPlan 9、そしてGo言語の開発で知られるRob Pike(ロブ・パイク)氏。彼が1989年に提唱した「プログラミングの5つのルール」は、30年以上経った今でもエンジニアが肝に銘じておくべき普遍的な知恵です。内容を簡潔にまとめました。 1. 推測するな、計測せよ:どこで時間がかかっているかは、実際に測るまで誰にもわからない。 2. 計測するまではチューニングするな:ボトルネックが特定されるまでは、高速化に手を出すな。 3. 凝ったアルゴリズムはnが小さいときは遅い:そして、n(データ量)はたいてい小さい。単純なアルゴリズムの方が速いことが多い。 4. 複雑なアルゴリズムはバグの温床:シンプルさこそが信頼性を生む。 5. データがすべてを決定する:適切なデータ構造こそがプログラムの核心。アルゴリズムはそれに付随するものに過ぎない。
ルール1と2は、今まで見たことないものを作る時にしか当てはまらない気がするな。同じようなドメインで同じようなシステムを何度も作ってれば、どこを事前に最適化すべきかなんて大体わかるもんだよ。俺たちの多くは、キャリアのほとんどを特定のドメインで似たようなシステム構築に費やすわけだし。経験と直感でパフォーマンスのボトルネックがどこになるか事前に予見できないなら、スタッフやプリンシパル級とは呼べないんじゃないかな。
ルール5にはこれ以上ないくらい同意。本当に厄介なプログラミングの問題って、結局はデータ構造(と、それが公開するAPI)を繰り返し洗練させていくことで解決されるんだよね。ここがバッチリ決まれば、プログラムの制御フローなんてのは自然と理解しやすくなる。ついでに大好きな話題に触れると、コーディングでLLMを使い倒してる俺から見ても、LLMはこの辺がすごく弱いと思う。Claudeなんかは、概念を構成可能な塊にカプセル化するチャンスを見つけるよりも、小さなデータ型に対して複雑な制御フローを提案したり拡張したりしがちだしね。ほとんどのコードがLLMによって生成・消費されるようになるからそんなのどうでもいい、っていう意見には賛成できないな。今のLLMだって、考え抜かれた設計のコードベースの方がずっと効果的に動く。人間と同じだよ。そこが変わる理由なんてある?
Pikeがこれらのルールを、元になった有名な格言のおかげだとしてるのは寛大で良いと思うけど、個人的にはPike版の方が優れてると思う。古典的な格言という「メンタル・クリックベイト」と一緒に並べるんじゃなく、単体で評価されるべきだね。Pike版は、有名なフレーズが元の文脈から切り離された時に失われた重要なニュアンスをちゃんと保持してる。例えば「早すぎる最適化は諸悪の根源」って言葉は、同じ議論の反対意見の両方をサポートするために引き合いに出されるのをよく耳にする。Pikeのルールの方がずっと明確で、勝手な解釈が入り込む余地が少ないんだよね。あと、最近はこれを聞かなくなったのが面白いな:「ルール5は、しばしば『賢いオブジェクトを使うバカなコードを書け』と短縮される」。文脈的には、データ構造の設計に十分な知恵を絞れば、問題を解決するためのコードはシンプルに書けるって意味だよね。でもオブジェクト指向全盛期のマインドセットで解釈すると、複雑な部分をメソッドの中に隠しさえすれば、いくらでもコードを複雑にしていいっていう、初心者がよくやる間違いを助長しかねない。たぶん「賢いオブジェクトを使うバカなコード」ってのはOO以前の時代の気の利いた知恵だったのが、OOという文脈で有害な解釈が生まれてしまったせいで、危険なものとして捨てられたんだろうな。
10年以上、少人数のチームで同じコードベースを運用して、ようやくこれらのルールが骨身に染みたよ。俺は昔からKISS(シンプルに保て)とかDRY(繰り返すな)を大事にしてきたけど、10年もやってると、もっとハイカラなデータベースを使いたくなったり、流行りのスタックで書き直したくなったりする誘惑が何度もある。でも、大規模な環境で実際にシステムを安定させてきたのは、退屈で枯れた技術と、本当に重要な場所だけを最適化することだったんだ。最近、自分たちの原則を書き出してみたんだけど、言葉こそ違えど中身はほぼPikeのルールそのままだったよ:https://www.geocod.io/code-and-coordinates/2025-09-30-develo...
ルール3のせいで、CS(計算機科学)専攻の人たちとよく揉めるんだ。俺は教育的には電気工学(EE)出身で、組み込みのCやアセンブリっていう泥臭いところからソフトの世界に入ったから、ビッグオー(オーダー記法)や計算量の正式な定義を知ったのはキャリアの後半だった。それまでのキャリアの大部分では、ルール3を守るのが一番理にかなってた。CS専攻の連中がビッグオーの話をしてきてウザい時でも、大抵「n」が極小だってことを忘れてるんだよね。でも仕事の内容が変わって、違う分野を扱うようになったら、急にみんなが文句を言ってるLeetCodeの面接みたいな話になってきた。今じゃ「n」は本当に巨大で、計算量がめちゃくちゃ重要になったよ。Rob Pikeが活躍した時代は、「大型コンピュータ(big iron)」向けのプログラミングが、今の組み込みマイコン向けプログラミングに近い感覚だったってことは覚えておいた方がいいね。
これ、Jonathan Blowのトークを思い出すな。彼は生産性の観点からこれを正当化してるんだ。『Braid』での実装は、ほぼすべて最初はただのレコードの配列だったって説明してる。ボトルネックが見つかってから初めて変更を加えたんだって。もし最初からあらゆる技術的課題に対して最適なデータ構造やアルゴリズムを探してたら、ゲームをリリースすることなんて一生できなかっただろうからね。「(速度やメモリ以外に)最適化すべき3つ目の要素がある。それはこれらよりずっと重要で、1つのプログラムを実装するのに必要な『自分の人生の残り年数』だ」って。もちろん、これは個人のインディーゲーム開発者の視点だけど、一考に値する面白くて良い視点だと思うよ。 [1] https://www.youtube.com/watch?v=JjDsP5n2kSM
ルール1の面白いところは、ルール3〜5がほぼ機械的に導き出されるところだね。「どこがボトルネックになるか予測できない」と心底受け入れれば、シンプルなコードを書いて計測するってのが唯一の合理的な戦略になる。問題は、ほとんどの人がこれらのルールを一つの前提から導かれる帰結としてじゃなく、バラバラのガイドラインとして扱ってるところだ。実務で失敗をよく見るのは、早すぎる最適化じゃなくて「早すぎる抽象化」だね。必要にもならない柔軟性のために手の込んだ間接レイヤーを作って、そのレイヤーが将来そのコードを読む全員に実害(コスト)を負わせる。複雑さを管理するための抽象化が、早すぎると逆に別の複雑さを生んじゃうっていうのは皮肉なもんだよ。
「計測せよ。計測するまでは速度のためにチューニングするな」というのと、Jeff Deanの「すべてのプログラマが知っておくべきレイテンシの数字」を対比させるのは興味深いね。Jeff Deanは(暗黙的に)パフォーマンスは見積もれるものだと言っていて、だからこそ計測する前、というか実装するものすら存在しない段階で、最初から速度を考慮した設計ができると言っている。おそらく両者とも、その中間にある「いい塩梅」のところでは同意するんだろうね。知識を使って速度を意識した設計をすべきなのは間違いないけど、まともな設計で実装した後は、計測して「チューニング」したり、少しずつ改善したりしていく必要があるって感じで。
90年代の話。夜中の2時に仕事をしてて、データセットの検索を実装しなきゃいけなかったんだ。その関数は最終的に全アイテムに対して呼ばれる予定だったから、線形探索で実装すると挙動は n^2 になっちゃう。でも、もう遅いし疲れ果ててたから、後で直すリストに入れて、とりあえず線形探索で済ませたんだ。その週の後半、全部動くようになってからその n^2 の検索をプロファイリングしてみた。そのソフトは工業用の試験装置を制御するもので、試験プロセス自体は完了まで4時間くらいかかる。で、あり得ないくらい最悪なケースのデータセットを使っても、もし n^2 のままにしておいた場合に増える時間は、4時間の実行時間に対してたったの6秒程度だったんだ。(結局は簡単だったから直したけど、重要だったからじゃなくて単に楽に直せたからだよ。)
はは、C++の初期の頃(1990年くらい)にルール3と4を適用したいい例があったよ。キャッシュ用ポインタを持った双方向連結リストを使ってたんだ(時系列データのブラウザだったから、ユーザーがズームすると参照が『近く』になりやすかった。最終的にはストリーミングデータも扱う仕様でね)。でも、ポインタ周りでクラッシュしちゃって(1990年当時はC++の経験なんて誰も持ってなかったし)。だから、問題がデータ構造のせいなのかそれ以外なのか切り分けるために、単に「配列をreallocするだけ」のめちゃくちゃバカげたバージョンを作ってみたんだ。そしたら、その「バカな」バージョンはクラッシュしないどころか、ずっと速かった(しかも計算量的にも!)。償却reallocのおかげだね。コストの高い操作をたまにしかやらないようにするってのは、本当にいい手だよ。