前回までの記事
新たな機能追加──今までなら、崩れてたかもしれない
「次、期日を入れてほしいって」
そう依頼された瞬間、若手は少しだけ手が止まった。
期日──ただの日付の追加に見えるが、「期限切れ表示」まで入れるとなると、一気にややこしくなる。
処理と表示の境界が曖昧になりやすい、地味に崩れやすい仕様だった。
若手:「前なら……これ追加しただけでコードぐちゃぐちゃだったかも。
期限判定とか、“いつやるべきか”のロジックが、全部UIの中に入ってたからな……」
今回は、変な不安がなかった。
どこで“判断”して、どこで“表示”すればいいか、もう🗺️頭の中に地図ができていた。
気づいたら、追加もテストもサクッと終わっていた
AIに構造化設計を使う指示して、コードを修正してみた。
トップ階層のコードのみ。階層のメソッドは省略
import { useState, useReducer } from 'react';
import OutputView from './OutputView';
import { todoReducer } from './todoReducer';
const App = () => {
// 🔵 入力:ユーザー入力(テキストと期日)
const [text, setText] = useState('');
const [dueDate, setDueDate] = useState(''); // ← 🆕 期日入力を追加
// 🟢 変換:状態と意味付け(reducer内で処理)
const [todos, dispatch] = useReducer(todoReducer, []);
const handleAdd = () => {
if (text.trim() === '' || dueDate === '') return;
dispatch({ type: 'add', payload: { text, dueDate } }); // ← 🆕 dueDateを含むpayload
setText('');
setDueDate(''); // ← 🆕 入力クリア処理
};
// 🟣 出力:UI構造(別コンポーネントに分離)
return (
<OutputView
text={text} // 🔵 入力値
onTextChange={setText} // 🔵 入力イベント
dueDate={dueDate} // 🔵 入力値(期日)← 🆕 追加
onDueDateChange={setDueDate} // 🔵 入力イベント(期日)← 🆕 追加
onAdd={handleAdd} // 🟢 意味づけと状態更新の指示
todos={todos} // 🟣 出力:変換結果(期限判断済)← 🆕 表示内容が変化
/>
);
};
export default App;
期日入力は「入力」
期限切れかどうかの判定は「変換(判断)」
表示する文言は「出力」
やるべきことが自然とこの3つに分かれていた。
しかも、それぞれの責任が**コード上でも“見える形”**になっていた。
若手は「入力」にフィールドを1つ追加し、
「変換」=reducer に“期限切れかどうか”のロジックを追加し、
「出力」は、ただ表示を分けるだけだった。
「少なくとも“期日のロジック”は reducer に集まってるから、そこだけテストすればいい。
“期日のロジック”だけみると、UIをいちいち動かさなくても、判断の正しさは確認できるって、めっちゃ楽になった」
どこに何を書くかがはっきりしているだけで、追加もテストもここまでスムーズになるとは思っていなかった。
前の書き方だったら、絶対こうはいかなかった
若手はふと思い返す。
以前のように、状態とロジックを UI にベタ書きしていたら、
こんなふうに冷静に追加なんてできなかった。
修正前コード
import { useState } from 'react';
function App() {
const [text, setText] = useState('');
const [dueDate, setDueDate] = useState(''); // 🟡 UIに状態が集中
const [todos, setTodos] = useState([]);
const handleAdd = () => {
if (text.trim() === '' || dueDate === '') return;
const today = new Date().toISOString().split('T')[0]; // 🟡 UIロジック内で期限判定
const isExpired = dueDate < today;
const newTodo = { text, dueDate, isExpired }; // 🟡 状態の意味付けもここで
setTodos([...todos, newTodo]);
setText('');
setDueDate('');
};
return (
<div style={{ padding: '2rem' }}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="やること"
/>
<input
type="date"
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
/>
<button onClick={handleAdd}>追加</button>
<ul>
{todos.map((todo, idx) => (
<li key={idx}>
{todo.text}(期限: {todo.dueDate})
{todo.isExpired && <span style={{ color: 'red' }}>期限切れ</span>}
</li>
))}
</ul>
</div>
);
}
export default App;
「text も dueDate も、useState 増やして if 文増やして……」
「前はロジックの場所がバラバラで、書くのもテストするのも全部めんどくさかった……」
「コードもどんどん長くなって、画面を上下にスクロールしないと全体が追えなくなってきてた」
「これで、さらに画面部品とロジックが増えるってなったら……想像しただけでゾッとする」
今は違う。
「少なくとも“期日のロジック”は reducer に集まってるから、そこだけテストすればいい」
「“期日のロジック”だけみると、UIをいちいち動かさなくても、判断の正しさは確認できるって、めっちゃラク……」
「コード全体はひと目で理解できるし」
ここまで迷いなく機能追加できたのは、設計が“根っこにあった”からだ。
設計が守ってくれる。“変化に振り回されない”構造とは、こういうことだった
若手は画面を閉じたあと、静かに深呼吸をした。
「正直、自分のコードを信じられたのは初めてかもしれない」
UIはそのまま。ロジックは reducer に集約されていて、
“どこに手を入れるか”がひと目でわかる。
その安心感が、作業スピードもテストのしやすさも、全部変えていた。
ただし、それは今の自分が書いたコードに限った話。
「でも──これが、他の人にも伝わる設計になってるだろうか?」
師匠のあの言葉が、心に残っていた。
師匠:「“設計の本番”は、自分以外の人と向き合ってから、なんだよ」
若手はそっとディスプレイを閉じた。
“次は、機能追加した設計がちゃんと伝わるか試してみたい”
次の挑戦に、静かに向き合おうとしていた──
次回の記事予告
ここまでお読みいただき、ありがとうございました。
本記事では、AIを使って開発を進める中で直面した「設計不在」の怖さと、それを乗り越えるために必要な“伝わる設計”の視点について紹介しました。
次回は、師匠から教わった設計の基本――「STS設計(入力・変換・出力)」というシンプルで強力な考え方を軸に、実際にReactアプリの構造を改善していくプロセスを紹介します。
もし、AIの出力をそのまま使ってコードが混乱してきた方、自分の設計に不安を感じている方には、きっと役立つ内容になるはずです。ぜひご期待ください!
また、今回紹介した内容をより実践的に学びたい方には、以下のUdemy講座もおすすめです。
Udemyコース(8,800円 → クーポンで割引中)
▶️ AIとC#で極める!クリーンコードの技法(限定クーポン付き)
- C#でクリーンコードと設計力を身につける実践講座
- ChatGPTの活用方法や、伝わるコードの考え方を解説
出版書籍『あきらめない者たち』
▶️ Amazonで見る
- 技術の基礎からやり直すために、なぜ一歩勇気を振り絞れたのかのノンフィクション作品です。