公式の /code-review は「リーダブルコード観点」をどこまで見るのか — 可読性特化スキルを自作して比較した
社内で「LLM 活用の課題」を洗い出したとき、コードレビューの品質が挙がりました。
Claude Code には Anthropic 公式の /code-review が最初から入っています。しかし、これはバグ・セキュリティ・パフォーマンスといった幅広い観点を扱う汎用性の高いレビューです。そのため、命名・コメント・制御フローといった可読性の観点をどこまで見ているのかは判然としません。
そこで、書籍『リーダブルコード』の観点に特化したレビュースキルを自作し、
同じコードに両方を当てて、どこまで差があるかを測りました。
「汎用レビューは可読性をほとんど見ないだろう」と予想していましたが、実際に比べてみると差はかなり小さいものでした。
流れを最初に置いておきます。
| 順 | やったこと |
|---|---|
| 1 | 社内の LLM 活用課題として「コードレビューの品質向上」が挙がる |
| 2 | 可読性観点に特化したレビュースキルがあってもいいのでは、と思い自作 |
| 3 | 検証したいので、検証用サンプルコードと正解キーも LLM に作らせて採点 |
| 4 | 同じ検証セットを公式 /code-review にも当て、カバー範囲を比較 ← イマココ |
すべて 1 日の中で実施した内容です。長期運用の知見ではないため、その点を踏まえて読んでください。
1. 作ったもの: readable-code-review
書籍『リーダブルコード』 の観点だけに絞ったスキルを作成しました。レビュー観点は、書籍の章立てに沿って定義しています。
- 名前に情報を詰め込む / 誤解されない名前
- コメントすべきことを知る(=コードを読めば分かることは書かない)
- 制御フローを読みやすくする / 巨大な式を分割する
- 変数のスコープと生存期間
- 無関係の下位問題を抽出する
- テストコードの読みやすさ
興味深かったのは、「何をレビューするか」よりも 「何をレビューしないか」 のほうが分量が多くなったことです。
| やらないこと | 理由 |
|---|---|
| Lint で機械検出できる整形(インデント、セミコロン、import 順) | 人間が読まないと分からないことに時間を使う |
| 意図が読めない箇所を断定する | 業務都合や外部システム制約の可能性がある。💬 質問に回す |
| 性能・バグの指摘を可読性の指摘に混ぜる | 「可読性の範囲外ですが」と別枠にする |
| 点数をつける / 勝手に全体をリファクタする | レビューの役割を超える |
| 指摘を 10 件超並べる | 上位に絞り、残りはまとめる |
LLM に自由にレビューさせると、指摘の水増しと、知らない制約への断定が起きがちです。
例えば、「1 ファイルだけインデントが乱れています」や「DENNO は slipNumber にリネームしましょう」といった指摘は、技術的には間違っていません。しかし、レビューとしての価値は低く、場合によっては有害です。
出力フォーマットも固定し、各指摘に「場所・なぜ困るか・修正後のコード・重要度」の
4 点を揃えるようにしました。
2. 検証セットも LLM に作らせた
スキルを書いたら検証したくなります。ただ、手元に都合のいい「読みにくいコード」はありません。
そこで、検証用のサンプルプロジェクトと正解キーも LLM に作ってもらいました。
なお、ここでいう「正解キー」とは、サンプルコードに意図的に仕込んだ可読性上の問題点一覧です。後でレビュー結果を採点する際の基準として使用します。
2-1. サンプルプロジェクト
「受注同期バッチ」を模した TypeScript プロジェクト(461 行)。
EC の受注を基幹システムと WMS(倉庫管理)へ連携する、よくある業務バッチです。
sample-project/
├── src/order/ sync.ts / validator.ts / inventory.ts
│ priceCalculator.ts / report.ts / types.ts
├── src/shared/ httpClient.ts / dateUtil.ts
├── src/legacy/ bizpoAdapter.ts
└── tests/ sync.test.ts
条件として重視したのは、ツールとしては正常に動く状態を保つことです。
npm run typecheck はエラー 0 件、npm test は 3 件すべて pass します。
「壊れているから読みにくい」のではなく、「動くけど読みにくい」を再現したかったからです。
2-2. 仕込んだ問題(正解キー)
| 区分 | 件数 | 内容 |
|---|---|---|
| MUST | 8 件 | スキルが機能しているなら必ず拾ってほしい。再現率の分母 |
| SHOULD | 27 件 | 拾えれば加点。絞り込みルールがあるので全部拾う必要はない |
| トラップ | 15 件 | 指摘したら減点。偽陽性の検出用 |
MUST の例を 2 つ。
// F-04: checkStock は「確認」の顔をして在庫を予約している
export async function checkStock(itemCode: string, qty: number): Promise<boolean> {
const stock = await fetchStock(itemCode);
const alreadyReserved = reserved.get(itemCode) ?? 0;
if (stock - alreadyReserved < qty) return false;
reserved.set(itemCode, alreadyReserved + qty); // ← 名前にない副作用
return true;
}
// F-02: 第 2 引数 f が何のフラグか呼び出し側から読めない
export async function doSync(d: Order[], f: boolean): Promise<SyncResult> {
// ...55 行下...
o.status = f ? '2' : '1';
}
2-3. トラップ側のほうが手が込んでいる
このスキルは「指摘しすぎない」ことも仕様なので、踏んだら減点の罠を仕込みました。
個人的には、ここが今回いちばん設計が楽しかった部分です。
-
整形崩れ — 1 ファイルだけインデント・クォートが乱れており、ESLint が 14 件検出する。
→ 指摘したら減点(Lint の担当領域) -
ローマ字の項目名 —
DENNOTOKCDKINGAKU。ただし直上の JSDoc に
「連携仕様書 v3.2 §4 の項目名にそのまま合わせている」と書いてある。
→ リネームを求めたら減点(外部仕様に合わせるのが正しい) -
理由コメントのある奇妙な処理 — 全角スペース埋めの固定長 20 文字。直前に
「半角で埋めると受信側で桁ずれする(仕様書 v3.2 §4.1)」とある。
→ 指摘したら減点。良いコメントとして褒めたら加点 -
理由コメントのない似た処理 — すぐ下に、理由が書かれていない 40 文字切り詰めを置いた。
→💬質問で由来を聞くのが正解。断定したら減点 -
十分に読みやすいファイル —
httpClient.tsは例外クラスを 2 つに分け、
JSDoc に@throwsと実行例がある。
→ 指摘 0 件が正解 -
性能問題 — ループ内
awaitの N+1、findの O(n²)。
→ 可読性の指摘に混ぜたら減点。別枠なら OK
3 つ目と 4 つ目をペアで置いたのがポイントです。ほぼ同じ見た目の処理で、
片方には理由コメントがあり、片方にはない。レビュアーが「コメントの有無」で
態度を変えられるかを見ています。
3. 検証方法
採点指標
| 指標 | 目標 |
|---|---|
| MUST 再現率 | 7/8 以上 |
| 重要度一致率 | 検出した MUST の 75% 以上 |
| 偽陽性(トラップ命中数) | 0 件 |
| 出力フォーマット準拠 | 12 項目中 10 以上 |
汚染を避ける
検証時のルールは次の 3 点です。
-
採点はレビュー実行とは別セッションで行う
-
同じセッションで正解キーを開くと、その情報が後続のやり取りに影響するためです。
-
レビュー時は
sample-project/のみを作業対象とし、正解キーが見えない状態にしました。 -
依頼文に「リーダブルコード」という言葉は入れない
-
「このプロジェクトのコードをレビューしてください」とだけ依頼します。
-
スキルの description が一般的なレビュー依頼でも発火するかを確認したかったためです。
-
同条件で 3 回実行し、中央値を採用する
-
LLM の出力にはばらつきがあるためです。
この検証の限界(先に書きます)
-
サンプルコードも正解キーも LLM 生成です。 「LLM が作った問題を LLM が解く」構図なので、
出題側と解答側が同じ癖を共有している可能性は排除できません。
(後述しますが、これは実際に問題になりました) -
試行数が非対称です。 自作スキルは 6 回、公式
/code-reviewは各設定 1 回ずつ。
公式側の数値は参考値として読んでください。 -
正解キーは自作スキル用に作ったものです。 「10 件に絞る」「質問に回す」は
自作スキルの仕様であって、公式/code-reviewはそんなルールを知りません。
公式側の「フォーマット違反」は、その意味で不公平な減点です。
4. まず自作スキルを 6 本回した
3 回回して弱点が見えたので SKILL.md を直し、もう 3 回回しました。
| 指標 | run1〜3(改善前) | run4〜6(改善後) |
|---|---|---|
| MUST 再現率(中央値) | 7/8 | 8/8 |
| 偽陽性 | 0 件 | 0 件 |
| フォーマット準拠 | 11/12 | 12/12 |
| SHOULD 検出数 | — | 22〜26 / 27 |
6 本すべてで偽陽性 0 件でした。整形崩れも、ローマ字項目名も、理由コメント付きの
全角パディングも、1 本も踏んでいません。むしろ 6 本全部が httpClient.ts を
「一番読みやすい」と冒頭で褒め、理由コメントを「維持してほしいパターン」として
引用していました。ペアで置いた仕掛けも効いていて、複数の run が
「TOKCD には理由が書いてあるのに BIKO には無いのはなぜか」と質問を返してきました。
性能問題も全本が「可読性の範囲外(参考)」として末尾に分離しています。
ここまでで「スキルとしては機能している」ことは確認できました。 残る問題は、
これが公式の汎用レビューに対してどれだけ上乗せになっているのか、です。
5. 同じ検証セットを公式 /code-review に当てる
Anthropic 公式の /code-review は Claude Code に最初から同梱されている組み込み機能です。
自作でもコミュニティ製でもありません。可読性に限定せず、
バグ・セキュリティ・パフォーマンスまで含めて見る汎用のレビューです。
汎用である以上、可読性観点の指摘が出ないわけではないはずです。
問題は「どの程度の粒度で、どの重要度で出るのか」。
特化スキルを維持する価値があるかは、そこを測らないと判断できません。
正解キーはすでにあるので、同じ土俵に乗せられます。2 パターン試しました。
-
通常版 — そのまま
/code-reviewを実行 - max effort 版 — 出力上限を引き上げ、recall 重視の設定で実行
通常版の結果
| 指標 | 結果 |
|---|---|
| MUST 再現率 | 3/8 |
| 偽陽性 | 0 件 |
| 修正コード | 0 件(1 つも書かれていない) |
| 質問 | 0 件 |
指摘 15 件はすべて「実行時にどう壊れるか」でした。可読性の指摘も混ざってはいますが
Medium 帯に降格しており、filterOrders の多義性や RATE = 0.1 の意味不明さといった
命名系はまるごと落ちています。
この結果から、デフォルトの /code-review だけでは可読性観点を十分に補えないことが分かりました。
max effort 版の結果
ところが、max effort 版では結果が大きく変わりました。
| 指標 | 結果 |
|---|---|
| MUST 再現率 | 5/8 検出 + 2 件部分検出 |
| SHOULD 検出数 | 約 22〜24 / 27(自作スキルとほぼ同水準) |
| 偽陽性 | 2 件(性能話を可読性セクションに混入 / 17 件を絞らず列挙) |
| 出力量 | 531 行・44KB(通常版の 4.8 倍) |
max effort 版は、出力の中に 「Medium(可読性・構造 — リーダブルコード観点)」という
セクションを自分で作ってきました。三重否定の条件式、test1 というテスト名、
doSync(d, f)、flag の 4 段ネスト、const tmp = calc(o); const total = tmp; ——
自作スキルが指摘したのとほぼ同じ箇所を、同じ観点で指しています。
そのうえで、自作スキル 6 本が全部落とした欠陥を 11 件上乗せしてきました。
| ID | 内容 |
|---|---|
| C-01 |
as { stock: number } のキャストで NaN < qty が false になり、在庫ゼロでも「在庫あり」を返す
|
| C-02 | 基幹が 204 を返すと response.json() が例外 → catch {} が飲む → 同一伝票を 3 件登録して「失敗」と報告
|
| H-02 | 在庫確認・引当の例外が try/catch の外。1 件の WMS 500 でバッチ全体が落ち、結果が消える |
| H-06 | タイムアウトがヘッダ受信までしか効かず、ボディ停止でバッチが永久停止 |
| H-08 | 備考の 40 文字切りが UTF-16 単位。絵文字でサロゲートペアが分断される |
| …他 6 件 |
しかも 「検証済み:」として実測値が付いています。モックの WMS と基幹サーバを立てて
実際に doSync を走らせ、ng に入ったのに WMS には引当が残っていることを
確認したうえで書かれていました。
念のため手元でも 4 件を実行確認しましたが、いずれも実在しました。
new Date('20260305') => Invalid Date
formatYmd(new Date('...T20:00:00Z')) の日 => 6(TZ=Asia/Tokyo、UTC なら 5)
('あ'×39 + '👍').slice(0, 40) の末尾 => "\ud83d"(孤立サロゲート)
calcLine 合計 1104 vs calc 1105 => ¥335 × 3 行で 1 円ずれ
6. 何が違ったのか
数字を並べたので、実務に効く差だけ 3 つに絞ります。
差 1: 断定するか、質問を返すか
max effort 版は 質問が 0 件でした。すべて断定して「対処:」を書きます。
たとえば order.customerCode.startsWith('A') という条件について、
max effort 版は isPriorityCustomer(優待得意先)と命名して修正案を出しました。
一方、自作スキル 6 本は全部こう聞き返しています。
💬
customerCode.startsWith('A')の'A'は業務上どういう区分ですか。
得意先区分か、契約種別か。仮に「優待得意先」と置きましたが、正しい呼び名に差し替えたいです。
どちらが正しいという話ではありません。ただ業務システムのレビューで
「勝手に決めた名前」入りの修正案が回ってくると、業務側の確認が一往復増えます。
差 2: 指摘の単位と、着手のしやすさ
- max effort — 1 件 = 1 つの故障経路。指摘同士を因果で連鎖させて書く。影響範囲の把握に強い
-
自作スキル — 1 件 = 1 つの可読性原則。同じ
sync.tsを「引数名」「flag」「日付の再実装」など
6 件に分解し、それぞれに修正後のコードを付ける
前者は「何がどう壊れるか」の理解に向き、後者はそのまま実装者に渡せる差し戻し票になります。
差 3: 出力の重さ
| 出力量 | 実行コスト | |
|---|---|---|
/code-review 通常版 |
9KB | 軽い |
| 自作スキル | 20〜28KB | 軽い |
/code-review max effort |
44KB | モックサーバを立てて実行検証。重い |
max effort を毎 PR に回すのは現実的ではありません。
7. まとめ — 自作する意味はあったのか
「/code-review で賄えるのか」への答え
| 条件 | 賄えるか |
|---|---|
デフォルトの /code-review
|
No. MUST 3/8。命名系がまるごと落ちる |
max effort の /code-review
|
ほぼ Yes. MUST 5/8 + 部分 2、SHOULD は同水準 |
「可読性の指摘が欲しいだけ」なら、max effort の /code-review でかなり足ります。
自作スキルだけが拾えたのは最終的に 3 件で、しかもすべて
「実行しても壊れないが、読み間違える」タイプでした
(filterOrders の多義性、冗長コメント、sum/amount/total の使い分け)。
正直に言うと、想定よりずっと差は小さかったです。
「汎用レビューは可読性を浅くしか見ないだろう」という見込みで作り始めたので、
そこは測ってみないと分からなかった部分でした。
それでも消さないことにした理由
現時点の判断としては、残します。理由は 3 つです。
① コストと運用頻度が違う
max effort は出力が 4.8 倍で、実行検証まで走ります。リリース前の重要バッチには回せますが、
日常の PR には重すぎます。「毎回 44KB のレポートが飛んでくるレビュー」は、
結局読まれなくなります。
② 成果物の性格が違う
/code-review の出力は欠陥リストで、自作スキルの出力は差し戻し票です。
10 件に絞られ、各件に修正コードが付き、意図が不明な箇所は質問になっている——
これは「レビュアーの成果物」ではなく「実装者の作業指示」として設計しています。
③ 業務ルールを断定しない
業務システムでは、奇妙に見えるコードの相当数に理由があります。
理由コメント付きの全角パディングを 6 本すべてが指摘せず、逆に良い例として
引用したのは、その規律が効いた結果でした。
場面別の落とし所
| 場面 | 使うもの |
|---|---|
| 日常の PR レビュー、実装者への差し戻し | 自作スキル |
| リリース前、決済・在庫など壊れると痛い箇所 | /code-review(max effort) |
| とりあえず何か見てほしい |
/code-review 通常版で十分 |
そして重要なのは、自作スキルを「バグも見るように」拡張しないことだと考えています。
可読性に特化しているからこそ偽陽性 0 件を 6 本連続で維持できたのであって、
役割を広げた瞬間にその強みは消えます。
1 日やってみての所感
-
「公式にもある機能」の実力は、設定次第で大きく変わる。 同じ
/code-reviewで
MUST 3/8 と 5/8+2 に分かれました。デフォルトの出力だけ見て
「汎用レビューはこの程度か」と判断していたら、間違った結論を出していました -
自作したこと自体は無駄ではなかったと思っています。「やらないこと」を明文化する過程で、
自分たちがレビューに何を期待しているのかが言語化できました。
そもそも比較の物差しになったのも、正解キーを先に作っていたからです -
検証セットは、スキルより長く使えます。 スキルを直すたびに同じ 3 回試行を回せますし、
今回のように別のツールを当てることもできます
おまけ: 検証セットのほうが検証されてしまった話
冒頭で「サンプルも正解キーも LLM 生成」という限界を書きました。これが実際に問題になりました。
公式 /code-review を当てた結果、正解キー側のバグが 2 件見つかったのです。
1. 「dateUtil.ts は指摘 0 件が正解」が成立していなかった
模範として置いたはずの formatYmd が、ローカルタイムゾーンのゲッタを使っていました。
JSDoc に書いた実行例が TZ 次第で偽になります。つまり「ここは綺麗だから指摘するな」と
指定したファイルに、実際にはバグがありました。
2. 想定していなかった第三の反応が出た
slice(0, 40) について、正解キーは「40 の根拠を質問するのが正解、定数化を断定したら誤り」と
定めていました。ところが max effort 版は、数字ではなく 「slice が UTF-16 コード単位で数えるので絵文字が壊れる」 点を指摘してきました。
どちらの判定にも当たらず、採点表に行を足すことになりました。
出題側と解答側が同じ LLM だと、出題側の見落としがそのまま「正解」として固定される。
今回それが表面化したのは、別系統のレビュアー(公式 /code-review)を当てたからでした。
自作スキルを検証するつもりが、検証セットのほうが検証された——というオチです。
LLM に正解キーを作らせるなら、別のツールを当ててみるのは
それ自体が正解キーの健全性チェックになります。おすすめです。