---
title: "言わない技術 第11回：AI にレビューをさせない技術"
author: garplab
publisher: TypingTube
license: CC BY 4.0
license_url: https://creativecommons.org/licenses/by/4.0/
license_scope: 「CC BY 4.0」の印から始まる節（仕組み・検証手順・コード）。印の無い本文は著作権を留保
canonical: https://typing-tube.net/articles/iwanai-11-no-review
series: "言わない技術"
language: ja
---


> この記事は、連載「言わない技術」の第 11 回、本編の最後です。各回はファイルやスクリプトを一つ置けば完結します。連載の全体像と各回の一覧は[序論](https://typing-tube.net/articles/iwanai-intro)にあります。

先に、謝っておきます。**このタイトルで、記事は書けませんでした。** 私は毎回、AI にレビューをさせています。「読まない技術」の[第 12 回](https://typing-tube.net/articles/1769779c2f1b4b)から繰り返し書いてきた、セッションの終わりに必ず打つ一文——「ここまで不足点はないですか？」——が、レビューそのものだからです。前作「AIの意見を聞かない技術」を書き上げたときも同じで、AI に全体を読ませて、不足点を出させました。記録が残っています。

それでも、この回を書いています。レビューで返ってきた指摘を並べたときに、気づいたことがあったからです。指摘の半分は、同じものがリポジトリの中にあれば、置いてある自動テストが落としていた種類のものでした。落ちなかったのは、記事のコードブロックの中のコードを、その自動テストが見ていなかったからです。私に足りなかったのはレビューではありませんでした。**置いた自動テストが何を見ていないかを、私が知らなかった。** そう分かったとき、書くべきものが「レビューをさせない」から「置いたものを誰が点検するか」に変わりました。

今回は、その話です。AI にレビューをさせれば、指摘はいくつも返ってきます。ただ、そのうち何件が、置いてある仕組みが拾えたはずのものかは、**数えるまで分かりません。**

前作「読まない技術」の[第 6 回](https://typing-tube.net/articles/569d598941c00f)で、**ルールを、破られたら落ちる自動テストに置き換える**と書きました。[第 1 回](https://typing-tube.net/articles/iwanai-01-no-verification)で引いたのと同じ回です。あのときは指示ファイルの側を見ました。今回は、**置いた自動テストのほうを疑います**。

まず一つだけ、思い出してみてください。直近で AI にレビューをさせたときに出た指摘です。そのうち、**あなたのリポジトリに既にある自動テストが拾えたはずのもの**は何件でしたか。

## 前作のレビューで、返ってきたもの

前作を書き終えたとき、私はサブエージェントを **5 体**立ち上げました。読者体験・技術・批判耐性・既存記事との整合の 4 体と、編集者の視点で通読する 1 体です。返ってきた指摘は、こうでした。

- **記事の中のコードが 5 件、コピペで動かなかった**（山括弧をシェルがリダイレクトと解釈する / 未定義の関数を呼ぶ / 正規表現が対象を素通りする）
- **他人の主張の帰属が間違っていた。** 引用した論の 1 つは、私が名前を挙げた人ではなく別の人の別の主張だった
- 表記の揺れ（「判る」と「分かる」の混在）、番号の対応が壊れた箇所、呼称の不統一

並べて分けると、2 種類になります。1 つ目の、記事の中のコードが動かなかった 5 件は、同じものが `app/` の中にあれば、置いてある自動テストが落としていた種類です。落ちなかったのは、**自動テスト 26 本はリポジトリを見ていて、記事のコードブロックの中のコードは見ていない**からです。同じ文字列でも、`app/` にあれば落ち、記事のコードブロックの中なら通る。**仕組みが見ている範囲の外は、こんなに近くにありました。**

残りの 2 つは性質が違います。他人の主張の帰属も表記の揺れも、**機械には真偽を決められません**。「AIの意見を聞かない技術」の[第 5 回](https://typing-tube.net/articles/kikanai-05-machine-check)で「禁止のうち機械に持たせられるのは半分以下」と書いた、同じ線引きです。

## リポジトリの側は、先にレビューされています

私の手元の本番プロジェクト——[typingtube](https://typingtube.net) という、YouTube の音楽動画でタイピング練習ができる Web サービスです——を数えました。

| 何が見ているか | 数 |
|---|---|
| 仕様・原則を突き合わせる自動テスト | **26 本** |
| コマンドや起動に割り込む hook | **6 本** |
| pre-commit hook のチェック | **8 つ** |
| 増え方を見張るラチェット | **8 項目** |
| 全量テスト | **14,149 件**（この記事を書いた時点で 0 失敗） |

**ここを通ったコードに、レビューで指摘できることは多くありません。** 命名も、禁止された列も、翻訳の抜けも、デザインの逸脱も、コミットの手前で落ちます。「レビューさせろ」の前に、**プロジェクトのほうが先にレビュアーになっています。**

育つほど、これは進みます。指示ファイルには「Ruby 3.3.x」と書いてありますが、`.ruby-version` を開けば **3.3.6** です。**実物のほうが正確で、書いた日から既にそうでした。** 公式の点検コマンドも同じ線を引いていて、**モデルがコードベースから導ける内容は削り、落とし穴と理由を残す**と書かれています。コードが育つほど「導ける」側が増え、**言うことは減ります。**

## だから、順番の話です

「AI にレビューをさせない」は、**させるなという話ではありません**。順番の話です。**仕組みが落とせるものは仕組みが落とし、残ったところにレビューを当てる。** 仕組みの無いところでレビューをやめたら、何も見ていないのと同じです。

問題は、その範囲を私が把握していなかったことです。26 本あることは知っていました。**その 26 本が何を見ていないかは、数えるまで知りませんでした。**

理解したことは一文にできます。

> **仕組みが見ている範囲は、疑って壊してみないと分からない。だから点検を頼む代わりに、自動テストが空回りしていないかを見る仕組みを置く。**

## 仕組み: 空回りしている自動テストを、外から数える

置くのは自動テスト 1 本です。ただし対象はコードではなく、**自動テストそのもの**です。

```ruby
# test/reference/zero_target_guard_test.rb（実物からの簡約版）
SCANNING = /Dir\.glob|Dir\[|\.glob\(/          # ファイル群を走査している
MINIMUM  = /assert_operator[^\n]*:>=|MIN_[A-Z_]*\s*=|refute_empty/  # 0 件なら落ちる、の表明
SCANNED_MIN = 20                                # ⚠️ この自動テスト自身の読み先が壊れたら落ちる

test "checks that scan a set of files never go green on zero targets" do
  files = Dir.glob(DIR.join("*_test.rb")).reject { |p| p == __FILE__ }
  assert_operator files.size, :>=, SCANNED_MIN, "this check is looking at the wrong place"

  unguarded = files.filter_map do |path|
    body = File.read(path)
    next unless body.match?(SCANNING)
    next if body.match?(MINIMUM)
    File.basename(path)
  end

  assert_empty unguarded, "these checks scan files but never state a minimum. " \
               "they stay green on zero targets, so nobody notices when they break\n  - " + unguarded.join("\n  - ")
end
```

走査する自動テストは、「違反が無い」と「そもそも何も見ていない」を**同じ合格で返します**。ファイルが移動しただけで対象は空になり、黙って空回りします。**置いた最初の実行で、1 本見つかりました。**

> 走査しているのに最低件数を表明していない自動テストがある。
> - `e2_pixel_art_not_in_core_jobs_test.rb`

ディレクトリの存在だけは確かめていて、**中のファイルが 1 件も無くても合格**でした。1 行足して塞ぎました。

この形にはもう一つ意味があります。**3 作 37 本のうち、「検証手順」を置いたのは 30 本。そのうち 13 本が、これと同じ形でした。**

| 回 | 検証手順 |
|---|---|
| [前作 第 4 回](https://typing-tube.net/articles/8f9ae8791b448d) | **外して**、止まることを確かめる |
| [前作 第 6 回](https://typing-tube.net/articles/569d598941c00f) | **破って**、落ちることを確かめる |
| [第 1 回](https://typing-tube.net/articles/iwanai-01-no-verification) | 機構の無い依頼を**足して**、落ちることを確かめる |
| [第 6 回](https://typing-tube.net/articles/iwanai-06-subagent-output) | 訳から見出しを**落として**、失敗になることを確かめる |
| [第 9 回](https://typing-tube.net/articles/iwanai-09-no-guessing) | 実在するが目印の無いファイルを**名指しして**、落ちることを確かめる |

**その 13 本でやっていたのは、置いた仕組みを壊すことでした。** 名前を付けていなかっただけです。この回の自動テストは、それを**人がやらなくても一度は目に入る形**にしたものです。

## させなくなったもの

「全体をレビューして」です。**レビュー自体はやめていません**——前作は 5 体に読ませました。やめたのは、**仕組みが落とせる範囲まで含めて頼むこと**です。いまは範囲を切ってから頼みます。

## 書けなかったタイトルは、3 つありました

冒頭で謝ったとおり、この回は**そのままでは書けませんでした。** 連載の候補に挙げて落としたタイトルが、3 つあります。

| 案 | 採否 | 落とした／通した根拠 |
|---|---|---|
| 計画を書かない技術 | ❌ | `docs/plan/` に**計画書が 200 本**あった |
| 計画を書けと言わない技術 | ✅ | 指示ファイル側に「計画書を書け」が**0 件**だった（[第 4 回](https://typing-tube.net/articles/iwanai-04-no-plan-request)） |
| AI にレビューをさせない技術 | ❌ → ✅ | 前作の記録に**レビューさせた記録が残っていた**。中身を変えて通した（この回） |

**3 件とも、決めたのは記憶ではなく記録のほうです。** そして私は、2 つとも逆を思っていました——「計画は書いていないはず」「レビューは、毎回の一文だけのはず」。証明されたのは「仕組みが効く」ではありません。

> **記録が残っていると、思い込みのほうが落ちる。**

## 注意: 落ちたことのない自動テストは、まだ何も証明していません

**一つ、この自動テストは書き方しか見ていません。** 「最低件数の表明があるか」を静的に見るだけなので、`>= 0` と書けば通ります。妥当な数かは測れません（[第 1 回](https://typing-tube.net/articles/iwanai-01-no-verification)から数えて 4 度目の同じ死角です）。

**二つ、26 本という数は自慢ではありません。** 小さなプロジェクトなら 2 本で足ります。見てほしいのは、**その 2 本が何を見ていないかを言えるか**のほうです。

**三つ、落ちたことのない自動テストは、まだ何も証明していません。** 前作の第 6 回のとおり、守っている自動テストと壊れている自動テストは**失敗を一度見るまで区別が付きません**。この回の自動テストも、1 本見つけて初めて動いていると分かりました。

## 検証手順: 自動テストの側を、二方向から壊す

**前提**

- 走査する自動テストが数本あり、それぞれに「走査対象が N 件以上あった」の表明が入っていること
- それを外から数えるメタテスト（自動テストを見る自動テスト）を置き終えていること
- 作業中の変更が無い状態（`git status` が空）から始めること

**所要時間**: 15 分

この回の自動テストは**自分と同じ壊れ方をしうる**ので、確かめる向きが 2 つあります。

**手順**

1. **あなたが**、見られる側を壊します。走査する自動テストのどれか 1 本から、最低件数の表明を 1 行消します（`assert_operator files.size, :>=, 1` に当たる行）。メタテストを走らせ、結果を控えます
2. **あなたが**、消した 1 行を戻し、メタテストが合格に戻ることを確かめます
3. **あなたが**、今度はメタテスト自身を壊します。メタテストが走査する先のディレクトリ名を、存在しない名前に変えます。メタテストを走らせ、結果を控えます

**合格条件**（すべて満たすこと）

- 手順 1 で、メタテストが**落ちる**
- 手順 1 のメッセージに、**表明を消したファイルの名前**が出ている
- 手順 3 で、**メタテストが自分で落ちる**（`Expected 0 to be >= 20` に当たる形）

**合格しなかったとき**

- **手順 1 で「どれかが足りない」だけ出て、名前が出ない** —— 直す側（AI でも、あなたでも）が、数本の中から探すことになります
- **手順 3 で落ちない** —— ⚠️ **ここが本体です。** 0 件で合格になる自動テストを探す自動テストが、0 件で合格になっています。**このメタテストは、置いた日から何も見ていません**

**後始末**

- ディレクトリ名を戻し、合格に戻ることと `git diff` が空になることを確かめます

---

次回は最終回、「何もしない技術」です。11 回かけて渡す言葉を減らしてきました。この回だけでも、私は自分の手元を 2 回数え違えています。

---

**連載「言わない技術」**

- ← 前回: [第 10 回 質問に答えない技術](https://typing-tube.net/articles/iwanai-10-no-answering)
- → 次回: [最終回 何もしない技術](https://typing-tube.net/articles/iwanai-finale)
- 全回の一覧: [序論](https://typing-tube.net/articles/iwanai-intro)
