コードレビュー中に、こんな関数に出会ったことはありませんか?
名前はgetUser()なのに、中ではユーザーを取得し、最終ログイン時刻を更新し、キャッシュを更新し、さらに休眠アカウントなら通知メールまで送っていました。
名前だけを見て「取得するだけだから安全だろう」とループ内で呼び出していたら、ユーザーにメール爆弾を送ることになりますよね。
今日取り上げるのは、こうしたコードを見抜くための最小驚きの原則です。KISS・DRY・YAGNI・SOLIDへと続いてきた開発原則シリーズですが、今回は名前からして面白い原則です。
最小驚きの原則(Principle of Least Astonishment、略してPOLA)が示すのは、次のことです。
コードは、読む人が予想したとおりに動作すべきです。利用者を驚かせる設計があるなら、その設計を見直しましょう。
1960〜70年代のシステム設計から生まれた古い原則ですが、UIデザインやAPI設計、日常的なコードまで幅広く適用できます。
なぜ驚きがコストになるのか?
プログラミングにおける「驚き」は感情の問題ではなく、コストの問題です。
開発者はコードを読むとき、名前や慣例をもとに仮説を立てます。getUserは取得だけを行い、isValidはBoolを返すだろう、といった具合です。コードが仮説どおりに動けば、読み進めるのは簡単です。
しかし仮説が崩れた瞬間、読む流れを止めて関数の中に入り、実際の動作を確認しなければなりません。こうした関数がコードベースに10個あるだけでも、「このコードでは名前を信用できない」という不信感が生まれます。そこからは、すべての関数を開きながら読むことになります。コードを読む速度は半分になります。
最悪なのは、確認せずに仮説どおり使って障害を起こすことです。先ほどのメール爆弾のように。
コードで驚きが現れる場面
ここでは、私が実際に経験したパターンを中心に整理します。
1. 名前と動作が異なる関数
// 名前は取得なのに、内部で状態を変更する
func getUser(id: Int) -> User {
let user = db.find(id)
user.lastSeenAt = Date() // 驚き 1: 副作用
db.save(user)
if user.isDormant {
mailer.send(to: user.email, message: "休眠解除のお知らせ") // 驚き 2: 外部呼び出し
}
return user
}
取得関数に隠れた書き込み処理は、驚きの代表例です。getは読み取りだけにし、状態を変更するならupdateやtouchのような名前を使うべきです。
2. 慣例を裏切る戻り値
同じコードベースで、見つからないとnilを返す関数、例外を投げる関数、空のオブジェクトを返す関数が混在していると、呼び出し側は毎回賭けをすることになります。どれか一つに統一することが重要です。
3. 何も知らせない失敗
func parseConfig(_ json: Data) -> Config {
guard let config = try? JSONDecoder().decode(Config.self, from: json) else {
return Config() // 驚き:設定が壊れているのに、何事もなかったかのように空の設定を返す
}
return config
}
設定ファイルが壊れているのに、黙ってデフォルト値に戻ると、ユーザーはずっと後になって「なぜ設定が反映されないのか」と苦しむことになります。失敗は大きく知らせたほうが、驚きは少なくなります。
驚きの少ないコードを書くコツ
私が守るようにしているのは、次のとおりです。
| コツ | 内容 |
|---|---|
| 名前は契約である | 名前が約束したことだけを行い、それ以上を行うなら名前を変える |
| 慣例に従う | 理由なく、言語・フレームワーク・チームの既存スタイルから外れない |
| 副作用を明示する | 状態を変更する関数だと名前から分かるようにする |
| 驚かせるなら文書化する | どうしても特殊な動作が必要なら、コメントとドキュメントで目立つように知らせる |
この中で最も強力なのは2つ目です。自分だけの賢いやり方より、退屈な標準方式のほうが、チーム全体で見ると常に勝ちます。前回のKISS編で、「目立つ一行」より普通のループのほうがよいと述べたのと、まったく同じ文脈です。
最後に
最小驚きの原則について、覚えておきたいことは3つです。
- コードは、名前と慣例が作る期待どおりに動作すべきです。期待を裏切るコードは、バグの温床です。
- 取得関数に副作用を隠さず、失敗を黙って握りつぶさないでください。
- チームでは、奇抜なコードより退屈で予測可能なコードが勝ちます。
良いコードの基準を一つだけ挙げるなら、私はこれを挙げます。読む人を驚かせないコードです。
最近コードを読んでいて「えっ、なぜこんな動きをするの?」と思った瞬間があったなら、それこそ、この原則が破られた現場です。

