Refactoring Golang dengan AI: Strategi Safe Large-Scale Changes
Cara melakukan refactoring Go yang aman dan efektif dengan AI. Dari rename sederhana hingga extract interface dan migrate pattern secara large-scale dengan Claude Code dan Cursor.
Refactoring dengan AI: Safe Large-Scale Changes
Refactoring golang dengan AI mengubah salah satu aktivitas paling berisiko dalam software development menjadi jauh lebih terkendali. Refactoring memang paling sering ditunda justru karena risikonya. AI mengubah persamaan ini secara signifikan: dengan AI yang bisa menganalisis impact secara komprehensif dan mengeksekusi perubahan di banyak file sekaligus, refactoring yang tadinya butuh 2 hari bisa selesai dalam 2 jam.
Yang perlu dijaga bukan kecepatannya, tapi disiplinnya. Sepanjang artikel ini kita akan bahas cara refactoring dengan AI yang tetap aman: safety net, langkah inkremental, dan verifikasi di setiap tahap.
13.1 Tipe Refactoring dan Best Tool untuk Setiap Tipe
Tidak semua refactoring punya risiko yang sama, dan tool yang tepat berbeda untuk tiap tipe. Peta berikut mengelompokkan refactoring dari yang paling lokal hingga yang menyentuh seluruh arsitektur, lengkap dengan rekomendasi tool dan strateginya.
1TIPE 1: Local Refactoring (dalam satu function/file)
2 Contoh: Simplify complex condition, extract helper method
3 Best tool: Cursor inline atau Copilot inline
4 Risk: RENDAH — mudah verify dengan go test
5 Strategy: Langsung, verify dengan test
6
7TIPE 2: Package-level Refactoring (dalam satu package)
8 Contoh: Extract interface, reorganize structs, rename types
9 Best tool: Claude Code atau Cursor Composer
10 Risk: MEDIUM — bisa affect tests dan callers di package lain
11 Strategy: Tests dulu, refactor, verify
12
13TIPE 3: Cross-package Refactoring
14 Contoh: Move struct ke package baru, change interface signatures
15 Best tool: Claude Code (reasoning) atau Gemini (large codebase)
16 Risk: TINGGI — compilation errors di banyak places
17 Strategy: Plan dulu, batch changes, verify setiap step
18
19TIPE 4: Architecture Refactoring
20 Contoh: Migrate dari monolith ke layered, extract domain entities
21 Best tool: Claude Code (paling reliable untuk reasoning)
22 Risk: SANGAT TINGGI — affect entire codebase
23 Strategy: Incremental over multiple PRs, extensive testingSemakin ke bawah tabel, semakin besar risikonya dan semakin butuh perencanaan — jangan perlakukan architecture refactoring dengan disiplin yang sama seperti extract helper.
13.2 Safety Net: Setup Sebelum Refactoring
Sebelum menyentuh satu baris kode pun, siapkan jaring pengaman agar setiap perubahan bisa diukur dan dibatalkan. Rangkaian perintah berikut wajib dijalankan sebelum refactoring apa pun, dan dibandingkan lagi setelahnya.
1# WAJIB sebelum refactoring apapun:
2
3# 1. Commit working state
4git status # pastikan clean atau stash
5git checkout -b refactor/cancel-order-interface
6git add -A && git commit -m "checkpoint: pre-refactoring"
7
8# 2. Verifikasi semua tests pass
9go test -race ./... # harus 100% pass
10
11# 3. Coverage baseline
12go test -coverprofile=pre-refactor.out ./...
13go tool cover -func=pre-refactor.out > pre-refactor-coverage.txt
14
15# 4. Verifikasi build
16go build ./...
17
18# 5. Verifikasi vet
19go vet ./...
20
21# Setelah refactoring, bandingkan:
22go test -coverprofile=post-refactor.out ./...
23go tool cover -func=post-refactor.out > post-refactor-coverage.txt
24diff pre-refactor-coverage.txt post-refactor-coverage.txt
25# Tidak ada file yang coverage-nya turunDengan baseline coverage dan checkpoint commit, kamu selalu punya titik acuan objektif: jika coverage turun atau test merah, kamu tahu persis ke mana harus kembali.
13.3 Extract Interface Refactoring
Salah satu refactoring paling umum di Go Clean Architecture adalah memecah interface yang terlalu gemuk. Prompt berikut menginstruksikan Claude Code untuk memecah OrderRepository menjadi dua interface yang lebih kecil sambil menjaga backward compatibility.
1# Scenario: OrderRepository interface sekarang terlalu fat
2# Perlu extract ke: ReadOrderRepository + WriteOrderRepository
3
4# Prompt ke Claude Code:
5claude
6> Refactor OrderRepository interface di internal/domain/order/repository.go
7> menjadi dua interface yang lebih kecil (Interface Segregation):
8>
9> ReadOrderRepository:
10> GetByIDAndUserID, GetByID, ListByUserID, CountByStatus
11>
12> WriteOrderRepository:
13> Create, CancelWithStockRestore, UpdateStatus, SoftDelete
14>
15> Requirements:
16> 1. Buat dua interface baru di file yang sama
17> 2. Buat OrderRepository interface yang embed kedua interface (backward compat)
18> 3. Update semua usecase yang saat ini pakai OrderRepository:
19> - cancel_order.go: hanya butuh ReadOrderRepository + WriteOrderRepository
20> - list_orders.go: hanya butuh ReadOrderRepository
21> - create_order.go: hanya butuh WriteOrderRepository
22> 4. Update PostgresOrderRepository struct untuk implement keduanya
23> 5. Run: go build ./... setelah setiap step untuk verify
24> 6. Jangan ubah tests yang sudah passPerhatikan poin 2 dan 6: dengan embedding interface lama dan tidak menyentuh test yang sudah hijau, refactoring ini berjalan tanpa memutus kode pemanggil yang ada.
13.4 Extract Domain Layer Refactoring
Kasus klasik berikutnya adalah business logic yang seharusnya ada di domain entity tapi malah tersebar di usecase. Cuplikan Go berikut memperlihatkan kondisi “sebelum”, di mana aturan bisnis bocor ke dalam usecase.
1// BEFORE (business logic di usecase — WRONG):
2func (uc *CancelOrderUseCase) Execute(ctx context.Context, input CancelOrderInput) error {
3 order, err := uc.repo.GetByIDAndUserID(ctx, input.OrderID, input.UserID)
4 if err != nil { return fmt.Errorf("cancelOrder.Execute: %w", err) }
5 if order == nil { return domain.ErrOrderNotFound }
6
7 // Business logic di usecase — harusnya di entity!
8 if order.Status != domain.StatusPending {
9 return &domain.OrderNotCancellableError{Status: order.Status}
10 }
11 if time.Since(order.CreatedAt) > 30*time.Minute {
12 return domain.ErrCancelWindowExpired
13 }
14 // ...
15}Masalahnya jelas: aturan “kapan order boleh dibatalkan” adalah pengetahuan domain, bukan orkestrasi usecase. Versi “sesudah” berikut memindahkan aturan itu menjadi method pada entity Order.
1// AFTER (business logic di entity — CORRECT):
2// domain/order/entity.go
3func (o *Order) CanBeCancelled() bool {
4 return o.Status == StatusPending
5}
6
7func (o *Order) IsCancelWindowOpen() bool {
8 return time.Since(o.CreatedAt) <= 30*time.Minute
9}
10
11func (o *Order) ValidateCancellation() error {
12 if !o.CanBeCancelled() {
13 return &OrderNotCancellableError{Status: o.Status}
14 }
15 if !o.IsCancelWindowOpen() {
16 return ErrCancelWindowExpired
17 }
18 return nil
19}
20
21// usecase
22func (uc *CancelOrderUseCase) Execute(ctx context.Context, input CancelOrderInput) error {
23 order, err := uc.repo.GetByIDAndUserID(ctx, input.OrderID, input.UserID)
24 if err != nil { return fmt.Errorf("cancelOrder.Execute: %w", err) }
25 if order == nil { return domain.ErrOrderNotFound }
26 if err := order.ValidateCancellation(); err != nil { return err }
27 // ...
28}Setelah dipindah, usecase menjadi ramping dan aturan bisnis bisa diuji langsung pada entity — inilah pemisahan tanggung jawab yang benar. Prompt berikut mengarahkan AI melakukan pemindahan itu langkah demi langkah.
1# Prompt Claude Code:
2claude
3> Refactor: pindahkan business logic dari cancel_order.go usecase
4> ke domain/order/entity.go.
5>
6> Business logic yang perlu dipindah:
7> 1. Status check → Order.CanBeCancelled() bool method
8> 2. Time window check → Order.IsCancelWindowOpen() bool method
9> 3. Combined validation → Order.ValidateCancellation() error method
10>
11> Steps:
12> 1. Add methods ke entity.go
13> 2. Update cancel_order.go untuk pakai entity methods
14> 3. Update cancel_order_test.go untuk test entity methods (bukan via usecase)
15> 4. Run: go test -race ./... — semua harus pass
16> 5. Run: go test -coverprofile=after.out ./...Instruksi step 3 penting: dengan menguji entity method secara langsung, test jadi lebih tahan lama karena mengecek behavior domain, bukan detail implementasi usecase.
13.5 Naming Convention Refactoring (Large Scale)
Rename lintas codebase terlihat sepele tapi paling gampang bikin kerusakan diam-diam kalau tidak hati-hati. Alur tiga langkah berikut — audit dulu, rename dengan word boundary, lalu verifikasi — membuat standardisasi penamaan tetap aman.
1# Scenario: inconsistent naming di seluruh codebase
2# ID fields: UserID, UserId, user_id (mixed)
3# Perlu standardize ke UserID (Go convention: acronyms uppercase)
4
5# LANGKAH YANG AMAN:
6
7# Step 1: Audit dulu dengan AI
8claude
9> Analyze seluruh codebase dan identifikasi SEMUA field/variable
10> yang menggunakan inconsistent naming untuk UUID fields:
11> Cari: UserId, userId, user_id (sebagai Go struct field, bukan JSON tag)
12> Expected: seharusnya UserID (Go convention)
13> List semua files dan line numbers.
14
15# Step 2: Claude generate gorename commands atau direct edits
16claude
17> Berdasarkan list tadi, rename semua occurrence dari UserId → UserID
18> di semua Go files (bukan JSON tags, bukan SQL, bukan comments).
19> Gunakan word boundary untuk avoid partial matches.
20> Setelah setiap file: go build ./... untuk catch errors.
21
22# Step 3: Verify
23go build ./...
24go test ./...
25grep -r "UserId" --include="*.go" . # should return emptyKunci keamanannya ada di Step 1: audit terlebih dahulu memberi kamu daftar eksplisit yang bisa direview manusia sebelum AI menyentuh satu file pun.
13.6 Error Handling Standardization Refactoring
Error handling yang tidak konsisten adalah utang teknis yang menyebar pelan-pelan. Prompt audit berikut meminta AI mengkategorikan pelanggaran konvensi wrapping sebagai CRITICAL atau WARNING sebelum apa pun diperbaiki.
1# Scenario: error handling tidak konsisten di seluruh codebase
2# Beberapa place: return err (tanpa wrap)
3# Beberapa place: return errors.New("...") (tanpa context)
4# Perlu standardize ke: fmt.Errorf("pkg.Method: %w", err)
5
6# Audit dengan Claude Code:
7claude
8> Scan seluruh internal/ directory dan identifikasi:
9> 1. Error returns tanpa wrapping: "return err" (tanpa fmt.Errorf)
10> 2. Error creation tanpa context: errors.New("message") di non-domain code
11> 3. Error wrapping tanpa function name: fmt.Errorf("error: %w", err)
12> (harusnya include function name: fmt.Errorf("pkg.FuncName: %w", err))
13>
14> Categorize sebagai:
15> CRITICAL: violates convention secara jelas
16> WARNING: borderline case
17>
18> Output: file:line:issue untuk setiap finding
19
20# Fix dalam batches:
21claude
22> Fix semua CRITICAL error handling issues di internal/repository/postgres/
23> (satu package dulu).
24> Setelah fix: go build ./internal/repository/... && go test ./internal/repository/...Dengan memperbaiki satu package per batch dan langsung build+test, kamu menjaga blast radius tetap kecil sehingga error kompilasi mudah ditelusuri asalnya.
13.7 Migrate dari package A ke package B
Migrasi dependency lintas codebase butuh urutan yang benar: analisis impact, update go.mod, batch update import, verifikasi, baru buang dependency lama. Langkah-langkah berikut memigrasikan github.com/golang/mock ke go.uber.org/mock dengan aman.
1# Scenario: perlu migrate dari deprecated internal package ke standard library
2# Contoh: github.com/golang/mock/gomock → go.uber.org/mock/gomock
3
4# Step 1: Impact analysis
5claude
6> Identify semua files yang import "github.com/golang/mock/gomock"
7> atau "github.com/golang/mock/mockgen"
8> List semua dengan full path.
9
10# Step 2: Update go.mod
11go get go.uber.org/mock/gomock@latest
12go get go.uber.org/mock/mockgen@latest
13go mod tidy
14
15# Step 3: Batch update imports
16claude
17> Update semua import paths dari:
18> "github.com/golang/mock/gomock" → "go.uber.org/mock/gomock"
19> "github.com/golang/mock/mockgen" → "go.uber.org/mock/mockgen"
20>
21> Di semua files yang di-identify tadi.
22> Setelah update: go build ./... untuk verify tidak ada compilation error.
23
24# Step 4: Verify
25go test -race ./...
26
27# Step 5: Remove old dependency
28go mod edit -droprequire github.com/golang/mock
29go mod tidyUrutan ini penting: membuang dependency lama (Step 5) hanya dilakukan setelah build dan test hijau, sehingga kamu tidak pernah berada di kondisi setengah-migrasi yang tidak bisa dikompilasi.
13.8 Refactoring Monolith ke Clean Architecture
Migrasi monolith ke Clean Architecture terlalu besar untuk satu sesi — harus dipecah ke fase yang masing-masing bisa di-merge. Rencana empat fase berikut mengekstrak domain, repository, usecase, lalu dependency injection secara berurutan.
1# Scenario: service lama yang semua logic di main.go atau handler
2# Perlu migrate ke Clean Architecture
3
4# Fase 1: Extract Domain (2-4 hari)
5claude
6> Analyze handlers/*.go dan identify semua:
7> 1. Entity/struct yang represent business objects (Order, Product, User)
8> 2. Business rules yang embedded dalam handler
9> 3. Error types yang digunakan
10>
11> Propose struktur internal/domain/ yang sesuai Clean Architecture.
12> Buat domain package skeleton TANPA implementasi dulu.
13
14# Fase 2: Extract Repository (2-3 hari)
15claude
16> Pindahkan semua database calls dari handlers ke repository layer.
17> Buat interface di domain, implement di internal/repository/postgres/.
18> Handlers saat ini akan panggil repository secara langsung (sementara).
19> Verify: go build ./... && go test ./...
20
21# Fase 3: Extract Usecase (2-3 hari)
22claude
23> Pindahkan business logic dari handlers ke usecase layer.
24> Handler sekarang hanya parse request dan return response.
25> Usecase berisi semua business logic dan panggil repository via interface.
26
27# Fase 4: Dependency Injection (1 hari)
28claude
29> Setup dependency injection di main.go atau cmd/app/main.go.
30> Wire: DB → repository → usecase → handler → router
31> Remove semua global state.Setiap fase menghasilkan kondisi yang tetap bisa di-build dan di-test, jadi kamu bisa merge di antara fase dan tidak pernah menahan satu PR raksasa yang berisiko selama seminggu.
13.9 Refactoring dengan Tests sebagai Guard
Pola paling aman untuk refactoring adalah “refactor under green tests” — setiap langkah kecil harus tetap hijau. Alur berikut memformalkan pola itu, termasuk instruksi eksplisit agar AI berhenti begitu ada test yang gagal.
1# Pattern yang paling aman: "Refactor under green tests"
2
3# Sebelum refactoring:
4go test ./... # harus 100% pass
5
6# Saat refactoring (setiap step kecil):
7# 1. Buat perubahan kecil
8# 2. go test ./... # harus masih pass
9# 3. Jika fail: revert step terakhir, analisis
10# 4. Jika pass: commit dan lanjut ke step berikutnya
11
12# AI yang ideal untuk refactoring:
13claude
14> Refactor ini step by step.
15> Setelah SETIAP step, jalankan: go build ./... && go test -race ./...
16> Jika ada failure, STOP dan report — jangan proceed ke step berikutnya.
17> Setiap step harus tetap dalam keadaan green.Instruksi “STOP dan report” mengubah AI dari eksekutor yang agresif menjadi partner yang disiplin: kegagalan ditangkap di langkah terkecil, bukan menumpuk sampai akhir.
13.10 Parallel Refactoring: Tim yang Bekerja Bersama
Ketika beberapa developer perlu me-refactor codebase yang sama, risiko terbesarnya bukan kode melainkan merge conflict. Strategi berikut memakai AI untuk merencanakan urutan refactoring yang meminimalkan tabrakan antar developer.
1Ketika tim yang multiple developer perlu refactor codebase yang sama:
2
3Problem:
4 Developer A refactor package order
5 Developer B refactor package payment (yang depend on order)
6 → Merge conflict yang kompleks
7
8Strategy dengan AI:
9 1. Pre-refactoring meeting (30 menit):
10 "AI, kita mau refactor X. Apa yang harus di-refactor dulu
11 untuk minimize conflict antar developer?"
12
13 2. Define clear ownership:
14 Developer A: domain layer (no external dependency)
15 Developer B: repository layer (depend on domain)
16 Developer C: usecase layer (depend on domain + repo interfaces)
17
18 3. Sequential refactoring:
19 Domain selesai dulu (A) → merge → repository (B) → merge → usecase (C)
20 Bukan paralel! Sequential minimize conflict
21
22 4. Interface-first approach:
23 Define interface changes dulu (domain) → semua developer bisa adapt
24 baru implement changes di layer mereka masing-masingPelajaran utamanya kontra-intuitif: untuk refactoring lintas layer, urutan sekuensial berbasis arah dependency justru lebih cepat total daripada paralel yang berujung konflik.
13.11 AI Refactoring Anti-Patterns
Sebelum masuk ke pola-pola lanjutan, ada baiknya mengenali cara-cara yang membuat refactoring dengan AI justru berantakan. Perbandingan BAD vs GOOD berikut merangkum empat kesalahan paling umum.
1Anti-pattern 1: "Refactor everything at once"
2BAD:
3 "Refactor seluruh codebase untuk follow Clean Architecture"
4
5GOOD:
6 "Refactor internal/usecase/order/cancel_order.go untuk
7 extract business logic ke domain entity.
8 Hanya file ini dulu."
9
10Anti-pattern 2: "Refactor tanpa test coverage"
11BAD:
12 Refactor code yang coverage-nya < 50%
13 Tidak ada way untuk verify behavior sama
14
15GOOD:
16 Minimal 80% coverage dulu, baru refactor
17
18Anti-pattern 3: "Trust AI tanpa verify"
19BAD:
20 AI says: "Refactoring selesai, semua oke"
21 Developer: langsung push ke main
22
23GOOD:
24 AI selesai → go build → go test -race → go vet → review diff → push
25
26Anti-pattern 4: "Terlalu banyak files dalam satu refactoring session"
27BAD:
28 "Refactor 50 files sekaligus"
29 → Jika ada error di tengah, susah trace
30
31GOOD:
32 5-10 files per session, commit setelah verifiedBenang merah keempat anti-pattern ini sama: scope yang terlalu besar tanpa jaring pengaman. Persempit scope dan tambahkan verifikasi, maka mayoritas masalah refactoring AI hilang.
13.12 Refactoring Pattern: Strangler Fig untuk Go
Strangler Fig adalah pola untuk mengganti kode legacy secara bertahap tanpa big-bang rewrite. Contoh Go berikut menaruh implementasi baru berdampingan dengan yang lama, lalu men-switch lewat feature flag.
1// Strangler Fig: replace legacy code incrementally
2
3// STEP 1: Create new implementation alongside old
4// old_cancel_order.go (masih berjalan)
5func CancelOrderLegacy(db *sql.DB, orderID string) error {
6 // old implementation
7}
8
9// new_cancel_order.go (baru, Clean Architecture)
10type CancelOrderUseCase struct {
11 repo OrderRepository
12}
13
14func (uc *CancelOrderUseCase) Execute(ctx context.Context, input Input) error {
15 // new implementation
16}
17
18// STEP 2: Feature flag untuk switch
19func (h *Handler) CancelOrder(c echo.Context) error {
20 if h.featureFlags.UseNewCancelOrder {
21 return h.cancelOrderUseCase.Execute(c.Request().Context(), input)
22 }
23 return h.cancelOrderLegacy(c)
24}
25
26// STEP 3: Gradually enable new implementation
27// Monitor: errors, latency
28// If stable for 1 week: remove old implementation
29
30// AI sangat membantu untuk:
31// - Generate new implementation yang equivalent
32// - Verify behavior equivalence via tests
33// - Clean up old implementation setelah verifiedDengan pola ini, kamu tidak pernah memaksakan pergantian total dalam satu deploy — implementasi baru dinaikkan bertahap di bawah feature flag dan bisa di-rollback instan kalau metrik memburuk.
13.13 Dokumentasi Refactoring untuk Tim
Refactoring besar sebaiknya meninggalkan jejak yang bisa dibaca tim. Template refactoring log berikut mencatat motivasi, perubahan, coverage, dan rencana rollback dalam format yang ringkas.
1# REFACTORING LOG — Cancel Order
2
3## Date
42026-07-15
5
6## Type
7Extract Domain Logic → Entity Method
8
9## Motivation
10Business rules (status check, time window) tersebar di multiple usecases.
11DRY violation: CancelOrder, AdminCancelOrder, BatchCancelOrder semua duplicate logic.
12
13## Changes Made
14- Added Order.ValidateCancellation() method (domain/order/entity.go)
15- Simplified cancel_order.go usecase (removed duplicated conditions)
16- Updated admin_cancel_order.go usecase (same simplification)
17- Updated batch_cancel_order.go usecase
18
19## Test Coverage
20Before: 76%
21After: 83%
22
23## Risk Assessment
24LOW: behavior preserved, tests confirm
25
26## AI Assistance
27Claude Code used for:
28- Impact analysis (which files affected)
29- Implementation of entity methods
30- Test generation for entity methods
31- Verification that all tests still pass
32
33## Rollback Plan
34git revert [commit hash] — single commit, clean rollbackDokumen sesederhana ini menjadi audit trail yang berharga saat suatu saat kamu perlu menjawab “kenapa kode ini di-refactor begini?” berbulan-bulan kemudian.
13.14 Tips & Gotchas
💡 Tip 1: Refactor dalam working hours yang tenang
Refactoring yang kompleks butuh focus yang tinggi. Hindari jam meeting yang padat. Set aside 2-4 jam blok waktu.
💡 Tip 2: Commit setiap verified step
Setiap step kecil yang sudah go test pass = commit. Ini memungkinkan bisect jika ada issue later.
💡 Tip 3: Refactoring PR yang kecil
Satu PR = satu jenis refactoring (misal: hanya extract interface). Jangan combine refactoring dengan feature di satu PR. Reviewer bisa fokus.
💡 Tip 4: Announce refactoring ke tim
Sebelum push PR yang affect banyak files: “Saya akan merge refactoring X besok pagi. Siapa yang sedang working on files yang affected?”
⚠️ Gotcha 1: AI yang terlalu aggressive
Claude Code atau Cursor kadang refactor lebih dari yang diminta. Selalu check git diff sebelum commit — baca semua changes.
⚠️ Gotcha 2: Tests yang test implementation (bukan behavior)
Refactoring yang benar tidak ubah behavior. Jika tests break padahal logic sama, tests itu mungkin test implementation detail. Fix tests, bukan stop refactoring.
⚠️ Gotcha 3: Rename yang incomplete
AI rename di Go files tapi miss: SQL queries, config files, API documentation, error messages. Manual check setelah AI rename.
13.15 Checklist Complete Refactoring
Agar tidak ada langkah aman yang terlewat, gunakan checklist lengkap berikut yang membagi refactoring ke tiga fase: sebelum, saat, dan sesudah, termasuk hal yang harus dicek reviewer.
1Pre-refactoring:
2□ Feature branch dibuat
3□ Semua tests pass
4□ Coverage baseline di-record
5□ Tim di-notify (jika affect banyak files)
6□ Scope refactoring terdefinisi jelas
7
8Saat refactoring:
9□ Step by step, tidak semua sekaligus
10□ go build setelah setiap step
11□ go test setelah setiap step
12□ git commit setelah setiap verified step
13
14Post-refactoring:
15□ go test -race ./... (full test suite)
16□ go vet ./...
17□ golangci-lint run ./...
18□ Coverage tidak turun dari baseline
19□ git diff review (tidak ada unintended changes)
20□ PR dengan deskripsi yang jelas (apa dan kenapa)
21
22Code review untuk refactoring PR:
23□ Behavior sama (test proves it)
24□ Tidak ada accidental feature changes
25□ Architecture improvement yang jelas
26□ CLAUDE.md diupdate jika ada convention baruChecklist ini mengubah “rasa aman” menjadi kriteria objektif — refactoring hanya dianggap selesai kalau setiap kotak tercentang, bukan ketika kode “terlihat benar”.
13.16 Studi Kasus: Refactoring Flash Sale Duplicate Logic
Mari lihat kasus nyata: logika flash sale yang terduplikasi di tiga tempat. Cuplikan Go “sebelum” berikut memperlihatkan pengecekan waktu dan kuota yang di-copy-paste antar usecase.
1// BEFORE: Logic duplicate di 3 tempat
2
3// claim_voucher_usecase.go
4func (uc *ClaimVoucherUseCase) Execute(ctx context.Context, input Input) error {
5 flashSale, _ := uc.repo.GetActive(ctx, input.FlashSaleID)
6 if flashSale == nil { return ErrFlashSaleNotFound }
7 // Duplicate: time check
8 if time.Now().Before(flashSale.StartTime) { return ErrFlashSaleNotStarted }
9 if time.Now().After(flashSale.EndTime) { return ErrFlashSaleEnded }
10 // Duplicate: claim check
11 if flashSale.CurrentClaims >= flashSale.MaxClaims { return ErrVoucherSoldOut }
12 // ...
13}
14
15// admin_extend_flashsale_usecase.go
16func (uc *AdminExtendFlashSaleUseCase) Execute(ctx context.Context, input Input) error {
17 flashSale, _ := uc.repo.GetByID(ctx, input.FlashSaleID)
18 if flashSale == nil { return ErrFlashSaleNotFound }
19 // Duplicate: same time check logic!
20 if time.Now().Before(flashSale.StartTime) { return ErrNotStartedYet }
21 // ...
22}Duplikasi ini adalah bom waktu: begitu aturan waktu berubah, kamu harus mengingat semua tempat yang meng-copy-nya. Prompt berikut meminta AI mengekstrak logika itu ke entity method.
1# Prompt untuk refactor:
2claude
3> Identifikasi semua business logic di flashsale domain yang duplicate
4> di multiple usecases (claim_voucher, admin_extend, batch_expire).
5>
6> Extract ke entity methods di internal/domain/flashsale/entity.go:
7> FlashSale.IsActive() bool
8> FlashSale.CanClaimVoucher() error (return specific error)
9> FlashSale.IsClaimable() bool
10>
11> Update semua usecases untuk gunakan entity methods.
12> Run tests setelah setiap update.
13> Output: coverage before dan after.Hasil “sesudah” berikut memindahkan seluruh aturan ke entity, sehingga tiap usecase cukup memanggil satu method dan tidak lagi menyimpan salinan logika.
1// AFTER: Logic di entity
2
3// internal/domain/flashsale/entity.go
4type FlashSale struct {
5 ID uuid.UUID
6 StartTime time.Time
7 EndTime time.Time
8 MaxClaims int
9 CurrentClaims int
10}
11
12func (fs *FlashSale) IsActive() bool {
13 now := time.Now()
14 return !now.Before(fs.StartTime) && !now.After(fs.EndTime)
15}
16
17func (fs *FlashSale) CanClaimVoucher() error {
18 if time.Now().Before(fs.StartTime) {
19 return ErrFlashSaleNotStarted
20 }
21 if time.Now().After(fs.EndTime) {
22 return ErrFlashSaleEnded
23 }
24 if fs.CurrentClaims >= fs.MaxClaims {
25 return ErrVoucherSoldOut
26 }
27 return nil
28}
29
30// Usecases sekarang lean:
31func (uc *ClaimVoucherUseCase) Execute(ctx context.Context, input Input) error {
32 flashSale, err := uc.repo.GetActive(ctx, input.FlashSaleID)
33 if err != nil { return fmt.Errorf("claimVoucher.Execute: %w", err) }
34 if flashSale == nil { return ErrFlashSaleNotFound }
35 if err := flashSale.CanClaimVoucher(); err != nil { return err }
36 // ...
37}Satu sumber kebenaran untuk aturan flash sale berarti perubahan aturan cukup dilakukan di satu method — persis nilai jual utama dari extract domain logic.
13.17 Automated Refactoring via CI
Sebagian sinyal refactoring bisa dideteksi otomatis di CI sebelum reviewer manusia terlibat. Workflow GitHub Actions berikut mengecek duplikasi logika, ukuran interface, dan pelanggaran arsitektur di setiap pull request.
1# Detect potential refactoring opportunities di CI
2# .github/workflows/refactoring-lint.yml
3
4name: Refactoring Opportunities
5
6on: [pull_request]
7
8jobs:
9 refactoring-check:
10 runs-on: ubuntu-latest
11 steps:
12 - uses: actions/checkout@v4
13 - uses: actions/setup-go@v5
14 with: { go-version: '1.22' }
15
16 - name: Check for duplicate logic
17 run: |
18 # Simplified: check untuk func yang identik atau sangat similar
19 # Tools seperti jscpd atau duplo bisa dipakai
20 go install github.com/mibk/dupl@latest
21 dupl -t 15 ./internal/usecase/...
22
23 - name: Check interface size
24 run: |
25 # Warn jika interface > 5 methods
26 go run scripts/check-interface-size.go ./internal/domain/...
27
28 - name: Check architecture violations
29 run: |
30 go run scripts/check-layer-imports.go ./...Dengan CI seperti ini, “utang refactoring” jadi terlihat sejak PR dibuka, bukan baru ketahuan setelah menumpuk berbulan-bulan.
13.18 Tools untuk Refactoring Support
Selain AI, ada tool Go native yang sangat membantu refactoring dengan kesadaran semantik. Kumpulan perintah berikut menunjukkan cara memakai guru, gorename, gopls, staticcheck, dan go mod graph, lalu meng-orkestrasinya lewat Claude Code.
1# Tool 1: guru (Go analysis tool)
2go get golang.org/x/tools/cmd/guru
3# Contoh: find all callers of a method
4guru -scope ./... callers internal/domain/order/entity.go:#245
5
6# Tool 2: gorename
7go get golang.org/x/tools/cmd/gorename
8# Rename dengan semantic awareness
9gorename -from '"github.com/santekno/santekno-shop/internal/domain/order".OrderRepository.GetByID' \
10 -to GetByIDAndUserID
11
12# Tool 3: gopls rename (via IDE)
13# VS Code: F2 pada symbol → Go-aware rename
14
15# Tool 4: staticcheck untuk detect dead code
16go install honnef.co/go/tools/cmd/staticcheck@latest
17staticcheck -checks U1000 ./...
18# U1000: exported func/var/type never used
19
20# Tool 5: gomod graph untuk dependency analysis
21go mod graph | grep santekno/shop
22
23# Semua tools ini bisa di-orchestrate via Claude Code:
24claude
25> Jalankan staticcheck untuk detect dead code di internal/usecase/
26> Identifikasi apa yang bisa di-remove sebagai bagian refactoringKombinasi terbaik adalah AI untuk reasoning dan planning, plus tool semantik seperti gorename/gopls untuk eksekusi rename yang benar-benar sadar tipe dan scope.
13.19 Commit Strategy untuk Refactoring yang Besar
Refactoring besar butuh strategi commit yang jelas agar riwayat git tetap bisa dibaca dan di-bisect. Ketiga strategi berikut — atomic commit, squash untuk PR, dan pemisahan concern — mencakup mayoritas kebutuhan.
1# Refactoring besar perlu commit strategy yang jelas
2
3# Strategy 1: Atomic commits (recommended)
4# Satu commit = satu logical change yang verified
5
6git commit -m "refactor(order): extract CanBeCancelled to entity method"
7git commit -m "refactor(order): extract IsCancelWindowOpen to entity method"
8git commit -m "refactor(order): extract ValidateCancellation combining both checks"
9git commit -m "refactor(usecase): use entity.ValidateCancellation in cancel_order"
10git commit -m "refactor(usecase): use entity.ValidateCancellation in admin_cancel"
11git commit -m "refactor(test): update tests to test entity methods directly"
12
13# Strategy 2: Squash untuk PR (cleaner history)
14git rebase -i HEAD~6
15# squash semua ke satu commit:
16# "refactor(order): extract business rules to domain entity"
17
18# Strategy 3: Separate commits untuk separate concerns
19# NEVER mix: refactoring + feature + bug fix di satu commitAturan emas yang tidak boleh dilanggar ada di Strategy 3: jangan pernah mencampur refactoring, feature, dan bug fix dalam satu commit, karena itu menghancurkan kemampuan bisect dan review.
13.20 Verifikasi Final Sebelum Merge
Sebelum me-merge PR refactoring, jalankan verifikasi akhir yang menyeluruh dalam satu tarikan napas. Skrip berikut merangkai build, test dengan race detector, vet, lint, dan perbandingan coverage.
1# Final verification checklist sebelum merge refactoring PR:
2
3echo "=== BUILD ===" && go build ./... && echo "✅ Build OK"
4echo "=== TESTS ===" && go test -race -count=1 ./... && echo "✅ Tests OK"
5echo "=== VET ===" && go vet ./... && echo "✅ Vet OK"
6echo "=== LINT ===" && golangci-lint run ./... && echo "✅ Lint OK"
7echo "=== COVERAGE ===" && \
8 go test -coverprofile=after.out ./... && \
9 go tool cover -func=after.out | tail -1 && \
10 echo "(compare dengan pre-refactoring baseline)"
11
12# Semua harus green sebelum PR di-mergeMenjalankan semuanya dalam satu skrip membuat gate merge menjadi biner: semua hijau berarti aman, satu saja merah berarti belum layak merge — tidak ada ruang abu-abu.
13.21 Menggabungkan Refactoring dengan Feature Sprint
Pertanyaan praktis yang sering muncul: bagaimana menyeimbangkan refactoring dengan pengiriman fitur? Ketiga opsi berikut — sprint khusus, aturan 20%, dan refactoring sebagai bagian fitur — punya trade-off masing-masing.
1Best practice untuk balancing refactoring dengan feature delivery:
2
3Option A: Dedicated Refactoring Sprint (setiap 3-4 sprint)
4 Sprint 1-3: Feature delivery
5 Sprint 4: Dedicated tech debt / refactoring
6
7 Pro: Focus, tidak ada distraction
8 Con: Tim mungkin resist "not shipping features"
9
10Option B: 20% Rule
11 80% sprint capacity untuk features
12 20% untuk refactoring dan tech debt
13
14 Pro: Sustainable, continuous improvement
15 Con: Perlu discipline untuk enforce percentage
16
17Option C: Refactoring as Part of Feature
18 Ketika implement feature yang touch file X:
19 "Refactor file X sedikit sebelum tambahkan feature baru"
20
21 Pro: Boy Scout Rule ("leave code better than you found it")
22 Con: Scope creep risk, perlu strict boundary
23
24Recommendation untuk tim Santekno dengan AI tools:
25 Option B (20% rule) dengan AI acceleration.
26 20% sprint = 4 hari → dengan AI, bisa accomplish 40 hari work of refactoring.Rekomendasinya jelas: aturan 20% yang berkelanjutan, dilipatgandakan oleh akselerasi AI, memberi improvement terus-menerus tanpa mengorbankan pengiriman fitur.
13.22 Ringkasan
Refactoring adalah investasi penting untuk kesehatan kode jangka panjang, dan dengan AI investasi ini menjadi jauh lebih terjangkau. Untuk memisahkan mana yang berubah dan mana yang tetap, mari rangkum dalam dua daftar berikut.
Yang berubah dengan AI:
- Impact analysis: dari 2 jam manual → 5 menit dengan AI
- Bulk changes: dari 1 hari → 30-60 menit
- Documentation: auto-generated refactoring log
Yang TIDAK berubah:
- Need for comprehensive tests
- Human judgment untuk scope dan approach
- Code review untuk verify correctness
- Communication ke tim
Poin intinya: AI mempercepat eksekusi, tapi manusia tetap bertanggung jawab atas keputusan scope, review, verifikasi, dan komunikasi. Refactoring yang paling bernilai — ekstraksi business logic ke domain entity — adalah yang paling sering tertunda sekaligus paling impactful untuk maintainability jangka panjang.