Flavor Wheel Engineering

珈琲とソフトウェアエンジニアリング

PRレビューで何を見るか

AIエージェントを使って開発をしていると常に他人が書いたコードや設計、提案をレビューすることになる。

レビューでみる項目は多数ある。例えば、以下のような項目が挙げられるだろう。

  • 誤字脱字
  • フォーマット
  • 問題が起こりやすい書き方をしていないか
  • テストはPASSしているか
  • ロジックの正しさ
  • テストケースの網羅性
  • メンテナンスし続けられるのか
  • 動作確認の方法が適切か

このうちフォーマットやバグを生みやすい書き方の一部は、Linterやformatterと GitHub actionsのようなCIを使うことで機械的なチェックが一部分は可能なのでレビュー中に神経質にならなくても良いことが多い。

最近ではCopilot reviewやDevin reviewで誤字脱字やテストケースをチェックしてくれて助かっている。特にコメントの誤字脱字は多少間違えていても読めてしまうし、間違えていても動作してしまうので見過ごしてしまいがちだった。 AIのレビューはまだ玉石混交という印象で、有益なコメントもあるが杞憂や前提条件が間違えていることもあるので人間の判断が必要だと思う。コンテキストの渡し方が悪かったり、レビューしやすい粒度でPRを作成できていない可能性もある。どちらにしてもモデルと一緒に改善されるだろう。

じゃあ、人間が注意すべき範囲はどこかといえば仕様と要件、運用に関わる部分だと思う。 AIは人間が決めた仕様を始めは知らないので、仕様に沿っているかの判断は開発段階ではAIにはできない。仕様駆動開発であれば先に仕様が固まっているのだろうが、それでも開発が進む中で未定義なケースがわかるものなので人の判断が必要になる。 ということはAIで爆速になった未来では、仕様を決めるためのコミュニケーションの比率が増えていくのだろう。 また、サービスの方向性を決めたり運用をするのはあくまで人なので、メンテナンスし続けられるかは自分で判断しないといけない。

AIエージェントと働く

AIエージェントを日常的に使って仕事をするようになって9ヶ月ほど経った。 初めは何もわからないまま使い始め、大量のコードが生成されるのに素朴に驚いていた。

コードに不満を持ってフィードバックすれば、次の瞬間には大量のリファクタリングが行われた書き換えられる。 当時は新規に作り始めたアプリケーションでAIエージェントをよく使っていたので、リンターやコードフォーマッターもなくコードの変更を追うのに限界を感じた。人間ならコードに秩序がなければ、理解するのが苦痛になり自然にリファクタリングをしたくなるが、AIエージェントは書き換え続けるのでコードが破茶滅茶になるばかりだった。 CIでテスト、リンター、フォーマッターを実行し、差分を確認しやすくしてセルフレビューにより秩序を保とうとした。 今なら循環的複雑度を一定以下に抑えるようなCIを導入してAIエージェントに自発的にリファクタリングさせる仕組みを作るかもしれない。 AIエージェント登場以前のプログラミングで大事だと言われていたことは登場以後も有効であると感じていたのも本格的に使い始めて1, 2ヶ月の頃だった。

最近はコードベースが既にあるリポジトリをよく触っているので、あまりに発散したコードになることはない。だが、そうなると初回に生成されるコードの質が気になってくる。 AIエージェントがタスクを上手くこなせないのは、コンテキストが適切に与えられていないからだと予想される。そのため、AGENTS.mdに全てを書いていた状態から整理したくなる。コンテキストに関係する情報が必要なだけ含まれるようにするのにパスベースで制御して参照されるrules やタスクごとに参照されるskillsの導入を進めた。 Claude codeにCopilot、Devinと特性が異なるAIエージェントも使い始めるが、同時にそれぞれが参照するファイルが異なる問題に直面することになる。 コードをDRYにしたいようにドキュメントもDRYにしたい。symlinkを作成することでお茶を濁しているが、より良い解決方法が欲しい。

AIエージェントを使うとコードが大量に生成されるので自分が高速になった気分になるが実際に同じタスクをして比較したことがないのでわからない。しかし、誰が見ても明確な作業をするのは圧倒的に楽になったと感じる。 例えば横並びに実装したコンポーネントを後から構造化したフォルダに整理し直すみたいなこと。 ワンショットなスクリプトを書きたいけれど例外を考えると難しいかも、な編集をAIエージェントにやらせると時間が節約できて特に嬉しい。

