Generate Go Code dari Spec: Iterasi dan Feedback Loop dengan Claude Code
Teknik iteratif generate kode Golang dari spesifikasi menggunakan Claude Code. Feedback loop yang efektif untuk menghasilkan implementasi yang sesuai spec Santekno Shop.
Generate Go Code dari Spec: Iterasi dan Feedback Loop
Cara generate go code dari spec claude code yang benar bukanlah satu prompt raksasa yang memuntahkan seluruh fitur sekaligus. Kita sudah punya spec yang solid, plan yang approved, dan task breakdown yang jelas. Sekarang saatnya melakukan apa yang paling terlihat: menulis kode.
Tapi “generate code” bukan berarti satu prompt → kode jadi → selesai. Di SDD, generate kode adalah proses iteratif dengan feedback loop yang ketat antara output AI dan spec yang menjadi referensi. Di artikel ini, kita bahas tekniknya secara konkret.
12.1 Iteratif vs One-Shot Code Generation
Ada dua pendekatan dalam menggunakan AI untuk generate kode. Yang pertama menghasilkan semuanya sekaligus dan sulit di-review.
1One-shot (tidak direkomendasikan untuk production):
2
3Prompt: "Implementasikan seluruh cancel order feature berdasarkan spec"
4Output: [200 baris kode sekaligus]
5Review: Sulit karena terlalu banyak yang perlu di-check sekaligus
6Risk: Miss AC/EC yang subtle, inconsistency dengan codebase existingPendekatan one-shot memindahkan seluruh beban verifikasi ke satu review besar. Bandingkan dengan pendekatan iteratif yang memecah generation menjadi langkah kecil yang bisa langsung diverifikasi.
1Iteratif (yang kita gunakan):
2
3Prompt 1: "Implementasikan CancelOrderInput dan CancelOrderUseCase struct"
4Output: [15 baris]
5Review: Quick, langsung verify terhadap spec
6
7Prompt 2: "Implementasikan method Execute"
8Output: [40 baris]
9Review: Trace ke setiap AC/EC
10
11Prompt 3: "Tambahkan error handling untuk case ketika order bukan PENDING"
12Output: [15 baris]
13Review: Verify error message sesuai spec AC9Pendekatan iteratif memberi kita kontrol lebih besar dan error yang lebih mudah diisolasi — setiap potongan kecil bisa dicocokkan ke spec sebelum lanjut.
12.2 Prompt Template untuk Code Generation
Agar generation konsisten, kita pakai template prompt untuk tiga jenis pekerjaan yang berulang: struct/interface, business logic, dan HTTP handler.
Template 1: Implementasi Struct dan Interface
Template pertama membatasi output hanya pada definisi struct dan constructor, menunda logika bisnis.
1Implementasikan [NamaStruct] untuk [NamaFeature] berdasarkan spec di [path-spec].
2
3Requirements:
4- Input: [field yang dibutuhkan]
5- Dependencies: [interfaces yang dibutuhkan]
6- Ikuti pola dari [referensi file]
7
8Jangan implementasikan method Execute dulu. Hanya struct definition dan constructor.Menahan Execute di tahap ini memaksa review fokus pada bentuk data dan dependency dulu — fondasi yang harus benar sebelum logika dibangun di atasnya.
Template 2: Implementasi Business Logic
Template kedua menyuntikkan AC dan EC yang relevan langsung ke prompt agar logika terikat ke spec.
1Sekarang implementasikan method Execute untuk [UseCase].
2
3Spec yang harus diikuti:
4- AC1: [description]
5- AC3: [description]
6- EC1: [description]
7
8Khusus untuk EC1 (concurrent cancel), gunakan pola yang sama
9dengan [referensi implementasi serupa].
10
11Setelah selesai, list setiap AC/EC dan di baris berapa di-implement.Meminta AI melisting AC/EC beserta nomor barisnya di akhir adalah trik ampuh: ia memaksa AI melakukan self-check terhadap spec sebelum kamu ikut memverifikasi.
Template 3: Implementasi HTTP Handler
Template ketiga menetapkan mapping error ke status HTTP secara eksplisit sehingga tidak ada yang ditebak.
1Implementasikan HTTP handler CancelOrder untuk Echo v4.
2
3Mapping dari usecase error ke HTTP response:
4- ErrOrderNotFound → 404 dengan body { error_code: "ORDER_NOT_FOUND", message: "order not found" }
5- OrderNotCancellableError → 409 dengan body { error_code: <dari error>, message: <dari error> }
6- Semua error lain → 500 dengan log error
7
8Success: 204 No Content (AC6 di spec)
9
10Pattern dari order_handler.go CreateOrder method.Dengan mapping error yang eksplisit, handler yang dihasilkan langsung sesuai kontrak API — bukan menebak status code yang “kira-kira benar”.
12.3 Generate CancelOrderUseCase: Step by Step
Mari terapkan template di atas untuk membangun CancelOrderUseCase secara bertahap, mulai dari struct hingga business logic.
Step 1: Struct dan Constructor
Prompt berikut meminta struct, interface publisher, error types, dan constructor — tanpa Execute dulu. Perhatikan dua baris pembukanya: prompt selalu merujuk spec dan plan lebih dulu, baru merinci. Tanpa rujukan itu, daftar di bawahnya terbaca seolah karangan sendiri — padahal seluruh isinya diturunkan dari plan yang sudah di-review di artikel 10.
1Baca specs/order/cancel-order.md dan docs/plans/cancel-order-2025-07-15.md.
2Kerjakan TASK 5 (UseCase Implementation) tahap pertama saja: struct dan
3constructor, BELUM method Execute.
4
5Sesuai plan, yang dibuat:
6
71. CancelOrderInput struct: OrderID uuid.UUID, UserID uuid.UUID
82. Interface publisher (Kafka): PublishOrderCancelled(ctx, orderID, userID uuid.UUID) error
93. CancelOrderUseCase struct: repo (OrderRepository), publisher (interface baru)
104. Constructor: NewCancelOrderUseCase(repo, publisher) *CancelOrderUseCase
115. Sentinel errors: ErrOrderNotFound; OrderNotCancellableError sebagai custom type
12
13Package: order. Import: context, fmt, errors, github.com/google/uuid, log/slog
14Ikuti naming convention dari internal/usecase/order/create_order.goDengan spec dan plan sebagai pembuka, kelima butir di atas berubah peran: bukan lagi daftar perintah, melainkan alat verifikasi. Kalau keluaran Claude menyimpang dari kelimanya, berarti ada yang putus antara plan dan eksekusi — dan itu ketahuan sekarang, bukan saat code review.
Output dari Claude adalah kerangka struct dan error yang bersih, siap diisi logika di step berikutnya.
1package order
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "log/slog"
8
9 "github.com/google/uuid"
10 domainorder "github.com/santekno/santekno-shop/internal/domain/order"
11)
12
13// Sentinel errors
14var ErrOrderNotFound = errors.New("order not found")
15
16// OrderCancelledPublisher defines the interface for publishing cancel events
17type OrderCancelledPublisher interface {
18 PublishOrderCancelled(ctx context.Context, orderID, userID uuid.UUID) error
19}
20
21// CancelOrderInput contains input data for cancelling an order
22type CancelOrderInput struct {
23 OrderID uuid.UUID
24 UserID uuid.UUID
25}
26
27// OrderNotCancellableError is returned when order status prevents cancellation
28type OrderNotCancellableError struct {
29 OrderID uuid.UUID
30 CurrentStatus domainorder.Status
31}
32
33func (e *OrderNotCancellableError) Error() string {
34 return fmt.Sprintf("order %s cannot be cancelled: status is %s",
35 e.OrderID, e.CurrentStatus)
36}
37
38func (e *OrderNotCancellableError) ErrorCode() string {
39 return "ORDER_NOT_CANCELLABLE"
40}
41
42// CancelWindowExpiredError is returned when cancellation window has passed
43type CancelWindowExpiredError struct {
44 OrderID uuid.UUID
45}
46
47func (e *CancelWindowExpiredError) Error() string {
48 return "cancellation window has expired"
49}
50
51func (e *CancelWindowExpiredError) ErrorCode() string {
52 return "CANCEL_WINDOW_EXPIRED"
53}
54
55// CancelOrderUseCase handles the cancel order business logic
56type CancelOrderUseCase struct {
57 repo domainorder.Repository
58 publisher OrderCancelledPublisher
59}
60
61// NewCancelOrderUseCase creates a new CancelOrderUseCase
62func NewCancelOrderUseCase(
63 repo domainorder.Repository,
64 publisher OrderCancelledPublisher,
65) *CancelOrderUseCase {
66 return &CancelOrderUseCase{
67 repo: repo,
68 publisher: publisher,
69 }
70}Review cepat: struct definition sudah benar, dan error types sudah menyertakan method ErrorCode() yang nanti dipakai handler untuk mapping — pondasi yang rapi sebelum menulis logika.
Step 2: Method Execute
Prompt berikut menjabarkan flow Execute berdasarkan AC dan EC dari spec.
1Sekarang implementasikan method Execute(ctx, input CancelOrderInput) error.
2
3Flow berdasarkan spec:
41. GetByIDAndUserID → jika nil, return ErrOrderNotFound (AC7, AC8)
52. Cek order.CanBeCancelled():
6 - Jika status bukan PENDING → return OrderNotCancellableError (AC9)
7 - Jika > 15 menit → return CancelWindowExpiredError (AC10)
83. repo.CancelWithStockRestore → atomic cancel (AC5, EC1, EC4 di repo)
94. publisher.PublishOrderCancelled → best effort (EC2: jika gagal, log warning, continue)
10
11Error wrapping: fmt.Errorf("cancel order: %w", err)
12Logging: slog.WarnContext untuk Kafka failureOutput-nya mengikuti flow tersebut dengan komentar yang memetakan setiap langkah ke AC/EC.
1// Execute performs the order cancellation.
2// Implements: specs/order/cancel-order.md v1.3
3// AC1-AC6: main cancellation flow
4// AC7-AC10: error cases
5// EC1, EC4: handled by repo.CancelWithStockRestore (SELECT FOR UPDATE + rollback)
6// EC2: Kafka failure is best-effort
7// EC3: DB timeout — belum ditangani, lihat spec
8func (uc *CancelOrderUseCase) Execute(ctx context.Context, input CancelOrderInput) error {
9 // AC7, AC8: Get order — returns nil if not found OR not owned by user
10 order, err := uc.repo.GetByIDAndUserID(ctx, input.OrderID, input.UserID)
11 if err != nil {
12 return fmt.Errorf("cancel order: get order: %w", err)
13 }
14 if order == nil {
15 return ErrOrderNotFound
16 }
17
18 // AC3, AC4, AC9, AC10: Validate cancellability (status + time window)
19 if !order.CanBeCancelled() {
20 if order.Status != domainorder.StatusPending {
21 return &OrderNotCancellableError{
22 OrderID: input.OrderID,
23 CurrentStatus: order.Status,
24 }
25 }
26 // Status is PENDING but window expired
27 return &CancelWindowExpiredError{OrderID: input.OrderID}
28 }
29
30 // AC5, EC1, EC4: Atomic cancel with stock restore (SELECT FOR UPDATE in repo)
31 if err := uc.repo.CancelWithStockRestore(ctx, input.OrderID); err != nil {
32 return fmt.Errorf("cancel order: cancel with stock restore: %w", err)
33 }
34
35 // EC2: Best-effort event publish
36 if err := uc.publisher.PublishOrderCancelled(ctx, input.OrderID, input.UserID); err != nil {
37 // EC2: Kafka failure doesn't rollback the cancellation
38 slog.WarnContext(ctx, "failed to publish ORDER_CANCELLED event",
39 "order_id", input.OrderID,
40 "error", err)
41 }
42
43 return nil // AC6: 204 No Content from handler
44}Trace terhadap spec menunjukkan AC7/AC8, AC9, AC10, dan EC2 sudah tertangani. Namun ada satu isu: order.CanBeCancelled() mengembalikan false untuk dua kondisi berbeda (status salah DAN window expired), sehingga perlu diperjelas. Prompt fix berikut menawarkan dua opsi.
1Ada issue di Execute: CanBeCancelled() return false untuk dua kondisi berbeda
2(wrong status dan expired window), tapi kita return error yang berbeda.
3
4Opsi 1: Ubah CanBeCancelled() menjadi dua method terpisah
5Opsi 2: CanBeCancelled() kembalikan error bukan bool
6
7Rekomendasi?Menemukan ambiguitas seperti ini di review adalah bukti feedback loop bekerja: bug logika halus tertangkap sebelum masuk ke commit, bukan setelah muncul di production.
12.4 Feedback Loop: Identifikasi dan Fix Issues
Feedback loop yang baik dalam SDD mengikuti siklus yang berulang: generate, review terhadap spec, identifikasi gap, request fix, generate ulang. Diagram berikut merangkum siklusnya.
1Generate kode → Review terhadap spec → Identifikasi gap → Request fix → Generate ulangSiklus tertutup ini memastikan setiap iterasi mendekatkan kode ke spec, bukan menjauh. Untuk mengeksekusinya, setelah setiap generation buat spec compliance check dengan prompt berikut.
1Kode yang baru dihasilkan:
2@internal/usecase/order/cancel_order.go
3
4Spec yang harus dipenuhi:
5@specs/order/cancel-order.md (AC1-AC10, EC1-EC4)
6
7Untuk setiap AC dan EC, apakah kode sudah mengimplementasikannya?
8Output format:
9✅ AC1: [implemented di baris X]
10❌ AC4: [tidak ada implementasi]
11⚠️ EC2: [partially implemented - missing X]Format output dengan simbol ✅/❌/⚠️ ini mengubah “sepertinya sudah sesuai” menjadi checklist eksplisit yang menyorot gap secara langsung.
12.5 Generate Repository Implementation
Setelah usecase, kita generate dua method repository yang menjadi tulang punggung operasi cancel. Prompt berikut menyertakan SQL persis dan aturan return value.
1Implementasikan dua method di internal/repository/postgres/order_repository.go:
2
31. GetByIDAndUserID(ctx, orderID, userID uuid.UUID) (*domain.Order, error)
4 SELECT ... FROM orders WHERE id=$1 AND user_id=$2 AND deleted_at IS NULL
5 Return nil, nil jika tidak ditemukan (bukan error)
6
72. CancelWithStockRestore(ctx, orderID uuid.UUID) error (dalam transaction):
8 SELECT id FROM orders WHERE id=$1 FOR UPDATE -- EC1: concurrent lock
9 UPDATE orders SET status='CANCELLED' WHERE id=$1 AND status='PENDING'
10 UPDATE products SET stock = stock + oi.quantity FROM order_items oi ...
11 Gunakan pgx.BeginTx, tx.Exec, tx.QueryRow pattern dari existing codeOutput menerjemahkan SQL itu menjadi Go idiomatik dengan transaction dan locking yang benar.
1// GetByIDAndUserID retrieves an order by ID and user ID.
2// Returns nil, nil if not found (handles both not found and not owner cases)
3// to avoid exposing order existence to unauthorized users.
4func (r *orderRepository) GetByIDAndUserID(
5 ctx context.Context,
6 orderID, userID uuid.UUID,
7) (*domain.Order, error) {
8 query := `
9 SELECT id, user_id, status, total_amount_cents, created_at, updated_at
10 FROM orders
11 WHERE id = $1 AND user_id = $2 AND deleted_at IS NULL`
12
13 var order domain.Order
14 err := r.pool.QueryRow(ctx, query, orderID, userID).Scan(
15 &order.ID,
16 &order.UserID,
17 &order.Status,
18 &order.TotalAmountCents,
19 &order.CreatedAt,
20 &order.UpdatedAt,
21 )
22 if err != nil {
23 if errors.Is(err, pgx.ErrNoRows) {
24 return nil, nil // not found or not owner → return nil
25 }
26 return nil, fmt.Errorf("get order by id and user id: %w", err)
27 }
28
29 return &order, nil
30}
31
32// CancelWithStockRestore cancels an order and restores all item stock atomically.
33// Implements EC1 (concurrent cancel) via SELECT FOR UPDATE.
34// Implements EC4 (partial failure) via transaction rollback.
35func (r *orderRepository) CancelWithStockRestore(ctx context.Context, orderID uuid.UUID) error {
36 tx, err := r.pool.BeginTx(ctx, pgx.TxOptions{})
37 if err != nil {
38 return fmt.Errorf("begin transaction: %w", err)
39 }
40 defer tx.Rollback(ctx) // noop if committed
41
42 // EC1: Lock the order to prevent concurrent cancellation
43 var orderStatus domain.Status
44 err = tx.QueryRow(ctx,
45 `SELECT status FROM orders WHERE id = $1 FOR UPDATE`,
46 orderID,
47 ).Scan(&orderStatus)
48 if err != nil {
49 return fmt.Errorf("lock order: %w", err)
50 }
51
52 // Double-check status (in case it changed between GetByIDAndUserID and here)
53 if orderStatus != domain.StatusPending {
54 return &domain.OrderNotCancellableError{CurrentStatus: orderStatus}
55 }
56
57 // Update order status to CANCELLED
58 result, err := tx.Exec(ctx,
59 `UPDATE orders SET status = 'CANCELLED', updated_at = NOW() WHERE id = $1`,
60 orderID,
61 )
62 if err != nil {
63 return fmt.Errorf("update order status: %w", err)
64 }
65 if result.RowsAffected() == 0 {
66 return fmt.Errorf("order %s not found for cancellation", orderID)
67 }
68
69 // Restore stock for all order items
70 _, err = tx.Exec(ctx, `
71 UPDATE products p
72 SET stock = p.stock + oi.quantity, updated_at = NOW()
73 FROM order_items oi
74 WHERE oi.order_id = $1 AND oi.product_id = p.id`,
75 orderID,
76 )
77 if err != nil {
78 return fmt.Errorf("restore stock: %w", err) // EC4: tx.Rollback called by defer
79 }
80
81 if err := tx.Commit(ctx); err != nil {
82 return fmt.Errorf("commit transaction: %w", err)
83 }
84
85 return nil
86}Perhatikan defer tx.Rollback(ctx) dan double-check status di dalam transaction: keduanya bersama-sama memenuhi EC1 dan EC4, memastikan cancel tetap atomic bahkan saat ada request konkuren.
12.6 Generate HTTP Handler
Lapisan terakhir adalah handler yang menerjemahkan error usecase menjadi status HTTP. Prompt berikut menetapkan mapping error lengkap.
1Implementasikan CancelOrder method di OrderHandler (Echo v4).
2
3Error mapping:
4- ErrOrderNotFound → 404, { error_code: "ORDER_NOT_FOUND" }
5- *OrderNotCancellableError → 409, { error_code: err.ErrorCode(), message: err.Error() }
6- *CancelWindowExpiredError → 409, { error_code: err.ErrorCode(), message: err.Error() }
7- Semua error lain → 500, log dengan slog.ErrorContext, { error_code: "INTERNAL_ERROR" }
8
9Path param :id (UUID). Auth: userID dari c.Get("userID").(uuid.UUID). Success: 204.Output memakai errors.Is dan errors.As untuk membedakan tipe error dengan tepat.
1// CancelOrder handles DELETE /api/v1/orders/:id
2// Implements specs/order/cancel-order.md v1.3
3func (h *OrderHandler) CancelOrder(c echo.Context) error {
4 orderID, err := uuid.Parse(c.Param("id"))
5 if err != nil {
6 return c.JSON(http.StatusBadRequest, ErrorResponse{
7 ErrorCode: "INVALID_REQUEST",
8 Message: "invalid order ID format",
9 })
10 }
11
12 userID, ok := c.Get("userID").(uuid.UUID)
13 if !ok {
14 return c.JSON(http.StatusInternalServerError, ErrorResponse{
15 ErrorCode: "INTERNAL_ERROR",
16 Message: "internal server error",
17 })
18 }
19
20 input := cancelorder.CancelOrderInput{
21 OrderID: orderID,
22 UserID: userID,
23 }
24
25 if err := h.cancelOrderUC.Execute(c.Request().Context(), input); err != nil {
26 var notCancellable *cancelorder.OrderNotCancellableError
27 var windowExpired *cancelorder.CancelWindowExpiredError
28
29 switch {
30 case errors.Is(err, cancelorder.ErrOrderNotFound):
31 return c.JSON(http.StatusNotFound, ErrorResponse{
32 ErrorCode: "ORDER_NOT_FOUND",
33 Message: "order not found",
34 })
35 case errors.As(err, ¬Cancellable):
36 return c.JSON(http.StatusConflict, ErrorResponse{
37 ErrorCode: notCancellable.ErrorCode(),
38 Message: notCancellable.Error(),
39 })
40 case errors.As(err, &windowExpired):
41 return c.JSON(http.StatusConflict, ErrorResponse{
42 ErrorCode: windowExpired.ErrorCode(),
43 Message: windowExpired.Error(),
44 })
45 default:
46 slog.ErrorContext(c.Request().Context(), "cancel order failed",
47 "order_id", orderID,
48 "user_id", userID,
49 "error", err)
50 return c.JSON(http.StatusInternalServerError, ErrorResponse{
51 ErrorCode: "INTERNAL_ERROR",
52 Message: "internal server error",
53 })
54 }
55 }
56
57 return c.NoContent(http.StatusNoContent) // AC6: 204
58}Pemakaian errors.As untuk tipe error konkret memastikan status 409 hanya muncul untuk error yang benar, sementara error tak terduga jatuh ke 500 dengan log — pemisahan yang persis diminta spec.
12.7 Final Spec Compliance Verification
Setelah semua kode selesai, lakukan final compliance check yang mencakup semua file dan semua AC/EC. Prompt berikut meminta output dalam bentuk tabel yang mudah diaudit.
1Lakukan spec compliance check untuk implementasi cancel order.
2
3Files yang diimplementasikan:
4- internal/domain/order/entity.go (CanBeCancelled method)
5- internal/usecase/order/cancel_order.go
6- internal/repository/postgres/order_repository.go
7- internal/delivery/http/handler/order_handler.go
8
9Spec: specs/order/cancel-order.md v1.3
10
11Untuk setiap AC (AC1-AC10) dan EC (EC1-EC4):
121. Di mana di-implementasikan (file + baris)?
132. Apakah implementasinya sudah benar?
143. Apakah ada test yang cover ini?
15
16Output tabel:
17| AC/EC | File | Status | Test? |Tabel compliance akhir ini menjadi bukti tertulis bahwa setiap AC dan EC punya rumah di kode dan test — dokumen yang berharga saat PR di-review maupun saat audit berbulan kemudian.
12.8 Menangani Kode yang Tidak Sesuai Spec
Kadang kode yang dihasilkan tidak sesuai spec. Tiga skenario berikut menunjukkan cara menulis prompt fix yang spesifik dan langsung menyasar akar masalah.
Scenario: Response format salah
1Ada inconsistency: spec AC6 bilang response 204 No Content,
2tapi handler return 200 dengan body.
3
4Tolong fix handler CancelOrder untuk return 204 No Content
5menggunakan c.NoContent(http.StatusNoContent).Scenario: Error code tidak sesuai
1Spec bilang error_code harus "ORDER_NOT_CANCELLABLE"
2tapi kode menggunakan "NOT_CANCELLABLE".
3
4Update OrderNotCancellableError.ErrorCode() untuk return
5"ORDER_NOT_CANCELLABLE" sesuai spec.Scenario: Edge case terlewat
1EC1 (concurrent cancel via SELECT FOR UPDATE) tidak ada di repository.
2Tambahkan SELECT id FROM orders WHERE id = $1 FOR UPDATE
3sebelum UPDATE orders SET status = 'CANCELLED'.Ketiga prompt fix ini punya pola sama: sebut spec yang dilanggar, tunjuk kode yang salah, dan berikan perbaikan konkret — presisi inilah yang membuat AI memperbaiki tepat sasaran tanpa merombak hal lain.
12.9 Iterasi untuk Improving Code Quality
Setelah fungsionalitas selesai, kita bisa iterasi untuk kualitas. Prompt berikut meminta peningkatan logging, observability, dan keamanan tanpa mengubah behavior.
1Kode CancelOrderUseCase.Execute sudah fungsional.
2Sekarang improve quality:
3
41. Tambahkan structured logging untuk setiap step penting:
5 - Log INFO saat cancel berhasil (order_id, user_id)
6 - Log WARNING saat Kafka gagal
7
82. Tambahkan request ID dari context ke semua log entries
9
103. Pastikan semua error message user-friendly (tidak expose internal details)
11
124. Review apakah ada potensi nil pointer panicMemisahkan fase “buat berfungsi” dan “buat berkualitas” menjaga review tetap fokus: kamu tidak mencampur pertanyaan benar-atau-salah dengan pertanyaan bagus-atau-buruk dalam satu putaran.
12.10 Code Generation untuk Test
Test generation juga bisa dilakukan secara iteratif. Prompt berikut menghasilkan satu test case dengan setup dan assertion yang eksplisit.
1Generate unit test untuk TestCancelOrder_Success.
2
3Setup:
4- Mock OrderRepository yang return order PENDING created 5 menit yang lalu
5- Mock OrderCancelledPublisher yang return nil (success)
6- CancelOrderUseCase dengan kedua mock
7
8Test assertions:
9- Execute return nil (no error)
10- repo.GetByIDAndUserID dipanggil dengan correct args
11- repo.CancelWithStockRestore dipanggil dengan correct orderID
12- publisher.PublishOrderCancelled dipanggil dengan correct args
13
14Gunakan testify/suite pattern dari create_order_test.goDengan menuliskan assertion yang diharapkan di prompt, test yang dihasilkan memverifikasi behavior yang kamu maksud — bukan sekadar memanggil fungsi dan berharap tidak error.
12.11 Strategi untuk Codebase yang Besar
Di codebase yang besar dengan banyak file, context yang diberikan ke AI harus dikelola. Dua strategi berikut menjaga context window tetap fokus dan relevan.
Strategi 1: Context yang Minimal
1Context untuk task ini (hanya file yang diperlukan):
2- internal/domain/order/entity.go (domain types)
3- internal/domain/order/repository.go (interface)
4- internal/usecase/order/create_order.go (reference pattern)
5
6JANGAN load seluruh codebase.Strategi 2: Incremental Context Building
1Iterasi 1: Hanya entity.go dan repository.go
2Iterasi 2: Tambahkan create_order.go untuk pattern reference
3Iterasi 3: Tambahkan handler jika perlu lihat response formatMembatasi context bukan hanya soal menghemat token — memberi file yang terlalu banyak justru mengaburkan pola yang ingin kamu tiru, sehingga output jadi kurang konsisten.
12.12 Handling Kode yang Sudah Ada
Ketika perlu modify kode existing (bukan create baru), prompt harus menegaskan batasan agar AI tidak menulis ulang file. Contoh berikut membatasi perubahan hanya pada penambahan.
1File: internal/domain/order/entity.go
2Kode existing (lihat file):
3@internal/usecase/order/cancel_order.go
4
5Perlu ditambahkan:
61. Konstanta StatusCancelled ke enum Status
72. Method CanBeCancelled() bool yang return true jika:
8 - Status == StatusPending
9 - time.Since(CreatedAt) <= 15*time.Minute
10
11PENTING: Jangan ubah kode yang sudah ada, hanya tambahkan.
12Tunjukkan hanya bagian yang ditambahkan (bukan seluruh file).Instruksi “jangan ubah yang sudah ada” ini penting: tanpanya, AI cenderung “memperbaiki” kode di sekitarnya dan menghasilkan diff besar yang sulit di-review serta berisiko mengubah behavior.
12.13 Review Kode yang Dihasilkan AI
Setiap code generation harus diikuti review. Checklist berikut memandu review terhadap correctness, idiom Go, arsitektur, dan keamanan.
1## Code Review Checklist (AI-Generated)
2
3### Correctness
4- [ ] Output sesuai dengan spec (trace setiap AC/EC)
5- [ ] Error handling lengkap untuk semua case
6- [ ] Tidak ada nil pointer risk
7- [ ] Context di-propagate ke semua panggilan
8
9### Go Idioms
10- [ ] Error wrapping menggunakan fmt.Errorf("...: %w", err)
11- [ ] Tidak ada panic yang tidak disengaja
12- [ ] Interface digunakan dengan benar
13- [ ] Logging menggunakan log/slog
14
15### Architecture
16- [ ] Tidak ada layer violation (domain tidak import delivery)
17- [ ] Dependency injection via constructor, bukan global
18- [ ] Tidak ada hardcoded values yang seharusnya configurable
19
20### Security
21- [ ] Tidak ada sensitive data di log
22- [ ] Input tidak digunakan langsung dalam SQL (parameterized queries)Checklist ini mengingatkan bahwa “kode yang compile” belum tentu “kode yang benar”: layer violation dan SQL injection lolos build tapi tertangkap di review yang disiplin.
12.14 Handling AI Hallucination dalam Code Generation
AI kadang “mengarang” API atau pattern yang tidak ada. Pengenalan red flags dan verifikasi cepat berikut membantu menangkapnya lebih awal.
Red flags yang perlu diwaspadai: import path yang tidak familiar, method/function yang tidak ada di stdlib atau library yang digunakan, dan pattern yang berbeda dari codebase existing.
Verifikasi paling sederhana adalah membiarkan compiler menjadi hakim. Perintah berikut menangkap hallucination berupa import atau fungsi yang tidak ada.
1# Setelah menerima generated code
2go build ./...
3# Jika ada import error atau undefined function → AI mungkin hallucinateJika go build gagal karena undefined symbol, itu sinyal kuat AI mengarang API — jauh lebih cepat ketahuan lewat compiler daripada lewat mata. Untuk mencegahnya sejak awal, batasi package yang boleh dipakai lewat prompt berikut.
1Implementasikan hanya menggunakan:
2- Package stdlib Go: context, fmt, errors, log/slog
3- github.com/jackc/pgx/v5 (pgx, pgx/v5/pgxpool)
4- github.com/google/uuid
5- Internal packages dari project ini
6
7Jangan gunakan package lain tanpa konfirmasi dulu.Membatasi daftar package yang diizinkan mempersempit ruang bagi AI untuk mengarang dependency — jika ia butuh sesuatu di luar daftar, ia harus bertanya dulu, bukan menebak.
12.15 Version Control untuk Iterasi Kode
Setiap iterasi yang signifikan sebaiknya di-snapshot. Contoh berikut memperlihatkan commit WIP granular yang menandai batas antar tahap generation.
1# Setelah generate struct dan types (Task 5a)
2git add internal/usecase/order/cancel_order.go
3git commit -m "feat(order): add cancel order types and constructor
4
5WIP: struct and constructor only, Execute not yet implemented"
6
7# Setelah generate Execute method (Task 5b)
8git add internal/usecase/order/cancel_order.go
9git commit -m "feat(order): implement CancelOrderUseCase.Execute
10
11Implements specs/order/cancel-order.md AC1-AC10, EC2
12EC1, EC4 handled by repository transaction"Snapshot granular ini memungkinkan rollback ke state yang masih baik jika iterasi berikutnya menghasilkan sesuatu yang buruk — jaring pengaman yang murah tapi sering menyelamatkan.
12.16 Menggunakan AI sebagai Code Reviewer
Setelah kode selesai, AI juga bisa dipakai untuk review sebelum PR. Prompt berikut memposisikan Claude sebagai senior Go engineer dengan pertanyaan review yang terarah.
1Berikut implementasi cancel order yang sudah selesai:
2@internal/usecase/order/cancel_order.go
3
4Tolong review sebagai senior Go engineer:
51. Apakah ada Go anti-pattern?
62. Apakah error handling sudah idiomatic Go?
73. Apakah ada potential goroutine leak atau resource leak?
84. Apakah ada cara yang lebih readable untuk bagian X?
95. Apakah komentar cukup jelas?
10
11Beri feedback yang spesifik dengan saran perbaikan yang actionable.Menggunakan AI sebagai reviewer kedua menangkap masalah yang mudah terlewat saat kamu sendiri yang menulis prompt-nya — asalkan kamu tetap menyaring sarannya, bukan menerimanya buta.
12.17 Performance Consideration saat Code Generation
Saat generate kode untuk endpoint yang performance-sensitive, sertakan pertimbangan performa di prompt. Contoh berikut meminta index usage dan proyeksi biaya query.
1Implementasikan GetByIDAndUserID dengan mempertimbangkan:
2- Query harus menggunakan index idx_orders_user_id_status (sudah ada)
3- Tidak perlu SELECT * (hanya field yang diperlukan)
4- Context cancellation harus di-respect
5
6Tambahkan komentar SQL EXPLAIN yang menunjukkan index usage:
7-- EXPLAIN: uses idx_orders_user_id_status for WHERE user_id = $2 AND status = ...
8-- Estimated cost: index scan (tidak table scan)Menuliskan ekspektasi performa di prompt membuat AI menghasilkan query yang sadar-index sejak awal, alih-alih kode yang benar secara fungsional tapi lambat di production.
12.18 Tips & Gotchas
💡 Tip 1: Satu generation, satu review — jangan queue banyak generation tanpa review di antaranya. Review setelah setiap generation lebih mudah menangkap issue lebih awal.
💡 Tip 2: Selalu sertakan “reference file” dalam prompt — “Ikuti pola dari create_order.go” jauh lebih efektif dari “ikuti best practice.” Claude butuh contoh konkret dari codebase yang ada.
💡 Tip 3: Minta komentar inline yang menjelaskan spec mapping — komentar seperti // Implements AC5: stock restored atomically membuat code review dan future maintenance jauh lebih mudah.
💡 Tip 4: Verifikasi build setelah setiap generation — go build ./... setelah setiap generation iteration menangkap compile error sebelum menumpuk.
⚠️ Gotcha 1: AI bisa generate kode yang “terlihat benar” tapi behavior-nya salah — terutama untuk edge case yang subtle. Selalu trace setiap EC ke implementasi.
⚠️ Gotcha 2: Context window bisa penuh untuk kode yang panjang — jika session sudah panjang, Claude mulai “lupa” kode yang dihasilkan sebelumnya. Start fresh session untuk setiap task besar.
⚠️ Gotcha 3: Jangan buta menerima saran “improvement” — AI kadang suggest “improvement” yang sebenarnya mengubah behavior. Selalu check apakah “improvement” masih sesuai spec.
⚠️ Gotcha 4: Kode yang generated tidak selalu idiomatic Go — review dengan sudut pandang “apakah ini cara Go yang idiomatic?” bukan hanya “apakah ini fungsional?”
12.19 Membandingkan Output dengan Referensi Implementation
Cara efektif untuk meningkatkan kualitas adalah membandingkan output baru dengan implementasi yang sudah ada. Prompt berikut meminta Claude mencari inkonsistensi antara Cancel dan Create.
1Bandingkan CancelOrderUseCase.Execute yang baru di-generate
2dengan CreateOrderUseCase.Execute yang sudah ada:
3@internal/usecase/order/cancel_order.go dan @specs/order/cancel-order.md
4
5Identifikasi:
61. Inconsistency dalam error handling style
72. Inconsistency dalam logging pattern
83. Inconsistency dalam struct naming atau field naming
94. Bagian dari Cancel yang harus disesuaikan dengan pola di Create
10
11Tujuan: keduanya harus feel seperti ditulis oleh orang yang sama.Menjadikan “ditulis oleh orang yang sama” sebagai target menjaga codebase tetap koheren — konsistensi lintas fitur inilah yang membuat kode mudah dinavigasi tim di kemudian hari.
12.20 Ringkasan
Generate Go code dari spec adalah proses iteratif yang terstruktur, bukan satu-shot dump.
Workflow yang terbukti efektif: Generate struct → review → generate business logic → review trace terhadap spec → generate tests → final compliance check.
Feedback loop yang ketat: Setelah setiap generation, lakukan spec compliance check. Identifikasi gap secara eksplisit dan request fix yang spesifik.
Kualitas melebihi kuantitas: Lebih baik generate 20 baris yang sempurna dari satu prompt daripada 200 baris yang perlu heavy editing. Mulai kecil, iterasi.
AI sebagai pair programmer, bukan code monkey: Kamu yang memutuskan design dan approach, AI yang mengeksekusi. Jangan surrender keputusan arsitektural ke AI.
Di artikel berikutnya, kita bahas bagaimana menulis unit test dari spec — menggunakan acceptance criteria sebagai test cases untuk memastikan implementasi benar-benar sesuai spec.