12 ポイント 投稿者 GN⁺ 2025-07-19 | 2件のコメント | WhatsAppで共有
  • 依存関係は無料の機能のように見えるが、実際にはさまざまなコストと複雑さを伴う
  • 悪い依存関係は学習コスト、突然のインターフェース変更、デプロイ・インストールの問題など多様なリスクを招く
  • 代表例としてTigerBeetleは、セキュリティ・性能・運用の単純さのために**「ゼロ依存」方針**を志向している
  • 著者は**依存関係評価フレームワーク(普及性、安定性、深さ、使いやすさ、完全性)**を提案している
  • 良い依存関係と悪い依存関係を見分ける批判的思考と、依存関係の選定時に短期的な生産性だけでなく総コストとリスクを考慮した慎重な判断が不可欠である

NIH(Not Invented Here)より悪い依存関係のほうが高くつく

依存関係の欠点と隠れたコスト

  • 多くの開発者は依存関係の追加を**「ただで使える機能」**のように考えがちだが、実際には次のようなコストが発生する
    • 学習にかかる時間と複雑さ
    • 依存関係そのもののインストールが難しい場合も少なくない
    • メジャーバージョン更新時には自分のコードまで大きく修正しなければならない負担
    • 依存関係は最終的にデプロイ/インストール環境に必ず含めなければならない(コンテナ、バンドリングなどの複雑さを誘発)
  • 誤った依存関係の導入によって、中核機能とは関係のない複雑なデプロイ構造が作られてしまうこともある

TigerBeetleのゼロ依存原則

  • TigerBeetleVanilla Zigベースの財務データベースで、ゼロ依存方針を採用している
    • Zig言語だけで開発し、Zigツールチェーン以外の外部依存を持たない
    • 依存関係によるサプライチェーン攻撃のリスク性能低下インストール時間の増加といった問題を避けることが目的
    • インフラに深く根付いたソフトウェアであるほど、依存関係によるコストはスタック全体に増幅される
    • 標準化された小さなツールボックスの活用は保守性と開発効率に有利である
  • Zigひとつで新しい問題に素早く対処し、複雑さを減らすことに注力している

依存関係評価フレームワーク

  • すべての開発者に完全な無依存は不可能だと認めつつも、依存関係は慎重に評価すべきだと強調している
  • 著者は次の5つの基準で依存関係を評価することを提案している
    • 普及性(Ubiquity): 対象環境にどれほど一般的に存在するか。別途デプロイ/インストールが必要かどうか
    • 安定性(Stability): 下位互換性、API変更の頻度、サポート終了などの問題がどれだけ起こるか
    • 深さ(Depth): APIの下に隠れている実際の機能量、直接実装する場合の代替難易度
    • 使いやすさ(Ergonomics): APIが直感的・宣言的で使いやすいか、ドキュメント整備状況
    • 完全性(Watertightness): 抽象化がどれほどうまく機能するか、下位技術をどれだけ意識せずに済むか
  • 開発コミュニティでは主に使いやすさだけが議論され、残りの4項目は見過ごされがちである

良い依存関係の例

  • POSIXシステムコール

    • 普遍性: Linux、Android、macOS、BSDなどほぼすべてのプラットフォームで利用可能
    • 安定性: インターフェース互換性が非常に高く、変化がほとんどない
    • 深さ: 単一のAPIで数十万行のカーネルコードを隠している
    • 使いやすさ: やや伝統的なCスタイルではあるが、使用に大きな支障はない
    • 完全性: ほとんど問題ないが、ストレージのデータ永続化処理など細かな論点はある
  • ECMA-48端末制御コード

    • 普遍性: Windowsのcmd.exeを除けば、ほとんどの端末でサポートされている
    • 安定性: 1991年以降変更なし
    • 深さ: 標準を自分で作るのは途方もなく難しい
    • 使いやすさ: Esc文字による読みにくさを除けば概ね問題ない
    • 完全性: ハードウェア依存性を心配する必要が非常に少ない
  • Webプラットフォーム(Web API、HTML、JS、CSSなど)

    • 普遍性: Webブラウザは世界中のほぼすべての環境にインストールされている
    • 安定性: 強力な下位互換性ポリシーがある
    • 深さ: 独自ブラウザの開発は現実的に不可能なほど深い
    • 使いやすさ: 多少複雑さはあるが、文書化と開発ツールが優れている
    • 完全性: ファイル、オーディオ、ビデオなどの特殊な状況を除けば非常に高い完全性を持つ

