第2話 UIに責任を詰め込むな──設計を“読める”形に変えるまで

# 前回までの記事
https://qiita.com/Siras-inc/items/841077184cfbe0aec291

# とりあえずコードを整理してみたつもりだった

AIからもらったコードをそのまま使っていた若手は、
前回の反省を踏まえて、自力でリファクタリングに取り組んだ。

変数名を見直し、関数を分け、処理の順番も少し変えた。
とくにUIが煩雑だったので、「画面の中で状態も処理も完結させた」つもりだった。

師匠に画面を見せながら、少し誇らしげに言う。

>若手：「とりあえず、整理してみました。UIの中でひと通り完結するようにしてあります」

:::note info
修正前コード
:::
```
import { useState } from 'react';

function App() {
  const [text, setText] = useState('');
  const [todos, setTodos] = useState<string[]>([]);

  const handleAdd = () => {
    if (text.trim() === '') return;
    setTodos([...todos, text]);
    setText('');
  };

  return (
    <div style={{ padding: '2rem' }}>
      <h1>Todoアプリ</h1>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="やることを入力"
      />
      <button onClick={handleAdd}>追加</button>
      <ul>
        {todos.map((todo, idx) => (
          <li key={idx}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;
```
:::note info
修正後コード
:::
```
import { useState } from 'react';

function App() {
  // 状態（state）がUIコンポーネント内部に集中している
  const [text, setText] = useState('');
  const [todos, setTodos] = useState<string[]>([]);

  // 入力チェックもこの中に定義
  const isInputValid = () => text.trim() !== '';

  // Todo追加処理も直接ここで完結している
  const handleAdd = () => {
    if (!isInputValid()) return;
    setTodos([...todos, text]);
    setText('');
  };

  return (
    <div style={{ padding: '2rem' }}>
      <h1>Todoアプリ</h1>

      <div style={{ display: 'flex', gap: '0.5rem' }}>
        <input
          type="text"
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="やることを入力"
        />
        {/* 入力チェックの判定もここで直接実行 */}
        <button onClick={handleAdd} disabled={!isInputValid()}>
          追加
        </button>
      </div>

      <ul>
        {/* todos の表示も UI 内に直接展開 */}
        {todos.map((todo, idx) => (
          <li key={idx}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

```

師匠はしばらくコードを眺めていたが、手を組んだまま、静かにこう言った。

>師匠：「……まぁ、見た目は多少は整ったけど」

若手は「よかった」と思いかけたが、その言い回しに、どこかモヤっとしたものが残った。

“整ったけど”──そのあとに続く言葉が、少し怖かった。


# 師匠の静かなひと言が刺さった

師匠は黙ったままコードを見ていた。
若手が自信をもって見せた、整理されたUIコンポーネントの中身。

それでも師匠は、顔色ひとつ変えずにぽつりとつぶやいた。

>師匠：「……表示の場所で“判断”までしてるのは、まずいんだよ」
>若手：「え？判断ですか？」
>師匠：「……表示する責任の場所で、“これを追加していいか”みたいな判断の責任までやるのは、まずいんだよ」

若手は一瞬止まり、少し自信ありげに答える。

>若手：「でも、UIの中でまとめた方が分かりやすくないですか？」

師匠は視線をコードから外さず、静かに言葉を続けた。

>師匠：「それ、“処理のフロー”なんだよ。動かす順番を並べただけ。
>フローチャートと同じで、それは“動的な設計”。悪くはないけど、それだけじゃ足りないんだよ。
>本来の設計ってのは、“意味のある構造”をつくること。つまり、静的な設計なんだよ。
>まず構造を決める。それから動きを組み合わせていく──順番が逆なんだよ」

若手は何も言い返せなかった。

「動かせるように並べただけ」。
言われてみれば、自分の設計は、まさにそれだった。

構造がない。意味も責任も、バラバラのままだった──

# 設計の“順番”と“責任”がバラバラだったと気づく

師匠の言葉が頭から離れなかった。
「処理のフロー」と「意味のある構造」──それは、自分がこれまで考えたことのない視点だった。

