Anatomi Spesifikasi yang Baik: User Story, AC, Edge Case untuk SDD Golang
Panduan lengkap menulis spesifikasi yang baik: User Story, Acceptance Criteria, dan Edge Case yang efektif untuk Specification-Driven Development dengan Claude Code di Golang.
Anatomi Spesifikasi yang Baik: User Story, Acceptance Criteria, Edge Case
Spec yang buruk menghasilkan kode yang buruk — bahkan dengan AI secanggih apapun. Spec yang baik menghasilkan kode yang tepat sasaran, mudah ditest, dan bisa diverifikasi oleh siapapun di tim.
Tapi apa yang membuat spec “baik”? Di artikel ini, kita bedah anatomi spesifikasi yang baik golang SDD: setiap komponen, apa yang membuat masing-masing berhasil atau gagal, dan bagaimana menulisnya untuk proyek Golang production.
05.1 Mengapa Kualitas Spec Lebih Penting dari Kuantitas
Ada kesalahpahaman umum: spec yang baik adalah spec yang panjang dan detail. Kenyataannya justru sebaliknya.
Spec yang efektif adalah spec yang:
- Bisa dibaca dan dipahami dalam 5-10 menit
- Setiap acceptance criteria bisa langsung ditulis sebagai test
- Tidak ada ambiguitas yang tersisa setelah dibaca
- Developer bisa langsung mulai coding tanpa perlu bertanya-tanya
Untuk merasakan bedanya, bandingkan indikator spec yang buruk berikut — yang terdengar lengkap tapi kosong makna.
1❌ "Sistem harus handle order management dengan baik dan efisien,
2 termasuk semua aspek yang diperlukan untuk e-commerce modern"Kalimat di atas tidak bisa diterjemahkan menjadi satu test pun. Bandingkan dengan indikator spec yang baik berikut, yang setiap barisnya bisa langsung diverifikasi.
1✅ "AC1: POST /orders menerima { cart_id (UUID, wajib), shipping_address_id (UUID, wajib) }
2 AC2: System memvalidasi cart milik user yang authenticated
3 AC3: System memvalidasi semua item di cart tersedia (stok > 0)
4 AC4: Jika validasi gagal → 422 dengan detail item mana yang kosong"Perbedaannya jelas: yang pertama tidak bisa ditest, yang kedua langsung bisa jadi test case — dan kemampuan “bisa jadi test” inilah tolok ukur utama kualitas spec.
05.2 User Story: Lebih dari Sekadar Template
User Story bukan sekadar mengisi template “Sebagai [X], saya ingin [Y] agar [Z].” User Story yang efektif menjawab satu pertanyaan fundamental: mengapa fitur ini ada?
Format Dasar User Story
Kerangka dasarnya tetap sama seperti format berikut, tapi kualitasnya ditentukan oleh seberapa spesifik tiap bagian diisi.
1Sebagai [role yang spesifik],
2saya ingin [action yang konkret],
3agar [benefit yang jelas dan measurable].Template kosong ini baru berguna jika setiap placeholder diisi dengan konteks nyata, bukan kata generik.
Contoh: User Story yang Buruk vs Baik
Mari lihat versi buruk terlebih dahulu, yang mengisi template tapi tanpa substansi.
1Sebagai user, saya ingin cancel order, agar bisa cancel.Problem versi ini: “user” terlalu generik, benefit tidak jelas, dan action tidak spesifik. Bandingkan dengan versi baik berikut yang kaya konteks.
1Sebagai customer Santekno Shop yang sudah place order tapi berubah pikiran,
2saya ingin cancel order yang belum diproses merchant
3agar stok kembali tersedia dan saya tidak perlu menunggu refund manual.Versi kedua langsung memberi tahu siapa penggunanya, dalam kondisi apa, dan manfaat konkret yang dicari — konteks yang akan memandu semua AC di bawahnya.
User Story untuk Santekno Shop: Cancel Order
Diterapkan pada fitur nyata, user story cancel order kita menjadi seperti berikut.
1### User Story
2
3Sebagai customer Santekno Shop,
4saya ingin membatalkan order yang masih dalam status PENDING
5agar stok produk dikembalikan dan saya dapat melakukan pembelian lain
6tanpa menunggu konfirmasi dari merchant.
7
8**Konteks:** Order bisa dibatalkan hanya dalam 15 menit pertama,
9setelah merchant mulai memproses (status CONFIRMED), cancel tidak lagi dimungkinkan.Perhatikan bagian Konteks — ia menyampaikan constraint bisnis penting yang nantinya menjadi dasar beberapa AC dan EC tanpa harus diulang di setiap baris.
Elemen User Story yang Sering Terlewat
Role spesifik, bukan “user”:
customer(bukan user)merchant admin(bukan admin)warehouse operator(bukan staff)
Benefit yang konkret:
- “agar stok kembali tersedia” ✅
- “agar saya puas” ❌
Konteks yang relevan:
- Kondisi yang harus terpenuhi
- Keterbatasan yang perlu diketahui
05.3 Acceptance Criteria: Kontrak yang Bisa Diverifikasi
Acceptance Criteria (AC) adalah kontrak antara product/business dan engineering: inilah kondisi-kondisi yang harus dipenuhi agar fitur dianggap selesai.
Karakteristik AC yang Baik
1. Satu AC = Satu behavior yang bisa diisolasi
AC yang menggabungkan terlalu banyak behavior sulit ditest dan sulit di-review. Perhatikan kontras antara AC yang menumpuk dan yang terfokus berikut.
1❌ AC yang menggabungkan terlalu banyak:
2"System menerima request, memvalidasi, menyimpan ke DB, dan mengirim notifikasi"
3
4✅ AC yang terfokus:
5AC1: System menerima POST /orders dengan { cart_id, shipping_address_id }
6AC2: System memvalidasi cart milik user yang sedang authenticated
7AC3: System membuat order record dengan status PENDING
8AC4: System mempublish event ORDER_CREATED ke Kafka
9AC5: System mengirim response 201 Created dengan order_id dan total_amount_centsDengan memecah menjadi satu behavior per AC, setiap baris bisa dipetakan ke satu test yang independen — memudahkan verifikasi maupun debugging.
2. Setiap AC bisa langsung jadi test
Karakteristik terpenting: tiap AC dapat diterjemahkan menjadi kerangka test Go, seperti dua contoh berikut yang diturunkan langsung dari AC2 dan AC4.
1// Dari AC2: "System memvalidasi cart milik user yang authenticated"
2func TestCreateOrder_CartBelongsToOtherUser_Returns422(t *testing.T) {
3 // Given: User A authenticated, cart milik User B
4 // When: POST /orders dengan cart_id milik User B
5 // Then: 422 dengan error "cart not found or not accessible"
6}
7
8// Dari AC4: "System mempublish event ORDER_CREATED ke Kafka"
9func TestCreateOrder_Success_PublishesKafkaEvent(t *testing.T) {
10 // Given: valid request
11 // When: POST /orders berhasil
12 // Then: Kafka publisher dipanggil dengan ORDER_CREATED payload
13}Kemudahan menurunkan test seperti ini adalah bukti bahwa AC-nya cukup presisi; jika sebuah AC sulit dibuatkan test, itu sinyal AC tersebut masih ambigu.
3. Explicit tentang HTTP status code dan response format
Error case yang tidak menyebut status code dan format response membuka ruang interpretasi. Bandingkan versi ambigu dan versi eksplisit berikut.
1❌ "System menolak request yang tidak valid"
2✅ "Request tanpa Authorization header → 401 Unauthorized
3 Body: { error_code: 'UNAUTHORIZED', message: 'authentication required' }"Versi eksplisit menghilangkan tebak-tebakan: developer tahu persis status code, error code, dan bentuk body yang harus dikembalikan.
Template AC yang Direkomendasikan
Untuk konsistensi antar fitur, gunakan struktur AC berikut sebagai kerangka.
1## Acceptance Criteria
2
3### Happy Path (kondisi normal berhasil)
4- AC1: [HTTP method + path] menerima [payload format dengan semua field]
5- AC2: [Validasi pertama yang dilakukan]
6- AC3: [Aksi utama yang terjadi]
7- AC4: [Side effect yang diharapkan (event, notification)]
8- AC5: Response: [HTTP status] [response body format]
9
10### Error Cases (kondisi yang ditolak)
11- AC6: [Kondisi error 1] → [HTTP status] dengan error_code [CODE]
12 message: "[pesan yang user-facing]"
13- AC7: [Kondisi error 2] → [HTTP status] dengan error_code [CODE]Kerangka ini memisahkan happy path dari error case sejak awal, sehingga tidak ada error case yang terlupa hanya karena fokus pada alur normal.
05.4 Acceptance Criteria untuk Create Order: Contoh Lengkap
Diterapkan penuh pada fitur create order, AC-nya menjadi seperti berikut — perhatikan bagaimana setiap error case punya status code dan error code yang eksplisit.
1## Acceptance Criteria — Create Order (Santekno Shop)
2
3### Happy Path
4- AC1: POST /orders menerima { cart_id: UUID, shipping_address_id: UUID }
5 keduanya wajib, tidak boleh null atau kosong
6
7- AC2: System memvalidasi bahwa cart_id merujuk ke cart yang:
8 (a) ada di database, (b) milik user yang sedang authenticated,
9 (c) tidak kosong (minimal 1 item)
10
11- AC3: System memvalidasi bahwa semua produk dalam cart:
12 (a) masih aktif (status ACTIVE), (b) memiliki stok >= quantity di cart
13
14- AC4: System membuat order record dengan status PENDING dan
15 meng-assign order_number (format: ORD-YYYYMMDD-XXXXXXX)
16
17- AC5: System mengurangi stok setiap produk dalam order secara atomic
18 dalam satu database transaction
19
20- AC6: System mempublish event ORDER_CREATED ke Kafka topic "order-events"
21 dengan payload: { order_id, user_id, total_amount_cents, items[] }
22
23- AC7: Response: 201 Created
24 Body: { order_id: UUID, order_number: string, total_amount_cents: int,
25 status: "PENDING", estimated_delivery: string (ISO 8601) }
26
27### Error Cases
28- AC8: cart_id atau shipping_address_id tidak valid UUID format →
29 400 Bad Request, error_code: INVALID_REQUEST
30 message: "invalid [field_name] format"
31
32- AC9: Cart tidak ditemukan atau bukan milik user →
33 422 Unprocessable Entity, error_code: CART_NOT_ACCESSIBLE
34
35- AC10: Cart kosong (tidak ada items) →
36 422, error_code: CART_EMPTY
37 message: "cannot create order from empty cart"
38
39- AC11: Produk tidak aktif →
40 422, error_code: PRODUCT_UNAVAILABLE
41 message: "product [product_name] is no longer available"
42
43- AC12: Stok tidak mencukupi →
44 422, error_code: INSUFFICIENT_STOCK
45 message: "insufficient stock for [product_name]: available [n], requested [m]"
46
47- AC13: shipping_address_id tidak ditemukan atau bukan milik user →
48 422, error_code: ADDRESS_NOT_FOUND
49
50- AC14: Tanpa Authorization header atau token invalid →
51 401 Unauthorized, error_code: UNAUTHORIZEDContoh selengkap ini menunjukkan bahwa “lengkap” bukan berarti “panjang tanpa arah”: setiap AC tetap satu behavior, tetap measurable, dan tetap bisa dibaca cepat oleh siapa pun di tim.
05.5 Edge Cases: Kondisi yang Menentukan Kualitas
Edge cases adalah kondisi batas yang sering membuat developer bilang “ah, saya tidak kepikiran itu.” Dan inilah yang menyebabkan bug production yang paling menyakitkan.
Kategori Edge Cases
Kategori 1: Concurrent Request — kondisi paling berbahaya karena bisa merusak integritas data. Contoh berikut menangani dua checkout atas stok terakhir secara bersamaan.
1EC1: Dua user checkout produk dengan stok terakhir bersamaan
2 → Hanya satu berhasil (201 Created)
3 → Yang lain mendapat 422 INSUFFICIENT_STOCK
4 Implementasi: pessimistic lock via SELECT FOR UPDATEEC ini menetapkan pemenang yang deterministik lewat locking, mencegah dua order sukses atas stok yang sama. Kategori berikutnya menyoroti perubahan data di tengah alur.
Kategori 2: Data Integrity — kondisi ketika data berubah antara satu langkah dan langkah berikutnya, seperti dua EC berikut.
1EC2: Produk dideactivate oleh merchant saat user sedang di cart
2 → Checkout gagal dengan error spesifik tentang produk tersebut
3 → User bisa remove produk dari cart dan checkout ulang
4
5EC3: Harga produk berubah antara waktu add to cart dan checkout
6 → Gunakan harga saat checkout (bukan harga saat add to cart)
7 → Tampilkan summary harga terbaru sebelum konfirmasi (frontend concern)Kedua EC ini menetapkan aturan yang jelas tentang “data mana yang menang” saat terjadi perubahan, sehingga behavior-nya tidak bergantung pada timing. Kategori ketiga menyangkut kegagalan dependency eksternal.
Kategori 3: External Dependency Failure — kondisi ketika layanan luar seperti Kafka atau DB gagal, seperti dua EC berikut.
1EC4: Kafka publish gagal setelah order berhasil disimpan di DB
2 → Log error level WARNING
3 → Order tetap dianggap berhasil (return 201)
4 → Kafka retry mechanism handle eventual consistency
5
6EC5: DB timeout saat commit transaction
7 → Return 500 Internal Server Error
8 → Client harus retry (idempotency key direkomendasikan)Perhatikan perbedaan sikap: kegagalan Kafka di-degrade dengan anggun (order tetap sukses), sedangkan kegagalan DB harus gagal jelas — pilihan yang ditentukan oleh seberapa kritis dependency itu bagi konsistensi data. Kategori keempat menangani penyalahgunaan.
Kategori 4: Rate Limiting dan Abuse — kondisi ketika user membanjiri endpoint, seperti berikut.
1EC6: User mencoba create order lebih dari 10 kali dalam 1 menit
2 → 429 Too Many Requests
3 → Retry-After header berisi timestamp kapan bisa coba lagiEC ini melindungi sistem dari abuse sekaligus memberi klien informasi kapan boleh mencoba lagi lewat header Retry-After. Kategori terakhir menyangkut batas data.
Kategori 5: Data Boundary — kondisi nilai di luar rentang wajar, seperti berikut.
1EC7: Cart berisi 100 item (lebih dari limit normal 50)
2 → Ini seharusnya tidak mungkin terjadi (enforced saat add to cart)
3 → Jika terjadi: 422 CART_EXCEEDS_LIMIT, log sebagai anomalyEC boundary seperti ini bertindak sebagai jaring pengaman: meski secara teori tidak mungkin, jika tetap terjadi ia ditangani dan dicatat sebagai anomali untuk diinvestigasi.
05.6 Menulis Edge Case yang Berguna
Ada seni dalam menulis edge case yang berguna vs edge case yang hanya menambah panjang dokumen.
Edge case yang berguna adalah yang:
- Bisa terjadi di production (bukan hanya secara teoretikal)
- Membutuhkan behavior yang berbeda dari happy path
- Ada implementasi yang jelas untuk mengatasinya
Sebaliknya, banyak “edge case” yang sebenarnya tidak berguna dan hanya membuat spec bengkak, seperti contoh berikut.
1❌ "EC: User menggunakan browser yang sangat tua"
2(ini bukan tanggung jawab backend API)
3
4❌ "EC: Database server meledak"
5(terlalu catastrophic, tidak ada recovery di level aplikasi)
6
7❌ "EC: User menyuntikkan SQL lewat product_name"
8(ini seharusnya di-prevent selalu, bukan edge case khusus)Ketiga contoh di atas gagal salah satu dari tiga syarat edge case berguna — entah di luar tanggung jawab API, terlalu catastrophic, atau seharusnya dicegah selalu. Untuk memutuskan mana yang layak masuk, pakai framework prioritas berikut.
1P0 — MUST handle:
2- Concurrent access yang bisa menyebabkan data corruption
3- Security (unauthorized access, injection)
4- Financial transaction integrity
5
6P1 — SHOULD handle:
7- External dependency failure (partial graceful degradation)
8- Input yang valid format tapi invalid business logic
9
10P2 — NICE to have:
11- Rate limiting per user
12- Audit logging untuk unusual patterns
13
14P3 — Out of scope (dokumentasikan saja):
15- Disaster recovery
16- Multi-datacenter failoverFramework ini membantu kamu mengalokasikan energi menulis EC ke tempat yang paling berdampak, alih-alih mengejar kelengkapan teoretis yang tidak pernah terjadi.
05.7 Non-Functional Requirements: Bagian yang Sering Terlewat
Non-Functional Requirements (NFR) mendefinisikan bagaimana sistem harus berperilaku — berbeda dari AC yang mendefinisikan apa yang harus dilakukan. Bagian NFR untuk create order kira-kira seperti berikut.
1## Non-Functional Requirements — Create Order
2
3### Performance
4- NFR1: Response time < 500ms untuk p95 di normal load (100 req/s)
5- NFR2: Response time < 2000ms untuk p99 di peak load (1000 req/s flash sale)
6
7### Availability
8- NFR3: Endpoint tersedia 99.9% uptime (downtime < 8.76 jam/tahun)
9- NFR4: Graceful degradation: jika Kafka down, order tetap bisa dibuat
10 (event dipublish nanti via retry mechanism)
11
12### Data Integrity
13- NFR5: Operasi stok decrement HARUS atomic
14- NFR6: Tidak ada duplicate order untuk request yang sama (idempotency)
15
16### Security
17- NFR7: Rate limit: 20 order creation per user per hour
18- NFR8: Semua request harus authenticated (JWT Bearer token)
19- NFR9: Input sanitization untuk semua string fields
20
21### Observability
22- NFR10: Metric yang WAJIB ada:
23 - order_creation_total (counter, label: status success/failure)
24 - order_creation_duration_seconds (histogram)
25 - order_creation_errors_total (counter, label: error_type)
26- NFR11: Structured log dengan level INFO untuk setiap order berhasil dibuat
27- NFR12: Alert jika error rate > 1% dalam 5 menitNFR yang eksplisit inilah yang membedakan fitur “jalan di laptop” dari fitur “siap production”: target performa, availability, dan observability jadi kontrak yang bisa diukur, bukan harapan implisit.
05.8 Spec untuk Fitur yang Berinteraksi dengan AI
Di era AI-powered features, spec juga perlu mendefinisikan behavior AI yang diharapkan. Ini area yang sering ambigu — contoh AC dan EC untuk generator deskripsi produk berikut menunjukkan bagaimana menetapkan batasannya.
1## Acceptance Criteria — AI Product Description Generator
2
3- AC1: POST /products/{id}/generate-description menerima { tone: enum(professional|casual|persuasive) }
4- AC2: System mengirim request ke LLM API dengan prompt yang terstruktur
5- AC3: Response diberikan dalam maksimum 30 detik (streaming atau regular)
6- AC4: Jika LLM timeout (>30 detik) → 408 Request Timeout dengan error_code: LLM_TIMEOUT
7- AC5: Generated description: minimum 100 kata, maksimum 500 kata
8- AC6: Description dalam bahasa Indonesia (default) atau sesuai param lang
9- AC7: Description tidak boleh berisi harga atau perbandingan dengan kompetitor
10
11## Edge Cases
12- EC1: LLM API down atau rate limited → 503 Service Unavailable
13 message: "AI service temporarily unavailable, please try again"
14- EC2: Product tidak punya description existing → generate dari nama dan kategori
15- EC3: Product tidak punya gambar → tidak include deskripsi visualKunci spec untuk fitur AI adalah membatasi output yang non-deterministik dengan aturan yang deterministik: batas waktu, batas panjang, bahasa, dan larangan konten — sehingga behavior tetap bisa diverifikasi meski isinya digenerate model.
05.9 Review Spec dengan Claude Code
Setelah menulis spec, gunakan Claude Code untuk melakukan “spec review” sebelum implementasi. Prompt berikut meminta AI menilai kualitas spec, bukan mengimplementasikannya.
1Saya baru selesai menulis spec untuk fitur cancel order.
2Berikut isinya:
3
4[paste spec content]
5
6Tolong review spec ini dan identifikasi:
71. AC yang tidak measurable (tidak bisa jadi test)
82. Edge case yang mungkin terlewat
93. Ambiguitas dalam bahasa yang digunakan
104. Konflik antar AC
115. NFR yang mungkin perlu ditambahkan
12
13Jangan implementasikan, hanya review kualitas spec.Claude Code biasanya menemukan 2-4 hal yang perlu diperbaiki — bahkan untuk spec yang sudah kita pikir sudah baik. Menjadikan review AI sebagai langkah wajib sebelum coding jauh lebih murah daripada menemukan celah spec setelah kode ditulis.
05.10 Spec Traceability: Menghubungkan Spec dengan Kode
Ketika spec sudah di-approve dan kode sudah diimplementasikan, penting untuk menjaga traceability — kemampuan menelusuri dari AC ke implementasi dan sebaliknya. Cara pertama adalah lewat komentar yang menautkan function ke AC/EC, seperti berikut.
1// cancel_order.go — implements specs/order/cancel-order.md v1.3
2
3// Execute performs order cancellation.
4// Implements: AC1 (authentication check via middleware)
5// Implements: AC2 (order ownership validation)
6// Implements: AC3 (status PENDING check)
7// Implements: AC5 (stock restore + status update atomic)
8// Implements: EC1 (concurrent cancel via SELECT FOR UPDATE)
9func (uc *CancelOrderUseCase) Execute(ctx context.Context, input CancelOrderInput) error {
10 // ...
11}Komentar semacam ini membuat reviewer bisa langsung mencocokkan tiap blok kode dengan AC yang diklaimnya. Cara kedua menautkan lewat nama test, seperti berikut.
1// Test names langsung reference AC/EC
2func TestCancelOrder_AC3_NonPendingStatus_Returns409(t *testing.T) { }
3func TestCancelOrder_EC1_ConcurrentRequest_OnlyOneSucceeds(t *testing.T) { }Dengan menyematkan kode AC/EC di nama test, hasil go test menjadi laporan coverage berbasis spec — kamu bisa melihat langsung AC mana yang sudah punya test dan mana yang belum.
05.11 Spec untuk REST API yang Sudah Ada
Kadang perlu menulis spec untuk endpoint yang sudah ada (reverse engineering spec). Ini berguna untuk memformalkan behavior yang sebelumnya implisit, membuat dokumentasi untuk API production, dan menyiapkan baseline sebelum refactoring. Prompt berikut meminta Claude Code menyusun spec dari kode yang ada.
1Prompt ke Claude Code:
2"Tolong buat spec dalam format yang sudah kita tentukan
3untuk endpoint berikut, berdasarkan implementasi yang ada:
4
5[paste kode handler]
6[paste kode usecase]
7[paste kode repository]
8
9Spec harus:
101. Deduce user story dari nama function dan komentar
112. Extract semua AC dari conditional logic dan return statements
123. Identify edge cases dari error handling
134. Note NFR jika ada (timeout, rate limiting, dll)
14
15Tandai asumsi yang kamu buat dengan [ASSUMPTION]."Penanda [ASSUMPTION] penting: ia memisahkan fakta yang terbaca dari kode dari tebakan AI, sehingga kamu tahu bagian mana dari spec hasil reverse-engineering yang masih perlu dikonfirmasi ke tim.
05.12 Spec untuk Kafka Events dan Async Operations
Fitur yang melibatkan event-driven architecture perlu spec yang mencakup contract event, bukan hanya HTTP API. Spesifikasi event ORDER_CANCELLED berikut mendefinisikan trigger, konfigurasi, schema, hingga jaminan pengiriman.
1## Event Specification — ORDER_CANCELLED
2
3### Trigger
4Dipublish oleh Order Service setelah cancel order berhasil (AC5 di cancel-order.md)
5
6### Kafka Configuration
7- Topic: `order-events`
8- Partition key: `order_id`
9- Message format: JSON
10
11### Event Schema
12{
13 "event_type": "ORDER_CANCELLED",
14 "event_version": "1.0",
15 "event_id": "uuid",
16 "occurred_at": "ISO 8601 timestamp",
17 "payload": {
18 "order_id": "uuid",
19 "user_id": "uuid",
20 "cancelled_at": "ISO 8601 timestamp",
21 "cancelled_by": "user|system",
22 "reason": "string | null",
23 "items": [
24 {
25 "product_id": "uuid",
26 "quantity": "int",
27 "price_cents": "int64"
28 }
29 ],
30 "refund_amount_cents": "int64"
31 }
32}
33
34### Consumer Contracts
35- Notification Service: kirim email konfirmasi cancel ke user
36- Inventory Service: restore stok (jika Inventory Service yang manage stok)
37- Analytics Service: update cancel rate metrics
38
39### Guarantees
40- At-least-once delivery
41- Idempotency: consumer harus handle duplicate events (cek event_id)Spec event seperti ini menjadi kontrak lintas service: setiap consumer tahu persis bentuk payload dan jaminan pengirimannya, termasuk kewajiban menangani duplikat karena delivery-nya at-least-once.
05.13 Spec Testing: Memastikan Spec Sendiri Berkualitas
Ada “meta-test” yang bisa dijalankan untuk verify kualitas spec sebelum satu baris kode ditulis:
Test 1: Bisa-jadi-test Test — untuk setiap AC, tulis satu-dua test function header (tanpa implementasi). Jika sulit menulis test header, AC-nya terlalu ambigu.
Test 2: Non-technical person understands Test — bacakan spec ke seseorang yang tidak teknikal (product manager atau business analyst). Jika mereka tidak mengerti AC, terlalu technical; jika mereka mengerti tapi punya pertanyaan yang tidak terjawab, ada ambiguitas.
Test 3: New developer can implement Test — bayangkan developer baru yang tidak tahu context proyek. Apakah dia bisa implement fitur ini hanya dari spec + CLAUDE.md? Jika ada yang harus ditanya, tambahkan ke spec.
Test 4: Conflict detection Test — baca ulang semua AC. Apakah ada yang bisa diinterpretasikan dua cara berbeda? Apakah AC 1 dan AC 3 punya implikasi yang berbeda? Jika ya, clarify.
05.14 Integrasi Spec dengan OpenAPI
Untuk API yang punya OpenAPI/Swagger spec, ada dua pendekatan. Pendekatan pertama memperlakukan feature spec sebagai superset dari OpenAPI, dengan pembagian file seperti berikut.
1specs/order/cancel-order.md ← business rules, AC, EC
2api/openapi.yaml ← HTTP contract, request/response schemaDi sini OpenAPI mengurus contract teknikal (format request/response), sementara feature spec mengurus business rules dan edge case yang tidak tertangkap OpenAPI. Pendekatan kedua justru men-generate OpenAPI dari feature spec, lewat prompt seperti berikut.
1Berdasarkan specs/order/cancel-order.md, generate OpenAPI 3.0 spec
2untuk endpoint DELETE /orders/{id}.
3
4Include:
5- Request parameters (path, headers)
6- Response codes dan schemas untuk semua AC dan EC
7- Error response format yang konsistenPendekatan kedua menjaga OpenAPI selalu turunan dari feature spec, sehingga dokumentasi teknikal tidak pernah menyimpang dari business rules yang menjadi sumber kebenaran.
05.15 Spec Review Checklist yang Bisa Dipakai Langsung
Agar review spec konsisten, pakai checklist siap pakai berikut yang mencakup keempat komponen anatomi spec.
1## Spec Quality Checklist v1.0
2
3### User Story
4- [ ] Role spesifik (bukan hanya "user")
5- [ ] Action jelas dan konkret
6- [ ] Benefit measurable dan valuable
7- [ ] Konteks yang relevan disertakan jika perlu
8
9### Acceptance Criteria
10- [ ] Setiap AC bisa ditulis sebagai satu test function
11- [ ] Happy path punya minimal 3 AC
12- [ ] Setiap error case menyebutkan HTTP status code eksplisit
13- [ ] Setiap error case menyebutkan error_code yang specific
14- [ ] Response format didefinisikan (field apa yang dikembalikan)
15- [ ] Tidak ada AC yang ambigu (bisa diinterpretasikan lebih dari satu cara)
16
17### Edge Cases
18- [ ] Minimal 1 concurrent access EC jika ada shared resource
19- [ ] Minimal 1 external dependency failure EC
20- [ ] EC untuk boundary conditions (max/min values)
21- [ ] Setiap EC punya behavior yang jelas (bukan hanya "handle dengan baik")
22
23### Non-Functional Requirements
24- [ ] Response time target ada (minimal untuk happy path)
25- [ ] Availability expectation ada
26- [ ] Rate limiting disebutkan jika relevan
27
28### Meta
29- [ ] Spec bisa dibaca dalam < 10 menit
30- [ ] Tidak ada implementasi detail yang tidak perlu
31- [ ] Tidak ada duplikasi dengan spec lain
32- [ ] Scope jelas: apa yang termasuk DAN tidak termasukChecklist ini bisa ditempel di PR template review spec, sehingga tidak ada komponen anatomi yang terlewat hanya karena reviewer terburu-buru.
05.16 Common Mistakes dalam Menulis AC
Mistake 1: AC terlalu granular — spec bukan unit test. Tidak perlu mendefinisikan setiap validasi field secara terpisah jika bisa diringkas, seperti kontras berikut.
1❌ Terlalu granular:
2AC1: product_id tidak boleh null
3AC2: product_id tidak boleh empty string
4AC3: product_id harus format UUID v4
5AC4: product_id harus ada di database
6
7✅ Lebih efisien:
8AC1: product_id wajib ada, format UUID, dan harus ada di database products
9 → 400 jika format salah, 404 jika tidak ditemukanVersi ringkas tetap measurable tapi tidak menenggelamkan pembaca dalam detail validasi yang bisa disatukan. Mistake 2: AC yang mengandung “dan/atau” berlebihan — perhatikan bagaimana satu kalimat panjang sebaiknya dipecah.
1❌ "System memvalidasi cart dan membuat order dan mengurangi stok dan publish event"
2✅ Pisah: AC masing-masing untuk validasi cart, create order, decrement stok, publish eventMemecah “dan” menjadi AC terpisah mengembalikan prinsip satu behavior per AC. Mistake 3: Error AC tanpa error code — error case wajib menyebut status dan error_code seperti berikut.
1❌ "Jika stok tidak cukup, kembalikan error"
2✅ "Jika stok tidak cukup → 422 dengan error_code: INSUFFICIENT_STOCK
3 message: 'insufficient stock for [product_name]: available [n], requested [m]'"Tanpa error code dan message yang eksplisit, klien tidak bisa membedakan jenis kegagalan secara terprogram — versi kedua menutup celah itu.
05.17 Evolusi Spec Seiring Fitur Berkembang
Spec bukan dokumen yang ditulis sekali dan selesai. Dia harus berkembang seiring fitur berkembang. Mulai dari versi MVP yang minimal seperti berikut.
1Cancel Order v1.0:
2AC1: Customer bisa cancel order yang PENDING
3AC2: Stok dikembalikan
4AC3: Status berubah ke CANCELLEDVersi awal ini sengaja minimal — cukup untuk mulai, belum sempurna. Setelah feedback awal, spec bertambah seperti berikut.
1Cancel Order v1.1 (tambahan):
2AC4: Cancel hanya dalam 15 menit pertama (berdasarkan user research)
3AC5: Merchant mendapat notifikasi jika order dicancelPenambahan ini datang dari pembelajaran nyata (user research), bukan spekulasi di awal. Setelah berjalan di production, muncul edge case dari data lapangan seperti berikut.
1Cancel Order v1.2 (perbaikan):
2EC3: Concurrent cancel (ditemukan dari stress test) → SELECT FOR UPDATE
3EC4: Kafka failure → best effort, log warning (bukan rollback)Setiap versi harus di-git dengan changelog yang jelas — inilah “living spec” yang mencerminkan accumulated learning tentang fitur tersebut, bukan tebakan lengkap di hari pertama.
05.18 Tips & Gotchas
💡 Tip 1: Tulis AC dari perspektif sistem, bukan user — perspektif sistem menghasilkan AC yang presisi dan bisa diverifikasi, seperti kontras berikut.
1❌ User-centric (ambigu): "User berhasil cancel order"
2✅ System-centric (precise): "System mengembalikan 204 No Content
3 dan stok semua item dalam order dikembalikan secara atomic"Versi system-centric menyebut output konkret (status code, efek pada stok) yang bisa langsung ditest, sedangkan versi user-centric menyisakan ambiguitas.
💡 Tip 2: Gunakan “must” language untuk AC — “System must validate…” lebih kuat dan jelas dari “System should validate…”.
💡 Tip 3: Spec yang baik membuat code review lebih mudah — saat code review, setiap baris kode bisa dikaitkan ke AC. Tidak ada perdebatan “apakah ini benar?” — tinggal cek apakah kode mengimplementasikan apa yang ada di spec.
💡 Tip 4: Review EC dengan senior engineer — edge cases paling valuable biasanya dari pengalaman production. Review EC bersama senior yang pernah mengalami bug production; mereka punya intuisi tentang apa yang bisa salah.
⚠️ Gotcha 1: AC yang terlalu detail bisa jadi constraint implementasi yang tidak perlu — jika AC mendefinisikan implementasi detail (bukan behavior), developer kehilangan fleksibilitas memilih solusi terbaik.
⚠️ Gotcha 2: Edge cases yang terlalu banyak bisa membuat spec tidak bisa dibaca — prioritaskan EC berdasarkan probability dan impact, bukan setiap kemungkinan teoretis.
⚠️ Gotcha 3: Spec yang terlalu sering direvisi bisa menurunkan kepercayaan tim — revisi normal, tapi terlalu sering merevisi AC yang sudah diapprove membuat developer frustrasi karena kode perlu diubah berulang.
⚠️ Gotcha 4: Jangan biarkan AI menulis seluruh spec dari deskripsi singkat — AI bisa draft spec, tapi kamu yang harus validasi setiap keputusan bisnis. AI mengisi form, kamu yang tentukan isinya.
05.19 Spec untuk Background Jobs dan Scheduled Tasks
Spec tidak hanya untuk REST API. Background jobs juga butuh spec yang jelas, mencakup trigger, processing, error handling, hingga NFR — seperti contoh cleanup cart berikut.
1# specs/jobs/cleanup-expired-carts.md
2
3## Overview
4Background job yang berjalan setiap jam untuk cleanup cart yang sudah expired.
5
6## User Story
7Sebagai sistem Santekno Shop, saya ingin membersihkan cart yang
8sudah tidak aktif lebih dari 24 jam agar database tidak penuh dengan
9data yang tidak diperlukan dan performa query tetap optimal.
10
11## Acceptance Criteria
12
13### Trigger
14- AC1: Job berjalan setiap jam (cron: `0 * * * *`)
15- AC2: Job bisa ditrigger manual via admin endpoint: POST /admin/jobs/cleanup-carts
16
17### Processing
18- AC3: Query cart dengan `updated_at < NOW() - INTERVAL '24 hours'`
19 AND status bukan CHECKED_OUT atau CONVERTED_TO_ORDER
20- AC4: Proses maksimum 1000 cart per run (batch processing)
21- AC5: Soft delete: update `deleted_at = NOW()`, bukan physical delete
22- AC6: Log jumlah cart yang dicleanup per run
23
24### Error Handling
25- AC7: Jika DB error saat cleanup → log error, skip batch, lanjut ke batch berikutnya
26- AC8: Tidak boleh delete cart yang punya ongoing checkout session
27
28## Edge Cases
29- EC1: Jika job sebelumnya belum selesai saat jam berikutnya tiba → skip run, log warning
30- EC2: Jika ada > 100.000 cart expired → proses dalam multiple runs, tidak dalam satu run
31
32## Non-Functional
33- NFR1: Satu run tidak boleh lebih dari 5 menit
34- NFR2: Tidak boleh lock table — gunakan index scan yang efficientSpec background job seperti ini membuktikan bahwa anatomi yang sama — user story, AC, EC, NFR — berlaku universal: baik untuk endpoint HTTP maupun job terjadwal yang berjalan tanpa interaksi user.
05.20 Ringkasan
Anatomi spec yang baik terdiri dari empat komponen yang saling mendukung:
User Story menjawab “mengapa” — memberi konteks bisnis yang membuat semua AC menjadi bermakna. User story yang baik spesifik tentang role, konkret tentang action, dan jelas tentang benefit.
Acceptance Criteria adalah kontrak yang bisa diverifikasi. Setiap AC harus bisa langsung jadi test function, menyebutkan HTTP status code untuk error cases, dan mendefinisikan response format secara eksplisit.
Edge Cases adalah kondisi batas yang membedakan spec yang “cukup” dari spec yang “production-ready.” Prioritaskan EC berdasarkan probability dan impact, bukan kelengkapan teoretis.
Non-Functional Requirements mendefinisikan “bagaimana” sistem harus berperilaku — performance, availability, security, observability. Sering terlewat, tapi krusial untuk sistem production.
Di artikel berikutnya, kita masuk ke teknik yang lebih advanced: prompt engineering untuk spec — bagaimana menggunakan Claude Code untuk membantu menulis, review, dan refine spesifikasi secara efektif.