ウェブエンジニア問題集
第18章

設計レビューの観点 — 何をチェックし、どう指摘するか

12
この章の目次開く

設計レビューは、設計書の誤字を探す場ではありません。実装に入る前に、要件の抜け、責務の曖昧さ、変更に弱い構造、運用時に困る点を見つけるための場です。

レビューの質が低いと、「なんとなく良さそう」で実装が進み、後から手戻りが発生します。逆に、レビューが細かすぎたり攻撃的だったりすると、設計者が防御的になり、議論が進みません。

この章では、設計レビューで見る観点と、相手に伝わる指摘の書き方を整理します。

学習者学習者

設計レビューって、何を見ればいいのか毎回迷う。コードレビューより抽象的で、指摘していいのか自信がない…

先生先生

設計レビューは「好み」を言う場ではなく、「この設計で要求を満たせるか」「変更や障害に耐えられるか」を確認する場だよ。観点を持って見れば、指摘はかなり具体的になる。

設計レビューで見る全体像

設計レビューでは、設計書そのものだけでなく、要件・実装・運用とのつながりを見ます。

レビュー観点は、大きく分けると次の6つです。

観点確認すること
要件との整合必要な機能・非機能要件を満たしているか
責務の分離画面、API、Service、DBの役割が混ざっていないか
データ設計正規化、制約、履歴、削除方針が妥当か
例外・失敗エラー、リトライ、部分失敗、権限不足を扱えるか
変更容易性仕様変更時に触る場所が限定されるか
運用・監視ログ、メトリクス、調査方法が考えられているか
設計レビューの目的は「正解を当てること」ではなく、「実装後に高くつく手戻りを、実装前に見つけること」です。

観点1: 要件との整合を見る

まず見るべきは、設計が要件を満たしているかです。ここを飛ばして構成や命名を見ても、本質的なレビューになりません。

確認する質問は次のようなものです。

  • 機能要件に書かれたユーザー操作が、画面・API・DBのどこかに対応しているか
  • 非機能要件(性能、セキュリティ、可用性、監査ログなど)が設計に反映されているか
  • 権限ごとの差分が画面・APIの両方で考慮されているか
  • 要件にない前提を、設計側で勝手に足していないか

たとえば「管理者だけがユーザーを停止できる」という要件があるなら、画面のボタン表示だけでは不十分です。API側の認可、監査ログ、停止済みユーザーのログイン制御まで設計に現れているかを確認します。

観点2: 責務の境界を見る

設計レビューでは、責務の境界が曖昧な場所を重点的に見ます。責務の境界が曖昧だと、実装時に判断が分かれ、同じロジックが複数箇所に散らばります。

よく見る境界は次のとおりです。

境界レビューで見ること
画面とServiceUIに業務ルールが入りすぎていないか
ServiceとRepositoryDBアクセスと業務判断が混ざっていないか
フロントとバックエンド信頼できない入力をフロントだけで検証していないか
APIとDBAPI都合の形式がDB設計を歪めていないか
同期処理と非同期処理待つべき処理と後でよい処理が分けられているか

画面遷移図と画面設計 で扱ったように、UIとロジックの境界は特に崩れやすい場所です。レビューでは「この判断は画面固有か、業務ルールか」を確認します。

設計レビューで議論しているチーム
レビューでは、好き嫌いではなく境界・前提・失敗時の振る舞いをそろえる

観点3: データと状態の変化を見る

システムは、画面よりもデータの寿命が長く残ります。画面は作り直せても、DBに入ったデータは簡単には消せません。そのため、設計レビューではデータの状態変化を必ず確認します。

確認するポイントは次のとおりです。

  • ステータスの種類と遷移条件が明確か
  • 削除は物理削除か論理削除か
  • 履歴や監査ログが必要な操作はどれか
  • 重複作成や二重送信に耐えられるか
  • 外部サービスと状態がずれたときに復旧できるか
