ライブラリ

特定の機能を提供する再利用可能なコードの集まりで、プログラムから呼び出して使う

プログラミング開発手法
ライブラリ」の技術書を見る →

ライブラリとは

ライブラリ (Library) は、特定の機能を提供する再利用可能なコードの集まりである。自分で一から書く代わりに、ライブラリを呼び出すことで開発を効率化できる。日付の計算、HTTP 通信、データのバリデーションなど、よくある処理はライブラリとして公開されている。

ライブラリとフレームワークの違い

ライブラリとフレームワークは混同されやすいが、制御の主体が異なる。

項目ライブラリフレームワーク
制御の主体開発者がライブラリを呼ぶフレームワークが開発者のコードを呼ぶ
自由度高い (必要な部分だけ使える)低い (規約に従う必要がある)
Lodash, Axios, date-fnsNext.js, Express, Django

この違いは「制御の反転 (Inversion of Control)」と呼ばれる。Martin Fowler はこの現象を「ハリウッドの原則 (Don't call us, we'll call you)」として説明し、フレームワークの重要な特徴は、利用者が書いたメソッドがフレームワーク自身の内部から呼ばれる点にあると述べている。ライブラリは道具箱、フレームワークは設計図に近い。

判定の基準は規約の多さではなく制御の向きである。表の「自由度」はあくまで傾向で、Express のように自らを「意見を押しつけない (unopinionated)」と説明するフレームワークもある。それでも要求を受け取って開発者のハンドラーを呼ぶのはフレームワーク側なので、制御の向きで見ればフレームワークに分類される。

パッケージマネージャ

ライブラリの取得と管理にはパッケージマネージャを使う。

言語パッケージマネージャレジストリ
JavaScript / TypeScriptnpm, pnpm, yarnnpmjs.com
Pythonpip, poetryPyPI
Rustcargocrates.io
Gogo mod中央レジストリなし (版管理システム + モジュールプロキシ)
JavaMaven, GradleMaven Central
# npm でライブラリをインストール
npm install date-fns

Go だけは中央レジストリを持たない仕組みになっている。モジュールは版管理システムから直接取得されるか、モジュールプロキシ経由で取得される。GOPROXY にはプロキシの URL を並べて書けて、direct と書けば版管理システムへ直接取りにいき、off なら取得しない。pkg.go.dev は公開済みモジュールの文書を読む場であって、配布元そのものではない。

ライブラリの選び方

ライブラリを選ぶ際は以下の観点で評価する。

観点確認方法
メンテナンス状況最終更新日、Issue の対応速度
利用者数npm の週間ダウンロード数、GitHub のスター数
ドキュメント公式ドキュメントの充実度
バンドルサイズbundlephobia.com で確認
型定義TypeScript の型が同梱されているか
ライセンスMIT, Apache 2.0 など商用利用可能か

これらの数字は代理指標にすぎない。週間ダウンロード数は CI やミラーの取得でも積み上がり、GitHub のスター数は関心の量であって、いま使われている版の証拠にはならない。最終更新日と Issue への応答、そして自分が使いたい機能が実際に動くかを確かめる方が確実である。

もう一つの観点は「抜けるときの費用」である。ライブラリ固有の型や書き方が自分のコードの隅々まで染み出す使い方をすると、後から差し替えられなくなる。メンテナンス停止は事前に読めないので、呼び出しを 1 箇所に寄せておくと選定の失敗を回収しやすい。

依存関係の管理

ライブラリは他のライブラリに依存していることがある。この依存の連鎖を依存関係 (Dependency) と呼ぶ。

自分のプロジェクト
├── ライブラリ A
│   ├── ライブラリ C
│   └── ライブラリ D
└── ライブラリ B
    └── ライブラリ C (A と同じ)

パッケージマネージャが依存関係を自動で解決するが、バージョンの競合が起きることがある。package-lock.jsonpnpm-lock.yaml は、このとき解決された木の全体を記録する。npm の説明では、途中の依存が更新されても同じ木を生成できるようにすることが目的として挙げられている。

ロックファイルには効く範囲がある。npm のロックファイルは公開できず、プロジェクトの最上位以外の場所に置かれていれば無視される。守られるのは自分のプロジェクトの再現性だけで、自分が公開するライブラリを誰かが入れるときの解決には関与しない。配る側が版の幅を狭めたいなら、package.json の範囲指定で表明することになる。

