5日目のゴールと今日のテーマ
5日目のテーマは
「“なんとなく動く設計”から、“変更に強い設計”へ一段引き上げること」です。
ログイン画面
一覧 → 詳細 → 編集
権限制御(擬似)
ここまでで、ひと通りの流れは作れている前提で、今日は
設計力:クラスの責任の境界をもう一段クリアにする
保守性:変更が来たときに“直す場所”がすぐ分かる構造にする
実務思考:現場で本当に起きる「仕様変更」を想定して設計を見直す
ここをじっくりやります。
「仕様変更が来た」と仮定して設計をチェックする
想定する変更 1:ログイン方法が変わる
例えば、こういう変更が来たとします。
ユーザーID+パスワードではなく、
メールアドレス+パスワードでログインしたい。
将来的には「外部サービスログイン(Google など)」も検討したい。
このとき、今の設計で「どこを直すか」がすぐ言えれば、
設計はかなり良い状態です。
Auth クラスの login の中身
User の持つ情報(id, name, role に加えて email)
ログイン画面の入力欄(userId → email)
AppController の attemptLogin の引数
逆に言うと、
ここ以外は触りたくないし、触らなくて済むべきです。
もし画面のあちこちで
auth.login(userId, password);
user.getId() を前提にした処理
が散らばっているなら、
それは「設計力不足のサイン」です。
Auth を「認証の窓口」として育てる
認証方法を増やせる形にしておく
今はこうだとします。
class Auth {
#currentUser;
constructor() {
this.#currentUser = null;
}
login(userId, password) {
if (userId === "admin" && password === "pass") {
this.#currentUser = new User(1, "管理者", "admin");
return true;
}
if (userId === "staff" && password === "pass") {
this.#currentUser = new User(2, "一般社員", "staff");
return true;
}
return false;
}
logout() {
this.#currentUser = null;
}
isLoggedIn() {
return this.#currentUser !== null;
}
getCurrentUser() {
return this.#currentUser;
}
}
JavaScriptこれを、将来の拡張を見据えて少し整理します。
ポイントは、
「認証の方法」と
「ログイン状態の保持」を
分けて考えることです。
class Auth {
#currentUser;
constructor() {
this.#currentUser = null;
}
loginWithIdAndPassword(userId, password) {
const user = AuthUserStore.findByIdAndPassword(userId, password);
if (!user) return false;
this.#currentUser = user;
return true;
}
loginWithEmailAndPassword(email, password) {
const user = AuthUserStore.findByEmailAndPassword(email, password);
if (!user) return false;
this.#currentUser = user;
return true;
}
logout() {
this.#currentUser = null;
}
isLoggedIn() {
return this.#currentUser !== null;
}
getCurrentUser() {
return this.#currentUser;
}
}
JavaScriptここで AuthUserStore は、
「認証用のユーザー情報を探す役割」を持つクラス(あるいはモジュール)です。
重要なのは、
Auth は「どう探すか」を知らない
AuthUserStore は「ログイン状態」を知らない
という分離ができていることです。
これにより、
認証方法を増やしたい → AuthUserStore を拡張
ログイン状態の扱いを変えたい → Auth を修正
と、直す場所がはっきり分かれます。
「一覧の仕様変更」に耐えられる設計かをチェックする
想定する変更 2:一覧にフィルタとソートを追加したい
例えば、こういう要求が来たとします。
名前で検索したい
メールアドレスで絞り込みたい
名前順・作成日順でソートしたい
このとき、
CustomerRepository と AppController の責任分担が
きちんとできているかが問われます。
CustomerRepository は
「データの集まりに対する操作」を担当します。
class CustomerRepository {
#customers;
constructor() {
this.#customers = [
// 省略
];
}
findAll() {
return this.#customers;
}
findById(id) {
return this.#customers.find(c => c.getId() === id) || null;
}
searchByName(keyword) {
const k = keyword.trim();
if (k === "") return this.#customers;
return this.#customers.filter(c => c.getName().includes(k));
}
// ソートはどうするか?
}
JavaScriptここで設計の分かれ道があります。
ソートのロジックを Repository に入れるか、
Controller に入れるか。
実務思考で考えると、
「ソートの基準が“データの意味”に関わるなら Repository」
「ソートの基準が“画面の都合”なら Controller」
という分け方がしっくりきます。
例えば、
作成日が新しい順 → データの意味に関わる
画面の列をクリックした順 → UI の都合
なので、
作成日順の取得は Repository に
「今どの列でソートしているか」は Controller に
という分担が自然です。
一覧のフィルタとソートを Controller から使う
Controller 側の責務を整理する
AppController は、
「ユーザーの操作に応じて、どの Repository メソッドを使うか」を決めます。
例えば、名前検索とソートを組み合わせるとします。
class AppController {
// 省略
#searchInput;
#sortSelect;
constructor(dom) {
// 省略
this.#searchInput = dom.searchInput;
this.#sortSelect = dom.sortSelect;
this.#setupEvents();
}
#setupEvents() {
this.#searchInput.addEventListener("input", () => {
this.#renderCustomerTable();
});
this.#sortSelect.addEventListener("change", () => {
this.#renderCustomerTable();
});
}
#renderCustomerTable() {
this.#customerTable.innerHTML = "";
const keyword = this.#searchInput.value;
const sortKey = this.#sortSelect.value;
let customers = this.#customerRepo.searchByName(keyword);
if (sortKey === "name") {
customers = [...customers].sort((a, b) =>
a.getName().localeCompare(b.getName())
);
}
customers.forEach(customer => {
// 行を作って追加(前回と同じ)
});
}
}
JavaScriptここでの責務分離はこうです。
検索(どの顧客が対象か) → Repository
ソート(どの順番で表示するか) → Controller(UI の都合)
この線引きができていると、
「検索条件が増えた」「ソート条件が増えた」ときに
直す場所が迷子になりません。
「権限の仕様変更」に耐えられる設計かをチェックする
想定する変更 3:権限ルールが細かくなる
例えば、こういう変更が来たとします。
admin:すべての顧客を編集可能
staff:自分が担当の顧客だけ編集可能
guest:一覧は見られるが詳細は見られない
このとき、Permission と User の設計が試されます。
User に「担当顧客IDのリスト」を持たせるか?
Customer に「担当者ID」を持たせるか?
Permission は「誰と何の組み合わせ」を見て判断するか?
例えば、こういう形が考えられます。
class User {
#id;
#name;
#role;
#assignedCustomerIds;
constructor(id, name, role, assignedCustomerIds = []) {
this.#id = id;
this.#name = name;
this.#role = role;
this.#assignedCustomerIds = assignedCustomerIds;
}
isAdmin() {
return this.#role === "admin";
}
isStaff() {
return this.#role === "staff";
}
isAssignedTo(customer) {
return this.#assignedCustomerIds.includes(customer.getId());
}
}
JavaScriptPermission はこうなります。
class Permission {
static canViewList(user) {
return !!user;
}
static canViewDetail(user, customer) {
if (!user) return false;
if (user.isAdmin()) return true;
if (user.isStaff()) return user.isAssignedTo(customer);
return false;
}
static canEditCustomer(user, customer) {
if (!user) return false;
if (user.isAdmin()) return true;
if (user.isStaff()) return user.isAssignedTo(customer);
return false;
}
}
JavaScriptController 側は、
「誰が」「どの顧客に対して」操作しようとしているかを渡すだけです。
const user = this.#auth.getCurrentUser();
const customer = this.#customerRepo.findById(id);
if (!Permission.canViewDetail(user, customer)) {
alert("この顧客の詳細を見る権限がありません");
this.#showList();
return;
}
JavaScriptここでの設計のキモは、
権限ロジックは Permission に集約
User は「自分に関する情報」を知っている
Customer は「自分に関する情報」を知っている
Controller は「誰が」「何に対して」操作するかをつなぐだけ
という構造になっていることです。
実務思考:テストしやすい設計になっているか?
「ブラウザを開かなくても確認できる部分」を増やす
実務で効く設計は、
「ブラウザを開かなくてもロジックをテストしやすい」
という特徴を持っています。
例えば、Permission のテストは
コンソールだけでできます。
const admin = new User(1, "管理者", "admin");
const staff = new User(2, "社員", "staff", [1, 3]);
const guest = null;
const c1 = new Customer(1, "A社", "a@example.com");
const c2 = new Customer(2, "B社", "b@example.com");
console.log(Permission.canEditCustomer(admin, c1)); // true
console.log(Permission.canEditCustomer(staff, c1)); // true
console.log(Permission.canEditCustomer(staff, c2)); // false
console.log(Permission.canEditCustomer(guest, c1)); // false
JavaScriptAuth のテストも同様です。
Customer のバリデーションも、
ブラウザ抜きで試せます。
これは全部、
ドメイン層と UI 層をきれいに分けているからこそできることです。
テストしやすい設計は、
そのまま「保守しやすい設計」です。
5日目でいちばん深く持ってほしい感覚
今日の本質は、
「仕様変更を仮定して設計を見直す」ことです。
ログイン方法が変わったら、どこを直す?
一覧に検索・ソートが増えたら、どこを直す?
権限ルールが細かくなったら、どこを直す?
この問いに対して、
ここ、とここだけ
他は触らなくていい
と自信を持って言える構造になっていれば、
設計力・保守性・実務思考はちゃんと噛み合っています。
もし余裕があれば、
ログイン方法を「メール+パスワード」に変える
一覧に「名前検索」を実装する
staff の権限ルールを自分なりに変えてみる
などを、
「どのクラスの責任か?」を意識しながら
実際に手を動かしてみてください。
その“変更してみる体験”が、
設計を本当に自分のものにしてくれます。


