優れたプログラマーはデータ構造とその関係を考える
(read.engineerscodex.com)- Linus Torvaldsの引用をもとに、優れた設計はコードを書く前にデータ構造とその関係を安定して定めることから始まる
- 適切に設計されたデータモデルはアプリケーションロジックを自然に単純化し、ソフトウェアをより信頼でき、理解しやすいものにする
- データモデルの検討を後回しにすると後の作業量は増えるが、初期段階で構造をうまく定めればマイグレーションや複雑なシステム拡張が容易になる
- あるプロジェクトでは、複雑なアルゴリズムの最適化ではなくデータの再構成によって問題のカテゴリ自体を取り除き、500行の関数を50行の関数とデータ構造に置き換えた
- 実務ではインターフェースやデータベースにより厳格な型を適用し、コードの細部より先にデータフローとコンポーネント間の相互作用を設計すべきである
データ構造がコード設計を左右する
- Linus TorvaldsはGitを安定していて文書化されたデータ構造を持つ単純な設計と見なし、コードをデータの周囲に配置する考え方を強調している
- 「悪いプログラマーはコードを気にし、良いプログラマーはデータ構造とその関係を気にする」という引用が核心となる文である
- Gitの成功理由の一つは、コードをデータ中心に設計したことにある
- 優れたデータ構造はコード設計と保守を容易にし、ソフトウェアの信頼性、システムの理解しやすさ、コードの可読性を高める
- アプリケーションロジックはデータモデルに沿っていることが多い
- データモデルを後で考えると、その後の作業量が増える
- 適切に設計されたデータモデルは、その後のマイグレーションや複雑なシステム拡張を容易にする
- 実際のプロジェクト事例では、複雑なアルゴリズムをさらに磨くよりもデータの再構成のほうが大きな効果を生んだ
- データ構造を変えることで、問題カテゴリ全体を取り除いた
- 500行の関数が、50行の関数と適切に設計されたデータ構造に置き換えられた
- 新しいコードはより高速になり、理解と保守もしやすくなった
- ただし既存データを再構成する必要があったため、労力は下位レイヤーへ移動した
複雑さはデータ側へ移したほうがよい
- The Art of Unix Programming の「Rule of Representation」は、知識をデータに込めることでプログラムロジックを単純かつ堅牢にすべきだと説明している
- 手続き的ロジックは人間が検証しにくいが、複雑なデータ構造はモデリングや推論がしやすい
- 50ノードのポインタ木の図は、50行のプログラムフロー図よりも表現力と説明力が高い場合がある
- 変換テーブルを配列の初期化で表現すれば、同じ内容を
switch文で書くよりも透明で明確になる - コードとデータ構造のどちらに複雑さを置くか選ばなければならないなら、複雑さはデータ構造側へ移したほうがよい
実務ではデータフローを先に設計する
- 最も直接的な実践方法は、データから始めることだ
- インターフェースやデータベースにより厳格な型を適用すれば、コードの複雑さを減らせる
- データ構造を前もってより長く検討する時間が必要になる
- これはコードが重要でないという意味ではなく、すべての要素がともに重要である
- コードの細部に入る前に、データがどのように流れ、コンポーネントがどのように相互作用するかを先に見る高レベルのアプローチが有用である
- Senior Engineer(L5)の要件例として、FAANGではより複雑なシステムのための高レベル設計文書の作成が一般的に含まれる
- チーム計画の主導や、中〜大規模機能のための優れたロードマップ構築もここに含まれる
- データフローとコンポーネント間の相互作用を先に設計する能力は、より高いレベルのエンジニアリング上の影響力につながる
1件のコメント
Hacker News のコメント
あの Substack の記事は、この Stack Exchange の投稿から引用文をいくつもそのまま写してきたように見える: https://softwareengineering.stackexchange.com/questions/1631...
「フローチャート[コード]を見せて、表[スキーマ]を隠されると、私はいつまでも途方に暮れるだろう。表[スキーマ]を見せてくれれば、普通はフローチャート[コード]は必要ない。それらは明らかになるからだ。」— Fred Brooks『The Mythical Man Month』第9章
「The Mythical Man Month を読め。あれほど古い本が今日のソフトウェア開発に関係のあることを言えるはずがない、と思った記憶があるが、私が間違っていた。」
そうなると、アプリケーション開発者は自分たちの方が速く、やることも多いという理由で、データベースを乱用し始める
データ構造と型は同じものではない。データ構造とは、ビットパターンと別のビットパターンへの参照、つまりポインタや関係のことだ。
型はプログラミング言語で使われる形として、そうしたビットパターンに制約をかけるが、それ以外にも多くの言語機能を表現できる。不必要な抽象化で複雑な型階層を作ることは「データ構造を気にかけている」という意味ではなく、賢いエンジニアでもよく陥る失敗パターンだ
型はディスク上のビットだ
ここで本来言いたかったことは、問題をもっと深く考え、あとで足を引っ張る構造を選ぶな、ということに近い。たとえば Unix パイプがどれほど広く行き渡り、どれほど多くの領域やユースケースへ拡張されたかを見てほしい。人間と機械の制約を尊重しながらシステムを作る方法を可視化する、優れたやり方だ。
Ken Thompson たちが、パイプのようなものが Unix で意味を持つと気づくまでにも、かなり時間がかかった。簡単に得られた洞察ではなく、システムの正しい構成ブロックを見つけようとする粘り強さと、その後の作業が必要だった
Linus は、ほかの人たちがぼんやり考えていたことを、いつも上手く要約する。この記事で述べられている内容は、失われた技術になった DDD にも似ている。
ここで「失われた」というのは、最近出会う多くの開発者が、自分の扱うドメインを理解してエンティティと相互作用をモデリングすることよりも、アルゴリズムと JSON をあちこちに移動させることに関心を持っている、という意味だ。現代的な AWS ベースの設計では、根拠の薄い DynamoDB GSI の束、貧血なオブジェクト、ハックの上にハックが積み重なったスクリプトのような「サービス」層として現れる。サービス境界の内側ではドメイン文脈が十分に定義されるはずだ、という暗黙の仮定があったのかもしれないが、良い仮定ではないと思う。
私たちの業界がどこで設計の厳密さを失ったのか分からない。学校だったのか、面接のパイプラインだったのか、基準を下げたせいなのか、あるいはそのすべてなのか。
さらに悪いことに、設計は簡単には自動化できないという大罪を犯している。そのため人々はツールが強制する設計に無批判に従い、自分のしていることをもっと深く考えるべきだという考えに居心地の悪さを覚える。みんなこの思考を「専門家」に外注したがっている。
きちんと教えられることもなく、何年もかけて自分で身につけなければならないうえ、コードより実在感が薄いものと見なされて重要度が低いと扱われるのも問題だ。しかし、こうした信念は結局、作れるものの水準を上級初心者の段階に縛り付ける。プログラマーたちは集団として基準をできるだけ低く保つ選択をしており、この話題ではほとんどカニのように互いを引きずり下ろす心理があるように見える。
構文的には同一でないため重複に見えないが、機能的には同じことをしているコードの重複も多く生まれる。
1 https://martinfowler.com/bliki/AnemicDomainModel.html
2 https://wiki.c2.com/?PrimitiveObsession
悪いコードの結果が目に見えにくいからだと思う。悪い橋は崩れるが、悪いコードはリファクタリングされるか、より多くのコードで置き換えられるだけだ。経営陣が理解できないテキストファイルが、経営陣が理解できない別のテキストファイルに変わるだけである。
そして、いったん何かが動くと停電のように思考が止まる。完全に動いてしまった一時的なハックほど永続的なものはない。しかし、一時的なハックが1000個あっても、よくエンジニアリングされたシステムにはならない。ソフトウェア開発で成熟するということは、コードを書くことよりもデータと関係に集中することだと思う。それをコードに変換できなければならないが、動くコードをデータモデルに変えるのではなく、データと関係をコードに変えるべきだ。
ドメインエンティティがすべてのロジックを持てるからといって、必ずそうすべきというわけではない。たとえばユーザー名がすでに存在するか確認する必要があるなら、データアクセス層に「依存できない」ドメインエンティティの中で、それをどうやれというのか。よく「ドメインサービス」のようなものが勧められるが、そうするとビジネスロジックが複数の場所に散らばるので、DDD の趣旨とは逆に感じる。
DDD は哲学としてはかなり好きだが、「戦術的 DDD」のパターンは極度に嫌っている。あまりにも多くの人が Domain-Driven Design を Domain-Driven Implementation と同一視していると思う。適切な場所ではリッチなドメインを作ろうとするが、すべてのプロジェクトに合うわけではないし、用語にのめり込まないようにしている。「Name」型が値オブジェクトなのか集約ルートなのかには興味がない。何より 境界づけられたコンテキストのほうが重要だ。DDD が時にはアプリケーションの複雑さを増やす一方で、得られるものがほとんどない場合もあることは認める。決して万能の解決策だとは言わない。
今後も DDD は使うだろうが、DDD は「ほら、オブジェクト指向プログラミングもそんなに悪くないでしょ?」と伝えようとする試みのように感じてしまう。そして、その目標を達成しているのかもよく分からない。
プロのエンジニアリングを始める前に、Matlab、R、初期の Python のような統計システムで毎日データと統計分析をしていたので、興味深く感じる。
そのため私のエンジニアリング観は、常に2つのもの、すなわち関数型の状態と データワークフローを管理することに基づいてきた。
10年間ソフトウェアエンジニアリングを仕事にしてみると、Minsky や Shannon のような「科学的」エンジニアの多くは、コンピューティングの世界を状態管理、データ変換、計算オーバーヘッドの管理として説明していた。ソフトウェア界の巨人や先駆者たちは皆、データと状態を非常に重視しており、初期のコンピューティングは事実上それがすべてで、今後もそのパターンが続くと期待されていた。
一方で、エンジニアリングシステム設計において常に真であり全員が従うような根本的前提には、まったく一貫性がなく、あったとしてもたいてい流行に近い。ほとんどの運用ソフトウェアでは、堅牢性、アンチフラジリティ、状態管理よりも ビジネス上のスケジュールのほうが、エンジニアリング上の優先順位と構造をはるかに大きく左右する。
ギルドや労働組合のような専門職組織は、ソフトウェアエンジニアたちからほぼ普遍的に拒否されている。IEEE を真剣に受け止めなくても不利益がないため、実際には誰も真剣に扱わない。その結果、土木や医用生体工学のように実務を強制したり自主規制したりする仕組みがなく、そちらでさえ辛うじて活用されている程度だ。
全体として、ソフトウェア開発の現在の状態は、非常に高尚で哲学的だったルーツから完全に切り離されており、金を持つ人々に金を稼がせるシステムを優先する企業が事実上主導している。だから「良いもの」は、インセンティブが与えられるものとはほとんど関係がない。
「私にフローチャート[コード]を見せて表[データ構造]を隠したら、私はずっと当惑したままだろう。私に表を見せてくれれば、たいていフローチャートは不要だ。それらは明白なはずだから。」— Fred Brooks
基盤となるテーブルと1:1で対応させるのは非常に制約が強く、現代の言語が提供する表現力を取り逃がすモデルにつながると思う
これは本質的に関数型プログラミングと圏論の観点だ。
あるデータオブジェクトがあり、その構造がどのように変換され得るかに制約を与える。そしてプログラムロジックはすべて、その構造を保つ変換に関するものになる。
変換はより単純になり、推論しやすくなり、最終的には変換がエッジで構造がノードであるグラフが残る。一般に、任意の命令型プログラムより推論しやすい
関数型プログラミングの観点は、オブジェクトを変換すべきではなく、変更を避けるべきだという考えに近く、この議論とは別のものだ。圏論の核心は、複数の数学分野に共通して現れる関係のパターンを扱うことであり、ここで議論している内容ともまったく関係がない。おそらく型理論のことを言いたかったのかもしれないが、それも関係ない
以前出した結論はこうだ。私たちがコードで行うあらゆる作業は、データに下した良い決定の一つよりもずっと短命である可能性が高い。
https://www.swyx.io/data-outlasts-code-but
この原則はビジネスレベルにも当てはまる。プロセス(コード)にこだわる一方で、まずエンティティとその関係(データ)を理解するために時間を使わないビジネスアナリストたちを、私はずっと相手にしている。
その結果、何かを作る段階になると、データモデルがどのような姿であるべきかを開発者とコミュニケーションできない。プロセスは実装されるが、データモデルは慎重に設計される代わりに、その場その場で場当たり的につぎはぎされる