UIの中で状態を管理し、入力チェックをして、処理を分岐し、さらにそのまま画面に反映している。

それは、「何を表示するか」だけではなく、
「どれが正しい入力か」や「追加すべきかどうか」まで判断していた。

>若手：「……僕、画面の中で全部やろうとしてました」

師匠はうなずきながら答える。

>師匠：「それじゃ“構造”が見えないんだよ。UIに責任を背負わせすぎなんだ」

画面に責任を詰め込んだ結果、「何をどこで決めているのか」が読みにくくなり、
設計としても、他の人には伝わらないものになっていた。

若手はようやく、「処理の順番」ではなく、「責任の並び」として設計を見ることの大切さに気づき始めた。

# 責任を分けることで、設計が“読める”ようになる

若手は、コードの中に詰め込んでいた処理を一つひとつ外に出し始めた。
入力の妥当性チェック、状態の更新、そして表示――
これまでUIコンポーネントの中でまとめていたものを、責任ごとに分けていく。

UIは「言われたとおりに見せるだけ」にし、
「何を見せるべきか」「どんなときに追加するか」は、UIの外で決めるように変えた。

>若手：「……これ、どこで何やってるか、ひと目で分かるようになりました」

:::note info
再修正後のコード
:::

```
import { useState } from 'react';

// UIの外にロジックを分離（責任を分ける）
function isInputValid(text: string): boolean {
  return text.trim() !== '';
}

function addTodo(todos: string[], text: string): string[] {
  return [...todos, text];
}

function App() {
  const [text, setText] = useState('');
  const [todos, setTodos] = useState<string[]>([]);

  const handleAdd = () => {
    if (!isInputValid(text)) return;
    const updated = addTodo(todos, text);
    setTodos(updated);
    setText('');
  };

  return (
    <div style={{ padding: '2rem' }}>
      <h1>Todoアプリ</h1>
      <div style={{ display: 'flex', gap: '0.5rem' }}>
        <input
          type="text"
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="やることを入力"
        />
        <button onClick={handleAdd} disabled={!isInputValid(text)}>
          追加
        </button>
      </div>
      <ul>
        {todos.map((todo, idx) => (
          <li key={idx}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;
```

そこで、ふと過去を思い出す。
以前のプロジェクトで、仕様の認識違いや不具合が繰り返されたこと。
レビューでも「この処理、なんでここにあるの？」と何度も聞かれたこと。
どれも「UIの中に全部詰めていたから」だったのかもしれない。

>師匠：「出力は、“言われたとおりに見せるだけ”でいいんだよ。
>「何をどう判断するかは、ちゃんと外に出しておかないと──伝わらないんだよ。

“責任を分ける”という設計の原則が、少しずつ自分の中に染み込んでいく。

コードの意味が見えるようになる。
誰かに説明できるようになる。
それが「読める設計」ということなんだ──

次回、「構造を並べ直す」という新たなステップへ。
“変化に強い構造”を目指して、設計の再構築が始まる──

# 次回の記事予告

https://qiita.com/Siras-inc/items/ee42d5685a1340f0cd98

ここまでお読みいただき、ありがとうございました。  
本記事では、AIを使って開発を進める中で直面した「設計不在」の怖さと、それを乗り越えるために必要な“伝わる設計”の視点について紹介しました。

次回は、師匠から教わった設計の基本――「STS設計（入力・変換・出力）」というシンプルで強力な考え方を軸に、実際にReactアプリの構造を改善していくプロセスを紹介します。

もし、AIの出力をそのまま使ってコードが混乱してきた方、自分の設計に不安を感じている方には、きっと役立つ内容になるはずです。ぜひご期待ください！

---

また、今回紹介した内容をより実践的に学びたい方には、以下のUdemy講座もおすすめです。

## Udemyコース（8,800円 → クーポンで割引中）

▶️ [AIとC#で極める！クリーンコードの技法（限定クーポン付き）](https://www.udemy.com/course/aic-hjke/?couponCode=C49C4B16FBE249DDFFDA)

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

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

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

- 技術の基礎からやり直すために、なぜ一歩勇気を振り絞れたのかのノンフィクション作品です。