インターフェースは利用側の要求仕様として定義する、という設計原則があります。ただしこの原則は、実装者と利用者が別人であるという前提の上に成り立っています。そしてその前提は、一人で書いているうちは体感しにくいものです。
一人で書くとき、境界は存在しない
自分で書いて自分で使うコードには、実装者と利用者の区別がありません。
引数の意味が曖昧でも困りません。頭の中に文脈があるからです。戻り値が null を返す条件が明文化されていなくても、書いた本人は知っています。仕様が変わったら、呼び出し側も一緒に直せばよいだけです。
つまり、両側を同時に変更できるうちは、契約が要らないということです。契約とは、片側だけでは変更できない関係を成り立たせるための仕組みだからです。
境界が生まれると、3 つの非対称が現れる
誰かが書いたコードを前提に書き始めた瞬間、対称だった関係が崩れます。
- 情報の非対称 — 利用者は実装を読みません。読めない場合もあれば、読む時間がない場合もあります。判断材料はシグネチャと名前とドキュメントだけです
- 権限の非対称 — 利用者は実装を直せません。実装者のほうも、自分のコードを誰がどう使っているか全員は把握できません
- 時間の非対称 — 実装者は今書き、利用者は半年後に読みます
3 つ目が重要です。時間の非対称は、相手が他人でなくても発生します。半年後の自分は、当時の文脈を失っている点で他人と変わりません。一人開発でも境界は生まれていて、ただ気づきにくいだけです。
ライブラリは、この関係が極端になった形
ライブラリとは、利用者が匿名かつ多数になった状態です。誰がどう使うかを実装者が知りようがありません。だから道具立てが要ります。
pub struct Client {
inner: HttpClient, // 非公開。自由に差し替えられる
}
impl Client {
pub fn get(&self, url: &str) -> Result<Response, Error> { /* ... */ }
}
pub を付けたものだけが契約になり、付けなかったものは今後も自由に変えられます。デフォルトを非公開にする設計が推奨されるのは、公開範囲がそのまま「将来変更できない範囲」になるからです。
版番号もこの関係のためにあります。セマンティックバージョニングでは、互換性を壊す変更でメジャー番号を上げます。利用者は番号を見るだけで、更新して壊れるかどうかを判断できます。実装を読まずに済ませるための約束事です。
ここで効いてくるのがハイラムの法則です。利用者が十分に多ければ、仕様に書いていない挙動にも誰かが依存する、という経験則です。ドキュメントに書いていないから変えてよい、とはなりません。観測できるものはすべて契約になりうる、と考えたほうが実態に合います。
フレームワークは、呼ぶ向きが逆になる
ライブラリとフレームワークの違いも、この軸で説明できます。
| ライブラリ | フレームワーク | |
|---|---|---|
| 呼ぶのは誰か | 利用者 | フレームワーク |
| 制御を持つのは誰か | 利用者 | フレームワーク |
| 利用者が書くもの | 呼び出しコード | 呼ばれるコード |
| 契約の性質 | 使える機能の一覧 | 従うべき規約 |
フレームワークでは制御が反転します。利用者が書くのは「呼ばれる側」であり、決められた名前・決められたシグネチャで所定の位置に置く必要があります。規約が強く感じられるのはこのためです。自由度を渡す代わりに配線を引き受ける、という取引になっています。
社内のコードにも同じ境界がある
外部ライブラリだけの話ではありません。モジュール境界、レイヤ境界、チーム境界にも同じ非対称が発生します。
隣のチームが使っている関数のシグネチャは、もう自分だけの都合では変えられません。相手のリリース都合を待つ必要があり、事前の合意も要ります。社内であっても、実質的には公開 API です。
一人開発で意識するなら、次の順に効きます。
- 公開するものを最小に保つ。
pubやexportを付ける前に、本当に外から必要かを確認する - 型で意図を伝える。ドキュメントは読まれないことがありますが、シグネチャは必ず読まれます
- 利用側からテストを書く。使いにくさは、利用者の位置に立って初めて見えます
- 変えたくなる部分ほど隠す。将来の変更余地は、非公開にしておいた範囲の広さと等しくなります
まとめ
- 実装者と利用者を同時に変更できるうちは、契約は必要ない
- 境界が生まれると、情報・権限・時間の 3 つの非対称が発生する
- 時間の非対称は一人開発でも発生する。半年後の自分は他人に近い
- ライブラリは利用者が匿名かつ多数になった形。公開範囲・版番号・ドキュメントはその対処
- 公開したものは仕様になる。観測できる挙動には誰かが依存すると考えておく
- フレームワークは制御が反転し、利用者が「呼ばれる側」を書く
- 将来変更できる範囲は、非公開にしておいた範囲と一致する