AIエージェントの使い熟しが大きく上達したかはわからない。モデルが賢くなったからユーザーのスキルが低くてもより良い出力を出せるようになっただけという悲しい可能性もある。プロンプトを書くときにサンプルを示そうとか、纏めさせてからSkillsを生成させるみたいな細かいテクは普段から使うようになっていても、それをもってスキルが身についたとは言いにくい。やっぱり、AIエージェントの動作そのものについての理解が必要なんだろうな。

この一年で機能が出揃っているように素人目には見えるけれど、次の一年はどんな機能が追加されるのだろうか。想像がつかない。

Next.js の実験的なビルドモード(--experimental-build-mode)の紹介

Next.jsは通常、ビルド時にクライアントからも参照可能なNEXT_PUBLIC_ で始まる環境変数をインライン化して、ビルド成果物に埋め込み、さらに静的ページの生成まで行います。 そのため、ビルド後に実行時に渡す環境変数でクライアント側の動作を切り替えることができません。 さらに静的ページの中でDBのクエリ結果やAPI呼び出しを表示していた場合、生成のためにそれらに接続できるようにすることが求められます。

前者の制約から、ステージング環境と本番環境用と別々にビルドしなければならず、同じ成果物を使えない問題があります。 この課題への対処としてクライアント側では環境変数を参照せずAPI経由でバックエンドから環境変数の値を取得したり、独自のplaceholderを環境変数を使いたい箇所に書いておいて実行前に自前で実際の値に置換してインライン化したりといった工夫がインターネットを検索すると紹介されていました。

この一度だけビルドして、環境ごとに変数を渡して振る舞いを変えたいという要望は一般的なので、Next.jsのGitHubリポジトリでも2023年1月からdiscussionされています。

github.com

ざっくりとdiscussionnを読むと、前述の環境ごとに別々にビルドしなければいけないという課題に対して2025年12月時点では次の案が検討されています。すなわち、next build を 1. ソースコードを実行可能な形式に変換するstep、2. NEXT_PUBLIC_ で始まる環境変数をインライン化するstep、3. SSG/ISRによる静的ページを生成するstepの合計3ステップに分けて実行できるようにするオプションを next build に追加することです。 すでに実装もされていて、 CLI: next CLI | Next.js--experimental-build-mode [mode] オプションで指定することで利用できます。 Next.js v13.5.3ではnext experimental-compilenext experimental-generateのサブコマンドとして実装されていましたが、Next.js v14.2.20 以降はnext build のオプションになりました。

ドキュメントでは [mode]compile / generate の2つが書かれていますが、実際にはgenerate-envを加えた3つのモードが指定できるようです。 それぞれのオプションの意味は以下の通りです。

  • compile: コンパイルのみ行う
  • generate-env: NEXT_PUBLIC_ で始まる環境変数のインライン化を行う
  • generate: NEXT_PUBLIC_ で始まる環境変数のインライン化に加えて、静的ページの生成を行う

NEXT_PUBLIC_ 変数を使わないなら next build --experimental-build-mode=compileコンパイルを実行するだけで良いです。 コンパイルが終わった段階では、仮に NEXT_PUBLIC_ で始まる環境変数を実行時に参照してもインライン化がなされていないので undefined になります。

NEXT_PUBLIC_ 変数を参照したい場合、静的ページを生成を生成するかで generate / generate-env のどちらかのビルドモードでビルドします。 静的ページを生成するなら next build --experimental-build-mode=generate 、ビルド時にDBに接続できない等の理由で静的ページを生成したくないなら next build --experimental-build-mode=generate-envを使います。

https://github.com/vercel/next.js/discussions/46544#discussioncomment-14743573 でサンプルリポジトリが公開されていました。

github.com

魅力的なオプションですが、standalone モードだとうまく動かないや ChunkLoadError が出て動作しないというコメントもあったので、まだ安定した実装にはなっていないようです。 また、環境変数のインライン化処理のロジックが微妙なようで一度変数に代入してから、その変数を参照するような場合に文法エラーがでました。 検索すると既にIssueとして報告されていました。 experimental-build-mode compile outputs invalid code · Issue #83863 · vercel/next.js

実験的なオプションなので当然と言えば当然ですが、採用する場合は念入りに動作確認した方が良いでしょう。

Next.js on Cloudflare WorkerでCron Triggersによる定期実行バッチを実装する

Cloudflare WorkersのCron Triggersを利用すると、定期的に実行されるバッチ処理をかけます。 Cron Triggers は、指定されたタイミングでscheduled() handlerを呼び出すというものです。

