if条件を並列に足すだけ──その積み重ねが“バグを呼ぶ構造”だった

──見落とされた「構造的従属」から設計を学べた話

# よくあるコードから始まった

はじまりは、よくあるコードだった。

```js
if (user && user.isAdmin) {
  showAdminPanel();
}
```

これは、ユーザーが存在しており、かつ管理者であれば管理者UIを表示する処理。  
短くて、読みやすく、問題も起きていなかった。

# 条件追加でバグが発生

仕様追加は「ただ1つの条件追加」のはずだった
ある日、新たな仕様が加わった。

>「ダークテーマを使っている管理者だけに表示してほしい」

それを反映するには、こうすればいいと思った。

```js
if (user && user.isAdmin && user.settings.theme === 'dark') {
  showAdminPanel();
}
```

見た目上、何の問題もない。  
条件が1つ増えただけで、既存の形を壊していないように見えた。

しかし、自分のミスで、本番で障害が発生した
特定のユーザーで画面が真っ白に。  
原因は user.settings が undefined だったこと。

```
TypeError: Cannot read properties of undefined (reading 'theme')
```

そこで、よくある短絡的な修正すると、
```js
if (user && user.isAdmin && user.settings && user.settings.theme === 'dark') {
  showAdminPanel();
}
```
見た目は正しい。評価順も安全。

しかし、意味の関係性がまた“対等に並列”に見えてしまう

このように条件を“並列に”積み重ねていくと、  
次の仕様追加でもまた **&&** で繋いでしまいがちになります。

たとえば「組織に所属している人だけ」や「2段階認証を有効にしている人だけ」など、  
次々と要件が追加されるたびに、if 文がどんどん長く、複雑になっていきます。

やがて──  

# if文の“条件地獄”が生まれるのです

読みづらく、変更しづらく、ミスが生まれやすい構造。  

その結果、また誰かが「ここに条件足せばいい」と勘違いしやすくなる。
そして、“なんとなく足される条件”が積み重なり、  
本来分けるべき責任が分けられないまま、技術負債が静かに膨らんでいきます。

実は、某有名なプログラミングの本でも、ファイルの有無とファイル保護状態のチェックを“並列の条件”として書いている例があり、どうしても違和感を覚えることがあります。  

本来は、その条件同士に“構造的な従属関係”があることを、  
コード上でも読み手に伝えるべきなのでは──そう感じたことがある人もいるのではないでしょうか。

# 従属関係を構造で見せる

「構造で見せる」とは、単に if 文を安全に書くという話ではありません。  
条件同士に“意味的な上下関係”があるときは、それをコード構造（ネスト）で表現することで、意図の誤解や将来的な事故を防ぐという考え方です。

たとえば、次のように
>「user が存在して初めて isAdmin を確認できる」「さらに settings の存在があって初めて theme を見られる」

という従属関係を、if 文の入れ子構造で視覚的に示します。

```js
if (user) {
  if (user.isAdmin) {
    const settings = user.settings;
    if (settings && settings.theme === 'dark') {
      showAdminPanel();
    }
  }
}
```

このように
>「user → isAdmin → settings.theme」

という意味的な依存関係を、コードの階層でそのまま見せることで、  
「この条件は、上の条件を前提にしているんだな」という設計意図が誰にでも伝わるようになります。

# ネストが深いときの代替手法（early return）

ネストが深くなりすぎると、たしかに読みづらさや保守性の低下につながることがあります。

ただし、意味的な従属関係が明確にある場合は、あえてネストによって構造化することで、  
その関係性を読み手に正しく伝えることができます。

ネストが浅く収まるなら、構造を見せる有効な手段になります。  
もし、ネストが深くなりすぎて読みづらくなる場合には、early return で“責任の順序”を保ったままフラットに表現するのも選択肢のひとつです。

```js
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（判断）部分を関数として切り出してみると、

```js
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#で極める！クリーンコードの技法（限定クーポン付き）](https://www.udemy.com/course/aic-hjke/?couponCode=C49C4B16FBE249DDFFDA)

- C#でクリーンコードと設計力を身につける実践講座  
- ChatGPTの活用方法や、伝わるコードの考え方を解説

### 📘 出版書籍『あきらめない者たち』

▶️ [Amazonで見る](https://amzn.asia/d/dK37sIu)

- 技術の基礎からやり直すために、なぜ一歩勇気を振り絞れたのか  
- 自身の成長と再出発を描いたノンフィクション作品