この記事は Wano Group Advent Calendar 2025 の1日目の記事となります。
トップバッターです!今年も張り切っていこうと思います。
ネタバレ
- この内容はほぼほぼ A Philosophy of Software Design の4章の要約だよ
- モジュール / クラス / 関数の使いやすさ インターフェース と 実装 の二軸で評価できるよ
- 優れたモジュールはインターフェースが少なく、実装が多いよ これを Deep Module というよ(逆は Shallow Module というよ)
-
Deep Moduleを実現するには どのように実装の複雑さを隠蔽するか が重要だよ
はじめに
こんにちは!@shibe_ です。TuneCore Japanでバックエンドエンジニアをしています。
2022年の3月にTuneCore Japanに入り(採用に至るまでの経緯などはこちら)、ゴリゴリと実装してきた中で役立った考え方を紹介します。
エピソードトーク
TuneCore Japanエンジニアチームでは週一回、輪読会を実施しています。
詳細はこちら:輪読会でチームを強くする
記念すべき第一回では A Philosophy of Software Design という書籍をみんなで読んで感想を話し合いました。
(英語の本ですが、内容の充実度に反して読み口は非常に軽くとにかく読みやすいです。異次元の文章力……)
それからというものチームの設計指標に「モジュールの深さ」が軸として考慮されるようになりました。
優れたモジュールはこのように評価されています。
👥 Deep Module がインストールされたメンバーの様子
何事なんだよ。
📏 モジュールの「深さ」って?
ここでいう モジュールの「深さ」 は「インターフェースの量」と「実装の量」の二軸で評価することができます。
ここでいう インターフェース とは、使い手が考慮すべき部分のことです。クラスであれば「関数(およびその引数)」を指すと考えてよいでしょう。APIであればリクエストとそこに含まれるプロパティの種類のことです。
A Philosophy of Software Design にも実装のイメージ図が載っていたので、それを見て自分でも書いてみました。
コーディングされる方であれば、すごく直感的に理解ができる図だと思います。
John Ousterhout『A Philosophy of Software Design』- Figure 4.1: Deep and shallow modules をもとにshibe_が作成
🌊 それで、Deep Moduleとは?
Deep Module とは、「インターフェース(使い手が考慮する必要のある部分)が少なく、実装(実際に受けられる恩恵)は多いモジュール」のことです。実装が複雑になってくると、それに伴って管理すべき要素が増えてゆきます。それらを使い手にとって最低限に留めることで使い手の認知負荷を軽減しているモジュールのことですね。
具体的な実装は、例えば以下のようになるでしょう。(本稿の実装はgoで書かれていますが、かなり平易なためプログラミングをやっていれば読めると思います。みなさまを信じます)
TODOコメントになっている 食べ物を加熱する実装 の部分に、電子レンジの複雑な機能が入ってくるはずです。
しかしながら、利用者はAtatameメソッドを呼び出すことで、それを意識することなく電子レンジを使うことができます。
// DenshiRenji 電子レンジモジュール
type DenshiRenji struct {
Food Food // 温め中の食べ物
}
type Food struct{}
// Atatame 食べ物を加熱
func (d *DenshiRenji) Atatame(food Food, seconds int) Food {
// TODO secondsの時間だけ、食べ物を加熱する実装
return food
}
実際に、現実世界の電子レンジは非常にDeepなモジュールととらえることができます(同著にも具体例として挙げられています)。
わたしたちはマイクロ波のしくみなどについて何もわからずとも、食べ物を入れる/スタートボタンを押す だけで食べ物を温めるという恩恵を得ることができます。
(余談ですが、最近の電子レンジって食材をいれてスタートボタンを押すだけでごはん用のあたためをしてくれたりするんですね。まさかワンタッチでここまでできるとは……)
⛲ では、Shallow Module とは?
それに対して Shallow Module は、「インターフェース(使い手が考慮する必要のある部分)が多く、実装(実際に受けられる恩恵)は少ないモジュール」を指します。
機能上、本来使う側は考えなくてもよいはずの部分が見えちゃっているというわけですね。
実装の具体例では、
- あたため関数を 開始 と 終了 に分けてみました
- マイクロ波を出すアンテナの角度を調整できるようにしてみました
一見すると細かい調整ができて便利に見えますが、使いたい人は 温まった食べもの という結果が欲しいだけなのに余計なことを考える必要があり、非常に不便です。なんだよアンテナの角度調整って。
// DenshiRenji 電子レンジモジュール
type DenshiRenji struct {
Food Food // 温め中の食べ物
MicroWaveAntenna MicroWaveAntenna // マイクロ波を出すアンテナ
isHeating bool // 加熱中フラグ
}
type Food struct{}
type MicroWaveAntenna struct {
Angle int // アンテナ角度
}
// Start あたためを開始する機能
func (d *DenshiRenji) Start(food Food, antennaAngle int) {
// 実装
d.Food = food
d.MicroWaveAntenna.Angle = antennaAngle
d.isHeating = true
for d.isHeating {
d.atatame(antennaAngle)
}
}
// atatame 食べ物を加熱する機能
func (d *DenshiRenji) atatame(antennaAngle int) {
// TODO 実装
}
// Stop あたためを終了し、食べ物を取り出す機能
func (d *DenshiRenji) Stop() (food Food) {
d.isHeating = false
return d.Food
}
モジュールがShallowになりがちな実装のマインドセット
同著では、「機械的に実装を細かく分割すると Shallow Module は生まれがち」と記述されています。
数十行まで書いたら関数を分ける必要がある などといったやり方ですね(恥ずかしながら、過去編の自分はこのように関数を分けていました……)。
実際に分割において必要なのは、実装したいモジュールに関わっている要素を以下のレイヤーに整理することです。
- 利用者が意識する必要のあるもの(=露出させるべきもの)
- 状況によって利用者が意識する必要があるもの(オプション的要素)
- 利用者が意識する必要のないもの(=隠蔽すべきもの)
これによって不必要な複雑性を使い手に見せることなく、その恩恵を受けられるようにする第一歩ですね。
あとに続く章で、
- どのように隠蔽するか
- モジュール内の複雑な部分における可読性をどう上げるか
などの記述もあります。気になる方はぜひ読んでみてください。
まとめ
Deep Module の考え方をインストールして、自分はけっこういろいろなものの見え方が変わりました。
自分はバックエンドの実装が多いですが、
この考え方はAPIなどの実装方針の検討にとどまらず、
UIの設計やコミュニケーションの組み立て方まで、
かなり多くの考え方に応用できるように見えますね。
この記事で、より多くの魂が救われることをここで祈り続けています。
TuneCore Japan では 一緒に働くメンバーを募集しています。
エンジニアだけでなく、Director等の各種ポジションをオープンしているので興味がわいた方はぜひご覧ください!



