3秒でわかる
1つの部品に仕事を持たせすぎず、変更理由ごとに切り分ける設計の考え方。直す場所が1か所に定まり、影響範囲が読める状態を作ります。
もう少し詳しく
どういうものか
責務分割は、1つの関数やクラスやコンポーネントが引き受ける仕事を1種類に絞る設計方針です。判断の基準は行数ではなく「変更を求めてくる相手が誰か」です。見た目の修正を頼むのはデザイナー、税率の変更を頼むのは経理、という具合に依頼元が違うなら、その2つは別の部品に置きます。
なぜ必要か
仕事が混ざった部品は、片方の都合で直したときにもう片方が壊れます。表示と計算が同じ関数にあると、ボタンの色を変える修正で合計金額のテストが落ちる、という事態が起きます。分けてあれば、変更の影響がその部品の中で閉じ、レビューで確認すべき範囲もはっきりします。テストも、画面を描かずに計算だけ検証できます。
具体例
React で、合計計算とデータ取得と表示が1つに詰まった状態から切り出す例です。
// 分ける前 — 取得も計算も表示も1つのコンポーネントの中
function Cart() {
const [items, setItems] = useState([]);
useEffect(() => { <a href="/glossary/fetch-api" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">fetch</a>("/api/cart").then(r => r.json()).then(setItems); }, []);
const total = items.reduce((s, i) => s + i.price * i.qty, 0);
return <p>合計 {Math.floor(total * 1.1)} 円</p>;
}
// 分けた後 — 計算は純粋関数、取得はフック、表示はコンポーネント
export function totalWithTax(items) {
const sum = items.reduce((s, i) => s + i.price * i.qty, 0);
return Math.floor(sum * 1.1);
}
function useCart() {
const [items, setItems] = useState([]);
useEffect(() => { fetch("/api/cart").then(r => r.json()).then(setItems); }, []);
return items;
}
function Cart() {
const items = useCart();
return <p>合計 {totalWithTax(items)} 円</p>;
}totalWithTax はブラウザなしでテストでき、税率が変わってもここだけ直せば済みます。
つまずきやすいところ
分けること自体が目的になり、1関数につき1ファイルまで刻んでしまう例が多く見られます。呼び出しを追うのに5ファイル開く状態は、混ざっているのと同じくらい読みにくくなります。同時に直す場所は同じところに置くのが原則です。
もう1つは、名前で分けたつもりになるパターンです。utils.js に何でも入れると、名前は分かれていても中身は雑多なままで、変更理由による分割にはなっていません。
覚え方
「この部品を直してほしいと言ってくる人は誰か」を数えます。2種類以上いるなら、まだ混ざっています。