──見落とされた「構造的従属」から設計を学べた話
よくあるコードから始まった
はじまりは、よくあるコードだった。
if (user && user.isAdmin) {
showAdminPanel();
}
これは、ユーザーが存在しており、かつ管理者であれば管理者UIを表示する処理。
短くて、読みやすく、問題も起きていなかった。
条件追加でバグが発生
仕様追加は「ただ1つの条件追加」のはずだった
ある日、新たな仕様が加わった。
「ダークテーマを使っている管理者だけに表示してほしい」
それを反映するには、こうすればいいと思った。
if (user && user.isAdmin && user.settings.theme === 'dark') {
showAdminPanel();
}
見た目上、何の問題もない。
条件が1つ増えただけで、既存の形を壊していないように見えた。
しかし、自分のミスで、本番で障害が発生した
特定のユーザーで画面が真っ白に。
原因は user.settings が undefined だったこと。
TypeError: Cannot read properties of undefined (reading 'theme')
そこで、よくある短絡的な修正すると、
if (user && user.isAdmin && user.settings && user.settings.theme === 'dark') {
showAdminPanel();
}
見た目は正しい。評価順も安全。
しかし、意味の関係性がまた“対等に並列”に見えてしまう
このように条件を“並列に”積み重ねていくと、
次の仕様追加でもまた && で繋いでしまいがちになります。
たとえば「組織に所属している人だけ」や「2段階認証を有効にしている人だけ」など、
次々と要件が追加されるたびに、if 文がどんどん長く、複雑になっていきます。
やがて──
if文の“条件地獄”が生まれるのです
読みづらく、変更しづらく、ミスが生まれやすい構造。
その結果、また誰かが「ここに条件足せばいい」と勘違いしやすくなる。
そして、“なんとなく足される条件”が積み重なり、
本来分けるべき責任が分けられないまま、技術負債が静かに膨らんでいきます。
実は、某有名なプログラミングの本でも、ファイルの有無とファイル保護状態のチェックを“並列の条件”として書いている例があり、どうしても違和感を覚えることがあります。
本来は、その条件同士に“構造的な従属関係”があることを、
コード上でも読み手に伝えるべきなのでは──そう感じたことがある人もいるのではないでしょうか。
従属関係を構造で見せる
「構造で見せる」とは、単に if 文を安全に書くという話ではありません。
条件同士に“意味的な上下関係”があるときは、それをコード構造(ネスト)で表現することで、意図の誤解や将来的な事故を防ぐという考え方です。
たとえば、次のように
「user が存在して初めて isAdmin を確認できる」「さらに settings の存在があって初めて theme を見られる」
という従属関係を、if 文の入れ子構造で視覚的に示します。
if (user) {
if (user.isAdmin) {
const settings = user.settings;
if (settings && settings.theme === 'dark') {
showAdminPanel();
}
}
}
このように
「user → isAdmin → settings.theme」
という意味的な依存関係を、コードの階層でそのまま見せることで、
「この条件は、上の条件を前提にしているんだな」という設計意図が誰にでも伝わるようになります。
ネストが深いときの代替手法(early return)
ネストが深くなりすぎると、たしかに読みづらさや保守性の低下につながることがあります。
ただし、意味的な従属関係が明確にある場合は、あえてネストによって構造化することで、
その関係性を読み手に正しく伝えることができます。
ネストが浅く収まるなら、構造を見せる有効な手段になります。
もし、ネストが深くなりすぎて読みづらくなる場合には、early return で“責任の順序”を保ったままフラットに表現するのも選択肢のひとつです。
function shouldShowAdminPanel(user) {
if (!user) return false;
if (!user.isAdmin) return false;
const settings = user.settings;
if (!settings) return false;
if (settings.theme !== 'dark') return false;
showAdminPanel();
}
STSで責任を分ける
STSとは、
「Source(入力)」「Transfer(判断・変換)」「Sink(出力)」
という責任の流れで 設計やコードを構造化する考え方です。
では、先ほどのコードを「責任の流れ」という視点で見直してみましょう。
先ほどのコードで言えば:
・user の存在チェック → Source
・isAdmin や theme の条件判断 → Transfer
・showAdminPanel の実行 → Sink
すべてが1つの関数に詰め込まれていたため、責任が混ざり、変更や拡張・レビューが難しくなっていました。
ここで Transfer(判断)部分を関数として切り出してみると、
function shouldShowAdminPanel(user) {
// --- Source: 入力チェック ---
if (!user) return false;
const settings = user.settings;
if (!settings) return false;
// --- Transfer: 条件の判断 ---
const isAdmin = user.isAdmin;
const isDarkTheme = settings.theme === 'dark';
const isEligible = isAdmin && isDarkTheme;
if (!isEligible) return false;
// --- Sink: 表示許可を出力 ---
return true;
}
if (shouldShowAdminPanel(user)) {
showAdminPanel();
}
設計とは、責任の流れを“構造で見せる”こと
「コードが動くかどうか」ではなく、その処理がどんな前提や流れで実行されるかを意識する
条件を横並びに書くのではなく、どの条件がどれに依存しているか(従属関係)を明確にする
ネストが深くなりすぎなければ、従属関係を読み手に正しく伝えるための“構造的なヒント”として、ネストは有効に機能します。
まとめ:構造は、設計意図の“言語”である
設計とは、動くこと以上に、“意図が伝わること”。
構造で意味を語れるコードは、バグを防ぎ、未来を守ります。
if文の条件は、ただの構文ではなく、その設計意図を伝える“構造”でもある。
条件を横に並べるだけでは、すべてが同じ重み・前提で評価されるように見えてしまう。
だからこそ、依存や従属の関係があるなら、それを“構造として見える形”で表現すべきなのだ。
このコード、あなたのプロダクトでも見たことありませんか?
もし、複雑な条件や仕様追加でバグが入りやすいと感じていたら──
今回紹介したような「構造で責任を見せる」設計、ぜひ試してみてください。
あわせて学びたいコンテンツ
また、今回紹介した内容をより実践的に学びたい方には、以下のUdemy講座もおすすめです。
🎓 Udemyコース(8,800円 → クーポンで割引中)
▶️ AIとC#で極める!クリーンコードの技法(限定クーポン付き)
- C#でクリーンコードと設計力を身につける実践講座
- ChatGPTの活用方法や、伝わるコードの考え方を解説
📘 出版書籍『あきらめない者たち』
▶️ Amazonで見る
- 技術の基礎からやり直すために、なぜ一歩勇気を振り絞れたのか
- 自身の成長と再出発を描いたノンフィクション作品