そのためWorkerにscheduled handlerを定義すれば良いのですが、ドキュメントを読んでもNext.jsを使った場合にCron Triggersを利用したバッチ実装方法がわかりにくかったので調べました。 [FEATURE] Workers scheduled · Issue #446 · opennextjs/opennextjs-cloudflare · GitHubでコメントされた内容を試して整理したものです。 Custom workerの詳細はCustom Worker - OpenNextを参照してください。

定期実行バッチの追加方法

Custom Worker を定義する

Next.jsで作ったアプリケーションをCloudflare workerにデプロイは、open-nextのCloudflare adapterを使って行います。 Cloudflare adapterは、Next.jsによるfetch handler(ルーティング処理部分)だけをexportしたworkerを生成するので、そのままだとCron Triggersを使えません。 そこで、Cloudflare adapterによるビルドで生成されたWorkerをラップして、Scheduled handlerが追加されたCustom Workerを定義します。

// path/to/custom-worker.ts

// open-nextのビルド結果からデフォルトハンドラをインポート
import { default as handler } from ".open-next/worker.js";
  
export default {
  // 既存のNext.jsルーティングを維持
  fetch: handler.fetch, 

  /**
   * Cron Triggersによる定期実行ハンドラ
   */
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext,
  ): Promise<void> {
    /* ここに定期実行したい処理を書く */
    /* 複数の定期実行バッチを定義したい場合は、`controller.cron` に入った起動時刻を元に分岐させます */
  },
};

custom-worker.ts の置き場所は基本的にどこでも良いのですが、.open-next/worker.js はビルドが終わるまで定義されていないのでlinterでエラーになったり、custom workerはNext.jsと関係ないファイルで特別扱いしないといけなかったりと悩ましいです。

理想的には、下記のようなフォルダ分けになるのでしょうか?

src
├── web // Next.js、React関係のファイル
│   ├── app
│   └── ... 
├── lib // ビジネスロジック
├── batch // 定期実行処理
├── ...
└─ worker
    └── custom-worker.ts

src/batch定期実行処理のエンドポイントとなる関数を定義し、その関数をsrc/worker/custom-worker.ts の scheduled handlerから呼び出す想定です。

wrangler.jsoncのworkerエントリーポイントをcustom workerに変更、およびCron Triggersの実行時刻を設定する

mainフィールドにcustom-worker.tsを、triggersフィールドに実行タイミングを設定します。

// wrangler.jsonc
{
  "main": "path/to/custom-worker.ts",
  "triggers": {
    "crons": [
      "*/3 * * * *",
    ]
  }
}

以上です。wrangler deploy でデプロイすれば、設定が反映されてバッチが実行されるようになります。

ローカルでの動作確認は、npx wrangler dev --test-scheduled で開発サーバーを実行した状態で、別端末から以下の形式でcurlリクエストを送信すればScheduled handlerを手動実行できます。

# 例: Cron式 "0 * * * *" に対応する実行をトリガー
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"

Claude Codeに追加した作業を効率化させるカスタムスラッシュコマンドと使い道

Claude codeにはスラッシュコマンドという / で始まるコマンドを定義できる機能がある。スラッシュコマンドを使うことで事前に定義したプロンプトをテンプレートとして呼び出せるため、繰り返し行う作業や定型タスクを効率化を図れる。 スラッシュコマンドは、~/.claude/commandsディレクトリ以下に command_name.md として作成することで自作できる。ファイル名がコマンドとなるので、実行時は/command_nameとする。

1ヶ月半ほどClaude codeと共にバイブコーディングをして基本的な使い方に慣れたので、最適な設定やスラッシュコマンドの活用法を試行錯誤している。最近、試験的に定義したスラッシュコマンドを紹介する。

レビュー

Claude codeには、/review が既に定義されているがレビュー結果の温度感がわかりにくい不満があったので自前でPRのレビューを行うコマンドを定義した。

工夫したポイントは、はじめにマージ可否の判断をしていること。レビュー観点を書き並べたほうが適切かもしれないが、今回はAIの回答を絞らないためにペルソナを与えるに留めた。

PRのレビューをしてください。

あなたは、非常に経験豊富なソフトウェアエンジニアとして、Pull Request をレビューしてください。
初めにマージ可否の立場を明確に表明してください。
指摘の際は、対応の温度感を伸べてください。

PRの作成、更新

次は、カレントブランチのPRを作成するコマンドを定義した。mainブランチにいたら新しくブランチを切ってからPR作成をするようにして、mainブランチのままpushしてしまわないようにした。英語にしたのは、日本語より指示に添いやすいと聞いたからだが、効果があるかはわからない。

