Anatomi #
Sebuah pull request terdiri dari lebih dari sekadar diff kode. Diff hanyalah satu komponen — ia menunjukkan apa yang berubah, tapi tidak menjawab mengapa berubah, bagaimana cara memverifikasinya, atau apa dampaknya ke sistem lain. Reviewer yang hanya melihat diff tanpa konteks apapun dipaksa menebak semua hal itu — dan reviewer yang menebak tidak bisa memberikan review yang bermakna.
PR yang dirancang dengan baik adalah dokumen yang berdiri sendiri. Siapapun yang membacanya — reviewer hari ini, engineer baru yang onboarding bulan depan, atau kamu sendiri yang enam bulan lagi penasaran kenapa keputusan tertentu dibuat — bisa memahami seluruh gambar tanpa harus menggali Slack, Jira, atau minta penjelasan dari si author.
Artikel ini membahas setiap komponen anatomi PR secara mendalam: apa fungsinya, seperti apa yang baik vs buruk, dan bagaimana menyatukannya menjadi PR yang benar-benar efektif.
Alur Hidup Sebuah Pull Request #
Sebelum membahas komponen, penting untuk memahami di mana PR berada dalam alur kerja tim. PR bukan hanya dokumen statis — ia adalah titik koordinasi antara author, reviewer, sistem CI, dan codebase.
flowchart TD
A([Buat branch dari main]) --> B[Tulis kode & test]
B --> C{Self-review}
C -- Ada yang perlu diperbaiki --> B
C -- Siap --> D[Buka PR dengan deskripsi lengkap]
D --> E{CI Pipeline}
E -- Gagal --> B
E -- Lulus --> F[Request review ke reviewer]
F --> G[Reviewer membaca PR]
G --> H{Keputusan reviewer}
H -- Request Changes --> I[Author merespons & revisi]
I --> G
H -- Approved --> J{Semua reviewer approve?}
J -- Belum --> G
J -- Ya --> K[Merge ke main]
K --> L([Delete branch])Diagram ini menunjukkan sesuatu yang penting: PR adalah sebuah loop komunikasi, bukan satu arah. Review bisa terjadi berkali-kali sebelum merge, dan CI menjadi gatekeeper otomatis yang bekerja paralel dengan review manusia.
Tujuh Komponen Anatomi PR #
1. Judul #
Judul PR adalah hal pertama yang dilihat reviewer saat membuka daftar PR. Ia harus bisa menjawab satu pertanyaan: “PR ini tentang apa?” — dalam satu baris, tanpa perlu membuka body-nya.
Konvensi yang paling umum dan direkomendasikan adalah Conventional Commits — format yang konsisten dan bisa di-parse otomatis oleh tooling seperti changelog generator.
Format Conventional Commits:
<type>(<scope>): <deskripsi singkat>
Type yang umum:
feat → fitur baru
fix → perbaikan bug
refactor → perubahan kode tanpa mengubah perilaku
perf → optimasi performa
test → menambah atau mengubah test
docs → perubahan dokumentasi
chore → pemeliharaan, dependency update, konfigurasi
ci → perubahan CI/CD pipeline
Contoh judul yang baik:
feat(auth): add rate limiting to login endpoint
fix(payment): handle timeout from payment gateway gracefully
refactor(order): extract order validation into separate service
perf(query): replace N+1 with batch loading on user list
test(cart): add edge case for empty cart checkout
Contoh judul yang buruk:
Update code ← tidak ada informasi
Fix bug ← bug apa?
WIP ← jangan push PR yang belum selesai
fix stuff ← tidak spesifik, lowercase semua
feat: implement the new user authentication system with JWT and OAuth ← terlalu panjang
Judul PR yang baik bisa langsung menjadi entri changelog. Jika kamu kesulitan merumuskan judul dalam satu baris yang spesifik, itu biasanya tanda bahwa scope PR terlalu lebar dan perlu dipecah.
2. Deskripsi #
Deskripsi adalah jantung dari sebuah PR. Di sinilah konteks hidup — dan tanpa konteks, reviewer hanya bisa menilai apakah kode berjalan, bukan apakah kode tepat.
Deskripsi yang baik menjawab empat pertanyaan:
Empat pertanyaan yang harus dijawab deskripsi PR:
1. APA masalahnya? (atau fitur apa yang dibutuhkan?)
→ Latar belakang yang cukup agar reviewer tidak perlu membuka Jira
2. MENGAPA pendekatan ini dipilih?
→ Alasan di balik keputusan teknis yang diambil
3. APA yang berubah secara spesifik?
→ Ringkasan perubahan, terutama yang tidak langsung terlihat dari diff
4. BAGAIMANA cara memverifikasinya?
→ Langkah testing dan hal yang perlu diperhatikan
Berikut perbandingan deskripsi PR yang buruk vs baik untuk kasus yang sama:
## ✗ Deskripsi buruk:
Add retry to payment callback.
---
## ✓ Deskripsi baik:
### Latar Belakang
Endpoint `/webhooks/payment` saat ini gagal total jika downstream payment
gateway mengalami timeout. Berdasarkan log produksi minggu lalu, ada ~12
transaksi per hari yang gagal di sini karena timeout sesaat dari gateway,
padahal transaksi di sisi gateway sebenarnya berhasil.
### Perubahan
Menambahkan retry mechanism dengan exponential backoff (3 kali, interval
1s → 2s → 4s) khusus untuk error timeout (HTTP 504, connection timeout).
Error non-retriable seperti 400 dan 401 tidak akan di-retry.
### Keputusan yang Diambil
- Menggunakan exponential backoff daripada fixed interval untuk menghindari
thundering herd jika gateway baru pulih dari gangguan
- Batas 3 retry dipilih berdasarkan p99 timeout gateway yang ~3 detik,
sehingga total maksimum wait ~7 detik masih dalam batas timeout upstream
### Cara Testing
1. Jalankan `make test` — semua test baru ada di `payment_callback_test.go`
2. Manual: gunakan script `scripts/simulate_timeout.sh` untuk mensimulasikan
timeout dari gateway dan pastikan log menunjukkan retry attempt
3. Pastikan metric `payment.callback.retry_count` muncul di Grafana
### Impact
- Menambah latency maksimal ~7 detik pada kasus timeout (sebelumnya langsung gagal)
- Tidak ada perubahan kontrak API atau schema database
- Deployment: tidak perlu migration, bisa langsung deploy
Perbedaannya bukan soal panjang — perbedaannya adalah seberapa banyak informasi yang bisa langsung digunakan reviewer untuk membuat keputusan yang bermakna.
3. Scope Perubahan #
Scope PR adalah pernyataan implisit tentang apa yang ada di dalam PR ini dan apa yang tidak. PR dengan scope yang jelas memungkinkan reviewer untuk fokus — mereka tahu apa yang perlu diperhatikan dan apa yang bisa diabaikan.
graph LR
A[Satu PR] --> B{Satu Tujuan?}
B -- Ya --> C[PR yang baik ✓]
B -- Tidak --> D[Pecah menjadi beberapa PR]
D --> E[PR 1: Tujuan A]
D --> F[PR 2: Tujuan B]
D --> G[PR 3: Refactor terkait]Tanda-tanda scope PR terlalu lebar:
✗ PR yang bermasalah dari sisi scope:
"Implement user authentication"
├── ADD: tabel users dan sessions
├── ADD: login endpoint
├── ADD: logout endpoint
├── ADD: JWT token generation
├── REFACTOR: error handling di semua endpoint lain
├── UPDATE: 3 dependency versions
└── FIX: typo di README
Masalah:
→ Reviewer harus memahami 6 konteks berbeda sekaligus
→ Satu bug di refactor bisa memblokir merge feature authentication
→ Jika perlu rollback, semua perubahan ikut
✓ PR yang baik — dipecah berdasarkan tujuan:
PR 1: "feat(auth): add users and sessions schema"
PR 2: "feat(auth): implement login and logout endpoints"
PR 3: "refactor(error): standardize error response format" (independent)
PR 4: "chore(deps): update authentication-related dependencies"
4. Perubahan Kode #
Diff kode adalah satu-satunya komponen yang dibuat secara otomatis — sisanya harus ditulis secara sadar oleh author. Tapi diff yang baik tetap membutuhkan upaya dari sisi author: commit yang terstruktur, kode yang mudah dibaca, dan tidak ada noise yang tidak perlu.
Prinsip diff kode yang memudahkan review:
✓ Commit atomik — setiap commit melakukan satu hal yang kohesif
Reviewer bisa review commit per commit jika lebih nyaman
✓ Tidak ada perubahan yang tidak terkait
Whitespace fix, reformatting, atau rename variabel yang tidak relevan
sebaiknya di PR terpisah atau commit terpisah yang jelas
✓ Komentar di kode untuk hal yang tidak terlihat jelas dari diff
Bukan komentar yang menjelaskan APA (kode sudah melakukan itu),
tapi komentar yang menjelaskan MENGAPA
✗ Komentar debug yang lupa dihapus
console.log("debug here"), fmt.Println("test"), print("DEBUG")
✗ TODO tanpa tiket
// TODO: fix this later ← "later" tidak pernah datang tanpa tiket
// TODO(JIRA-123): optimize this query after load test
✗ Kode yang di-comment out
// old code that used to work
// result = oldFunction(x) ← hapus saja, git history ada
5. Test dan Validasi #
Test bukan hanya tentang coverage angka — test dalam konteks PR adalah bukti bahwa perubahan ini sudah dipikirkan dampak dan edge case-nya.
## Bagian Testing dalam deskripsi PR:
### Test yang Ditambahkan
- Unit test: `TestPaymentCallback_RetryOnTimeout` — mensimulasikan 3 kali timeout
- Unit test: `TestPaymentCallback_NoRetryOnBadRequest` — memastikan 400 tidak di-retry
- Integration test: `TestPaymentCallbackIntegration` — test dengan mock gateway
### Manual Testing
- [ ] Jalankan `make test` dan pastikan semua hijau
- [ ] Gunakan `scripts/simulate_timeout.sh` untuk test manual
- [ ] Verifikasi log menunjukkan "Retrying request, attempt 2/3"
- [ ] Verifikasi setelah 3 kali gagal, error dikembalikan ke caller
### Edge Case yang Sudah Dipertimbangkan
- Timeout di attempt ke-2 tapi berhasil di attempt ke-3 → transaksi berhasil
- Gateway mengembalikan 200 tapi body tidak valid → tidak di-retry (bukan timeout)
- Context cancelled oleh caller sebelum retry selesai → retry dihentikan gracefully
6. Impact dan Risiko #
Setiap perubahan memiliki konsekuensi — beberapa terlihat, banyak yang tidak. Bagian ini membantu reviewer dan tim ops memahami apa yang perlu diperhatikan saat PR di-merge dan di-deploy.
Kategori impact yang perlu dicantumkan:
Breaking change:
→ Apakah ada perubahan kontrak API (endpoint, parameter, response format)?
→ Apakah ada perubahan schema database yang mempengaruhi data yang ada?
→ Apakah ada perubahan behavior yang consumer lain bergantung padanya?
Deployment notes:
→ Apakah perlu migration database sebelum atau setelah deploy?
→ Apakah ada environment variable baru yang perlu dikonfigurasi?
→ Apakah ada urutan deploy yang perlu diperhatikan (backend sebelum frontend)?
→ Apakah aman untuk rollback jika ada masalah?
Performance impact:
→ Apakah ada endpoint yang latency-nya akan berubah?
→ Apakah ada query baru yang perlu dimonitor setelah deploy?
Dependencies baru:
→ Apakah ada library atau service baru yang ditambahkan?
→ Apakah ada konfigurasi tambahan yang dibutuhkan di production?
7. Checklist PR #
Checklist adalah mekanisme sederhana yang sangat efektif untuk memastikan tidak ada hal penting yang terlewat — baik dari sisi author saat mempersiapkan PR, maupun reviewer saat melakukan review.
Checklist yang baik bukan daftar yang harus dicentang semua tanpa berpikir — ia adalah pengingat untuk hal-hal yang mudah terlupa saat fokus pada perubahan itu sendiri.
Template PR yang Lengkap #
Berikut template PR yang bisa langsung digunakan di GitHub (.github/pull_request_template.md) atau GitLab (.gitlab/merge_request_templates/Default.md):
## Latar Belakang
<!-- Jelaskan masalah yang diselesaikan atau fitur yang diimplementasi.
Berikan cukup konteks agar reviewer tidak perlu membuka Jira/Notion. -->
## Perubahan
<!-- Ringkasan singkat perubahan yang dilakukan. Highlight hal-hal yang
tidak langsung terlihat dari diff. -->
## Keputusan Teknis
<!-- Jelaskan keputusan desain yang diambil dan mengapa.
Mention alternatif yang dipertimbangkan jika ada. -->
## Cara Testing
<!-- Langkah-langkah untuk memverifikasi perubahan ini bekerja dengan benar. -->
- [ ] Jalankan `make test` / `go test ./...` / `pytest`
- [ ] Manual testing: ...
## Impact
- **Breaking change:** Tidak / Ya — jelaskan
- **Database migration:** Tidak / Ya — jelaskan urutan deploy
- **Environment variable baru:** Tidak / Ya — sebutkan
- **Rollback aman:** Ya / Tidak — jelaskan jika tidak
## Checklist
### General
- [ ] PR menjawab satu tujuan yang bisa diringkas dalam satu kalimat
- [ ] Tidak ada perubahan di luar scope PR ini
- [ ] Self-review sudah dilakukan
### Code Quality
- [ ] Tidak ada kode debug (console.log, fmt.Println, print)
- [ ] Tidak ada TODO tanpa nomor tiket
- [ ] Tidak ada kode yang di-comment out tanpa alasan
- [ ] Error handling sudah dipertimbangkan
### Testing
- [ ] Test sudah ditambahkan atau diperbarui
- [ ] CI hijau (linting, test, build)
- [ ] Edge case yang relevan sudah dicakup test
### Architecture
- [ ] Perubahan mengikuti pola arsitektur yang disepakati tim
- [ ] Tidak ada coupling baru yang tidak perlu
### Security
- [ ] Tidak ada credential atau data sensitif yang ter-expose
- [ ] Input validation sudah memadai
- [ ] Tidak membuka celah keamanan baru
### Ops & Deployment
- [ ] Migration database terdokumentasi (jika ada)
- [ ] Perubahan aman untuk di-rollback
- [ ] Tidak ada konfigurasi tersembunyi yang diperlukan
Contoh PR Lengkap: Dari Buruk ke Baik #
Berikut perbandingan dua PR untuk perubahan yang identik — implementasi rate limiting pada endpoint login.
PR yang Buruk #
Judul: fix login
Deskripsi:
add rate limit
Testing:
tested
Checklist:
[x] done
Apa yang tidak bisa dijawab reviewer dari PR ini: mengapa rate limiting dibutuhkan, algoritma apa yang digunakan, threshold berapa yang dipilih dan mengapa, apa yang terjadi ketika limit terlampaui, apakah ada dampak ke endpoint lain, dan bagaimana cara memverifikasinya. Reviewer dipaksa membaca seluruh diff dan menebak semua konteks ini.
PR yang Baik #
Judul: feat(auth): add rate limiting to login endpoint
Deskripsi:
### Latar Belakang
Selama dua minggu terakhir, endpoint /auth/login mengalami beberapa kali
serangan credential stuffing yang menyebabkan peningkatan load signifikan
di database. Log menunjukkan satu IP bisa mencoba 500+ kombinasi per menit.
Tiket: SEC-2341.
### Perubahan
- Tambah middleware rate limiting menggunakan sliding window algorithm
- Limit: 10 request/menit per IP, 5 request/menit per username
- Response 429 Too Many Requests dengan header Retry-After
- Implementasi menggunakan Redis (sudah ada di stack)
### Keputusan Teknis
Sliding window dipilih daripada fixed window karena lebih resisten terhadap
burst di ujung window (burst attack 10 detik sebelum window reset).
Threshold 10/menit per IP dan 5/menit per username dipilih berdasarkan
analisis pola login normal: user rata-rata login maksimal 2-3x per menit
bahkan di kasus mistype berulang.
Redis digunakan sebagai store daripada in-memory karena ada multiple app
instance. TTL key diset ke 1 menit (sama dengan window).
### Cara Testing
1. `make test` — test baru ada di `auth/rate_limit_test.go`
2. Manual: jalankan `scripts/test_rate_limit.sh` yang akan hit endpoint
12x dalam 1 menit dari IP yang sama — request ke-11 dan 12 harus 429
3. Cek Redis: `redis-cli keys "rate:*"` untuk melihat counter yang terbentuk
### Impact
- Tidak ada breaking change kontrak API
- Header Retry-After ditambahkan ke response 429 (additive)
- Deployment: tidak perlu migration, perlu pastikan Redis tersedia
- Rollback: aman, middleware bisa di-disable via feature flag
- Perlu update dokumentasi API untuk mencantumkan rate limit info
Perbedaannya bukan hanya soal panjang — PR yang baik memungkinkan reviewer untuk langsung fokus pada hal yang penting: apakah threshold yang dipilih sudah tepat, apakah sliding window adalah pilihan yang benar, apakah Redis bisa menjadi single point of failure. Ini adalah diskusi yang bernilai. PR yang buruk memaksa reviewer menghabiskan energi hanya untuk memahami apa yang dilakukan PR ini.
Alur Review Setelah PR Dibuka #
Setelah PR dibuka dengan anatomi yang lengkap, ada pola interaksi yang sehat antara author dan reviewer.
sequenceDiagram
participant A as Author
participant R as Reviewer
participant CI as CI Pipeline
A->>R: Buka PR + request review
A->>CI: Push trigger CI
CI-->>A: CI result (hijau/merah)
alt CI merah
A->>A: Fix, push ulang
A->>CI: Trigger CI lagi
end
R->>R: Baca deskripsi dulu
R->>R: Review diff
R-->>A: Komentar (blocking/suggestion/nitpick)
alt Ada request changes
A->>A: Merespons setiap komentar
A->>A: Revisi kode jika perlu
A->>R: Push & notify reviewer
R->>R: Re-review perubahan
end
R-->>A: Approve
A->>A: Merge PR
A->>A: Delete branchPola ini menunjukkan bahwa review adalah loop, bukan satu arah. Author harus merespons setiap komentar — bahkan yang tidak menghasilkan perubahan kode — agar reviewer tahu bahwa komentar sudah dibaca dan dipertimbangkan.
Anti-Pattern yang Harus Dihindari #
✗ Judul yang tidak informatif
"Update", "Fix", "Changes", "WIP"
→ Reviewer tidak tahu apa yang akan mereka baca
✗ Deskripsi kosong atau "see commit"
→ Author memaksa reviewer membaca semua commit untuk memahami konteks
✗ PR dibuka dengan CI merah
→ Reviewer diminta review kode yang bahkan tidak bisa di-build
✗ PR menggabungkan refactor besar dengan feature baru
→ Tidak bisa rollback feature tanpa rollback refactor (dan sebaliknya)
✗ Checklist dicentang semua tanpa benar-benar diperiksa
→ Checklist menjadi ritual tanpa nilai
✗ Screenshot sebagai satu-satunya cara testing
→ Reviewer tidak bisa memverifikasi edge case dari screenshot
✗ PR dibuka di Jumat sore untuk merge hari itu juga
→ Review terburu-buru, kemungkinan bug lolos lebih tinggi
Checklist Review Anatomi PR #
JUDUL:
□ Menggunakan format Conventional Commits (feat/fix/refactor/dst)
□ Spesifik dan bisa dipahami tanpa membuka body PR
□ Tidak lebih dari 72 karakter
DESKRIPSI:
□ Menjelaskan latar belakang masalah atau kebutuhan
□ Menjelaskan pendekatan dan keputusan teknis yang diambil
□ Mencantumkan alternatif yang dipertimbangkan (jika relevan)
□ Ada instruksi testing yang konkret dan bisa diikuti
SCOPE:
□ PR menjawab satu tujuan yang bisa diringkas satu kalimat
□ Tidak ada perubahan tidak terkait yang "sekalian" dimasukkan
□ Ukuran diff reasonable — jika > 400 baris, pertimbangkan pecah
KODE:
□ Self-review sudah dilakukan — tidak ada debug log atau TODO tanpa tiket
□ Commit message informatif (bukan "fix", "wip", "update")
□ Tidak ada file yang tidak sengaja ter-include
TESTING:
□ Test baru ada atau test lama diperbarui
□ CI hijau sebelum request review
□ Edge case yang relevan sudah dicakup
IMPACT:
□ Breaking change (jika ada) disebutkan dengan jelas
□ Deployment notes ada (migration, env var, urutan deploy)
□ Rollback safety disebutkan
TEMPLATE:
□ PR template diisi — bukan dikosongkan atau dihapus
□ Checklist di template sudah diperiksa sungguh-sungguh
Ringkasan #
- PR terdiri dari tujuh komponen — judul, deskripsi, scope, kode, test, impact, dan checklist. Diff kode hanyalah satu di antaranya; sisanya sama pentingnya.
- Judul menggunakan Conventional Commits — format
type(scope): deskripsiyang konsisten memudahkan pembacaan daftar PR dan bisa langsung menjadi entri changelog.- Deskripsi menjawab empat pertanyaan — apa masalahnya, mengapa pendekatan ini dipilih, apa yang berubah, dan bagaimana cara memverifikasinya.
- Scope yang sempit bukan kelemahan — PR yang kecil dan fokus lebih cepat direview, lebih mudah di-rollback, dan lebih kecil risikonya. Jika PR terasa besar, pecah.
- Self-review sebelum request review — tidak ada debug log, tidak ada TODO tanpa tiket, tidak ada perubahan yang tidak disengaja. Author adalah reviewer pertama untuk PR-nya sendiri.
- Test adalah bukti, bukan angka — test dalam PR menunjukkan bahwa perubahan sudah dipikirkan edge case-nya, bukan hanya bahwa kode bisa berjalan di happy path.
- Impact dan deployment notes melindungi tim ops — breaking change, migration, dan rollback safety harus disebutkan eksplisit agar tidak ada kejutan saat deploy.
- Template PR adalah investasi tim — template yang baik mengurangi kognitif load author dan memastikan reviewer selalu mendapat informasi yang cukup untuk review yang bermakna.
- PR yang baik adalah dokumentasi yang hidup — enam bulan kemudian, orang akan mencari di PR history untuk memahami mengapa keputusan tertentu dibuat. Tulis untuk pembaca masa depan itu.
- Review adalah loop, bukan satu arah — author harus merespons setiap komentar reviewer, bahkan yang tidak menghasilkan perubahan kode. Loop belum selesai sampai semua komentar ditutup dengan keputusan yang jelas.
← Sebelumnya: Fundamental Berikutnya: Small vs Big Pull Request →