4日目のゴールと今日のテーマ
4日目のテーマは
「“とりあえず動く業務アプリ”から、“壊れにくい業務アプリ”に育てる設計」です。
同じログイン/一覧 → 詳細 → 編集/権限制御でも、
今日は特に
- 入力チェック(バリデーション)をどこに書くか
- エラーメッセージをどこで決めるか
- 「想定外の状態」をどう扱うか
を、設計力・保守性・実務思考の観点から深掘りします。
コードを書くというより、
「この if はどのクラスに属するべきか?」を徹底的に考える日です。
バリデーションは「画面の都合」ではなく「ドメインのルール」
ありがちな“全部画面に書く”パターン
例えば、編集画面で名前とメールを入力させるとします。
よくあるのは、編集画面側でこう書くパターンです。
if (name === "") {
alert("名前は必須です");
return;
}
if (!email.includes("@")) {
alert("メールアドレスの形式が不正です");
return;
}
JavaScript一見普通ですが、これをやると
- 別の画面から顧客を作るときに、同じチェックをコピペする
- 仕様変更(「名前は2文字以上」など)が入ると、全部探して直す
- どこか1か所だけ直し忘れてバグになる
という“業務アプリあるある地獄”に入ります。
ここで大事なのは、
「名前やメールのルールは“顧客という概念”の一部」
という視点です。
だから、本来は Customer クラス側に閉じ込めるべきです。
Customer に「ルール」を閉じ込める設計
バリデーション専用メソッドを持たせる
Customer クラスを、少し“賢く”します。
class Customer {
#id;
#name;
#email;
constructor(id, name, email) {
const result = Customer.validate(name, email);
if (!result.ok) {
throw new Error("不正な顧客データ: " + result.message);
}
this.#id = id;
this.#name = name;
this.#email = email;
}
static validate(name, email) {
if (!name || name.trim() === "") {
return { ok: false, message: "名前は必須です" };
}
if (name.trim().length < 2) {
return { ok: false, message: "名前は2文字以上で入力してください" };
}
if (!email || email.trim() === "") {
return { ok: false, message: "メールアドレスは必須です" };
}
if (!email.includes("@")) {
return { ok: false, message: "メールアドレスの形式が不正です" };
}
return { ok: true, message: "" };
}
update(name, email) {
const result = Customer.validate(name, email);
if (!result.ok) {
return result;
}
this.#name = name;
this.#email = email;
return { ok: true, message: "" };
}
getId() {
return this.#id;
}
getName() {
return this.#name;
}
getEmail() {
return this.#email;
}
}
JavaScriptここでの重要ポイントは、はっきりこうです。
- 「何が有効な名前・メールか」というルールは Customer の責任
- 画面側はルールの中身を知らなくてよく、結果(ok / message)だけ見ればいい
- ルールが変わっても Customer だけ直せばよい
これが、設計力と保守性が効いている状態です。
エラーメッセージは「どこで決めるか」を分けて考える
ルールのメッセージと、UI のメッセージは別物
さっきの Customer.validate は、
すでに message を返しています。
では、画面側ではどう使うのが自然でしょうか。
#saveCustomer(id, newName, newEmail) {
const user = this.#auth.getCurrentUser();
if (!Permission.canEdit(user)) {
alert("編集権限がありません");
this.#showDetail(id);
return;
}
const customer = this.#customerRepo.findById(id);
if (!customer) {
alert("対象の顧客が見つかりません");
this.#showList();
return;
}
const result = customer.update(newName, newEmail);
if (!result.ok) {
alert(result.message);
return;
}
this.#customerRepo.save(customer);
this.#showDetail(id);
}
JavaScriptここでの分担はこうなります。
- 「何がダメか」を決めるのは Customer(ドメイン)
- 「どうユーザーに伝えるか」(alert にするか、画面に表示するか)は Controller(UI)
つまり、
ルールのメッセージ(ドメインの言葉)
と
表示の仕方(UI の都合)
を分けている、ということです。
実務では、
「エラーメッセージを画面の下に出したい」
「英語版も欲しい」
などの要求が出てきます。
そのとき、
ルールと UI が混ざっていると地獄を見ます。
分かれていれば、UI 側だけ差し替えれば済みます。
「想定外の状態」をどう扱うかも設計の一部
ありえないはずのケースは、本当にありえないか?
例えば、詳細画面でこう書いていたとします。
const customer = this.#customerRepo.findById(id);
console.log(customer.getName());
JavaScript「一覧から来ているんだから、必ず存在するでしょ」
と油断すると、実務では普通に事故ります。
- 別タブで開いている間にデータが削除された
- データ移行で ID が変わった
- URL を直接叩かれた
など、「ありえないと思っていた状態」は
現場では普通に起きます。
だからこそ、Controller では必ずこう書きます。
const customer = this.#customerRepo.findById(id);
if (!customer) {
alert("対象のデータが見つかりません");
this.#showList();
return;
}
JavaScriptここでの設計ポイントは、
- 「存在しない ID を渡されたら null を返す」のは Repository の責任
- 「そのときどう振る舞うか」を決めるのは Controller の責任
という分担を守っていることです。
権限制御を「仕様変更に強い形」にする
Permission クラスを“if の墓場”にしない
Permission をこう書いていたとします。
class Permission {
static canViewList(user) {
return !!user;
}
static canViewDetail(user) {
return !!user;
}
static canEdit(user) {
if (!user) return false;
return user.isAdmin();
}
}
JavaScriptここまではシンプルですが、
実務ではすぐにこうなります。
- 「staff も一部編集できるようにしてほしい」
- 「特定の顧客だけ編集禁止にしたい」
- 「自分が担当の顧客だけ編集可にしたい」
このとき、
Permission の中身をどう育てるかが“設計力”です。
例えば、こういう方向性があります。
class Permission {
static canEditCustomer(user, customer) {
if (!user) return false;
if (user.isAdmin()) return true;
if (user.isStaff()) {
// 例えば「担当者IDが一致する顧客だけ編集可」など
return customer.getId() % 2 === 0; // 擬似ルール
}
return false;
}
}
JavaScriptController 側はこう変わります。
const customer = this.#customerRepo.findById(id);
if (!customer) {
alert("対象の顧客が見つかりません");
this.#showList();
return;
}
if (!Permission.canEditCustomer(user, customer)) {
alert("この顧客を編集する権限がありません");
this.#showDetail(id);
return;
}
JavaScriptここでの大事なポイントは、
- 権限ロジックは Permission に集約されている
- Controller は「誰が」「どの顧客に対して」操作するかを渡すだけ
- 仕様変更が来ても Permission を中心に直せばよい
という構造になっていることです。
「実務思考」とは“未来の自分を楽させる思考”
今だけ動けばいいコードと、育てられるコード
4日目まで来ると、
すでに「動くミニ業務アプリ」は作れています。
でも、実務思考で一番大事なのは、
「このアプリに、1年後・2年後に機能追加が来たとき、
自分は泣かずに済むか?」
という視点です。
今日やったことは、全部そこにつながっています。
- バリデーションを Customer に閉じ込める
→ 入力ルールが変わっても、1か所直せばいい - エラーメッセージの“ルール”と“表示方法”を分ける
→ UI を変えても、ドメインのルールはそのまま - 「存在しないデータ」「権限不足」を Controller で必ず扱う
→ 想定外の状態でもアプリが落ちない - 権限ロジックを Permission に集約する
→ 仕様変更が来ても if 文を全画面から探さなくていい
これが、
設計力・保守性・実務思考が
一つにつながった状態です。
4日目の締め:if 文の“居場所”を意識する
最後に、今日の一番大事な問いを一つだけ残します。
「この if は、本当はどのクラスの事情を見ている?」
名前やメールのルールを見ているなら → Customer
顧客の集まりを見ているなら → Repository
ユーザーの権限を見ているなら → User / Permission
画面の見せ方を見ているなら → Controller / UI
この問いを癖にしておくと、
if 文が迷子にならなくなります。
それがそのまま、
“壊れにくい業務アプリ”を作る力になります。