セマンティックバージョニング

多くのライブラリはセマンティックバージョニング (SemVer) に従う。

メジャー.マイナー.パッチ
  2      .  3   .  1
変更意味
メジャー (2.x.x)後方互換でない API 変更
マイナー (x.3.x)後方互換のある機能追加
パッチ (x.x.1)後方互換のあるバグ修正

ただし仕様がこの規則を課すのは、メジャーが 1 以上の版に限られる。0.y.z は初期開発用の版と定められ、いつ何が変わってもよいものとして扱われる。公開 API が定まるのは 1.0.0 を出した時点である。したがって 0.x のライブラリに、マイナー更新なら壊れないという期待を持ってはいけない。

範囲指定は SemVer ではなく npm の記法

package.json に書く ^2.3.1~2.3.1 は SemVer 仕様の一部ではなく、npm が使う範囲指定 (node-semver) の記法である。

記法解決される範囲実際に動く桁
^2.3.1>=2.3.1 <3.0.0-0マイナーとパッチ
^0.2.3>=0.2.3 <0.3.0-0パッチのみ
^0.0.3>=0.0.3 <0.0.4-0更新なし
~2.3.1>=2.3.1 <2.4.0-0パッチのみ
~2>=2.0.0 <3.0.0-0マイナーとパッチ

^ の定義は「メジャー・マイナー・パッチの並びのうち、左端の非ゼロ要素を変えない更新を許す」である。2.3.1 では左端の非ゼロがメジャーなのでマイナーまで動き、0.2.3 ではマイナーが左端の非ゼロになるためパッチしか動かない。0.0.3 に至っては動く余地がない。^ を一律に「マイナーまで上がる」と覚えていると、0.x のライブラリで挙動が違う理由を説明できなくなる。

上限が 3.0.0 ではなく 3.0.0-0 と書かれるのは、3.0.0 のプレリリース版までを範囲から外すためである。

ライブラリのリスク

リスク対策
メンテナンス停止代替ライブラリを事前に把握
脆弱性npm audit で定期チェック
破壊的変更範囲指定を絞りロックファイルを commit したうえで、更新を小口で行う
サプライチェーン攻撃CI では npm ci・導入前に依存の数と発行元を確認・依存自体を減らす
バンドルサイズ肥大tree-shaking 対応のライブラリを選ぶ

下の 2 行は、対策の書き方でそのまま効き方が変わる。「ロックファイルで版を固定する」だけでは破壊的変更は防げない。固定は版が勝手に上がらないようにするだけで、更新する瞬間に破壊的変更が入ってくること自体は止まらない。効くのは、固定して再現性を保ちながら更新を小口に分け、1 回の差分を読める大きさに保つ運用である。放置すればいつか一気に版を上げる必要に迫られ、そのときの差分はもう読めない。

サプライチェーン攻撃も「信頼できるパッケージだけ使う」では止まらない。実際の攻撃はメンテナーのアカウント乗っ取りや、名前の取り違えを狙う形で入ってくるため、すでに信頼されているパッケージが加害側になる。効くのは CI で npm ci を使って木を固定すること、npm audit を定期的に回すこと、そして依存の数を減らすことである。

自作 vs ライブラリ利用の判断

自作が適するケースライブラリが適するケース
処理が数行で済む複雑なロジック (暗号化、日付計算)
プロジェクト固有の要件汎用的な処理
依存を増やしたくない実績のある実装が必要

分かれ目は行数ではなく、仕様の難しさと間違えたときの被害である。日付とタイムゾーン、文字コード、暗号は、素朴な実装でも動いて見えるのに境界条件で壊れる領域なので、実績のある実装に任せる。逆に数行で書ける処理をパッケージで済ませると、依存の数とその更新の手間だけが増える。

導入したライブラリは、入れた時点ではなく使い続ける間ずっと費用がかかる。更新を追う手間、破壊的変更への追随、脆弱性が出たときの入れ替え。この費用を引き受けられる数に依存を保つことが、個々の選定より効く。

この記事は役に立ちましたか?

関連用語

関連する記事