データ設計のレビューでは、「今どんな値を持つか」だけでなく、「どの操作でどの状態へ変わるか」を確認します。

原則違反のコード

注文キャンセルの条件がAPIハンドラに直書きされています。

// ❌ 状態遷移のルールがAPIに埋もれている
export async function cancelOrder(orderId: string, userId: string): Promise<void> {
  const order = await db.order.findUnique({ where: { id: orderId } });
 
  if (!order) {
    throw new Error('ORDER_NOT_FOUND');
  }
 
  if (order.userId !== userId) {
    throw new Error('FORBIDDEN');
  }
 
  if (order.status === 'shipped' || order.status === 'delivered') {
    throw new Error('CANNOT_CANCEL');
  }
 
  await db.order.update({
    where: { id: order.id },
    data: { status: 'cancelled' },
  });
}
ts

この形だと、管理画面、バッチ処理、外部連携でも同じキャンセル可否をコピーしがちです。レビューでは「状態遷移のルールはどこに集約されていますか?」と聞けます。

改善したコード

状態遷移を明示的な関数に切り出します。

type OrderStatus = 'pending' | 'paid' | 'shipped' | 'delivered' | 'cancelled';
 
type Order = {
  id: string;
  userId: string;
  status: OrderStatus;
};
 
function canCancelOrder(order: Order): boolean {
  return order.status === 'pending' || order.status === 'paid';
}
 
class OrderService {
  constructor(private readonly orders: OrderRepository) {}
 
  async cancelOrder(orderId: string, userId: string): Promise<void> {
    const order = await this.orders.findById(orderId);
 
    if (!order) {
      throw new Error('ORDER_NOT_FOUND');
    }
 
    if (order.userId !== userId) {
      throw new Error('FORBIDDEN');
    }
 
    if (!canCancelOrder(order)) {
      throw new Error('CANNOT_CANCEL');
    }
 
    await this.orders.updateStatus(order.id, 'cancelled');
  }
}
ts

状態遷移のルールが canCancelOrder に集約されるため、レビューでもテストでも確認しやすくなります。さらに厳密にするなら、ステータス遷移表を設計書に載せて、コードと対応づけます。

観点4: 失敗時の振る舞いを見る

設計レビューで差が出るのは、失敗時の確認です。正常系だけなら多くの設計はきれいに見えます。実際のシステムでは、外部APIが落ちる、DB更新の途中で失敗する、メール送信だけ失敗する、ユーザーが二重クリックする、といったことが起きます。

失敗時のレビュー質問は次のように具体化できます。

  • 外部APIがタイムアウトしたら、ユーザーには何を見せるか
  • リトライしてよい処理か、してはいけない処理か
  • 一部だけ成功した場合、どう補償するか
  • 同じリクエストが2回来た場合、同じ結果になるか
  • エラー調査に必要なログは残るか

観点5: 変更しやすさを見る

設計は、今の仕様だけに合わせて作るとすぐ壊れます。ただし、未来を予測しすぎた抽象化も危険です。レビューでは、「近い将来ありそうな変更」に対して、どこを触ることになるかを確認します。

変更シナリオを1つ置くと、設計の弱さが見えやすくなります。

変更シナリオ見ること
会員ランクが増える分岐の追加が1か所で済むか
決済手段が増える既存の注文処理を壊さず追加できるか
管理者権限が細分化される権限判定が画面ごとに散らばっていないか
監査ログが必須になる重要操作の入口が集約されているか
外部APIを差し替える具体実装への依存が閉じているか

これは SOLID原則 で扱った「変更の影響範囲を小さくする」という考え方そのものです。

学習者学習者

将来の変更に備えるって、どこまで考えればいいの?考えすぎると設計が重くなりそう。

先生先生

全部の未来に備える必要はないよ。レビューでは「この機能で現実的に起きそうな変更」を2〜3個だけ置いて、触る場所が爆発しないかを見る。それで十分実用的な判断になる。

