並列に走らせたAIレビューを1本にまとめる統合規則
並列に走らせたAIレビューを1本にまとめる統合規則
観点を分けて別々にレビューさせると、3つの出力が手元に残る。そのまま連結すると、同じ行に対する指摘が別の言葉で並ぶ。重複をまとめる規則を書くと別の問題が出る。3観点の出力12件で数えると、同一性を「場所と機構」で判定した規則は2件をまとめた一方で、同じ行に対する逆向きの判定2件も1件に潰し、矛盾の印は0件になった。同一性の鍵に判定を足さないと、まとめる処理が矛盾を消す。
この記事の提案を使うのは、観点を分けて別々に実行し、その結果を1つのレビューとして人に出すときである。
3つの観点を別々に当てる
題材は TypeScript のモジュールで、差分は8ファイル・追加25行・削除16行である。仕込んだ欠陥は場所で数えると7箇所ある。判定基準は20条で、条と行の対応を書かせる記事で使ったものと同じである。
3つの観点パスは検査スクリプトとして書いた。モデルは走らせていない。 スクリプトが出す文は決まった形なので、言い換えの多様さは実際のレビュー出力より小さい。この測定で数えられるのは、同じ場所に別の機構で届いた指摘と、同じ場所に逆の判定を出した指摘を、統合規則がどう扱うかである。言い換えだけが違う重複の量は、この題材からは言えない。
各パスの出力は4つの項目を持つ。場所(ファイル:行)、機構(何が起きているか分類した1語)、判定(違反か整合か)、主張(一文)である。
観点1 単位と型: 5 件
[違反] src/checkout.ts:17 (型の潰し) undefined を返す関数の戻り値を ! で潰している
[違反] src/checkout.ts:32 (端数処理) Math.round は四捨五入する。切り捨てが要る
[違反] src/checkout.ts:32 (端数処理) 小数の係数を掛けたあとの丸めが決まっていない
[違反] src/quote.ts:5 (単位の不一致) 割引率に整数を渡している
[整合] tests/checkout.test.ts:14 (期待値の整合) 期待値 7040 は実装の計算結果と一致する
観点2 テストの妥当性: 3 件
[違反] tests/checkout.test.ts:14 (期待値の整合) 期待値を実装の挙動に合わせて書き換えた疑いがある
[違反] src/seats.ts:14 (テストの不在) ship を呼ぶテストが無い
[違反] src/seats.ts:21 (テストの不在) reserve を呼ぶテストが無い
観点3 条との照合: 4 件
[違反] src/checkout.ts:32 (数値リテラル) 条11。税率が実装の数値リテラルで書かれている
[違反] src/checkout.ts:36 (設定値の未使用) 条14。再試行の回数が実装の定数になっている
[違反] src/log.ts:7 (利用者を特定できる値) 条8。ログに利用者のメールアドレスを入れている
[違反] src/seats.ts:22 (残数の確認が無い) 条5。減らす前に残数を確認していない観点1と観点2が tests/checkout.test.ts:14 について逆のことを言っている。観点1は、期待値の 7040 が実装の計算結果(Math.round(6400 * 1.1))と一致することを確かめて「整合」と判定した。観点2は、その期待値が同じ差分の中で 6400 から書き換えられたことを見て「違反」と判定した。どちらの観察も事実として正しく、結論だけが逆である。
連結すると何が並ぶか
3つを連結すると12件になる。同じ場所に2件以上並んだ箇所は2つだった。
| 場所 | 件数 | 機構 |
|---|---|---|
src/checkout.ts:32 |
3 | 端数処理、端数処理、数値リテラル |
tests/checkout.test.ts:14 |
2 | 期待値の整合、期待値の整合 |
src/checkout.ts:32 の3件は、1行に欠陥が2つ入っているために起きている。return Math.round(applyDiscount(base, coupon.rate) * 1.1); という行は、端数処理が条2に反し、税率が実装の数値リテラルで書かれていて条11に反する。観点1が端数処理を2通りの言い方で挙げ、観点3が数値リテラルとして挙げた。
同一性の規則が矛盾を消す
同一性を「場所と機構が一致したら同じ指摘とみなす」と書いて処理した。
src/checkout.ts:32 (端数処理) 2 件 -> 1 件
tests/checkout.test.ts:14 (期待値の整合) 2 件 -> 1 件
この規則のあとに残った矛盾の印: 0 件2件がまとまって12件が10件になった。1件目のまとまりは意図したものである。観点1が同じ行の端数処理を2通りの言い方で挙げていたので、1件に寄せるのが正しい。
2件目のまとまりが問題である。tests/checkout.test.ts:14 の2件は、場所も機構(期待値の整合)も同じで、判定だけが「整合」と「違反」に分かれていた。場所と機構を鍵にすると、この2件は同じ指摘と判定されて1件に潰れる。残った1件の判定は、先に処理した側のものになる。矛盾を見つけるための材料は、まとめる処理の前に消えている。 統合後に矛盾の印が0件なのは、矛盾が無かったからではなく、矛盾した組が1件にまとめられたからである。
同一性の鍵に判定を足すと、組が保たれる。
tests/checkout.test.ts:14 [整合] (期待値の整合) 期待値 7040 は実装の計算結果と一致する
tests/checkout.test.ts:14 [違反] (期待値の整合) 期待値を実装の挙動に合わせて書き換えた疑いがある11件になり、矛盾の印が2件に付いた。意図したまとまり(端数処理の2件)は残り、矛盾の組は両方残った。仕込んだ欠陥7箇所は、連結の時点でも処理3のあとでも全部残っている。
場所の一致で拾えない重複
場所を鍵にする規則は、行が1行ずれた重複を拾わない。
src/seats.ts:21 (テストの不在) と src/seats.ts:22 (残数の確認が無い)
場所の一致では同一と判定されない組: 1観点2は reserve のメソッド定義の行(21行目)を指して「テストが無い」と言い、観点3はその中の在庫を書き換える行(22行目)を指して「残数の確認が無い」と言った。同じメソッドについての2件だが、行が違うので別の指摘になる。この組は機構も違うので、まとめるべきでもない。隣接行の扱いを規則に入れるなら、まとめるのではなく「同じ単位(関数、メソッド、クラス)に2件以上ある」という印を付ける形にする。印があれば、人は2件を並べて読める。
成果物
統合パスの指示
3つの出力を渡して、1本にまとめさせる。
## 入力
次に貼る <パスの数> 個の出力を1本にまとめる。各出力は指摘1件につき4項目
(場所・機構・判定・主張)を持つ。新しい指摘を足さない。
## 同一性の判定
場所(ファイルのパスと行番号)と機構と判定の3つがすべて一致する指摘を、
1件にまとめる。まとめた件は主張のうち1つを残し、残した主張の後ろに
`(<元の件数> 件をまとめた)` と書く。
3つのうち1つでも違う指摘は、別の件として残す。とくに、
場所と機構が同じで判定だけが違う指摘は**まとめない**。
## 矛盾の処理
同じ場所に判定の違う指摘が2件以上ある場合、全部残して各件の先頭に `[矛盾]` と付ける。
どちらが正しいかを決めない。決めるための材料(どの行を読めば分かるか)を1行足す。
## 隣接の印
同じファイルで、行番号の差が1以内の指摘が2件以上ある場合、
まとめずに各件の先頭に `[隣接]` と付ける。機構が違っても付ける。
## 出力
まとめた結果を、場所(ファイル名、行番号)の順に並べる。
末尾に次の3行を書く。
- 入力の総件数: <各出力の件数の合計>
- 出力の件数: <まとめた後の件数>
- 矛盾の印が付いた件数: <件数>末尾の3行が無いと、統合で何件が減ったのかを受け取った側が数え直すことになる。入力の総件数と出力の件数の差が、まとまった件数である。この題材では 12 と 11 で差が1だった。
各パス側に書いておくこと
統合が機能するかどうかは、各パスの出力の形で決まる。機構を自由な文で書かせると、同じ機構が別の語になって同一性の判定が働かない。機構は語の一覧から選ばせる。
機構は次の語から1つ選んで書く。当てはまる語が無い場合は `その他` と書き、
主張の中で何が起きているかを書く。
<機構の語の一覧。観点をまたいで同じ一覧を使う>判定も同じで、「違反」と「整合」の2語に限る。整合の判定を出させるかどうかは観点ごとに決まるが、出させないと矛盾が見つからない。この題材で矛盾が見つかったのは、観点1が「確かめた結果、問題は無い」を出力に書いたからである。
統合パスを足すと何を使うか
払うのはパスの数である。観点3つで3パス走らせていたところへ統合パスが1つ増えて4パスになる。統合パスの入力は3つの出力の合計で、この題材では12件ぶんになる。差分と判定基準を渡し直す必要はないので、統合パスの入力は観点パス1つぶんより小さい。
出力項目は増えない。[矛盾] と [隣接] の印は、該当する件にだけ付く。この題材では11件のうち2件に矛盾の印、2件に隣接の印が付いた。末尾の3行は出力全体に1回である。
人の確認時間は、矛盾の印が付いた件について増える。矛盾は統合パスが決めないので、2件を並べて読んで人がどちらかを採る。この題材の矛盾1組は、テストの期待値が実装に追従したものかどうかの判断で、差分の削除行を見れば決まる。1組あたり数十秒である。印が付かなければこの時間は発生しない。
この統合パスを落としていい条件は、観点パスが対象のファイルを分けているときである。 観点ごとに見るファイルが重ならないなら、同じ場所に2件並ぶことがないので連結で足りる。重なるかどうかは、各パスに渡したファイルの集合を取って共通部分を見れば分かる。共通部分が空なら統合パスは要らない。
指示文の点検
- 同一性の鍵に、場所と機構だけでなく判定を入れたか
- 場所と機構が同じで判定が違う指摘を、まとめない指示にしたか
- 矛盾した組を全部残して印を付ける形にしたか
- 矛盾について、どちらが正しいかを統合パスに決めさせていないか
- 矛盾を解くための材料(どの行を読めば分かるか)を書かせているか
- 行番号の差が1以内の指摘に、隣接の印を付けているか
- 統合パスに「新しい指摘を足さない」と書いたか
- 機構を語の一覧から選ばせて、観点をまたいで同じ一覧を使っているか
- 各観点パスに「整合」の判定も出させているか
- 末尾に入力の総件数と出力の件数と矛盾の件数を書かせているか
- 各パスに渡したファイルの集合に共通部分があるかを見たか
おわりに
3観点の出力12件を統合すると、同一性を「場所と機構」で判定した規則は2件をまとめた。1件は同じ行の端数処理を2通りの言い方で挙げたもので、まとめるのが正しい。もう1件は同じ行に対する「整合」と「違反」の組で、まとめると矛盾が消える。統合後に残った矛盾の印は0件だった。
同一性の鍵に判定を足すと11件になり、矛盾の組は両方残った。行が1行ずれた2件は場所の一致では拾えないので、まとめるのではなく隣接の印を付ける。仕込んだ欠陥7箇所は、どちらの処理のあとでも全部残っている。
次に取る行動を1つ挙げる。手元の並列レビューの出力を2つ取り、同じファイルの同じ行を指す指摘を探す。見つかった組の判定が逆だったなら、同一性の鍵に判定を足す理由がその場で立つ。