Pull Request を作成、更新してください。

# Work Plan

If you run this command on the main branch, please create a new branch, commit, and push it before creating the PR.
If a review already exists for the current branch, please update the PR title and content.
If not, please add a new PR.

If .github/PULL_REQUEST_TEMPLATE.md is not available, please write your description according to the following rules:
At the beginning of the description, please include a few lines about the changes you made in the PR.
Then, write in more detail.
If there is anything that was not done in the PR, please list it as TODO.

# Restrictions

- Please write in Japanese.
- Please write your PR content according to .github/PULL_REQUEST_TEMPLATE.md, if available.

作られたPRの説明が整理しきれていないので、例を示したり、巨大なPRになっていたら分割を検討させるプロンプトを含めると、より好ましい応答になるかもしれない。

ペルソナの呼び出し

「あなたは熟練のソフトウェアエンジニアです」のようにAIへの指示でペルソナを定義すると思考を要する指示でより良い回答が得られやすい。タスクに合わせて毎回タイプするのは手間なので、ペルソナを楽に呼び出すコマンドを実験的に定義した。同僚との会話で思いついたジャストアイディアだが、実用的だと思う。

あなたは経験豊富で熟練したWebアプリケーションエンジニアです. $ARGUMENTS

次のように利用する。

/engineer Googleアカウント連携を使ったログイン機能を実装したい。実装計画を立ててください。

ペルソナの定義が非常に簡潔で余白を持っているので、「常に結論から話し始める」などの振る舞いの仕方も方向づけるなどすると応答が安定して改善できるかもしれない。

POやiOSアプリエンジニアなどのペルソナも同様に定義した。

POのペルソナは、プロトタイピングをするときに追加機能がコアコンセプトに沿っているかを判断する時の壁打ちに利用している。ただし、POとして振る舞えといってもAIはサービスが提供する価値が何かを知らないので、コアコンセプトを纏めたドキュメントを元に回答させている。AIによるPOの話題は、スクラムマスター研修でもあった。 tomato3713.hatenablog.com

その他のスラッシュコマンドにしたい作業

  • PRの分割
  • 不具合の原因調査、修正
  • 実装調査
  • ドキュメントの作成

まとめ

簡単なスラッシュコマンドを定義するだけでも作業の手間を大きく減らせた。

スラッシュコマンドにすると定型作業にAIの先導を受けられるので、頭をあまり使わずに進められる点が心地よかった。

AIと開発するときにも初期から始めた方が良いこと

開発時に標準的に使われるコマンドを整備する

Next.js のプロジェクトであれば pnpm run typecheck pnpm run build などがあることは当然だろうから、それらはpackage.jsonに書いて実行可能な状態にしておくと良い。AIは既存のコードを学習しているので、これらのコマンドを変換結果の確認に使おうとする。その時に独自のコマンド名にしていたり、コマンドを準備していないと実行に失敗して無駄な試行錯誤の時間を取られてしまう。

また、AIがコードを編集したとしてもレビューをするのは人間なので差分を見やすい状態に維持しておきたい。AIは大量にコードを書くし、Vibe Codingだと差分が大きくなりがち。ただでさえ差分が多いと量的にレビューが大変なのに加えてフォーマットのような本質的でない差分が含まれているとよりレビューが辛くなる。全部必要だが個人的な優先順は、ビルド→フォーマット→リンターなどの順。

AIにコンテキストを与えるためのファイルを整備する

READMEやCLAUDE.mdなど人が都度与えるプロンプトとは別にAIに事前情報を与えるファイルのこと。 リポジトリが小さくて、開発に必要なコンテキストが小さいうちから徐々に整備したほうが良い。 AIに自動生成させることもできるがリポジトリが巨大になってから生成しようとしても、情報が多くて扱いきれない。 AIにとっても全てを網羅し、整理整頓されたドキュメントを一度で生成することは現状はできないということもあるし、人間がそのドキュメントが正しいかどうかをチェックすることもできない。

コードの関心の分離が維持されるように努力する

AIがコードを書く場合でもコードの関心が分離されていないと、場当たりな実装をされてしまう。なので、意識的に整理されたコードを書いているかどうかをAIにレビューさせてリファクタリングさせたり、関心が分離した実装をしてくださいと依頼することが大事になる。コード量が少ない時に整理しておくと、その後開発がしやすいし人が実装する時と同様に少しずつ整理した方がうまくいきやすい。 AIにリファクタリングを支持することはできても、現状では、コードが乱雑になりエントロピーが増えていくことにAIが自律的に気がついて整理することはできない。まだ人の舵取りが必要な部分だと思う。 もちろん、自律的に関心の分離を維持させる方法もあるはずで、たとえば実装を依頼するときに「リファクタリングも検討してください」のような一文を追加しておくというやり方が考えられる。

