Guardrail CI untuk membatasi ledakan kompleksitas kode berarti menjadikan pipeline sebagai pagar pembatas, bukan sekadar alat menjalankan test. Saat tim backend atau platform mengurangi asumsi umum di codebase—misalnya hardcoded tenant, satu mode deployment, satu backend penyimpanan, atau satu alur autentikasi—sistem memang menjadi lebih umum. Namun biaya teknisnya sering muncul dalam bentuk branch logika tambahan, konfigurasi baru, flag yang menumpuk, dan kombinasi jalur eksekusi yang makin sulit diuji.
Masalah utamanya bukan perubahan itu sendiri, melainkan ketika fleksibilitas bertambah lebih cepat daripada kemampuan tim untuk meninjau, menguji, dan mengoperasikan kode. Di titik ini, CI perlu berfungsi sebagai guardrail: memberi sinyal dini ketika perubahan mulai mendorong codebase ke arah yang sulit dipelihara. Pendekatan yang efektif bukan memblokir semua perubahan besar, tetapi menerapkan metrik yang bisa diautomasi, aturan fail versus warn yang jelas, dan rollout bertahap supaya pengalaman developer tidak memburuk.
Mengapa mengurangi asumsi sering memicu ledakan kompleksitas
Dalam sistem backend, banyak asumsi awal sengaja dibuat agar implementasi sederhana. Contohnya:
- Satu jenis database atau message broker
- Satu mode autentikasi untuk semua klien
- Satu jenis tenant atau region
- Satu format payload atau strategi retry
- Satu jalur eksekusi untuk job sinkron dan asinkron
Saat asumsi itu dihapus, desain menjadi lebih umum, tetapi biasanya ada konsekuensi berikut:
- Cabang logika bertambah, misalnya
if tenant_type == ...atauif storage_backend == .... - Konfigurasi bertambah, baik dalam environment variable, file konfigurasi, maupun database.
- Jalur eksekusi bertambah, sehingga coverage test yang sebelumnya cukup, sekarang tidak lagi mewakili perilaku sistem.
- Review menjadi lebih sulit, karena satu PR bisa mengubah banyak area sekaligus.
- Waktu build naik, karena lebih banyak test, matrix, atau analisis statik yang harus dijalankan.
Karena itu, guardrail CI sebaiknya tidak hanya bertanya “apakah test lulus?”, tetapi juga “apakah perubahan ini menambah kompleksitas di batas yang masih bisa diterima?”
Metrik guardrail CI yang paling berguna
Tidak semua metrik harus langsung dipakai. Untuk tim backend atau platform, enam metrik berikut cukup praktis dan bisa diautomasi tanpa terlalu banyak tebakan.
1. Ukuran diff
Ukuran diff adalah indikator awal terhadap beban review dan risiko perubahan. Ia tidak sempurna—perubahan kecil bisa sangat berisiko, perubahan besar bisa bersifat mekanis—tetapi tetap berguna sebagai sinyal.
Yang biasanya diukur:
- Jumlah file yang berubah
- Baris yang ditambahkan dan dihapus
- Jumlah file pada direktori sensitif, misalnya
auth/,billing/,migrations/, atauinfra/
Mengapa ini bekerja: semakin besar diff, semakin sulit reviewer memahami interaksi antarperubahan. Ukuran diff cocok dijadikan sinyal warn, lalu dinaikkan menjadi fail hanya untuk area yang benar-benar sensitif.
Praktik yang masuk akal: jangan gagal hanya karena total diff besar jika mayoritas perubahan adalah generated code, lockfile, atau rename mekanis. Kecualikan kategori itu dari perhitungan bila memungkinkan.
2. Cyclomatic complexity
Cyclomatic complexity mengukur banyaknya jalur keputusan dalam fungsi atau metode. Saat tim menghilangkan asumsi tunggal, angka ini sering naik diam-diam karena kode dipenuhi percabangan untuk mode, backend, region, atau flag yang berbeda.
Mengapa ini bekerja: metrik ini cukup dekat dengan penyebab ledakan jalur eksekusi. Jika complexity naik pada fungsi inti, kemungkinan besar test dan review juga perlu lebih ketat.
Yang perlu dipantau:
- Ambang maksimum complexity per fungsi
- Kenaikan complexity dibanding branch dasar
- File atau modul yang complexity-nya terus bertambah dari minggu ke minggu
3. Jumlah flag dan opsi konfigurasi
Feature flag, environment variable, dan opsi konfigurasi sangat membantu rollout. Masalah muncul ketika semuanya menumpuk tanpa batas waktu dan tanpa kepemilikan. Setiap flag baru bukan hanya saklar; ia menambah kombinasi state sistem yang harus dipahami.
Mengapa ini bekerja: menghitung jumlah flag memaksa tim mengakui bahwa fleksibilitas ada biayanya. CI dapat menandai PR yang menambah flag baru tanpa metadata yang memadai.
Minimal yang sebaiknya ada untuk setiap flag:
- Nama yang jelas
- Tujuan atau alasan bisnis/teknis
- Owner
- Tanggal target penghapusan atau evaluasi ulang
- Default value yang aman
4. Coverage untuk jalur kritis
Coverage total sering menipu. Kenaikan jumlah baris yang teruji tidak otomatis berarti cabang paling berisiko ikut tertutup. Untuk guardrail CI, lebih efektif memantau coverage pada jalur kritis daripada coverage global saja.
Contoh jalur kritis backend/platform:
- Autentikasi dan otorisasi
- Pembayaran atau penagihan
- Provisioning resource
- Retry dan idempotency untuk worker
- Fallback saat dependency eksternal gagal
Mengapa ini bekerja: ketika asumsi dikurangi, jalur fallback dan mode alternatif biasanya justru yang rawan luput dari test. Coverage jalur kritis membantu memastikan cabang baru tidak hanya “ada”, tetapi juga benar-benar dijalankan dalam test.
5. Waktu build dan waktu feedback
CI yang terlalu lambat akan diakali: test dipindah ke nanti, job dimatikan, atau orang berhenti peduli pada warning. Karena itu, waktu build adalah guardrail untuk kualitas proses, bukan hanya performa pipeline.
Yang perlu diukur:
- Waktu sampai hasil pertama muncul
- Durasi job test inti
- Durasi analisis statik
- Apakah perubahan tertentu memicu matrix test yang terlalu besar
Mengapa ini bekerja: guardrail yang baik harus cukup cepat agar dipakai. Bila overhead-nya terlalu besar, DX memburuk dan tim mulai mencari jalan pintas.
6. Aturan review berbasis risiko
Tidak semua PR butuh tingkat review yang sama. Guardrail CI dapat menentukan kapan PR harus mendapat reviewer tambahan, kapan perlu test plan eksplisit, atau kapan perubahan harus dipecah.
Contoh aturan:
- PR yang menyentuh modul autentikasi wajib dua reviewer
- PR dengan diff di atas ambang tertentu wajib menyertakan rencana rollout
- PR yang menambah flag baru wajib mencantumkan owner dan masa berlaku
- PR yang menaikkan complexity pada jalur kritis wajib menambah test kasus
Desain kebijakan: kapan fail, kapan warn
Kesalahan umum adalah menjadikan semua metrik sebagai hard fail sejak hari pertama. Hasilnya biasanya buruk: noise tinggi, banyak pengecualian manual, dan tim menganggap CI sebagai penghambat.
Pola yang lebih sehat adalah membagi metrik menjadi tiga kategori:
Hard fail
- Test gagal pada jalur kritis
- Static analysis menemukan pelanggaran yang sudah disepakati sebagai blocker
- Coverage jalur kritis turun di bawah baseline yang ditetapkan
- PR menambah flag baru tanpa metadata wajib
Soft fail atau warning
- Diff terlalu besar
- Waktu build meningkat melewati ambang tertentu
- Complexity naik, tetapi belum melewati batas maksimum
- Jumlah file sensitif yang berubah melebihi pola normal
Informational only
- Trend complexity mingguan
- Jumlah flag aktif per domain
- Durasi pipeline per jenis branch
Aturan praktis: pakai hard fail hanya untuk sinyal yang akurasinya tinggi dan tindak lanjutnya jelas. Bila tindakan yang diharapkan masih ambigu, mulailah dari warning.
Contoh implementasi GitHub Actions
Berikut contoh workflow yang memisahkan guardrail menjadi beberapa job: ukuran diff, complexity, test jalur kritis, dan validasi metadata flag. Contoh ini sengaja generik agar bisa disesuaikan untuk bahasa dan tool yang dipakai tim.
name: ci-guardrails
on:
pull_request:
push:
branches: [main]
jobs:
diff-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Hitung ukuran diff
run: |
BASE_REF="${{ github.base_ref || 'main' }}"
git fetch origin "$BASE_REF" --depth=1
ADDED=$(git diff --numstat origin/$BASE_REF...HEAD | awk '{a+=$1} END {print a+0}')
DELETED=$(git diff --numstat origin/$BASE_REF...HEAD | awk '{d+=$2} END {print d+0}')
FILES=$(git diff --name-only origin/$BASE_REF...HEAD | wc -l)
echo "added=$ADDED" >> $GITHUB_OUTPUT
echo "deleted=$DELETED" >> $GITHUB_OUTPUT
echo "files=$FILES" >> $GITHUB_OUTPUT
- name: Warning jika diff besar
run: |
# contoh ambang, sesuaikan dengan repo
FILES=$(git diff --name-only origin/${{ github.base_ref || 'main' }}...HEAD | wc -l)
if [ "$FILES" -gt 40 ]; then
echo "::warning::PR menyentuh lebih dari 40 file. Pertimbangkan pecah PR atau tambah reviewer."
fi
complexity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Jalankan linter / static analysis
run: |
./scripts/check-complexity.sh
critical-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Jalankan test jalur kritis
run: |
./scripts/test-critical-paths.sh
feature-flags:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validasi metadata flag
run: |
./scripts/validate-flags.shPoin penting dari workflow di atas:
- Job dipisah agar sumber kegagalan mudah dibaca.
- Diff check menghasilkan warning, bukan langsung fail.
- Complexity dan critical tests lebih cocok dijadikan fail jika tim sudah sepakat dengan baseline.
- Validasi flag cocok menjadi fail karena aturannya objektif: metadata ada atau tidak.
Contoh skrip validasi flag
Bila tim menyimpan flag dalam YAML atau JSON, CI bisa memeriksa apakah field wajib tersedia.
#!/usr/bin/env bash
set -euo pipefail
missing=0
for file in flags/*.yaml; do
grep -q '^owner:' "$file" || { echo "Missing owner in $file"; missing=1; }
grep -q '^expires_on:' "$file" || { echo "Missing expires_on in $file"; missing=1; }
grep -q '^default:' "$file" || { echo "Missing default in $file"; missing=1; }
done
exit $missingIni sederhana, tetapi cukup efektif untuk mencegah flag liar yang tidak punya pemilik atau rencana pembersihan.
Contoh implementasi GitLab CI
Jika tim memakai GitLab CI, pola yang sama bisa diterapkan dengan stage terpisah dan artifact laporan.
stages:
- guardrails
- test
variables:
GIT_DEPTH: "0"
diff_guardrail:
stage: guardrails
script:
- BASE_REF="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-main}"
- git fetch origin "$BASE_REF"
- FILES=$(git diff --name-only origin/$BASE_REF...HEAD | wc -l)
- |
if [ "$FILES" -gt 40 ]; then
echo "WARNING: merge request menyentuh lebih dari 40 file"
fi
complexity_guardrail:
stage: guardrails
script:
- ./scripts/check-complexity.sh
flags_guardrail:
stage: guardrails
script:
- ./scripts/validate-flags.sh
critical_tests:
stage: test
script:
- ./scripts/test-critical-paths.shUntuk GitLab, Anda juga bisa menambahkan aturan approval di level merge request untuk path tertentu, misalnya file pada direktori infrastruktur, schema database, atau modul otorisasi.
Contoh aturan linter dan static analysis
Tool yang dipilih akan bergantung pada bahasa yang dipakai. Yang penting bukan mereknya, melainkan kebijakan yang ditegakkan. Untuk topik ini, fokuslah pada aturan yang benar-benar berkaitan dengan ledakan kompleksitas.
Aturan yang layak diprioritaskan
- Batas complexity per fungsi
- Batas nesting agar branch bertingkat tidak menumpuk
- Batas panjang fungsi untuk mendorong ekstraksi alur
- Larangan boolean parameter tertentu pada API internal, karena sering menjadi indikator branch tersembunyi
- Deteksi duplicate conditional logic bila tool mendukung
Contoh kebijakan yang masuk akal
# pseudo-policy untuk CI
- fail jika ada fungsi baru dengan complexity > 10 di modul kritis
- warn jika complexity file naik dibanding main branch
- fail jika menambah parameter boolean pada service publik tanpa justifikasi review
- warn jika file konfigurasi baru ditambahkan tanpa dokumentasi singkatMengapa aturan ini efektif: ia menargetkan pola yang biasanya muncul saat asumsi dikurangi secara agresif: fungsi menjadi pengendali mode, parameter boolean makin banyak, dan konfigurasi menyebar ke banyak tempat.
Membangun coverage untuk jalur kritis, bukan sekadar coverage total
Salah satu kesalahan paling umum adalah mengejar angka coverage global sambil membiarkan branch penting tidak pernah dijalankan. Untuk backend atau platform, lebih berguna membuat daftar jalur kritis yang harus selalu punya test representatif.
Cara menentukan jalur kritis
- Identifikasi operasi yang paling berisiko jika salah.
- Daftar mode alternatif yang muncul setelah asumsi dihapus.
- Tentukan kombinasi minimum yang wajib diuji di CI utama.
- Pindahkan kombinasi yang mahal ke nightly build atau pipeline terjadwal.
Contoh: jika sebelumnya job provisioning hanya mendukung satu provider, lalu kini mendukung beberapa backend dan fallback, jangan langsung menguji semua kombinasi di setiap PR. Pilih subset kritis:
- Jalur default yang paling sering dipakai
- Satu jalur alternatif utama
- Satu skenario gagal yang memicu fallback
- Satu skenario retry atau idempotency
Dengan pendekatan ini, coverage tetap relevan tanpa membuat pipeline meledak.
Aturan review yang melengkapi guardrail CI
CI tidak bisa memahami konteks arsitektur sebaik manusia. Karena itu, guardrail terbaik biasanya menggabungkan otomasi dan kebijakan review.
Checklist review untuk PR yang menambah fleksibilitas sistem
- Apakah perubahan ini benar-benar perlu menghapus asumsi lama?
- Apakah fleksibilitas baru bisa diisolasi di satu lapisan, bukan menyebar ke seluruh codebase?
- Apakah cabang logika baru punya test untuk default path dan non-default path?
- Apakah flag/config baru punya owner dan rencana penghapusan?
- Apakah perubahan lebih baik dipecah menjadi beberapa PR?
- Apakah observability diperbarui untuk mode baru, misalnya log, metric, atau tracing?
Kapan reviewer tambahan diperlukan
Reviewer tambahan masuk akal jika:
- PR menyentuh lebih dari satu bounded context
- PR menambah konfigurasi yang memengaruhi operasional runtime
- PR mengubah fallback, retry, atau error handling
- PR menambah jalur baru yang tidak aktif secara default
Aturan ini penting karena banyak ledakan kompleksitas justru tersembunyi pada interaksi antarmodul, bukan pada satu file tertentu.
Rollout bertahap agar DX tidak memburuk
Guardrail yang baik seharusnya menaikkan kualitas tanpa membuat developer merasa setiap PR adalah perjuangan melawan pipeline. Karena itu, rollout bertahap jauh lebih efektif daripada langsung menerapkan semua blokade.
Fase 1: observasi
- Jalankan metrik dalam mode laporan saja
- Kumpulkan baseline: diff normal, complexity tipikal, durasi build, jumlah flag aktif
- Catat area yang paling sering menimbulkan noise
Fase 2: warning terarah
- Aktifkan warning untuk diff besar, complexity naik, dan build melambat
- Terapkan format komentar otomatis pada PR agar reviewer tahu tindakan yang diharapkan
- Mulai dokumentasikan pengecualian yang sah
Fase 3: hard fail pada kasus objektif
- Wajibkan metadata pada flag baru
- Gagalkan PR jika test jalur kritis gagal
- Gagalkan PR jika aturan complexity pada modul sensitif dilanggar
Fase 4: penyempurnaan dan pembersihan
- Evaluasi apakah ambang terlalu ketat atau terlalu longgar
- Hapus guardrail yang tidak menghasilkan tindakan nyata
- Tambahkan pengecualian yang terdokumentasi, bukan bypass ad hoc
Prinsip penting: setiap guardrail harus punya tujuan operasional yang jelas. Jika sebuah warning tidak pernah ditindaklanjuti, ia hanya menambah kebisingan.
Trade-off dan batasan yang perlu dipahami
Metrik bisa dimainkan
Developer bisa memecah fungsi agar complexity terlihat turun padahal alur bisnis tetap kusut. Karena itu, metrik harus dipadukan dengan review desain.
Coverage tidak menjamin kualitas test
Branch bisa saja dieksekusi tanpa assertion yang bermakna. Guardrail coverage perlu dilengkapi dengan review test case dan fokus pada skenario penting.
Build cepat versus build lengkap
Mengujikan semua kombinasi pada setiap PR jarang realistis. Solusinya adalah memisahkan pipeline cepat untuk feedback awal dan pipeline lebih lengkap untuk branch utama atau jadwal tertentu.
Warning berlebihan akan diabaikan
Jika hampir semua PR menghasilkan warning, orang berhenti membaca. Jaga agar sinyal tetap sedikit tetapi bernilai tinggi.
Pola implementasi yang biasanya berhasil di tim backend/platform
Jika Anda ingin mulai tanpa membuat proyek besar, urutan berikut cukup aman:
- Tambahkan pengecekan ukuran diff sebagai warning.
- Terapkan complexity threshold pada direktori paling kritis.
- Definisikan daftar jalur kritis dan jalankan test khusus untuk jalur itu.
- Wajibkan metadata untuk setiap flag atau konfigurasi baru.
- Tambahkan aturan review untuk modul sensitif dan PR berisiko tinggi.
- Pantau waktu build agar guardrail tidak merusak feedback loop.
Urutan ini bekerja karena tim mendapatkan manfaat lebih awal tanpa harus lebih dulu menyempurnakan semua metrik.
Penutup
Mengurangi asumsi di codebase memang sering diperlukan, terutama pada sistem backend atau platform yang mulai melayani banyak mode operasi. Tetapi fleksibilitas tanpa batas mudah berubah menjadi ledakan kompleksitas yang mahal untuk diuji, direview, dan dioperasikan. Guardrail CI untuk membatasi ledakan kompleksitas kode membantu menahan biaya itu dengan sinyal yang bisa diautomasi: ukuran diff, cyclomatic complexity, jumlah flag, coverage jalur kritis, waktu build, dan aturan review.
Kuncinya bukan membuat CI semakin keras, melainkan semakin relevan. Mulailah dari metrik yang objektif, bedakan mana yang cukup warning dan mana yang pantas fail, lalu rollout secara bertahap. Dengan begitu, tim tetap bisa mengurangi asumsi yang memang perlu dihilangkan, tanpa membiarkan codebase berubah menjadi sistem yang terlalu umum untuk dipahami.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!