結論

  • コピー&ペースト依存関係の乱用ではなく、批判的思考と総合的なコスト分析が必須である
  • 依存関係を導入するときはすべての依存関係のコストと利得を批判的に評価し、
    システム全体の潜在的なリスクとコストも必ず考慮して慎重に選ぶことが、長期的にははるかに安く安全である

2件のコメント

 
GN⁺ 2025-07-19
Hacker Newsの意見
  • TigerBeetleという例を初めて聞いたので、自分で調べてみた。もし安全性と性能が重要な Zig ベースの金融台帳データベースを作っていて、CPU 1コアで毎秒100万件のトランザクションを処理しなければならないなら、依存関係の追加を避けるのはリスクの大きさを考えても完全に合理的だと思う。だが、ほとんどの一般的な開発者は普通の CRUD システムを作っており、たいていはそこまで強くないことが多い。多くの依存関係はバグだらけかもしれないが、大多数の開発者が自分で作るものより品質が高い場合も多い。企業も実際には採用できる開発者のレベルによってボトルネックが生じる。それぞれ状況が違うのだから、相反する助言もそれぞれの文脈ではどちらも正しいことがあり得ると覚えておくべきだ

    • 実際に TigerBeetle で働いている。要点は文脈だ。TigerBeetle や rust-analyzer には強い開発文化があるが、解いている問題が違うため異なる文化が形成されている。記事で言及されている依存関係は、POSIX、ECMA-48、Web プラットフォームのように、ライブラリというよりシステムインターフェースだ。ライブラリ依存は問題が起きたら自分で書き直せば済むが、システムインターフェースのような根本的なものは変えるのが事実上不可能だったり、コストが高かったりする。ソフトウェアのスコープに合わないことをしないと決めるのは強力だ。たとえば行列積コードを作る専門チームなら、コア業務と関係ない別のライブラリは外部のものを使ってもよいが、製品の責務分解をもっとよく設計すべきだと思う。そうすれば必須依存をシステム内でよりうまく隔離できる

    • この見方の問題は、底辺レベルの開発者だけを念頭に置いた場合にしか成り立たないことだ。技術分野では助言を最も低いレベルに合わせて薄める傾向があるが、正直に言って、私が働いてきた場所では、開発者が壊れた依存関係をフォークして問題を解決できないほど弱かった環境はほとんどなかった。CRUD への見下しも多いが、悪い抽象化は CRUD で莫大な時間の浪費と苦痛を引き起こす。人気のあるものでも、本当に基礎的なチュートリアルレベルを超えると問題が多く、生産性が低いことが多い

    • これは開発者のレベル自体とは関係ない話だ。どんなツールチェーンや製品を使っていても、結局は他人が作った依存関係を使っている。身の回りで自分で行列積コードを実装する人はごく少数だし、そういう人たちですら、自分とは無関係なオープンソースライブラリまで自分で実装したりはしない。たいていは規制要件や個人的な関心、あるいは特定のライブラリへの思い入れがあるときに依存関係そのものへ執着するようになる。みんながこの原則を完全に適用したら、浜辺で砂を拾って生きることになるだろう

    • 「平均的な CRUD 開発者は強くない」という意見は断定しすぎだ。ほとんどの開発者は自分が働くシステムを選べないし、開発期間中も常にリソースが不足している。『安い』依存関係を活用しなければ、正常に動くソフトウェアを素早く出荷できない。この現実をちゃんと知らずに言っているように見える

    • TigerBeetle は比較的最近立ち上がったスタートアップで、まだ 1.0 正式版でもない。このアプローチが本当に有効か判断するには、まだ早すぎると思う

  • NIH(Not Invented Here) 自体は、どこまで責任を持つかを現実的に判断して使うなら非常に有用だ。たとえば、自分のドメインにぴったり合う Web フロントエンドフレームワークなら、独自に作って保守する価値があることは多い。しかし、データベース、ゲームエンジン、Web サーバー、暗号関連の基本機能などは話が別だ。既存のソリューションでは解決できないほど難しいなら、まず問題の再定義を考えるべきだ。SQLite のテスト一式を最初から作り直すくらいなら、問題そのものを整理し直すほうがはるかに安いと思う

    • とはいえ、データベース、ゲームエンジン、Web サーバー、暗号プリミティブなども、場合によっては自分で作るほうがよい事例は多い。単純なファイルとランタイムインデックスだけで十分なら、SQLite ですら過剰な選択であることは多い。多くのゲームでは、チームが小規模であるほどカスタムエンジンが有利なことが多い。完成済みエンジンの利点はパイプラインくらいだが、大きなオーバーヘッドを伴うからだ。Web サーバーも FastCGI アプリと複雑さに大差はない。暗号も、すべての状況がセキュリティ問題というわけではないので、単純なハッシュ確認のようなものは自分で実装しても構わない。難しそうに見えるテーマに対して学習性無力感を持つのはよくないと思う。既存のソリューションが問題を解決するからといって、それが最適あるいは最も効率的な方法だとは限らない点も重要だ

    • それなら、なぜこれほど多くのデータベースエンジンが存在するのか。結局のところ、複雑なコンピュータシステムにはそれぞれ固有のトレードオフがある。制約条件、スケーラビリティ、並行性、セキュリティ、データ特性、保存方式など、選択肢は非常に多様だ。私は依存コストが大きいほど、その依存関係自体が他の依存を最小化しようとしていると、より信頼できると感じる。自動化された依存管理システムはかえって問題を複雑にしがちで、慎重な手動管理のほうが負担を減らしてくれると思う

    • サードパーティ依存を使う理由は2つあると思う。(1) サービス提供者が自ら公開しており、比較的ライフサイクルが一致している場合。(2) 自分が書きたくない複雑なコードを代替してくれる場合。(1) はビジネス上の理由があるので問題ない。ただし、そのサービスが更新されたときに大きな変化が起きることは受け入れる必要がある。(2) は、自分が避けたいコードの複雑さに応じて価値が変わる。依存関係を導入するということは、相手のスケジュールに合わせて自分が更新やテストに時間とリソースを費やし、その責任を引き受けるということだ

    • RDBMS では解決できない問題にすぐ行き当たる。RDBMS は同時データ更新と可変データセットのサポートを中心に設計されているので、それが不要なら単純なインデックスだけでも大幅な性能向上が可能だ。データが不変なら、RDBMS よりはるかに高速な独自実装が可能になる

    • RDBMS の例は興味深い。Wikipedia には 100 種類を超える RDBMS があり、それぞれ解決できる問題と解決できない問題がある。実用的な解を考え、実際に実行してきた結果だ

  • 依存関係はリスクを生むが、まったく使わなければ開発や市場投入の競争力で後れを取る可能性がある。だから依存関係管理プロセスが重要になる

    1. オープンソース依存関係のみを対象に考える
    2. 新規導入時はコードレビューだけでなく、ライセンス確認、除去に必要な労力、セキュリティ脆弱性やバグ履歴、更新の継続性、コミュニティの活力など、多角的に検討する
    3. 可能なら依存関係のセキュリティ問題を定期的に検査する必要がある。大規模にやるとコストが大きな問題になる。(関連アイデア共有: https://blog.majid.info/supply-chain-vetting/
    4. 保守やフォークの負担を引き受けられる依存関係だけを導入する。少なくともソースから直接ビルドした経験はあるべきだ
    5. すべての依存関係を事前にフォークしておく。left-pad の件のように、リポジトリが突然消えることがあるからだ
    • 4番、5番は本当に重要なのに、よく忘れられる。個人プロジェクトでも、しばらく放置してから戻ると依存関係が古くなっていたり、リポジトリが削除されていたりすることを経験する。だから最近はソース自体を非公開でフォークして自分でビルドしてみて、依存関係の依存関係までソースレベルでフォークしている。こうしておけば、あとでエコシステムが急変しても被害を最小限にできる。バイナリよりソースライブラリを好むようになった

    • 5番のフォークは負担が大きすぎることもある。自前の git やキャッシュプロキシに依存関係を置いておく、いわゆるベンダリングもよい方法だ。特に長寿命のプロジェクトに向いている。NodeJS のように依存ファイルが多い場合は、Yarn や PNPM のようなツールのほうが効率的だ

    • 4番に関連して、SQLite のような有名な依存関係は、自分の作る製品よりはるかに長く存続する。自分の製品がそのオープンソース自体より長生きすると考えるなら、それこそ傲慢な姿勢だ。Linux カーネルを自分でビルドしようとも思わない

    • すべてのコードは、少なくともネットワーク接続なしでビルドできるべきだ。バイナリアーティファクトがないのが最良だが、常に現実的とは限らない

    • 素晴らしい洞察だ。これを自分の依存関係導入手順の文書に追加するつもりだ。問題は JavaScript のように依存ツリーが深すぎるケースだ

  • 長年の経験から言うと、結局すべては "It Depends™" に行き着く。若いころは原則だけを守り、例外を認めないことに執着していたせいで、不適切なライブラリやパラダイムを無理に適用し、最悪のコードを書いたこともあった。今では、よく使うものは自分でライブラリ化してパッケージとして切り出し、必要なもので自分にできない部分だけを外部依存で補う。品質や管理性に納得できるなら、外部のものも喜んで受け入れる

  • エネルギー業界は意図的に依存関係を避ける傾向がある。外部依存を導入すると、変更点をすべて確認しなければならないからだ。AI コードツールは大いに役立っており、主に CLI ツールの生成などに使っている。OpenAPI ドキュメントも LLM を使って作り、それを Go 標準ライブラリでサービスしている。LLM 自体は外部依存だが、それで作る CLI ツールは実際のコードとは無関係なので、品質要求が低い。もちろんフロントエンド開発者が React なしで働きたがるわけではないが、そうした製品は外部向けに扱うので例外だ。エンジニアの品質依存のこだわりを和らげる小道具を提供すれば、依存最小化ポリシーはもっとやりやすくなる

    • LLM が学習に使ったオープンソースコードの一部をそのままコードとして吐き出すことがあるが、その場合は依存関係をフォークして自分の所有物のように扱うのと、実質的に大差ないのではないかと思う
  • 依存関係の良し悪しを見分けられることは重要な能力だ。私の考えでは、有償の依存関係はたいてい不利だ。提供企業がロックインを誘導するために設計している可能性が高いからだ。"依存ミニマリズム" はよいコンセプトだ(VitalikButerin の関連ツイート

    • 有償依存はサポート窓口が一か所しかないため、提供会社が倒れればプロジェクト全体が危険にさらされる。ほとんどの会社は永遠には続かないのだから、依存関係の将来が自分のプロジェクトの軌道に影響するかどうかは必ず見極めるべきだ

    • 非技術系のチームに強制された有償依存でひどい経験をしたことがある。一方で Sidekiq のようにコミュニティで広く使われている「オープンコア」依存は、突然消える可能性がはるかに低いので信頼できる。有償の利点は、会社が健全に運営されている限りサポートを心配しなくてよいことだ

    • 企業提供の有償・無償コンポーネントのどちらにもベンダーロックインは存在する。リスク管理は統合チームの役割であり、代替案を探したりモジュール化したりしてリスクを調整しなければならない

    • 有償なら、ロックインを防ぐために、必ずオープン標準または代替実装のあるインターフェースで納品されるべきだ。他の選択肢があれば、切り替えの余地を維持できる

    • 有償依存が悪いというのは、予算が足りないというシグナルかもしれない。自分のコードを支えることが誰かの『仕事』であってほしい。サポートする人が多かったり、自発的に責任を負う開発者が多ければ安定するし、個人プロジェクトなら、ソースが公開されていても問題が起きたときに支えてくれる人が必要だ。会社が撤退しても放置しない責任感が重要になる

  • 多くの人は新しいコードを書くことに執着しがちだが、実際には、ひどい依存関係ですら使うほうが圧倒的に効率的な場合が9割だ

    • 依存関係は諸刃の剣だ。大手ソフトウェア企業では、コードを維持するより捨てて書き直すほうが安いのでそうすることが多い。小規模な Web / ブランディング会社では、高品質なバックエンドが実質的に不要なことも多い。一方で、忌まわしいエンタープライズパターンが生まれた理由は、5年後に文書も業界の記憶も失われたあとでも、外部依存なしに保存されたコードを隔離し保守できるようにするためだ。外部依存には、サポート終了と破壊的変更という二つのリスクがある。結局は機能開発の流れにも影響する。内部コンポーネントなら、こうしたトレードオフも内部で制御できる。SaaS なら短期的成功のために素早く依存関係を使うのが正しく、安全性と長期サポートが必須なら長い目で見ることが成功につながる。新規コードを書くことが組織のボトルネックになることはほとんどない

    • 会社でセキュリティ脆弱性やライセンスをどれほど真剣に管理しているのか気になる。以前は依存関係に寛容だったが、セキュリティとライセンスに厳しい会社へ移ってから、見方が大きく変わった

  • ライブラリとフレームワークの違いも重要だ。ライブラリは一つの仕事をうまくこなすツールで、フレームワークはアプリ全体の構造を規定する。Go コミュニティは大規模フレームワークを避け、標準ライブラリと軽量ライブラリ、そして必要ならソースのコピー&ペーストを好む。たとえば Gin(Web API)や GORM(ORM)のようなフレームワークは使い勝手はよいが、内部構造を制限し複雑性を増やす。Go は標準 SDK 自体がかなり強力なので、必要以上に依存関係を追加しないほうがよいと思う

  • 著者はニュージーランド出身だ。ニュージーランドの Number 8 wire 精神、つまり「手元にあるものと器用さで何とかする姿勢」が背景にある(Number 8 wire の Wikipedia 記事

    • ニュージーランド以外の国でも、経験豊富な開発者が同様に同意することは多い。間違った依存関係の選択や、不要なライブラリアップグレードで苦労した経験はほぼ誰にでもある

    • ニュージーランド出身の感覚では、Number 8 Wire メンタリティはもう20年前に消えた気がする

    • 今日初めて知った。オーストラリアの "She'll buff out, mate" という言い回しを思い出す

  • 依存関係の大規模利用や商用展開の実績は、スケーラビリティにも影響する。自分より 100~1000 倍の規模でデプロイしている場所で使われているツールなら、自分の問題で限界にぶつかる可能性は低い。その規模でバグも先に見つかったり修正されたりするので、結果的に自分にとってもより安全に動く

    • 大規模ライブラリでも、小規模環境ではまったく動かないことがある。たとえば Swift の Protocol Buffers コンパイラは、以前は想定外のフィールドでクラッシュしていた。多くの大企業も、大規模用途以外の経路までは自分たちでテストしていない

    • 大企業(Meta、Google、Microsoft など)が作った有名ライブラリで深刻なバグを見つけた経験がある。Issue を報告しても直るまで時間がかかり、変更への抵抗も強い。こういう状況では、結局自分で実装したほうがかえって速く、性能も向上した。特にコンサル業界では、顧客の不合理な要求で作業の方向性が揺さぶられることがよくある。開発者として自分でできるという確信が強まると、巨大な外部依存より自分で実装するほうがよい場面は多い。もちろんブラウザや AI モデルのような本当に大規模な課題まではやらないが、たとえばローカル推論エンジンや HTML レンダラー、自作のグラフデータベースなどは自分で実装している。クライアントが期待しているのは新しさ(イノベーション)ではなく、リスク低減だ。自分で開発すれば、スケジュールを守るのもはるかに容易になる。Google や他の大企業の文書を読み込む時間より、自分で作るほうが効率的だ。ここ3か月、前のチームが放置したプロジェクトを救うために 12 時間労働を続けていて、夜遅くになるとこういうことをよく考える