Anatomi Pull Request #
Bayangkan seorang reviewer membuka PR yang hanya bertitle “fix bug” dengan deskripsi kosong dan 400 baris perubahan tersebar di 15 file. Apa yang dia lakukan? Kemungkinan besar: approve dengan komentar singkat “LGTM” karena tidak tahu harus melihat ke mana. Bug yang seharusnya bisa dicegah lolos ke production, dan reviewer tidak bisa disalahkan — mereka tidak punya konteks apapun. PR yang buruk bukan hanya membuang waktu reviewer; ia secara aktif melemahkan proses quality control yang seharusnya menjadi pertahanan terakhir sebelum kode menyentuh production. Artikel ini membahas setiap komponen PR secara mendalam — apa fungsinya, bagaimana menulisnya dengan baik, dan seperti apa hasilnya dalam praktik.
Mengapa Anatomi PR Penting? #
PR yang terstruktur dengan baik memberikan dampak yang terukur pada proses engineering:
PR tanpa struktur:
Reviewer tidak tahu harus mulai dari mana
→ Membaca seluruh diff dari atas ke bawah tanpa konteks
→ Review memakan waktu 2x lebih lama
→ Komentar yang diberikan tidak tepat sasaran
→ Bug lolos karena reviewer kelelahan
PR dengan struktur yang baik:
Reviewer langsung memahami konteks dan tujuan
→ Fokus pada bagian yang paling kritis
→ Review lebih cepat dan lebih bermakna
→ Diskusi teknikal yang lebih produktif
→ Bug lebih mudah terdeteksi karena reviewer tahu apa yang harus dicari
PR yang baik juga berfungsi sebagai dokumentasi keputusan teknikal — enam bulan kemudian, ketika ada yang bertanya “kenapa kode ini ditulis seperti ini?”, jawabannya ada di thread PR, bukan di kepala engineer yang sudah mungkin tidak lagi bekerja di tim.
Komponen Anatomi PR #
flowchart TD
PR[Pull Request] --> T[Judul\nRingkasan satu baris yang deskriptif]
PR --> D[Deskripsi\nKonteks, solusi, dan dampak]
PR --> S[Scope\nBatas perubahan yang jelas]
PR --> C[Code Changes\nImplementasi yang bersih]
PR --> TV[Test & Validasi\nBukti perubahan sudah diuji]
PR --> IR[Impact & Risiko\nDampak ke sistem lain]
PR --> CL[Checklist\nPenjaga konsistensi kualitas]Judul PR #
Judul adalah hal pertama yang dilihat reviewer, stakeholder yang membaca changelog, dan engineer yang mencari PR lama di riwayat. Judul yang baik adalah ringkasan satu baris yang menjelaskan apa yang berubah dan mengapa penting.
Format yang Disarankan #
Format yang paling konsisten dan mudah dibaca mengikuti pola: [Tipe] Deskripsi yang spesifik.
// Format berdasarkan tipe perubahan
feat: Add rate limiting to public API endpoints
fix: Resolve race condition in concurrent order status update
refactor: Extract payment validation logic into dedicated service
chore: Upgrade grpc-go dependency to v1.62
docs: Update API documentation for order status endpoints
perf: Optimize product listing query with composite index
// Tanpa prefix — tetap harus spesifik
Add retry mechanism with exponential backoff to payment callback
Fix null pointer when user has no active subscription
Remove deprecated coupon calculation logic
Perbandingan Judul Baik vs Buruk #
// ✗ Judul yang tidak membantu reviewer atau masa depan
"Update code"
"Fix bug"
"WIP"
"Changes"
"Improve performance" ← apa yang diimprove? seberapa?
"Fix issue from QA" ← issue apa? dari QA session kapan?
"Hotfix" ← hotfix untuk apa?
// ✓ Judul yang memberikan konteks langsung
"Fix race condition in concurrent order status update causing stale data"
"Add rate limiting (100 req/min) to public product search endpoint"
"Refactor payment service to use strategy pattern for multi-gateway support"
"Fix null pointer exception when user has no active payment method"
"Migrate session storage from database to Redis for better performance"
Test judul PR-mu dengan pertanyaan ini: “Jika ada orang yang membaca judul ini di changelog atau git log enam bulan dari sekarang, apakah mereka langsung mengerti apa yang berubah?” Jika jawabannya tidak, perbaiki judulnya.
Deskripsi PR #
Deskripsi adalah komponen terpenting dari sebuah PR. Deskripsi yang baik bisa menghemat 30–60 menit waktu reviewer karena mereka tidak perlu menebak-nebak konteks dari kode yang dibaca.
Struktur Deskripsi yang Efektif #
## Latar Belakang
[Jelaskan kondisi saat ini dan mengapa perubahan ini diperlukan.
Sertakan data atau contoh konkret jika ada.]
## Perubahan
[Apa yang berubah secara teknikal. Fokus pada keputusan penting,
bukan detail implementasi yang sudah terlihat dari kode.]
## Alternatif yang Dipertimbangkan
[Pendekatan lain yang dipikirkan dan alasan mengapa tidak dipilih.
Bagian ini opsional tapi sangat berguna untuk keputusan yang tidak obvious.]
## Testing
[Bagaimana perubahan ini sudah diuji. Test apa yang ditambahkan,
dan bagaimana cara menjalankan manual test jika diperlukan.]
## Dampak dan Risiko
[Apa dampak ke sistem lain? Ada tidak breaking change?
Perlu migration? Ada risiko yang perlu dimonitor setelah deploy?]
Contoh Deskripsi Sebelum vs Sesudah #
// ✗ Deskripsi yang tidak berguna
## Description
Fixed the payment issue that was reported.
// ✓ Deskripsi yang memberikan konteks penuh
## Latar Belakang
Endpoint callback payment (`POST /webhooks/payment`) gagal total ketika
terjadi network timeout dari payment gateway. Dalam 30 hari terakhir,
ada 47 callback yang gagal (error rate ~3.2%) yang menyebabkan order
tetap berstatus `pending` meskipun pembayaran sebenarnya sudah sukses.
User harus menghubungi CS untuk konfirmasi manual.
## Perubahan
Menambahkan retry mechanism dengan exponential backoff untuk network errors:
- Maksimal 3 kali retry
- Initial delay: 1 detik, multiplier: 2x, maksimal: 8 detik
- Hanya retry untuk error yang bisa pulih (timeout, 503) — bukan untuk 400/401
Implementasi menggunakan `retryablehttp` library yang sudah ada di dependency.
## Alternatif yang Dipertimbangkan
**Dead Letter Queue (DLQ):** Pendekatan yang lebih robust, tapi butuh
infrastruktur message queue yang belum ada. Simpan sebagai RFC untuk
evaluasi quarter depan jika retry masih tidak cukup.
## Testing
- Unit test: `TestPaymentCallbackWithRetry` di `internal/webhook/payment_test.go`
- Integration test: simulasi timeout dengan mock server (lihat `testdata/mock_timeout_server.go`)
- Manual: set `PAYMENT_GATEWAY_URL` ke mock server yang return 503
## Dampak dan Risiko
- Latency pada kasus gagal naik maksimal ~9 detik (3 retry × 3 detik average)
- Tidak ada breaking change di API contract
- Idempotency key sudah ada di payment gateway — aman untuk retry
- Monitor `payment_callback_retry_count` metric setelah deploy
Scope Perubahan #
Scope yang jelas membantu reviewer memahami batas PR dan menghindari review yang melebar ke hal-hal yang di luar konteks.
// ✗ Scope yang tidak jelas
PR berjudul "Update payment service" tapi isinya:
- Fix callback handler (relevan)
- Refactor seluruh service layer (tidak related)
- Update 3 dependency (bisa PR tersendiri)
- Fix typo di berbagai file (noise untuk reviewer)
// ✓ Scope yang terbatas dan konsisten
PR berjudul "Fix payment callback retry on network timeout":
- Hanya file yang berkaitan dengan callback handling
- Tidak ada perubahan di luar domain yang disebutkan di judul
- Perubahan yang tidak related dikeluarkan ke PR terpisah
Cara mengidentifikasi scope yang perlu dipisah:
Pertanyaan: "Apakah perubahan ini bisa di-rollback sendiri tanpa
mempengaruhi perubahan lain di PR ini?"
Jika tidak → pisahkan ke PR yang berbeda
Perubahan Kode #
Ini adalah inti PR, tapi kualitasnya ditentukan bukan hanya oleh apa yang berubah, tapi oleh bagaimana perubahan itu bisa dipahami.
Panduan Kode yang Mudah Direview #
// Beri konteks melalui naming yang jelas
// ✗ Sulit dipahami tanpa membaca seluruh fungsi
func process(d []byte, n int) ([]byte, error) {
// ...
}
// ✓ Naming yang menjelaskan intent
func encryptPaymentData(rawPayload []byte, keyVersion int) ([]byte, error) {
// ...
}
// Gunakan komentar untuk keputusan yang tidak obvious dari kode
// ✗ Angka magic tanpa konteks
time.Sleep(3 * time.Second)
// ✓ Konstanta dengan penjelasan
const paymentGatewayTimeoutBuffer = 3 * time.Second // Buffer melebihi SLA gateway (2 detik)
time.Sleep(paymentGatewayTimeoutBuffer)
// Pecah fungsi panjang menjadi unit-unit yang lebih kecil
// ✗ Fungsi 100 baris yang melakukan segalanya
func handlePaymentCallback(w http.ResponseWriter, r *http.Request) {
// parse, validate, process, retry, respond — semuanya di satu fungsi
}
// ✓ Fungsi kecil dengan tanggung jawab tunggal
func handlePaymentCallback(w http.ResponseWriter, r *http.Request) {
payload, err := parseCallbackPayload(r)
if err != nil { ... }
if err := validateCallbackSignature(payload); err != nil { ... }
if err := processPaymentWithRetry(payload); err != nil { ... }
respondSuccess(w)
}
Arahkan Reviewer ke Bagian yang Paling Penting #
Untuk PR yang besar, author bisa menambahkan komentar di PR untuk memandu reviewer:
// Komentar author di PR yang bisa mengarahkan reviewer
Komentar di file `internal/webhook/handler.go`:
"Ini adalah perubahan utama — fungsi baru untuk retry logic.
Mohon fokus review di bagian error classification di baris 45-67."
Komentar di file `internal/webhook/retry.go`:
"File baru ini. Implementasi retry mengikuti pola yang sama dengan
service lain di payment domain — lihat `internal/payment/client.go`
sebagai referensi."
Test dan Validasi #
Test bukan hanya tentang coverage — ini tentang memberikan reviewer (dan diri sendiri di masa depan) keyakinan bahwa perubahan bekerja seperti yang diharapkan.
Yang Harus Disertakan #
UNIT TEST:
□ Test untuk setiap jalur logika yang baru atau berubah
□ Test untuk happy path — kasus normal yang diharapkan
□ Test untuk error handling — bagaimana kode merespons kegagalan
□ Test untuk edge case yang relevan (input kosong, nilai batas, concurrent)
INTEGRATION TEST (jika relevan):
□ Test yang melibatkan interaksi antar komponen
□ Test dengan mock untuk external dependency
CARA MENJALANKAN TEST (di deskripsi PR):
# Unit test
go test ./internal/webhook/...
# Test dengan race detector (untuk concurrency)
go test -race ./internal/webhook/...
# Integration test
go test -tags=integration ./internal/webhook/...
MANUAL TEST SCENARIO (untuk perubahan yang sulit ditest otomatis):
1. Setup: Set PAYMENT_GATEWAY_URL=http://localhost:9999 (mock server)
2. Trigger: POST /webhooks/payment dengan payload yang valid
3. Expected: Request di-retry 3x, log menunjukkan retry count
4. Cleanup: Reset PAYMENT_GATEWAY_URL ke nilai production
Test sebagai Dokumentasi #
Test yang ditulis dengan baik adalah dokumentasi yang tidak bisa berbohong — kode berubah tapi test yang gagal langsung memberi tahu:
// ✗ Test yang tidak informatif
func TestRetry(t *testing.T) {
// test sesuatu
assert.NoError(t, err)
}
// ✓ Test yang mendokumentasikan perilaku yang diharapkan
func TestPaymentCallback_RetriesOnNetworkTimeout_MaxThreeAttempts(t *testing.T) {
// Given: payment gateway yang selalu timeout
mockGateway := newMockGateway(t).AlwaysTimeout()
handler := NewCallbackHandler(mockGateway)
// When: callback diterima
err := handler.ProcessWithRetry(validPayload)
// Then: terjadi 3 percobaan sebelum akhirnya return error
assert.ErrorIs(t, err, ErrMaxRetriesExceeded)
assert.Equal(t, 3, mockGateway.CallCount())
}
Impact dan Risiko #
Bagian ini adalah yang paling sering dilewatkan tapi paling kritikal untuk tim ops dan on-call. Perubahan yang tidak mendokumentasikan dampaknya adalah perubahan yang tidak siap untuk production.
// Template impact dan risiko yang lengkap
## Dampak ke Sistem Lain
| Komponen | Dampak | Kategori |
|---|---|---|
| `payment-service` | Latency naik maks 9 detik pada kasus gagal | Performa |
| `order-service` | Tidak ada perubahan | - |
| Monitoring | Tambah metric `payment_callback_retry_count` | Infrastruktur |
## Breaking Change
Tidak ada breaking change di API contract.
Response schema tidak berubah.
## Database / Migration
Tidak ada perubahan schema database.
## Deployment Notes
- Deploy bisa dilakukan langsung tanpa koordinasi khusus
- Tidak perlu feature flag
- Rollback aman — tidak ada perubahan data yang tidak reversible
## Yang Perlu Dimonitor Setelah Deploy
- `payment_callback_retry_count` — diharapkan ada tapi tidak boleh terlalu tinggi
- `payment_callback_error_rate` — diharapkan turun dari ~3.2%
- Latency P99 endpoint `/webhooks/payment` — boleh naik sedikit, alert jika > 15 detik
Template PR Siap Pakai #
Berikut template PR yang bisa langsung digunakan atau disesuaikan sebagai PR template di GitHub/GitLab:
## Latar Belakang
<!-- Jelaskan kondisi saat ini dan mengapa perubahan ini diperlukan.
Sertakan data atau referensi ticket jika ada. -->
Closes: #[nomor issue]
## Perubahan
<!-- Apa yang berubah secara teknikal. Fokus pada keputusan penting. -->
## Alternatif yang Dipertimbangkan
<!-- Pendekatan lain yang dipertimbangkan dan mengapa tidak dipilih.
Hapus bagian ini jika tidak relevan. -->
## Testing
<!-- Bagaimana perubahan ini diuji. -->
**Cara menjalankan test:**
```bash
# [perintah untuk menjalankan test]
Manual test scenario: (jika relevan) 1. 2. 3.
Dampak dan Risiko #
- Tidak ada breaking change di API contract
- Tidak ada perubahan schema database yang tidak backward-compatible
- Rollback aman
Yang perlu dimonitor setelah deploy: #
Checklist Author #
- Self-review sudah dilakukan
- CI sudah lulus (test, lint, build)
- Tidak ada debug code atau TODO yang terlupakan
- Deskripsi sudah cukup memberikan konteks untuk reviewer
- Test mencakup happy path dan error case yang penting
---
## Anti-Pattern Anatomi PR yang Harus Dihindari
// ✗ Judul generik yang tidak memberikan konteks “Update”, “Fix”, “Changes”, “WIP”, “Hotfix” // ✓ Judul spesifik yang menjawab “apa yang berubah dan kenapa”
// ✗ Deskripsi kosong atau satu kalimat untuk PR besar PR 800 baris: Description: “Added new feature” // ✓ Deskripsi yang menjelaskan latar belakang, perubahan, dan dampak
// ✗ PR yang mencampur terlalu banyak konteks Feature baru + refactor tidak terkait + update dependency + fix typo → Reviewer bingung harus fokus ke mana // ✓ Satu PR, satu tujuan — pisahkan yang tidak terkait
// ✗ Tidak ada informasi tentang cara testing “Saya sudah test, harusnya oke” // ✓ Sertakan perintah test dan scenario manual jika diperlukan
// ✗ Tidak menyebutkan breaking change atau dampak ke sistem lain PR merubah API response schema tanpa menyebutkan di deskripsi → Frontend tiba-tiba error karena tidak tahu ada perubahan // ✓ Selalu sebutkan breaking change dan koordinasikan dengan pihak yang terdampak
// ✗ Checklist PR dikosongkan atau di-check semua tanpa diverifikasi
- Semua test lulus (padahal CI masih merah) // ✓ Checklist diisi dengan jujur — item yang belum selesai lebih baik tidak di-check
---
## Checklist Anatomi PR yang Baik
JUDUL: □ Spesifik — menggambarkan apa yang berubah dan mengapa □ Tidak lebih dari 72 karakter □ Menggunakan format yang konsisten dengan tim (feat:/fix:/refactor: dll)
DESKRIPSI: □ Latar belakang menjelaskan kondisi saat ini dan mengapa perubahan diperlukan □ Perubahan teknikal dijelaskan di level yang tepat (tidak terlalu detail, tidak terlalu abstrak) □ Alternatif yang dipertimbangkan dicantumkan jika keputusannya tidak obvious □ Cara testing dijelaskan — termasuk perintah yang bisa langsung dijalankan □ Dampak ke sistem lain disebutkan secara eksplisit
SCOPE: □ Satu PR, satu tujuan — tidak mencampur konteks yang berbeda □ Perubahan di luar scope disebutkan secara eksplisit jika terpaksa ada
KODE: □ Naming jelas dan konsisten □ Tidak ada debug code atau TODO yang terlupakan □ Tidak ada kode yang di-comment-out tanpa alasan □ Komentar menjelaskan mengapa, bukan apa
TEST: □ Test ditambahkan untuk setiap logika baru □ Happy path dan error case sudah dicover □ CI sudah lulus sebelum PR dibuka untuk review
IMPACT: □ Breaking change disebutkan secara eksplisit (atau “tidak ada breaking change”) □ Perubahan database / migration dicantumkan □ Yang perlu dimonitor setelah deploy disebutkan
---
## Ringkasan
- Judul PR harus deskriptif — reviewer membaca puluhan PR; judul yang jelas menghemat waktu mereka sebelum membuka diff.
- Deskripsi adalah komponen terpenting — konteks yang baik di deskripsi menghasilkan review yang lebih cepat dan lebih bermakna.
- Jelaskan mengapa, bukan hanya apa — kode menunjukkan apa yang berubah; deskripsi harus menjelaskan mengapa perubahan ini diperlukan.
- Satu PR, satu tujuan — PR yang mencampur terlalu banyak konteks tidak bisa direview dengan baik.
- Sertakan informasi testing yang konkret — perintah yang bisa langsung dijalankan lebih berguna dari pernyataan “sudah ditest”.
- Selalu sebutkan breaking change dan dampak — perubahan yang tidak mendokumentasikan dampaknya memindahkan risiko ke reviewer dan tim ops.
- PR adalah dokumentasi keputusan teknikal — enam bulan dari sekarang, deskripsi PR yang baik bisa menjawab pertanyaan yang tidak ada di kode.
- Template PR menghilangkan friction — tim tidak perlu mengingat apa saja yang harus ada; template memastikan konsistensi secara otomatis.
---
← Sebelumnya: Fundamental
Berikutnya: Small vs Big PR →