レビューチェックリスト
ストーリー
「マインドセットは分かったが、実際に何を見ればいいか分からない...... という状態だろう?」
松本先輩がチェックリストの書かれた資料を渡してくれた。
「レビューは『感覚』でやるものじゃない。 体系的なチェックリストに沿って確認することで、 見落としを防ぎ、一貫した品質を保てるんだ」
「このチェックリストを使えば、経験が浅くても 効果的なレビューができるようになる。一つずつ見ていこう」
レビューチェックリストの全体像
コードレビューでは、以下の7つの観点で確認を行います。
┌──────────────────────────────────────────┐
│ レビュー7つの観点 │
├──────────────────────────────────────────┤
│ 1. 正確性(Correctness) │
│ 2. 設計(Design) │
│ 3. 可読性(Readability) │
│ 4. セキュリティ(Security) │
│ 5. パフォーマンス(Performance) │
│ 6. テスト(Testing) │
│ 7. ドキュメント(Documentation) │
└──────────────────────────────────────────┘
1. 正確性(Correctness)
コードが意図した通りに動作するか確認します。
チェック項目
- 要件を正しく満たしているか
- エッジケースは考慮されているか
- エラーハンドリングは適切か
- null / undefined のチェックはあるか
例:エッジケースの見落とし
typescript
// レビュー対象のコード
function calculateDiscount(price: number, discountRate: number): number {
return price * (1 - discountRate);
}
// レビュー コメント:
// ⚠️ [Must Fix] エッジケースが考慮されていません
// - price が負の値の場合はどうなりますか?
// - discountRate が 0〜1 の範囲外の場合は?
// - discountRate が 1 を超えるとマイナスの価格になります
// 改善案
function calculateDiscount(price: number, discountRate: number): number {
if (price < 0) {
throw new Error('Price must be non-negative');
}
if (discountRate < 0 || discountRate > 1) {
throw new Error('Discount rate must be between 0 and 1');
}
return Math.round(price * (1 - discountRate));
}2. 設計(Design)
コードの構造と設計の妥当性を確認します。
チェック項目
- 単一責任の原則(SRP)に従っているか
- 適切な抽象化がされているか
- 既存のコードベースと一貫性があるか
- 過度な複雑さはないか(YAGNI)
例:責務の分離
typescript
// レビュー対象のコード
class UserService {
async createUser(data: CreateUserDto) {
// バリデーション
if (!data.email.includes('@')) throw new Error('Invalid email');
if (data.password.length < 8) throw new Error('Password too short');
// DBに保存
const user = await this.db.insert('users', data);
// メール送信
const transporter = nodemailer.createTransport({ /* ... */ });
await transporter.sendMail({
to: data.email,
subject: 'Welcome!',
html: '<h1>Welcome to our service!</h1>',
});
// ログ出力
console.log(`User created: ${user.id}`);
return user;
}
}
// レビューコメント:
// ⚠️ [Should Fix] UserService がバリデーション、DB操作、
// メール送信、ログ出力の全てを担当しています。
// 単一責任の原則に基づいて分離することを提案します。
// 改善案
class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly emailService: EmailService,
private readonly logger: Logger,
) {}
async createUser(data: CreateUserDto): Promise<User> {
const email = Email.create(data.email); // Value Object でバリデーション
const password = Password.create(data.password);
const user = await this.userRepository.save(
User.create({ email, password })
);
await this.emailService.sendWelcome(user.email);
this.logger.info(`User created: ${user.id}`);
return user;
}
}3. 可読性(Readability)
コードが他の開発者にとって理解しやすいか確認します。
チェック項目
- 変数名・関数名は意図を表しているか
- ネストが深すぎないか(最大3階層が目安)
- マジックナンバーはないか
- 適切なコメントがあるか
例:命名とネスト
typescript
// レビュー対象のコード
function proc(d: any[]) {
const r: any[] = [];
for (let i = 0; i < d.length; i++) {
if (d[i].s === 1) {
if (d[i].a > 18) {
if (d[i].t === 'premium') {
r.push(d[i]);
}
}
}
}
return r;
}
// レビューコメント:
// ⚠️ [Should Fix] 命名とネストの改善を提案します
// - 関数名・変数名が省略されすぎて意味が不明です
// - ネストが4階層と深く、読みにくくなっています
// - any 型は避けてください
// 改善案
interface User {
status: number;
age: number;
tier: string;
}
function filterActivePremiumAdults(users: User[]): User[] {
return users.filter(user =>
user.status === UserStatus.ACTIVE &&
user.age > MINIMUM_ADULT_AGE &&
user.tier === 'premium'
);
}
const UserStatus = { ACTIVE: 1, INACTIVE: 0 } as const;
const MINIMUM_ADULT_AGE = 18;4. セキュリティ(Security)
セキュリティ上の問題がないか確認します。
チェック項目
- SQLインジェクションの可能性はないか
- XSS(クロスサイトスクリプティング)のリスクはないか
- 認証・認可のチェックは適切か
- 機密情報がハードコードされていないか
- 入力値のサニタイズは行われているか
例:SQLインジェクション
typescript
// レビュー対象のコード
async function findUser(name: string) {
const query = `SELECT * FROM users WHERE name = '${name}'`;
return await db.execute(query);
}
// レビューコメント:
// 🚨 [Must Fix] SQLインジェクションの脆弱性があります
// ユーザー入力を直接クエリに結合しています。
// name に "'; DROP TABLE users; --" が渡されると
// テーブルが削除されます。
// 改善案: パラメータ化クエリを使用
async function findUser(name: string) {
const query = 'SELECT * FROM users WHERE name = $1';
return await db.execute(query, [name]);
}
// さらに良い: ORMを使用
async function findUser(name: string) {
return await userRepository.findOne({ where: { name } });
}5. パフォーマンス(Performance)
パフォーマンスに問題がないか確認します。
チェック項目
- N+1クエリ問題はないか
- 不要なループや計算はないか
- 大量データの処理は考慮されているか
- 適切なインデックスが利用されているか
例:N+1クエリ問題
typescript
// レビュー対象のコード
async function getOrdersWithProducts() {
const orders = await orderRepository.find(); // 1回のクエリ
for (const order of orders) {
order.products = await productRepository.findByOrderId(order.id); // N回のクエリ
}
return orders;
}
// → 注文が100件あれば、101回のDBクエリが発生する
// レビューコメント:
// ⚠️ [Should Fix] N+1クエリ問題が発生しています。
// 注文数に比例してクエリ数が増加し、パフォーマンスが劣化します。
// 改善案: JOINまたはEager Loading を使用
async function getOrdersWithProducts() {
return await orderRepository.find({
relations: ['products'], // Eager Loading で1回のクエリに
});
}6. テスト(Testing)
テストが十分に書かれているか確認します。
チェック項目
- 新しいコードに対応するテストがあるか
- 正常系・異常系の両方がテストされているか
- テストが独立して実行できるか
- テスト名が意図を表しているか
例:テスト不足
typescript
// レビュー対象のテスト
describe('calculateDiscount', () => {
it('should calculate discount', () => {
expect(calculateDiscount(100, 0.1)).toBe(90);
});
});
// レビューコメント:
// ⚠️ [Should Fix] テストケースが不足しています。
// 以下の追加をお願いします:
// - 割引率 0% のケース
// - 割引率 100% のケース
// - 境界値(price = 0)
// - 異常値(負の価格、範囲外の割引率)
// 改善案
describe('calculateDiscount', () => {
it('should apply 10% discount', () => {
expect(calculateDiscount(100, 0.1)).toBe(90);
});
it('should return original price with 0% discount', () => {
expect(calculateDiscount(100, 0)).toBe(100);
});
it('should return 0 with 100% discount', () => {
expect(calculateDiscount(100, 1)).toBe(0);
});
it('should handle zero price', () => {
expect(calculateDiscount(0, 0.5)).toBe(0);
});
it('should throw for negative price', () => {
expect(() => calculateDiscount(-100, 0.1)).toThrow();
});
it('should throw for invalid discount rate', () => {
expect(() => calculateDiscount(100, 1.5)).toThrow();
expect(() => calculateDiscount(100, -0.1)).toThrow();
});
});7. ドキュメント(Documentation)
必要なドキュメントが更新されているか確認します。
チェック項目
- 公開APIのドキュメントは更新されているか
- 複雑なロジックにコメントがあるか
- READMEの更新が必要ではないか
- 変更履歴(CHANGELOG)は更新されているか
レビューの進め方
推奨の確認順序
1. PRの説明文を読む(なぜこの変更か理解する)
↓
2. 変更ファイルの一覧を確認(全体像を把握する)
↓
3. テストを最初に読む(仕様を理解する)
↓
4. 実装コードを確認(正確性・設計・可読性)
↓
5. セキュリティ・パフォーマンスを確認
↓
6. ドキュメントの更新を確認
まとめ
| 観点 | 重要度 | 主なチェック項目 |
|---|---|---|
| 正確性 | 最高 | バグ、エッジケース、エラーハンドリング |
| 設計 | 高 | SRP、抽象化、一貫性 |
| 可読性 | 高 | 命名、ネスト、マジックナンバー |
| セキュリティ | 最高 | SQLi、XSS、認証認可、機密情報 |
| パフォーマンス | 中 | N+1、不要なループ、インデックス |
| テスト | 高 | カバレッジ、正常系・異常系 |
| ドキュメント | 中 | API docs、コメント、README |
チェックリスト
- レビューの7つの観点を列挙できる
- 正確性の観点でエッジケースを指摘できる
- 設計の観点でSRP違反を検出できる
- セキュリティの観点でSQLインジェクションを検出できる
- テストの観点でテスト不足を指摘できる
- レビューの推奨確認順序を説明できる
次のステップへ
チェックリストを手に入れたら、次はレビューコメントの書き方を学びます。相手に伝わる、建設的なフィードバックの方法を見ていきましょう。
推定読了時間: 25分