みなさん、AIコーディングエージェントへの指示ってどのように書いてますか?
「AをBして」みたいな抽象的な指示だとなかなか期待通りにならず、何回も指示・修正を繰り返してませんか?
期待通りに結果を出すにはより具体的な指示が必要です。「目的・修正箇所・問題点・期待する挙動」なんかを指示してる人もいるかと思います。
もちろんこれが必要な場面もあると思います。ただ僕はこのやり方に疲れました。
人間もAIエージェントも、コードの上では平等です。それならコード上で語らおうじゃありませんか!
ということで思いついたのが、TODOコメントをひたすらAIエージェントに解消させる作戦です。
ざっくりとAIエージェントにコードを書いてもらって、そこから期待通りのコードに寄せていく作業の中で実践できる方法です。
さっそく見ていきましょう!
※AIエージェントはJunieを、ModelはJunieデフォルトのGPT-5を使用しています。
TODOコメント駆動でAIエージェントに指示を出す
以下にサンプルコードを示します。
これはAIに生成してもらったコードに、TODOコメントを追加したものです。
リファクタリングしたいポイントがちらほらありますね。
func main() {
// TODO: 設定値の取得、ログ出力はまとめる
host := os.Getenv("APP_HOST")
if host == "" {
host = "0.0.0.0"
}
log.Println("app host =", host)
port := os.Getenv("APP_PORT")
if port == "" {
port = "8080"
}
log.Println("app port =", port)
// TODO: ベタ書きせず環境変数でのみ取得する
dbUser := os.Getenv("DB_USER")
if dbUser == "" {
dbUser = "postgres"
}
dbPass := os.Getenv("DB_PASSWORD")
if dbPass == "" {
dbPass = "postgres"
}
dbName := os.Getenv("DB_NAME")
if dbName == "" {
dbName = "app"
}
dbHost := os.Getenv("DB_HOST")
if dbHost == "" {
dbHost = "localhost"
}
dbPort := os.Getenv("DB_PORT")
if dbPort == "" {
dbPort = "5432"
}
// TODO: DSNの組み立てとエラーハンドリングをinitDB関数に切り出す
dsn := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
dbHost, dbPort, dbUser, dbPass, dbName,
)
var err error
db, err = sql.Open("postgres", dsn)
if err != nil {
log.Println("db open error:", err)
os.Exit(1)
}
if err := db.Ping(); err != nil {
log.Println("db ping error:", err)
os.Exit(1)
}
log.Println("db connected")
// TODO: サーバー起動処理をnewServer関数に切り出し
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
addr := fmt.Sprintf("%s:%s", host, port)
server := &http.Server{
Addr: addr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
log.Println("server starting at", addr)
if err := server.ListenAndServe(); err != nil {
log.Println("server error:", err)
}
}
人間として何を指示するか
人間である私の作業は大きく2つです。
- このコードにTODOコメントを追加する
- 上記のコードで追加されているTODOコメントです
- 「TODOを解消して」とJunieに指示を出す
- どのファイルに対してTODOを解消するのか指定します
たったそれだけです。早速実行してみましょう!
結果、以下のようにリファクタリングを実行してくれました。
type Config struct {
AppHost string
AppPort string
DBUser string
DBPass string
DBName string
DBHost string
DBPort string
}
func LoadConfig() (Config, error) {
cfg := Config{
AppHost: os.Getenv("APP_HOST"),
AppPort: os.Getenv("APP_PORT"),
DBUser: os.Getenv("DB_USER"),
DBPass: os.Getenv("DB_PASSWORD"),
DBName: os.Getenv("DB_NAME"),
DBHost: os.Getenv("DB_HOST"),
DBPort: os.Getenv("DB_PORT"),
}
// 必須環境変数チェック(ベタ書きのデフォルトは禁止)
if cfg.AppHost == "" || cfg.AppPort == "" ||
cfg.DBUser == "" || cfg.DBPass == "" || cfg.DBName == "" ||
cfg.DBHost == "" || cfg.DBPort == "" {
return Config{}, errors.New("missing required environment variables: APP_HOST, APP_PORT, DB_USER, DB_PASSWORD, DB_NAME, DB_HOST, DB_PORT")
}
return cfg, nil
}
func initDB(cfg Config) (*sql.DB, error) {
dsn := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBPass, cfg.DBName,
)
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, fmt.Errorf("db open error: %w", err)
}
if err := db.Ping(); err != nil {
_ = db.Close()
return nil, fmt.Errorf("db ping error: %w", err)
}
return db, nil
}
func newServer(addr string) *http.Server {
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
return &http.Server{
Addr: addr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
}
func main() {
cfg, err := LoadConfig()
if err != nil {
log.Println("config error:", err)
os.Exit(1)
}
// 設定のサマリをログ出力(パスワードはマスク)
log.Printf("app host = %s", cfg.AppHost)
log.Printf("app port = %s", cfg.AppPort)
log.Printf("db host = %s", cfg.DBHost)
log.Printf("db port = %s", cfg.DBPort)
log.Printf("db name = %s", cfg.DBName)
log.Printf("db user = %s", cfg.DBUser)
log.Printf("db pass = %s", mask(cfg.DBPass))
var dberr error
db, dberr = initDB(cfg)
if dberr != nil {
log.Println(dberr)
os.Exit(1)
}
defer db.Close()
log.Println("db connected")
addr := fmt.Sprintf("%s:%s", cfg.AppHost, cfg.AppPort)
server := newServer(addr)
log.Println("server starting at", addr)
if err := server.ListenAndServe(); err != nil {
log.Println("server error:", err)
}
}
指示せずともパスワードのログ出力をマスキングしてくれたり、必須の環境変数をチェックしてくれたり、いい感じですね!
Junieで繰り返し改善
まだまだ直したいところがあるよ!ということであれば新しくTODOコメントを追加し、「TODOを解消して」と指示できます。これを繰り返していくうちに「今回の修正だけ戻したい」と思うこともあるかもしれません。
そんなときJunieでは "Rollback" で一つ前の指示に戻れます!

「何回か繰り返してもなんか期待通りにならない!」というときも、"Rollback All" でチャット開始時点まで戻すこともできます!

便利ですね! ![]()
得たもの、失ったもの
このような方法を取ったことで、AIエージェントへの指示にかける時間がとても短くなりました。
特に僕みたいなAI向けに指示を書くのが面倒だったり、できるだけ自分が思っているとおり実装させたい人に向いている方法かもしれません。
または比較的経験が浅く、AIエージェントにどのように指示すれば良いか悩んでいる人にもオススメです。
しかしこのような使い方を初めた結果、Juniの作業ログが「TODO解消タスク」というログで埋め尽くされてしまいました。これはこれで、後々どこで何をしたか分からずに困る場面が出てくるかもしれませんね。。。