変更容易性のレビューは、「もしこの仕様が変わったら、どのファイルを直しますか?」という質問で始めると具体的になります。

指摘の書き方

レビューの指摘は、正しくても伝え方が悪いと議論になりません。設計レビューでは、相手の人格や能力ではなく、設計上のリスクに焦点を当てます。

悪い指摘と良い指摘を比べます。

避けたい指摘伝わりやすい指摘
これは微妙ですこの設計だと、決済失敗時に注文だけ作成済みになる可能性があります
Serviceが汚いです注文可否の判定が画面とServiceに分散しているため、ルール変更時に修正漏れが起きそうです
なんでこうしたんですかこの案を選んだ前提を確認したいです。外部APIのタイムアウトは何秒想定ですか
普通はこうしません将来、支払い方法が増える予定があるなら、分岐をStrategyに寄せる方が変更範囲を小さくできます
良い指摘は「問題点」だけでなく、「起きるリスク」と「確認したい前提」まで含みます。

指摘には優先度も付けます。すべてを同じ強さで言うと、相手は何から直せばよいか分かりません。

  • must — 要件未達、セキュリティリスク、データ不整合など、直さないと実害が大きい
  • should — 変更に弱い、責務が曖昧、テストしづらいなど、直す価値が高い
  • nit — 命名や表現の改善など、品質は上がるがブロッカーではない
  • question — 前提確認。いきなり修正要求にしない

レビューを受ける側の準備

良いレビューは、レビューを出す前の準備でかなり決まります。設計者は、レビュー相手が判断できる材料を先に置いておきます。

レビュー依頼には、少なくとも次の情報を含めます。

  • 何を実現したい設計か
  • レビューしてほしい範囲
  • まだ迷っている点
  • 採用しなかった案と理由
  • 特に見てほしいリスク

設計書にすべてを書き込む必要はありませんが、前提が見えないレビューは「質問だけで終わる」ことが多くなります。

チェックしながら作業する人
レビュー依頼では、見てほしい範囲と迷っている点を先に示す

よくあるハマりどころ

好みの議論になる

「この構成が好き」「この名前は嫌い」だけだと、合意できません。好みを言いたくなったら、「その選択によって何が変わるのか」に言い換えます。変更範囲、テストしやすさ、障害時の調査しやすさなどに落とすと、議論が前に進みます。

レビュー範囲が大きすぎる

100ページの設計書を一度にレビューしても、深く見られません。画面遷移、API、DB、エラー設計のように観点ごとに分けると、指摘の質が上がります。

レビューが遅すぎる

実装直前まで設計レビューをしないと、指摘されても直せません。大きな設計は、粗い段階で一度レビューし、詳細化した後でもう一度見る方が手戻りを減らせます。

指摘を全部反映しようとする

レビューコメントはすべて正しいとは限りません。前提が違う指摘、優先度が低い指摘、今回のスコープから外れる指摘もあります。受ける側は、対応する・しない・別チケットにする、の判断を明確に返します。

ちゃんと使うためのポイント

  • 設計レビューでは、要件との整合、責務の境界、データ状態、失敗時の振る舞い、変更容易性、運用性を見る。
  • 正常系だけでなく、権限不足、外部API失敗、二重送信、部分失敗を確認する
  • 「この変更が来たらどこを触るか?」という質問で、責務分割の弱さを見つける
  • 指摘は、問題点・起きるリスク・確認したい前提をセットで書く
  • コメントの優先度を分ける。must、should、nit、question を混ぜない
  • レビュー依頼では、見てほしい範囲、迷っている点、採用しなかった案を先に示す

設計は一度書いて終わりではありません。レビューで前提をそろえ、実装で分かったことを戻し、運用で見えた課題を次の設計に反映していくものです。ここまでの章で扱った要件定義、API、DB、エラー、ディレクトリ構成、設計原則、画面遷移を、レビューの場でつなげて確認できれば、チームの設計力は着実に上がっていきます。

参考リンク