前回までの記事
見た目は整理できた。でもまた積み上げて壊れる気がする
若手は、コードを眺めながらため息をついた。
前回、UIの中に詰め込んでいた処理を外に出し、見た目はだいぶスッキリした。
変数名も整えた。関数も分けた。
でも──どこか引っかかる。
若手:「何がどこで起きてるのか、まだちゃんと整理できてないんですよね……。
またこれ、同じように積み上げてったら、どこかで崩れる気がして……」
師匠はゆっくりとうなずきながら言った。
師匠:「動きじゃなくて、“責任”を分けて考えてみな。
処理の順番より、“どこが何を担当してるか”の方が大事なんだよ。
たとえば建設現場でも、配線工が土台つくってたらおかしいだろ?
見た目が完成してても、責任がバラけてたら、あとから崩れるんだよ」
その瞬間、若手の中で“設計=順番”という思い込みが崩れ始めた。
意味で分ける。責任で整理する。
それが、構造の設計というものなのかもしれない──
useReducerで「どう変えるか」ではなく「なぜ変えるか」を整理
責任を分けろ。
順番より、「どこが何を担当しているか」が大事だ。
そう言われたものの、若手はまだモヤモヤしていた。
若手:「意味のある構造……処理の順番じゃなくて、責任の整理……」
師匠はそんな若手の様子を見て、静かに言葉を添えた。
師匠:「たとえば、“どう変わるか”のルールを一箇所にまとめると、整理しやすくなるぞ。
変わる“意味”がそこに集まっていれば、見通しもつく」
若手はハッとしたように顔を上げた。
若手:「……あ、それって、Reactでいうと useReducer ですね!」
師匠:「Reactわからんから、コードで説明するな。“WHATで説明しろ”って言っただろ」
若手:「……ですよね(切り替え)――“何を変えたいのか”を UIの外で決められるってことです」
師匠:「まあ、それでええやろ」
若手:「つまり、状態の“意味付け”を、UIから切り離せるんですよ」
さっそく useReducer を導入してみた。
UIは入力を受け取って dispatch を投げるだけ。
どう変えるかの判断や、状態の意味付けは reducer に集中。
UIは「言われた通りに見せるだけ」の役割に落ち着いた。
修正したコード
import { useReducer, useState } from 'react';
// ✅ 状態の“意味付け”と“更新ルール”をまとめる reducer(Transferの役割)
function todoReducer(todos: string[], action: { type: string; payload?: string }): string[] {
switch (action.type) {
case 'ADD_TODO':
// 空文字は追加しない(意味のチェック)
if (!action.payload || action.payload.trim() === '') return todos;
return [...todos, action.payload.trim()];
default:
return todos;
}
}
function App() {
// ✅ 入力用の一時状態(入力Source)
const [text, setText] = useState('');
// ✅ useReducerでTodoリストの状態と更新ロジックを保持(意味のある状態)
const [todos, dispatch] = useReducer(todoReducer, []);
// ✅ UIから「追加してほしい」という命令だけを送る(判断は外へ)
const handleAdd = () => {
dispatch({ type: 'ADD_TODO', payload: text });
setText('');
};
return (
<div style={{ padding: '2rem' }}>
<h1>Todoアプリ</h1>
<div style={{ display: 'flex', gap: '0.5rem' }}>
{/* ✅ ユーザー入力(Source) */}
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="やることを入力"
/>
{/* ✅ 命令ボタン(出力ではなく、入力の起点) */}
<button onClick={handleAdd} disabled={!text.trim()}>
追加
</button>
</div>
{/* ✅ Todo一覧の表示(Sink:出力専用) */}
<ul>
{todos.map((todo, idx) => (
<li key={idx}>
{/* ✅ “意味の確定”された状態の出力。判断や加工なし */}
{todo}
</li>
))}
</ul>
</div>
);
}
export default App;
構造を“言葉で整理”しただけで、AIの出力も変わった
useReducer によって、若手は「状態の変化ルール」をUIの外に出し、
責任の整理という感覚を少しずつ掴み始めていた。
それでも、どこか“ピースが足りない”感覚が残っていた。
あるとき、若手はふと考えはじめた。
若手:「UIで受け取って、reducerで変化を決めて、UIで表示する……。
あれ、これって──入力、判断、出力……?」
頭の中にある構造を、紙に書き出してみる。
入力、変換、出力――
気づけば、**処理の流れではなく、“意味の流れ”**が整理されていた。
若手:「……この構造って、名前つけるとしたら……?」
師匠:「それが、構造化設計でいう“STS設計”ってやつだよ」
若手は驚き、少し戸惑いながら返す。
若手:「STS……確かそれって、昔のCOBOLとか、古い基幹系で使われてた設計ですよね?
今のオブジェクト指向とか、Reactとかのフレームワークには、もう合わないんじゃ……」
師匠はすぐに答えた。静かに、でも力強く。
師匠:「何も変わらないんだよ。
組み込みだろうが、WEB・スマホだろうが──。
設計の本質は、時代がどんなに進んでも、変わらないんだよ」
若手は、思わず言葉を失った。
設計とは“ツールの話”ではなく、“考え方”の話だったのだ。
気を取り直し、若手は AI にこう投げてみた。
「UIは表示専用。状態は“入力→変換→出力”に責任を分けて」
返ってきたコードは、以前とまるで違っていた。
責任が整理され、構造が見えていた。
なにより、「意味の流れ」がそのままコードに表れていた。
層 | 役割 | 具体例 |
---|---|---|
Source | 入力・指示 | input, button, handleAdd |
Transfer | 状態の変化ルールや判断 | todoReducer + dispatch |
Sink | 表示専用 | TodoList(propsで表示するだけ) |
若手:「……構造を言葉にしただけで、AIのコードって、こんなに変わるんだ……。
これが本来の設計なんですね。理解できた気がします」
師匠は一拍置いてから、静かに言った。
師匠:「何言ってんの。まだ“設計の形”にはなってないよ」
若手:「えっ……?」
師匠:「Appメソッドの中、見てごらん。ひと目で“どこが何を担当してるか”見えないだろ。
このコードだと、画面を上下にスクロールで何度も往復しないと、全体の意味がつかめないじゃないか。
なんでそうなるかっていうと、責任がごちゃごちゃに混ざってるからなんだよ。
他の人が見て理解しやすい構造ってのはな、“責任を分けて、ひと目で理解できる形”になって、初めて意味があるんだよ」
若手は、黙ってコードを見つめ直す。
意味は分けた。責任も整理した。でも──それが“見える形”になっていない。
そこにまだ、“読める設計”の壁があった。
変化に強い構造とは、“責任を並べて見える形”だった
責任を分けて、意味を整理して、useReducerも導入した。
でも――師匠の言葉で、若手は気づいた。
「構造」は、“分けただけ”では見えない。
“人に見える配置”になって初めて意味を持つ。
若手は、コードを整理し直した。
Appの中で「入力」「状態の意味付け」「表示」の責任を横に並ぶ形で再配置した。
・入力(InputForm)
・変換(todoReducer)
・出力(TodoList)
import { useReducer, useState } from 'react';
import TodoList from './TodoList'; // Sink(出力)コンポーネント
// Transfer:状態の意味付けと変化ルールを一箇所に集約
function todoReducer(todos: string[], action: { type: string; payload?: string }): string[] {
switch (action.type) {
case 'ADD_TODO':
if (!action.payload || action.payload.trim() === '') return todos;
return [...todos, action.payload.trim()];
default:
return todos;
}
}
function App() {
// Source:ユーザーの入力状態
const [text, setText] = useState('');
const [todos, dispatch] = useReducer(todoReducer, []);
const handleAdd = () => {
dispatch({ type: 'ADD_TODO', payload: text });
setText('');
};
return (
<div style={{ padding: '2rem' }}>
<h1>Todoアプリ</h1>
<div style={{ display: 'flex', gap: '0.5rem' }}>
{/* Source:入力 */}
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="やることを入力"
/>
<button onClick={handleAdd} disabled={!text.trim()}>
追加
</button>
</div>
{/* Sink:出力専用。propsで受け取り表示するだけ */}
<TodoList todos={todos} />
</div>
);
}
export default App;
若手:「……これなら、コードを読んだだけで“どういう構造か”が見えてきます」
師匠:「そう。“責任がどこにあるか”がひと目で分かれば、あとから誰が読んでも怖くないんだよ」
実際、若手は自分で追加機能のイメージをしてみた。
どこに何を書けばいいかが、すぐに思い浮かぶ。
修正も追加も“迷いがない”構造になっていた。
若手:「……やっと、“壊れにくいコード”の正体が分かってきました」
構造は、「あとから読める」「意味が伝わる」ためのもの。
それが、変化に強い=育てられる設計の土台だった。
次回の記事予告
ここまでお読みいただき、ありがとうございました。
本記事では、AIを使って開発を進める中で直面した「設計不在」の怖さと、それを乗り越えるために必要な“伝わる設計”の視点について紹介しました。
次回は、師匠から教わった設計の基本――「STS設計(入力・変換・出力)」というシンプルで強力な考え方を軸に、実際にReactアプリの構造を改善していくプロセスを紹介します。
もし、AIの出力をそのまま使ってコードが混乱してきた方、自分の設計に不安を感じている方には、きっと役立つ内容になるはずです。ぜひご期待ください!
また、今回紹介した内容をより実践的に学びたい方には、以下のUdemy講座もおすすめです。
Udemyコース(8,800円 → クーポンで割引中)
▶️ AIとC#で極める!クリーンコードの技法(限定クーポン付き)
- C#でクリーンコードと設計力を身につける実践講座
- ChatGPTの活用方法や、伝わるコードの考え方を解説
出版書籍『あきらめない者たち』
▶️ Amazonで見る
- 技術の基礎からやり直すために、なぜ一歩勇気を振り絞れたのかのノンフィクション作品です。