一人で書いていると、レビューが来ない
個人開発の一番の穴はレビューが無いことだと思う。設計を間違えても誰も止めてくれないし、うっかり書いた分岐も誰も見ない。
なので個人のリポジトリでも PR を切って、AI のコードレビューを通すようにしている。しばらく続けて指摘を全部コードで直してきたので、たまった分をまとめて読み返してみた。
読み返してみると、指摘が刺さる場所が驚くほど偏っていた。しかもその偏りには理由があった。以下、実際に直したものを型に分けて書く。全部すでに直っている話だ。
型1: 一人で動かしていると絶対に踏まないもの
一番多かったのがこれで、しかも一番ヒヤッとした。
監査ログの一覧を返す API が、ログインしている人の分だけでなく全件を返していた。where 句にユーザーの絞り込みが入っていなかった。自分ひとりで動かしている限り、返ってくるのは自分のログだけなので、画面上はどこまでも正しく見える。二人目が入った瞬間に他人のメールアドレスやアクセス元が混ざる。
同じ形の指摘がもう1件あった。エラーの詳細をそのままクライアントに返していたところ。開発中は自分がその文字列を読むので便利なのだが、そのまま本番に出ると内部構造を配って歩くことになる。
この型の共通点は、症状が「利用者が増えたとき」にしか出ないことだ。テストも通るし、手で触っても気づかない。自分のアカウントしか存在しない環境では、スコープ漏れは無害に見える。
一人で開発していると、この種のバグを見つける手段が原理的にない。ここは素直に助かった。
型2: パース関数が思ったより緩い
これは知識の問題だった。
URL から受け取った ID を Number.parseInt(value, 10) で数値にしていた。よく見る書き方だと思う。
Number.parseInt("5abc", 10) // → 5
途中まで読めたらそこで止まって、読めた分を返す。 つまり "5abc" のような文字列がそのまま ID 5 として通る。URL を手で書き換えれば、意図しないレコードを触れてしまう。
直し方は単純で、正規表現で文字列全体を検証してから変換する共通関数に寄せた。
// 文字列全体が正の整数であることを確認してから通す
export function parsePositiveInt(v: string): number | null {
return /^[1-9]\d*$/.test(v) ? Number(v) : null;
}
大事なのは1箇所に寄せたことのほうだと思っている。同じ指摘が3つのエンドポイントで同時に出ていた。個別に直していたら、4つ目を書くときにまた同じことをやる。
型3: リトライの適用範囲が広すぎた
DB のコネクションが切れることがあったので、クエリを横断的にリトライするようにしていた。全操作に一律でかけていた。
指摘されて気づいたのだが、これは書き込みで二重実行になる。DB 側では完了しているのに接続が切れてクライアントだけエラーになった場合、リトライがもう一度同じ書き込みを投げる。
リトライ対象を読み取り系だけに限定した。あわせて、リトライする条件も「接続断を示す特定のエラーコードのとき」に絞った。何でもかんでも再試行すると、本当は失敗すべきものまで粘ってしまう。
リトライは「安全に繰り返せる操作」にしか付けられない。 頭では知っていたのに、横断的に設定できる仕組みを使った瞬間に「全部にかける」を選んでいた。便利な仕組みほど、適用範囲を考えずに広げてしまう。
ついでにもう1件、リトライ絡みで指摘があった。最大5回試行するループで、5回目が失敗したあとにも待機が入っていた。もう次が無いのに10秒待ってから落ちる。CI が毎回その分だけ遅くなる。「最終試行なら待たない」の1行を入れ忘れるのは、自分でもよくやる。
型4: 境界が2箇所にあって揃っていない
これは地味だが、数字を出すアプリでは効く。
将来推計を出す処理があって、計算そのものは 2080 年まで回していた。一方、途中で適用する条件のほうは別の設定値(既定 2068 年)を上限にしていた。計算の終端と、条件の適用範囲の終端が違う。
結果として、2069 年から 2080 年までの区間だけ条件が抜け落ちた推計が出る。エラーは出ない。グラフは最後まで描画される。数字がおかしいことに気づく手がかりがない。
直したあと思ったのは、これは「同じ意味を持つ数字が2箇所にある」という、もっと一般的な形だということだった。片方だけ直る日が必ず来る。
AIレビューが指摘してこなかったもの
偏りの話に戻る。刺さったのは、スコープ、パース、適用範囲、境界。つまりコードを読めば閉じて判断できるものばかりだった。
逆に、一度も指摘されなかったものがある。この機能をそもそも作るべきかどうか。この抽象は早すぎないか。この指標は目的と制約を混ぜていないか。設計判断の部分は、当然ながら何も言ってこない。前提を知らないので言えない。
なので受け取り方としては、レビュアーというよりリンタの上位互換だと思っている。人間のレビュアーが一番時間を使うべき「そもそもこれでいいのか」は手つかずで残る。そこが空くのはありがたい。
そしてもうひとつ。指摘を全部直したのが良かったかというと、たぶん違う。ここに書いた型はどれも直して正解だったが、書式や命名の好みに寄った指摘も混ざっていて、それを全部反映していたら PR が指摘対応で埋まる。採否を自分で決めるのは変わらず自分の仕事だというのは、やってみて強く思った。
指摘を型に分けて読み返したのは今回が初めてだったが、これは定期的にやったほうがいい。同じ型を何度も踏んでいるのが見えるので、次に書くときに手が止まる。