Refactoring Golang dengan Claude Code: Aman dan Terstruktur dengan Safety Net
Cara melakukan refactoring kode Golang dengan Claude Code secara aman dan terstruktur. Gunakan spec dan test sebagai safety net untuk refactoring yang confidence di Santekno Shop.
Refactoring dengan Claude: Aman, Terstruktur, dengan Safety Net
Refactoring Golang dengan Claude Code yang aman berangkat dari satu prinsip: refactoring adalah seni mengubah struktur kode tanpa mengubah perilakunya. Kata kuncinya adalah tanpa mengubah perilaku. Inilah yang membuat refactoring berbeda dari rewriting — dan inilah yang membuatnya berisiko jika dilakukan tanpa safety net yang benar.
Di SDD, kita punya dua safety net yang kuat: spec (mendefinisikan perilaku yang diharapkan) dan test (memverifikasi perilaku itu). Dengan keduanya, refactoring bisa dilakukan dengan confidence, dan Claude Code membantu mengeksekusinya dengan efisien.
15.1 Mengapa Refactoring Itu Berisiko (dan Bagaimana Mitigasi)
Risiko utama refactoring adalah silent regression — kode yang berubah perilaku tanpa ada yang menyadari, karena tidak ada test yang menangkapnya atau test yang ada tidak meng-cover kasus yang berubah. Contoh berikut memperlihatkan bug satu karakter yang sangat mudah lolos.
1// SEBELUM refactoring (BENAR — sesuai AC4, batas inklusif):
2func (o *Order) CanBeCancelled() bool {
3 return o.Status == StatusPending && time.Since(o.CreatedAt) <= 15*time.Minute
4 // ^--- <= sesuai AC4
5 // AC4: order berumur tepat 15 menit 00 detik MASIH BISA dibatalkan
6}
7
8// SESUDAH refactoring (ada bug!):
9func (o *Order) CanBeCancelled() bool {
10 return o.Status == StatusPending && time.Since(o.CreatedAt) < 15*time.Minute
11 // ^--- changed <= to <
12 // Sekarang "exactly 15 minutes" dianggap expired, bukan dalam window
13 // Ini MELANGGAR AC4 — behavior change yang sangat subtle
14}Perhatikan: <= bukan pilihan bebas, melainkan perilaku yang sudah ditetapkan spec. AC4 menyatakan batas 15 menit bersifat INKLUSIF — order berumur tepat 15 menit 00 detik masih bisa dibatalkan, dan window baru tertutup mulai 15 menit lewat 1 detik. Artinya versi <= adalah yang benar, dan mengubahnya menjadi < adalah bug: ia menolak pembatalan tepat di detik ke-900 yang menurut spec masih sah.
Tanpa test yang meng-cover edge case “tepat di menit ke-15”, bug perubahan <= menjadi < ini tidak akan tertangkap sampai ada komplain dari pengguna. Mitigasinya bertumpu pada SDD:
- Spec sudah mendefinisikan “within 15 minutes” secara eksplisit lewat AC4 — inklusif,
time.Since(CreatedAt) <= 15*time.Minute - Test yang ditulis dari spec meng-cover boundary case ini (tepat 15 menit → masih bisa cancel; 15 menit 1 detik → window expired, AC10)
- Refactoring dilakukan dengan test berjalan setelah setiap perubahan
15.2 Tiga Kategori Refactoring
Tidak semua refactoring punya tingkat risiko yang sama. Memilah dulu kategorinya membantu menentukan seberapa ketat pengawasan yang dibutuhkan.
Kategori 1: Code Structure Refactoring
Mengubah organisasi kode tanpa mengubah behavior:
- Extract method
- Rename variable/function
- Move code to different file
- Extract interface
Risk level: LOW — jika test coverage bagus, ini aman dilakukan dengan AI.
Kategori 2: Pattern Refactoring
Mengubah pola implementasi, behavior tetap sama:
- Mengubah dari if-else ke switch
- Mengubah dari struct embedding ke composition
- Mengubah error handling style
Risk level: MEDIUM — perlu review manual untuk memastikan behavior sama.
Kategori 3: Algorithmic Refactoring
Mengubah cara kerja internal, output tetap sama:
- Optimize query
- Mengubah data structure
- Mengubah concurrency pattern
Risk level: HIGH — perlu test yang exhaustive dan human review.
Semakin tinggi kategorinya, semakin sedikit yang boleh dipercayakan penuh ke AI — kategori 3 hampir selalu butuh mata manusia yang paham konteks bisnis.
15.3 Safety Net: Test Coverage Sebelum Refactoring
Sebelum mulai refactoring, verifikasi dulu coverage-nya. Perintah berikut mengukur coverage dan menyorot file yang akan disentuh.
1# Ukur coverage sebelum refactoring
2go test -race -cover -coverprofile=before-refactor.out ./...
3
4# Review coverage untuk file yang akan di-refactor
5go tool cover -func=before-refactor.out | grep create_order
6
7# Output:
8# ...create_order.go:Execute 91.3%
9# ...create_order.go:total 88.4%Aturan praktisnya: jangan refactor file yang coverage-nya di bawah 80% sebelum menambahkan test dulu. Setelah tahu angkanya, konfirmasikan kesiapan itu ke Claude dengan prompt berikut.
1Saya mau refactor CreateOrderUseCase.Execute untuk extract
2stock validation logic ke method tersendiri.
3
4Coverage saat ini: 88.4%
5
6SEBELUM refactoring, identifikasi:
71. Apakah ada behavior yang di-cover oleh test existing?
82. Apakah ada edge case dari spec EC1-EC4 yang belum di-cover?
93. Apakah ada test tambahan yang perlu dibuat sebelum refactoring dimulai?
10
11Setelah memastikan coverage cukup, baru mulai refactoring.Dengan memeriksa coverage lebih dulu, kamu memastikan jaring pengaman sudah terpasang sebelum melangkah — bukan menyadari lubangnya setelah regresi terjadi.
15.4 Refactoring dengan Claude: Extract Method
Extract method adalah refactoring paling umum dan paling aman. Mari lihat kasus mengekstrak logika validasi stok dari CreateOrderUseCase.
Sebelum Refactoring
Kondisi awalnya: logika validasi stok tertanam inline di dalam Execute, membuat method-nya panjang dan sulit dites terpisah.
1// internal/usecase/order/create_order.go
2
3func (uc *CreateOrderUseCase) Execute(ctx context.Context, input CreateOrderInput) (*Order, error) {
4 cart, err := uc.cartRepo.GetByIDAndUserID(ctx, input.CartID, input.UserID)
5 if err != nil {
6 return nil, fmt.Errorf("get cart: %w", err)
7 }
8 if cart == nil {
9 return nil, ErrCartNotFound
10 }
11
12 // Stock validation logic — candidate for extraction
13 for _, item := range cart.Items {
14 product, err := uc.productRepo.GetByID(ctx, item.ProductID)
15 if err != nil {
16 return nil, fmt.Errorf("get product %s: %w", item.ProductID, err)
17 }
18 if product == nil {
19 return nil, &ProductUnavailableError{ProductID: item.ProductID}
20 }
21 if !product.IsActive() {
22 return nil, &ProductUnavailableError{ProductID: item.ProductID, Name: product.Name}
23 }
24 if product.Stock < item.Quantity {
25 return nil, &InsufficientStockError{
26 ProductID: item.ProductID,
27 Name: product.Name,
28 Available: product.Stock,
29 Requested: item.Quantity,
30 }
31 }
32 }
33 // ... rest of order creation
34}Bagian loop validasi di atas adalah kandidat ekstraksi yang jelas: ia satu unit tanggung jawab yang bisa berdiri sendiri.
Prompt Refactoring
Prompt berikut mengarahkan Claude melakukan ekstraksi dengan constraint yang ketat: behavior tidak boleh berubah dan test lama harus tetap lulus.
1Refactor CreateOrderUseCase.Execute dengan extract stock validation
2ke method tersendiri.
3
4Requirements:
51. Method baru bernama: validateCartItems(ctx, items []CartItem) error
62. Behavior TIDAK BOLEH berubah — return error yang sama untuk semua kasus
73. Semua test yang ada harus tetap lulus tanpa modifikasi
84. Ikuti error handling style yang ada (fmt.Errorf wrapping)
9
10Constraint:
11- Jangan ubah signature Execute
12- Jangan ubah error types yang ada
13- Hanya extract, jangan tambahkan atau ubah logic
14
15Setelah refactoring, jalankan: go test -race ./internal/usecase/order/...
16Konfirmasi semua test lulus.Constraint eksplisit “hanya extract, jangan ubah logic” adalah rem pengaman — tanpa itu, AI kadang “membantu” dengan mengubah perilaku yang tidak kamu minta.
Setelah Refactoring
Hasilnya: logika validasi kini berada di method terpisah dengan perilaku identik, dan Execute menjadi jauh lebih ringkas.
1// internal/usecase/order/create_order.go
2
3// validateCartItems verifies all cart items are available and have sufficient stock.
4// Extracted from Execute to improve readability.
5// Behavior: identical to pre-extraction inline code.
6func (uc *CreateOrderUseCase) validateCartItems(ctx context.Context, items []CartItem) error {
7 for _, item := range items {
8 product, err := uc.productRepo.GetByID(ctx, item.ProductID)
9 if err != nil {
10 return fmt.Errorf("get product %s: %w", item.ProductID, err)
11 }
12 if product == nil {
13 return &ProductUnavailableError{ProductID: item.ProductID}
14 }
15 if !product.IsActive() {
16 return &ProductUnavailableError{ProductID: item.ProductID, Name: product.Name}
17 }
18 if product.Stock < item.Quantity {
19 return &InsufficientStockError{
20 ProductID: item.ProductID,
21 Name: product.Name,
22 Available: product.Stock,
23 Requested: item.Quantity,
24 }
25 }
26 }
27 return nil
28}
29
30func (uc *CreateOrderUseCase) Execute(ctx context.Context, input CreateOrderInput) (*Order, error) {
31 cart, err := uc.cartRepo.GetByIDAndUserID(ctx, input.CartID, input.UserID)
32 if err != nil {
33 return nil, fmt.Errorf("get cart: %w", err)
34 }
35 if cart == nil {
36 return nil, ErrCartNotFound
37 }
38
39 // Use extracted method — same behavior, more readable
40 if err := uc.validateCartItems(ctx, cart.Items); err != nil {
41 return nil, err
42 }
43
44 // ... rest of order creation
45}Perhatikan bahwa error type dan urutan pengecekan sama persis dengan versi inline — itulah tanda ekstraksi yang benar: test lama lulus tanpa satu pun modifikasi.
15.5 Refactoring Error Handling Pattern
Error handling yang tidak seragam antar file adalah kandidat pattern refactoring yang bagus. Prompt berikut menunjukkan tiga gaya berbeda dan meminta Claude menyeragamkannya tanpa mengubah behavior.
1Saya menemukan inconsistency di error handling di beberapa usecase:
2
3File A (create_order.go):
4return nil, fmt.Errorf("create order: get cart: %w", err)
5
6File B (cancel_order.go):
7return fmt.Errorf("get order: %w", err) // tanpa "cancel order:" prefix
8
9File C (get_order.go):
10return nil, err // tidak ada wrapping sama sekali
11
12Tolong:
131. Identify semua inconsistency di internal/usecase/order/
142. Definisikan convention yang konsisten:
15 "[usecase_action]: [operation]: %w"
16 Contoh: "cancel order: get order: %w"
173. Apply convention ke semua file
184. Pastikan tidak ada behavior change (hanya string format yang berubah)
195. Jalankan go test ./internal/usecase/order/... setelah perubahan
20
21Tidak boleh ubah: error types (ErrOrderNotFound, dll), control flow, return valuesKunci refactoring ini: yang berubah hanya string konteks di dalam wrapping — error type, control flow, dan return value harus tetap identik agar test dan caller tidak terpengaruh.
15.6 Refactoring Struct dengan Interface Extraction
Mengganti dependency ke concrete type dengan interface meningkatkan testability dan fleksibilitas. Prompt berikut merencanakan ekstraksi interface untuk publisher Kafka di usecase cancel order.
1Usecase CancelOrderUseCase saat ini punya direct dependency ke
2*kafka.Producer dari library confluent-kafka-go.
3
4Saya mau refactor untuk depend ke interface, bukan concrete implementation.
5Ini diperlukan untuk:
61. Testability (bisa di-mock)
72. Flexibility (bisa ganti implementasi Kafka tanpa ubah usecase)
8
9Langkah yang diinginkan:
101. Definisikan interface EventPublisher di internal/domain/event/publisher.go
112. Update CancelOrderUseCase untuk depend ke interface
123. Buat wrapper struct KafkaEventPublisher yang implement interface
134. Update dependency injection di main.go atau wire.go
14
15Constraint:
16- Behavior tidak boleh berubah
17- Test yang ada harus tetap lulus
18- Hanya usecase layer yang boleh depend ke interface
19
20Apakah ada concern arsitekturan sebelum kita mulai?Dengan bergantung pada interface, usecase menjadi bisa di-unit-test tanpa Kafka nyata — dan implementasi konkret bisa diganti tanpa menyentuh logika bisnis sama sekali.
15.7 Database Query Refactoring
Refactoring query untuk performa masuk kategori berisiko tinggi, jadi butuh safety net berupa pengukuran sebelum dan sesudah. Prompt berikut menjaga agar return value tetap identik sambil mengoptimalkan query.
1Query GetByIDAndUserID saat ini:
2
3SELECT id, user_id, status, total_amount_cents, created_at, updated_at
4FROM orders
5WHERE id = $1 AND user_id = $2 AND deleted_at IS NULL
6
7Saya mau refactor untuk:
81. Tambahkan LIMIT 1 (query return single row, tapi bisa lebih explicit)
92. Tambahkan ORDER BY untuk deterministic result
103. Pastikan query menggunakan index yang tepat
11
12CONSTRAINT: Return value harus identik dengan sebelumnya
13- Behavior: nil jika tidak ditemukan, *Order jika ditemukan
14- Error: nil untuk tidak ditemukan, error untuk DB error
15
16Sebelum refactoring:
17- Jalankan integration test dengan query lama dan catat timing
18- Jalankan EXPLAIN ANALYZE untuk query yang ada
19
20Setelah refactoring:
21- Jalankan integration test lagi, compare timing
22- Jalankan EXPLAIN ANALYZE untuk verify index usageUntuk refactoring performa, EXPLAIN ANALYZE sebelum-dan-sesudah adalah bukti objektif — jangan pernah berasumsi query baru “pasti lebih cepat” tanpa mengukurnya.
15.8 Incremental Refactoring Strategy
Refactoring besar tidak boleh dikerjakan sekaligus. Prompt berikut memecah pemecahan usecase 300 baris menjadi beberapa fase yang masing-masing punya commit dan PR sendiri.
1Refactoring besar: Pisahkan CreateOrderUseCase yang sudah terlalu besar (300 baris)
2menjadi beberapa smaller usecase yang focused.
3
4JANGAN langsung split semuanya. Lakukan incremental:
5
6Phase 1 (minggu ini): Extract validateCartItems
7Phase 2 (minggu depan): Extract calculateOrderTotal
8Phase 3 (dua minggu): Extract createOrderRecord
9Phase 4 (tiga minggu): Buat separate OrderValidator usecase
10
11Untuk Phase 1 saja sekarang. Rules:
12- Satu commit per phase
13- Test harus lulus setiap commit
14- PR setiap phase (bukan semua di satu PR)Memecah menjadi fase-fase kecil membuat setiap langkah bisa direview dan di-rollback secara independen — jauh lebih aman daripada satu PR raksasa yang mustahil ditinjau dengan teliti.
15.9 Refactoring Naming Conventions
Nama type yang tidak konsisten antar file menyulitkan navigasi codebase. Prompt berikut mengaudit penamaan input struct dan menyeragamkannya mengikuti satu konvensi.
1Audit naming conventions di internal/usecase/order/:
2
3Saat ini ada inconsistency:
4- create_order.go menggunakan `CreateOrderInput`
5- cancel_order.go menggunakan `CancelInput` (tanpa "Order" suffix)
6- get_order.go menggunakan `GetOrderByIDInput`
7
8Tolong:
91. Propose naming convention yang konsisten
102. Rename type-type yang tidak sesuai konvensi
113. Update semua references (files yang import/menggunakan types ini)
124. Pastikan semua test masih lulus
13
14Konvensi yang diusulkan: `{Action}OrderInput`
15Contoh: CreateOrderInput, CancelOrderInput, GetOrderInputRename lintas file adalah pekerjaan mekanis yang rawan terlewat satu-dua referensi — di sinilah AI unggul, asalkan diakhiri dengan go build dan test untuk memastikan tidak ada yang tercecer.
15.10 Verifikasi Refactoring: Sebelum dan Sesudah
Setiap refactoring wajib diakhiri verifikasi menyeluruh. Rangkaian perintah berikut memeriksa coverage, race condition, regresi, performa, dan static analysis sekaligus.
1# 1. Coverage tidak boleh turun
2go test -race -cover -coverprofile=after-refactor.out ./...
3# Compare dengan before-refactor.out
4
5# 2. Tidak ada race condition baru
6go test -race ./...
7
8# 3. Tidak ada regression
9go test ./... -count=3 # run 3 kali untuk catch flaky tests
10
11# 4. Tidak ada performance regression (untuk algorithmic refactoring)
12go test -bench=. -benchmem ./internal/usecase/order/...
13
14# 5. Static analysis
15golangci-lint run ./internal/usecase/order/...Lima pemeriksaan ini adalah checklist “hijau berarti aman”: jika coverage tetap, tidak ada race baru, dan benchmark tidak memburuk, kamu punya bukti kuat bahwa perilaku tidak berubah.
15.11 Refactoring yang Aman: Pola “Strangler Fig”
Untuk refactoring besar yang berisiko, pola “strangler fig” memungkinkan migrasi bertahap tanpa big-bang cutover. Prompt berikut merencanakan migrasi repository lama ke pola baru secara paralel.
1Kita mau refactor OrderRepository dari raw pgx ke pattern yang lebih structured.
2Ini besar dan berisiko.
3
4Gunakan pendekatan Strangler Fig:
51. Buat interface OrderRepository yang baru
62. Buat implementasi baru di internal/repository/v2/postgres/
73. Jalankan KEDUANYA secara parallel dengan feature flag:
8 if useNewRepo { ... } else { ... }
94. Pindahkan satu endpoint ke implementasi baru
105. Monitor di production
116. Jika OK, pindahkan semua endpoint satu per satu
127. Hapus implementasi lama setelah semua dipindahkan
13
14Buat rencana untuk Phase 1 (interface + implementasi baru) saja dulu.Pola strangler fig menjaga sistem lama tetap berjalan sementara yang baru tumbuh di sampingnya — risiko migrasi tersebar tipis di banyak langkah kecil, bukan menumpuk di satu momen berbahaya.
15.12 Refactoring Documentation
Mendokumentasikan setiap refactoring membangun jejak keputusan yang berguna di masa depan. Format log berikut mencatat tipe, dampak, dan motivasi tiap perubahan.
1## Refactoring Log
2
3### 2025-07-01: Extract validateCartItems
4**File:** internal/usecase/order/create_order.go
5**Type:** Extract Method (Category 1 — Code Structure)
6**Before:** 85-line Execute method
7**After:** Execute + validateCartItems (separated concerns)
8**Behavior change:** None
9**Test coverage:** 88.4% → 88.4% (no change)
10**Motivation:** Improve readability, enable independent testing of validation logic
11
12### 2025-07-15: Standardize error wrapping
13**Files:** All usecase files in internal/usecase/order/
14**Type:** Pattern Refactoring (Category 2)
15**Convention applied:** "[usecase]: [operation]: %w"
16**Behavior change:** None (only error message strings changed)
17**Test coverage:** 88.4% → 89.1% (added boundary tests during verification)Log seperti ini menjawab pertanyaan “kenapa kode ini dulu dipecah begini?” berbulan-bulan kemudian — mencatat motivasi mencegah refactoring yang sama diperdebatkan ulang.
15.13 AI-Assisted Refactoring Limitation
AI hebat untuk sebagian refactoring, tapi ada batas yang harus disadari. Pembagian berikut memisahkan mana yang aman didelegasikan dan mana yang sebaiknya dipimpin manusia.
Sebaiknya human-led:
- Refactoring yang mengubah arsitektur signifikan (misal: domain model restructuring)
- Refactoring yang melibatkan business logic yang subtil
- Algorithmic refactoring yang punya correctness proof yang complex
Sangat baik via AI:
- Extract method/function
- Rename dengan consistency
- Standardize patterns
- Format dan style consistency
- Simple query optimization
Aturan praktisnya: semakin refactoring menyentuh keputusan arsitektur atau logika bisnis yang halus, semakin besar porsi manusia — AI paling andal untuk pekerjaan mekanis yang polanya jelas.
15.14 Test-First Refactoring
Untuk refactoring berisiko, tambahkan test dulu sebelum menyentuh kode. Prompt berikut menerapkan disiplin TDD pada perubahan aturan bisnis CanBeCancelled.
1Saya mau refactor method CanBeCancelled() untuk handle tambahan business rule baru:
2"Merchant bisa cancel order yang CONFIRMED jika belum masuk fulfillment"
3
4Sebelum refactoring:
51. Tulis test yang verify existing behavior (sehingga kita tahu jika ada regression)
62. Tulis test untuk behavior baru (yang akan fail dulu)
73. Lakukan refactoring sampai kedua set test lulus
8
9Ini adalah TDD untuk refactoring.Dengan menulis test existing-behavior lebih dulu, kamu mengunci perilaku lama sebagai jaring pengaman — regresi apa pun langsung terlihat merah sebelum sempat sampai ke production.
15.15 Refactoring dan Spec Update
Kadang proses refactoring justru mengungkap bahwa spec-nya sendiri ambigu. Prompt berikut menangani situasi di mana batas “tepat 15 menit” ternyata belum terdefinisi jelas di spec.
1Saat refactoring CanBeCancelled, saya menemukan bahwa spec belum
2mendefinisikan behavior "exactly at 15 minutes" — apakah inclusive atau exclusive?
3
4Saat ini kode pakai `<=` (inclusive): time.Since(CreatedAt) <= 15*time.Minute
5
6Sebaiknya:
71. Cek di spec apakah ada definisi yang jelas
82. Jika tidak ada, tambahkan klarifikasi ke spec
93. Buat test yang explicitly test boundary case
104. Pastikan implementasi konsisten dengan klarifikasi spec
11
12Jangan refactor kode sampai spec klarifikasinya ada.Hasil klarifikasinya menjadi AC4: batas 15 menit bersifat inklusif — order berumur tepat 15 menit 00 detik masih bisa dibatalkan, dan window tertutup mulai 15 menit lewat 1 detik. Jadi time.Since(CreatedAt) <= 15*time.Minute di kode bukan kondisi yang kebetulan tertulis begitu, melainkan implementasi langsung dari AC4 — dan sejak klarifikasi itu masuk, mengganti <= menjadi < bukan lagi soal selera, tapi pelanggaran spec yang harus ditangkap test boundary (lihat §15.1).
Menemukan ambiguitas spec saat refactoring sebenarnya kabar baik: perbaiki spec dulu, baru kode — karena mengubah kode di atas spec yang kabur hanya memindahkan ambiguitas, bukan menyelesaikannya.
15.16 Batch Refactoring dengan AI
Untuk perubahan mekanis yang sama di banyak file, batch refactoring sangat efisien. Prompt berikut mengganti semua panggilan slog.Error menjadi slog.ErrorContext di seluruh usecase.
1Saya perlu apply perubahan yang sama di semua usecase files:
2Ganti semua:
3slog.Error("something failed", "err", err)
4dengan:
5slog.ErrorContext(ctx, "something failed", "err", err)
6
7Files yang perlu diupdate:
8- internal/usecase/order/create_order.go
9- internal/usecase/order/cancel_order.go
10- internal/usecase/order/get_order.go
11- internal/usecase/order/update_order.go
12
13Constraint:
14- Semua fungsi sudah punya ctx parameter
15- Jangan ubah yang lain, hanya logging calls
16
17Setelah update, jalankan go build ./... untuk verify.Batch change seperti ini adalah pekerjaan repetitif yang rawan human error jika dilakukan manual — AI menyapunya konsisten di semua file, asalkan diakhiri go build sebagai verifikasi.
15.17 Refactoring untuk Testability
Kode yang sulit dites biasanya menyimpan dependency tersembunyi seperti time.Now(). Prompt berikut merencanakan injeksi clock agar window 15 menit bisa dikontrol di test.
1CancelOrderUseCase.Execute saat ini sulit di-test karena:
21. Direct dependency ke time.Now() untuk 15-minute window check
3 (sulit control di test)
42. Direct dependency ke concrete Kafka implementation
5
6Refactoring yang diinginkan untuk testability:
71. Inject clock dependency:
8 type Clock interface {
9 Now() time.Time
10 }
11 Sehingga test bisa inject fake clock
12
132. Interface sudah ada untuk Kafka (dari refactoring sebelumnya)
14
15Buat rencana refactoring untuk Clock injection:
161. Interface Clock
172. RealClock implementation
183. Update CancelOrderUseCase constructor
194. Update entity.CanBeCancelled(clock Clock) atau tetap di usecase?
205. Update all testsMenginjeksi clock mengubah test yang tadinya flaky (bergantung waktu sistem) menjadi deterministik — kamu bisa memaksa “sekarang” ke detik mana pun untuk menguji boundary window dengan pasti.
15.18 Tips & Gotchas
💡 Tip 1: Test setelah setiap perubahan kecil — jangan tunggu refactoring selesai seluruhnya baru test. Test setelah setiap step kecil, sehingga jika ada yang gagal, mudah mengisolasi di mana masalahnya.
💡 Tip 2: Gunakan git diff sebelum commit — review diff sebelum commit refactoring. Pastikan tidak ada perubahan yang tidak diinginkan menyelinap masuk.
💡 Tip 3: Refactoring dan feature baru dalam PR terpisah — jangan mencampur refactoring dengan feature baru dalam satu PR. Reviewer jadi sulit membedakan mana yang “just refactoring” dan mana yang “perubahan behavior”.
💡 Tip 4: Dokumentasikan alasan refactoring — commit message yang baik seperti refactor(order): extract validateCartItems for readability and testability jauh lebih berguna daripada tanpa keterangan.
⚠️ Gotcha 1: AI bisa introduce subtle behavior change — terutama untuk edge case yang tidak ada test-nya. Selalu jalankan test dengan -race setelah refactoring oleh AI.
⚠️ Gotcha 2: Refactoring yang terlalu besar dalam satu PR — PR yang diff-nya 1000 baris “just refactoring” hampir tidak bisa direview dengan baik. Pecah menjadi PR yang lebih kecil.
⚠️ Gotcha 3: Jangan refactor kode yang belum punya test — refactoring tanpa test adalah berjalan di atas tali tanpa jaring pengaman. Tulis test dulu, baru refactor.
⚠️ Gotcha 4: Performance refactoring perlu benchmark — jangan berasumsi refactoring “lebih efficient”. Benchmark sebelum dan sesudah untuk membuktikannya.
15.19 Contoh Commit Message yang Baik untuk Refactoring
Commit message yang baik untuk refactoring menegaskan bahwa behavior tidak berubah. Contoh berikut membandingkan pesan yang deskriptif dengan yang tidak berguna.
1# Good
2git commit -m "refactor(order): extract validateCartItems from Execute
3
4Extract stock validation logic into separate private method.
5Motivation: improve readability and enable independent unit testing.
6
7Behavior: identical to pre-extraction code.
8Coverage: maintained at 88.4% (unchanged)
9Tests: all 7 test cases in CancelOrderUseCaseSuite pass"
10
11# Bad
12git commit -m "refactor some stuff"
13git commit -m "cleanup"
14git commit -m "moved code around"Pesan “Good” mencatat motivasi dan menegaskan “behavior: identical” — informasi yang berharga saat menelusuri git log untuk mencari kapan sebuah regresi masuk; pesan “Bad” tidak memberi petunjuk apa pun.
15.20 Ringkasan
Refactoring dengan Claude Code adalah powerful tapi memerlukan disiplin. Safety net yang terdiri dari spec (mendefinisikan behavior yang diharapkan) dan test (memverifikasi behavior) adalah fundamental untuk refactoring yang aman.
Tiga kategori risiko: Code structure (low risk) → Pattern refactoring (medium risk) → Algorithmic refactoring (high risk). Semakin tinggi risiko, semakin perlu human oversight.
Workflow yang aman: Verify coverage → add missing tests → refactor incrementally → test setelah setiap step → review diff sebelum commit.
AI sangat baik untuk: Extract method, rename consistency, standardize patterns. Kurang cocok untuk: architectural refactoring besar, business logic changes.
Jangan refactor tanpa test — ini non-negotiable. Coverage di bawah 80% = tambah test dulu sebelum refactoring.
Ini adalah artikel terakhir di Part 3 seri ini. Di artikel berikutnya, kita masuk ke Part 4: SDD di Skala Tim — dimulai dengan bagaimana SDD bekerja di environment microservice dengan API contract antar service.