この記事はこんな人におすすめ
- AIを使いながら開発している
- 外部ライブラリを使った実装で、AIの提案をどこまで信頼してよいか迷う
- AIが出したコードを「動きそう」という理由で採用したことがある
- 公式ドキュメントを確認するタイミングについて考えたい
この記事で得られること
この記事では、AIから提案された独自実装を採用したものの、後からライブラリに同じ目的の公式機能があることを知った経験から、
- AIが出した実装案をどう扱うか
- 独自実装の前に何を確認するか
- 実装方法が変わったとき、検証方法をどう考えるか
を整理します。
今回の学びを一言で表すと、
「どう書く?」を考える前に、「そもそも書く必要がある?」を確認する。
です。
AIが提案した独自実装
認証ライブラリが提供する特定のAPIを無効化する方法を実装していたとき、AIからNext.jsのRoute Handlerに独自の遮断処理を書く方法が提案されました。
要件を単純化すると、
認証ライブラリが提供する特定のAPIを利用できないようにする
というものです。
AIから提案されたのは、リクエストパスを判定し、対象のAPIならBetter Authへ処理を渡す前に404を返す方法でした。
例えば、次のようなイメージです。
import { auth } from "@/lib/auth";
const disabledPaths = new Set([
"/api/auth/example-a",
"/api/auth/example-b",
]);
function normalizePathname(request: Request) {
return new URL(request.url).pathname.replace(/\/+$/, "") || "/";
}
export async function POST(request: Request) {
const pathname = normalizePathname(request);
if (disabledPaths.has(pathname)) {
return new Response("Not Found", {
status: 404,
});
}
return auth.handler(request);
}
処理としては分かりやすく見えます。
Request
↓
Route Handler
↓
対象パスか?
├─ Yes → 404
└─ No → Better Authへ委譲
対象APIを遮断するという要件も満たせそうだったため、この方法で実装を進めました。
しかし、この時点で一つ重要な確認が抜けていました。
Better Auth自身に、同じことをするための公式機能はないのか?
という確認です。
Better Authには公式機能が用意されていた
その後、Better Authの公式ドキュメントを確認すると、特定の認証パスを無効化するためのdisabledPathsが用意されていることが分かりました。
例えば、次のように設定できます。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
disabledPaths: [
"/example-a",
"/example-b",
],
});
Next.jsとの連携も、現在の公式ドキュメントではtoNextJsHandlerを使った方法が案内されています。
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);
つまり、自分たちでパス判定を持たなくても、
Request
↓
Better Auth
↓
disabledPathsによって対象パスを無効化
という形にできます。
disabledPathsの存在を事前に把握し、それが要件を満たすことを確認できていれば、独自の遮断処理を書く必要はありませんでした。
問題は「AIのコードが間違っていた」ことではなかった
今回、自分にとって一番重要だったのはここです。
AIが提案した独自実装が、まったく動かないコードだったわけではありません。
問題だったのは、
要件
↓
AIが実装案を生成
↓
コードを見る
↓
要件を満たせそう
↓
採用
と進めてしまったことでした。
本来は、その途中に、
要件
↓
利用しているライブラリの公式機能を確認
↓
既存機能で実現できる?
├─ Yes → 公式機能を検討
└─ No → 独自実装を検討
という調査が必要でした。
つまり今回の問題は、
AIが間違ったコードを書いたことではなく、AIがコードを提案してくれたことで、「そもそもこのコードを書く必要があるのか」を確認しなかったこと
でした。
「どう書く?」の前に「書く必要がある?」
AIを使うと、具体的な実装案へ到達するまでの時間が非常に短くなります。
だからこそ、
どう実装する?
↓
AIに聞く
↓
コードが出る
↓
動くか確認する
という流れに入りやすくなります。
しかし、外部ライブラリを使っているなら、その前に次を確認する必要があります。
- 公式の設定項目はないか
- 公式APIはないか
- 公式のhookや拡張ポイントはないか
- 利用中のバージョンで使えるか
- プロジェクト内に既存の実装パターンはないか
つまり、
「どう書く?」より前に「書く必要がある?」を確認する。
ということです。
もちろん、公式機能が存在すれば必ずそれを使うべき、という話ではありません。
要件を満たせなかったり、別の制約があったりして、独自実装が適切な場合もあります。
ただし、独自実装には自分たちで考えるべきことが増えます。
今回のようなパス判定なら、
- どのパスを対象にするか
- URLをどう正規化するか
- 末尾スラッシュをどう扱うか
- どのレスポンスを返すか
- ライブラリ側の変更へどう追従するか
なども、自分たちの保守対象になります。
独自実装
↓
コードが増える
↓
考慮する仕様が増える
↓
テスト対象が増える
↓
将来の保守対象も増える
だからこそ、まず公式機能を確認し、それでは要件を満たせない理由がある場合に独自実装を検討する、という順番が大切だと学びました。
AIは「答え」ではなく「実装方法の候補」
今回の経験から、AIが出したコードを、
答え
ではなく、
実装方法の候補
として見るようになりました。
AIが独自処理を提案したら、
この実装で要件を満たせる?
だけでなく、
なぜこの処理を自分たちで書く必要がある?
公式機能はない?
既存コードに同じ仕組みはない?
ライブラリが提供する拡張ポイントはない?
まで確認する。
AIが生成したコードの正しさだけでなく、
そのコードの存在自体が必要なのか
を見るということです。
そのため、自分の中では次の順番を意識するようになりました。
① 要件を理解する
↓
② 使用しているライブラリを特定する
↓
③ 公式ドキュメントを確認する
↓
④ 公式の設定・API・拡張ポイントを探す
↓
⑤ プロジェクト内の既存実装も確認する
↓
⑥ 実装方法を決める
↓
⑦ AIに実装を補助してもらう
AIを調査に使うこと自体は有効です。
例えば、
この要件をBetter Authの
公式機能だけで実現できるか調べてください。
該当する公式ドキュメントも示してください。
と依頼すれば、いきなり実装コードを生成させるよりも良い出発点になります。
ただし、最終的には公式ドキュメントそのものを確認します。
AIの回答には、古いバージョンの情報や別ライブラリとの混同、細かな利用条件の見落としなどが含まれる可能性があるためです。
AI
↓
調査を速くする
公式ドキュメント
↓
仕様を確認する
という役割分担で考えています。
公式機能へ変更すると、検証方法も変わった
独自実装では、Route Handler自身に分岐があります。
そのため、例えば、
expect(response.status).toBe(404);
expect(mockAuthHandler).not.toHaveBeenCalled();
のようなUnit Testで、自分たちが書いた遮断処理を確認できます。
しかし、公式のdisabledPathsへ変更すると、遮断処理そのものはBetter Auth側に移ります。
ここでBetter Authのhandlerをmockしてしまうと、
Request
↓
mock handler
となり、実際のdisabledPathsの設定を通りません。
つまり、
テストがPASSしていても、今回変更した設定そのものを確認していない
という状態が起こり得ます。
そこで、
今までのUnit Testをどう残すか
ではなく、
この設定が実際に機能していることを、どの方法なら確認できるか
から検証方法を考え直しました。
実際の処理経路を確認する
今回は、実際のhandlerを通るHTTPリクエストを送り、対象パスへのリクエスト結果を確認しました。
例えば、ローカル環境で次のように確認できます。
curl \
-o /dev/null \
-w "%{http_code}\n" \
-X POST \
http://localhost:3000/api/auth/example-a
今回確認した環境では、対象パスに対して404が返ることを確認しました。
HTTP Request
↓
Next.js
↓
Better Auth
↓
disabledPathsによる無効化
↓
今回の環境では404を確認
ここで確認できたのは、
今回の環境・設定・対象パスについて、期待した結果になった
ということです。
すべてのHTTPメソッドや対象パス、本番環境、将来のBetter Authのバージョンまで、この一度の確認で保証できるわけではありません。
ここでも一つ学びがありました。
実装方法が変われば、適切な検証方法も変わる。
独自実装のために作ったUnit Testを残すこと自体を目的にするのではなく、
今回、本当に確認したいものは何か?
から検証方法を選ぶ必要があります。
テストが減ることより「何を検証しているか」を見る
公式機能へ変更したことで、独自実装を対象にしていたテストの一部は不要になります。
しかし、
テストが多い
=
安全
ではありません。
例えば、
- mock自身の挙動しか確認していない
- 実際の設定を通っていない
- 変更したコードや設定とは別の経路を確認している
のであれば、そのテストがPASSしていても今回の変更の保証にはなりません。
重要なのは件数よりも、
そのテストは何を検証しているのか
です。
実装方法が変わったなら、既存テストを機械的に残すのではなく、変更後の処理経路に対して何を検証すべきかを考え直す必要があります。
コメントには「なぜ」を残す
公式機能へ変更すると、実装自体はかなり短くなります。
export const auth = betterAuth({
disabledPaths: [
"/example-a",
"/example-b",
],
});
ただ、後からこの設定を見た人には、
なぜこのAPIを無効化しているのか?
までは分からない可能性があります。
必要であれば、「何をしているか」ではなく「なぜ必要なのか」をコメントとして残します。
export const auth = betterAuth({
// この設定が必要な理由を記載する
disabledPaths: [
"/example-a",
"/example-b",
],
});
disabledPathsでパスを無効化していること自体はコードから読み取れます。
コメントで残したいのは、
なぜこの設定が必要なのか
という背景です。
今回の失敗から作ったチェックポイント
今回の経験から、外部ライブラリに関係する実装では、コードを書く前に次を確認したいと思います。
[ ] 何を実現したいのか整理したか
[ ] 使っているライブラリの
公式ドキュメントを確認したか
[ ] 公式の設定・API・hookなどで
実現できないか調べたか
[ ] 利用中のバージョンでも
その機能を使えるか確認したか
[ ] プロジェクト内に
既存の実装例がないか確認したか
[ ] 独自コードを書く必要性を
説明できるか
[ ] 選んだ実装に合った
検証方法になっているか
AIにコードを書いてもらう前にこの確認を挟むだけでも、不要な独自実装を減らせると思います。
まとめ
今回、AIが提案した独自実装を採用した後に、同じ目的の公式機能が用意されていることを知りました。
振り返ると、重要だったのは高度な実装テクニックではなく、
独自実装を始める前に、まず公式機能を確認する。
という基本的な調査でした。
AIは実装を高速化してくれます。
一方で、実装案がすぐ手に入るからこそ、
AIがコードを出した
↓
動きそう
↓
採用
と進みやすくなります。
その間に、
AIがコードを出した
↓
そもそも、このコードは必要?
↓
公式機能はない?
↓
既存の仕組みはない?
↓
その上で実装方法を決める
という確認を入れる。
そして、実装方法が変わったなら、その実装で本当に確認したいものに合わせて検証方法も考え直す。
今回の経験から得た判断基準は、シンプルです。
AIの提案を、調査の終点にしない。
そして、
「どう書く?」の前に、「そもそも書く必要がある?」を確認する。
AIを使った開発を続けるうえで、今後も意識したいと思います。
