
はじめに
これは、ソフトウェア設計を「自由度の配置」という観点から眺めてみる、ひとつの思考実験です。
設計とは意思決定なのか
最近、ソフトウェア設計について考えるとき、「設計とは意思決定である」と捉えることが増えました。
設計では大小さまざまなことを決めていきます。データをどこに持つのか、APIをどのような形にするのか、どの責務をどのパッケージに置くのか。
では、意思決定とは具体的には何をしているのでしょうか。
これまでを振り返ってみると、複数あった選択肢から一つを選び、それ以外を選ばないと決める、ということを繰り返してきたように思います。
そう考えると、設計には、それまであった選択肢を閉じていく面がありそうです。
では、設計とは自由度を減らしていくことなのでしょうか。
決めると、いくつかの選択肢が閉じる
例えば、システムが使うデータベースを決めれば、実装者が機能ごとに好きなデータベースを選ぶ余地は小さくなります。
APIの形を決めれば、利用者はその形に沿って呼び出すことになります。パッケージ構成を決めれば、新しいコードをどこに置くかもある程度決まります。
設計する前には複数の可能性があり、設計した後にはその一部が選べなくなる。
この部分だけを見れば、設計は自由度を減らしていく作業に見えます。
ただ、すべてを設計者が決め切るとは限りません。
たとえばライブラリが利用者に設定項目を公開することもあります。サーバーで一つの挙動に固定せず、クライアントから指定できるようにすることもあります。
設計時には決めず、デプロイ時に環境に合わせて選べるようにすることもあります。
「ここでは決めず、別のところで決められるようにする」こと自体も、設計判断として扱えそうです。
自由度はどこに残されるのか
ここでいう自由度は、単純な選択肢の数ではありません。
その時点ではまだ固定されておらず、誰かが判断できる余地、くらいの意味です。
使い道のない設定項目が百個あっても、それだけでよい設計になるとは思いません。
では、自由度がどこに残されているかを、もう少し具体的に見てみます。
一つは、誰が決めるのかです。ライブラリの提供者が決めるのか、それを使うアプリケーションが決めるのか。チームで共通の方針を決めるのか、個々の実装者に任せるのか。
もう一つは、どこで決めるのかです。クライアントかサーバーか、コードか設定か、アプリケーションかデータベースか。同じ挙動でも、判断する場所が変われば、変更するときに触る場所や影響を受ける範囲が変わります。
粒度もあります。システム全体で一つに決めるのか、テナント単位で変えられるのか、リソースごとか、リクエストごとか。
細かい単位で決められるほど個別の事情には合わせやすくなりますが、システムの状態は一様ではなくなります。
これらを、自由度の分類としてきれいに整理したいわけではありません。
同じ設計判断を見ても、「誰が」「どこで」「どの粒度で」を変えて眺めると、何を固定し、何を残したのかが少し見えやすくなります。

いつ決めるか
自由度には時間の方向もあります。
設計時に決めることもあれば、build時やdeploy時、起動時に決めることもあります。
runtimeになって初めて、そのときの入力や状態に応じて決まることもあります。
決定を後ろへ送れば設計時にはなかった情報を使えます。
例えば、デプロイ先の環境が決まってから接続先を選ぶ、リクエストの内容を見て処理を変える、といったことができます。
一方で、遅く決めるほどよいわけではありません。
runtimeに判断を残すと、実行時に複数の状態や分岐を扱う必要が出ることがあります。
確認すべき組み合わせが増えたり、問題が起きたときに「そのとき何が選ばれていたのか」を追う必要が出たりします。
いつ決めるかも、設計で選んでいることの一つです。

自由度を残すと、判断も残る
APIにoptionalなparameterを追加する場面を考えてみます。
これはそれまで提供側で一つに決めていた挙動を、利用者が選べるようにする変更です。
「APIが柔軟になった」と表現できますが、自由度の配置として見ると、提供側が引き受けていた判断の一部を利用者へ渡したとも言えます。
利用者は、どの値を指定すべきか理解して判断することになります。
提供側では、値ごとの挙動を検証し、使い方を文書に書き、公開した選択肢の互換性を考える必要が出てきます。
運用中に問題が起きたときは、どの値が使われていたかも調査対象になるかもしれません。
これは、自由度を増やすと同じ量の複雑さが必ず別の場所へ移る、という話ではありません。
うまい抽象化によって、利用者にも提供者にも分かりやすくなることはあります。また、利用者ごとに事情が違うなら、選べること自体に十分な価値があります。
それでも、自由度を残すならその自由度を誰がどう扱うのかは考えておきたいところです。
判断する人には必要な情報が届くのか、どこで検証するのか、増えた組み合わせをどこまで支えるのか、、、