利用している技術について人間も勉強する

AIは巷の情報を学習しているので、学習元によっては筋がよくない実装方法を提案していくることがある。TypeScriptであれば型が一致しない場合に as any のようなコードを大量に書いてきたり、Reactであれば不必要に useEffect を多用するような本質的ではない対応をされることがしばしばある。このような筋がよくない実装を油断していると混ぜてくるので、人間側もレビューで防いだり早期に軌道修正できるように学習が必要になる。

AIになぜXXXを使っているのか? のように質問すると回答がもらえるので、それを読んで確認するでも良いが、自分は公式ドキュメントなど信頼できる情報源を自分で読みにいく方が確実なので好み。

決まった手順があって複数回行うものの仕組みを整備する

たとえばDBのマイグレーションは、スキーマを書いて、ORM等でマイグレーションファイルを生成し、適用するというように手順がほぼ決まっている。こういったものはリポジトリが小さいうちに整備しておいた方が、後々コードが増えて影響範囲が広くなってから作るよりも初期の頃にしておいた方がコスパがいいと思う。 カスタムコマンドとしてリポジトリに追加してしまうのも良いだろう。 

影響がないコマンドを自動実行できるようにしておく

たとえば、型チェックやリンターの実行は、自分が開発しているリポジトリであれば変なコードは入っておらず実行されても安全なはず。そして、型チェックなどはAIが自分の書いたコードが動くかどうかを判定するために頻繁に実行するので都度の許可なしに実行できるようにするのがおすすめ。

また、反対にpackage-lock.json や go.sumなどのようにプログラムを介して生成されるファイルの書き込みを禁止しておくとAIが直接書き換えてしまって変に詰まることがなくなって良い。人間にとっては読み書きが厳しいテキストであってもAIにとっては他のファイルと同じなので必要と判断されたら編集されてしまうためだ。Claude Code であれば、permission rule として { "permissions": { "deny": [ "Write(go.sum)" ] }, ... } を設定に追加する。hooks やプロンプトで正しい方法を示すことも有効だろう。

こうして整理してみると、AIによるサポートを受けたコーディングも過去の蓄積があって成り立っていて、過去に良いとされ実践されてきたことの多くが今でも変わらず有効だとわかる。

フッ素加工フライパンとテキストエディタ

フッ素加工フライパンの食材がくっつきにくいメリットは理解しつつも、金属製の道具等の硬いもので擦ってはいけなかったり、物を重ねてはいけなかったりと気にかけるべき点が多い。フッ素加工を労って生きたくない。なので、コーティング無しのフライパンや鍋の方が使い勝手が実は良いだろうというようなことを考えていた。

ここでフッ素加工について想いを馳せると、フッ素加工とはテキストエディタにおける補完機能のようなものではないだろうか。補完機能のないエディタでプログラムを書くことは考えられない。勿論、補完なしにプログラミングすることもできるが、今よりも記憶力や注意力が求められることになる。

同じくフッ素加工の無いフライパンも考えられない。表面コーティングがなされていないフライパンでは食材を加熱する前に予熱し油を全体に馴染ませる手順を踏まなくなては食材が器具にくっついてしまう。フッ素加工があれば、予熱せずとも、油を馴染ませずとも食材はへばり付かず手軽に加熱できる。

現代では時間がないし調理技術を勉強しないので予熱をせず使いこなせることは非常に大事なのだろう。

テキストエディタの補完が人がプログラミングにかける注意力を補助しているように、フッ素加工も人の食材とフライパンを調理技術を補助している。

共通点がある一方でフッ素加工が解決する食材がくっついてしまう課題は、意外とコストのかからない手順で解決できる。くっつく調理の代表だと焼き魚や目玉焼きがあたるのだろうが、予熱をインターネットで調べて試すとくっつかせずに焼けた。素人でもできるのだから、予熱とは容易に再現可能で料理は暮らしに必須なので練習しやすい技術だと言えるはずだ。

難しいと思いがちな課題であったとしても、簡単に身につけられる技術で解決できることもある。 世の中には謎な技術を用いて実装されたかのようにみえるサービスやプログラミングライブラリがあるのだけれど、それらも、よく調べてみれば簡単なことしかしていないのかもしれない。先入観で敷居を上げずに新しい技術、ことに挑戦していきたい。