制約によって考えなくてよくなることもある
APIで自由度を残す側を見ると、では自由度は少ない方がよいのか、とも思います。 では逆に、自由度を閉じることにはどんな意味があるのでしょうか。
gofmtやrustfmtのようなformatterは、コードをどう整形するかという自由度をかなり減らします。
書き手が読みやすいと思う形を毎回選ぶのではなく、決められた形へ機械的に揃えます。
ただし、その結果として失うものはあります。
好みの書き方ができなくなりますし、formatterの出力が常に自分の読みやすい形になるとも限りません。
一方で書式については毎回考えなくてよくなります。
レビューで空白や改行について議論する必要も減り、挙動や設計の差分へ注意を向けやすくなります。
もちろん制約そのものがよいわけではありません。
早すぎる固定や、事情の違う利用者を無理に一つへ揃えることは、別の不自由さを生むこともあります。
ただ、自由度には、選ぶための時間と注意が必要です。
あまり意味のない判断を制約で減らすことで、別の判断に注意を向けられる場合があります。
ADRに、残した自由度も書いてみる
設計を「自由度の配置」として捉えるこの思考実験を、実際の開発プロセスに当てはめてみるとどうなるでしょうか。
たとえば、設計判断の記録であるADR(アーキテクチャ・デシジョン・レコード)です。
ADRには通常、何を決めたのか、なぜそう決めたのかを書きます。
それを「どの自由度を、なぜこの時点で閉じたのか」の記録として読むこともできます。
そうすると、決めたことだけでなく、まだ決めていないことも気になります。
例えば、次のように残した自由度を記録しておくのです。
- 誰に残したか: 今回はシステム全体の方針だけを決め、個々の実装方法は各チームに残した。
- いつまで残したか: 今は環境の情報が足りないので、接続先はdeploy時に決めることにした。
- コード上でどう残したか: 将来の要件変更を見据え、現時点では特定のデータベースに依存させず、インターフェースのみ定義して技術選定の自由度を残した。
そうした「どこに自由度を残したか」も、後から設計判断を理解する重要な材料になります。
もちろん、すべての未決事項をADRへ書けばよいわけではありません。
少なくとも、意図して残した余地と、単に決め忘れたことを区別できると、その後の判断はしやすくなりそうです。
まとめ
この記事の冒頭で、「設計とは自由度を減らしていくことなのだろうか」と問いかけました。
ここまで考えてみると、その景色は少し違って見えます。
たしかに設計で何かを決めれば、いくつかの選択肢は閉じます。
しかし、すべての自由度がそこで消滅するわけではありません。利用者に委ねることもあれば、別の場所へ置くことも、あえて実行時まで遅らせることもできるのです。
そう考えると、私たちが設計で目を凝らすべきは「自由度の多い・少ない」だけではありません。
「誰に、どこに、いつ、どの粒度で判断を残すか」という、自由度の配置そのものなのかもしれません。
自由度を残せば、それを扱う人が判断するための情報や、検証、文書化、運用が必要になります。
逆に、あまり意味のない自由度をあえて閉じることで、より重要な判断にチームの注意を向けることもできるでしょう。
だからこそ「ここを柔軟にした方がよいか」と問われたとき、単に設定項目を増やすかどうかではなく、次のように問い直してみたいのです。
- 誰が判断するのか
- どこで判断するのか
- いつ判断するのか
- どの粒度で変えられるのか
- その判断を誰がどう支えるのか
こう考えるだけで、今まさに何を選ぼうとしているのかが、少しクリアに見えてこないでしょうか。
もちろん、これは絶対的な設計レビューの手順でも、万能なチェックリストでもありません。何でも「自由度」という言葉で説明できるとも思っていません。
それでも、自分が「柔軟にしたい」と考えたとき、その柔軟さをどこに置こうとしているのかを捉えるための視点として、しばらく使ってみたいと思っています。
【余談】この記事の執筆について
現在、手元でAIを使った執筆環境を構築しており、この記事はその環境のドッグフーディングとして執筆しました。頭の中にあったぼんやりとした思考を、AIと壁打ちしながら言語化しています。
以上です。