Code review adalah proses di mana developer lain memeriksa kode sebelum di-merge ke branch utama. Tujuannya bukan mencari kesalahan penulis kode, melainkan menangkap bug yang lolos dari tes, menjaga konsistensi style, dan berbagi pengetahuan tentang codebase antar anggota tim. Bab ini menyajikan checklist code review berlapis untuk C—bug logika, keamanan, performa, dan style—beserta contoh kode nyata yang memiliki bug yang harus tertangkap di review. Setelah membaca bab ini, kita akan punya daftar pertanyaan konkret yang dapat dipakai saat me-review pull request rekan tim, dan tahu jenis bug apa yang paling sering lolos jika review dilakukan terburu-buru.
Empat Lapis Checklist #
Code review yang efektif memeriksa empat aspek secara berurutan. Jangan loncat ke aspek berikutnya sebelum lapisan sebelumnya jelas—bug keamanan yang ditemukan setelah merge jauh lebih mahal daripada bug yang tertangkap saat review.
| Lapisan | Fokus | Pertanyaan kunci |
|---|---|---|
| Bug logika | Apakah kode melakukan apa yang seharusnya? | Apakah ada off-by-one? Apakah return value diabaikan? |
| Keamanan | Bisakah input memicu crash atau kebocoran data? | Apakah panjang input dibatasi? Apakah ada buffer overflow? |
| Performa | Apakah ada loop yang tidak perlu atau alokasi berulang? | Apakah kompleksitas bisa diturunkan? |
| Style | Apakah konsisten dengan kode sekitarnya? | Apakah nama deskriptif? Apakah format rapi? |
Lapisan 1: Bug Logika #
Bug logika adalah bug di mana kode berperilaku tidak sesuai harapan tetapi tetap compile. Berikut dua bug yang sering lolos review karena terlihat “benar secara sintaks”:
/* File: test_review1.c - Contoh kode dengan bug yang harus tertangkap code review */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Bug 1: format string tanpa cek panjang - buffer overflow */
static void sapa_buruk(char *nama)
{
char buf[10];
/* BUG: jika nama lebih panjang dari 9 char, buf overflow */
sprintf(buf, "Halo %s", nama);
printf("%s\n", buf);
}
/* Versi aman: pakai snprintf dengan size eksplisit */
static void sapa_baik(const char *nama)
{
char buf[32];
snprintf(buf, sizeof(buf), "Halo %s", nama);
printf("%s\n", buf);
}
/* Bug 2: strcmp return value salah dipakai sebagai boolean */
static int cari_key_buruk(const char *key)
{
const char *keys[] = {"alpha", "beta", "gamma"};
for (int i = 0; i < 3; i++) {
/* BUG: strcmp mengembalikan 0 jika sama, bukan non-zero */
if (strcmp(keys[i], key)) {
return i;
}
}
return -1;
}
/* Versi benar: cek == 0 */
static int cari_key_baik(const char *key)
{
const char *keys[] = {"alpha", "beta", "gamma"};
for (int i = 0; i < 3; i++) {
if (strcmp(keys[i], key) == 0) {
return i;
}
}
return -1;
}
int main(void)
{
/* Buffer overflow aman karena nama pendek */
sapa_buruk("Budi");
sapa_baik("BudiSangatPanjangNama");
/* Bug: cari_key_buruk mengembalikan index salah */
printf("cari_key_buruk('beta') = %d (expect 1, got ?)\n",
cari_key_buruk("beta"));
printf("cari_key_baik('beta') = %d (expect 1)\n",
cari_key_baik("beta"));
return 0;
}Output yang Dihasilkan:
Halo Budi
Halo BudiSangatPanjangNama
cari_key_buruk('beta') = 0 (expect 1, got ?)
cari_key_baik('beta') = 1 (expect 1)Bug pertama: sprintf(buf, "Halo %s", nama) menulis ke buf[10] tanpa batasan panjang nama. Jika nama lebih panjang dari 5 karakter ("Halo " = 5 char + terminator), buffer overflow ke stack. Versi aman snprintf(buf, sizeof(buf), ...) memakai ukuran buffer eksplisit—snprintf tidak akan menulis lebih dari sizeof(buf) byte, termasuk terminator.
Bug kedua: if (strcmp(keys[i], key)) memakai strcmp seolah mengembalikan true jika string sama. Padahal strcmp mengembalikan 0 jika sama, dan non-zero (positif atau negatif) jika berbeda. Akibatnya, if (strcmp(...)) justru masuk ke cabang ketika string tidak sama. Output cari_key_buruk('beta') mengembalikan 0 (index "alpha") padahal seharusnya 1. Versi benar membandingkan strcmp(...) == 0.
Lapisan 2: Keamanan dan Validasi Input #
Bug keamanan di C sering berasal dari asumsi “input pasti valid”. Fungsi atoi adalah contoh klasik—ia mengembalikan 0 untuk input non-angka, dan tidak mendeteksi overflow. Reviewer wajib menanyakan: “Apa yang terjadi jika input adalah string kosong, 1000 karakter, atau berisi huruf?”
/* File: test_review2.c - Checklist security: input validation */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
/* Bug: konversi string ke int tanpa validasi overflow */
static int parse_id_buruk(const char *s)
{
return atoi(s); /* BUG: tidak mendeteksi overflow atau non-angka */
}
/* Versi aman: strtol dengan validasi endptr dan errno */
static int parse_id_baik(const char *s, int *out)
{
if (s == NULL || *s == '\0') return -1;
char *end;
long v = strtol(s, &end, 10);
if (*end != '\0') return -2; /* ada karakter non-digit */
if (v < 0 || v > INT_MAX) return -3; /* di luar rentang int */
*out = (int)v;
return 0;
}
/* Bug: tidak batasi panjang input - DoS via string panjang */
static size_t hitung_kata_buruk(const char *s)
{
/* BUG: jika s NULL, crash; jika s tidak diakhiri \0, baca tak hingga */
size_t n = 0;
while (*s) {
if (isspace((unsigned char)*s)) n++;
s++;
}
return n + 1;
}
/* Versi aman: terima batas panjang dan cek NULL */
static size_t hitung_kata_baik(const char *s, size_t max_len)
{
if (s == NULL) return 0;
size_t n = 1;
for (size_t i = 0; i < max_len && s[i] != '\0'; i++) {
if (isspace((unsigned char)s[i])) n++;
}
return n;
}
int main(void)
{
int id;
printf("parse_id_buruk(\"123abc\") = %d\n", parse_id_buruk("123abc"));
int rc = parse_id_baik("123abc", &id);
printf("parse_id_baik(\"123abc\") -> rc=%d\n", rc);
rc = parse_id_baik("42", &id);
printf("parse_id_baik(\"42\") -> rc=%d, id=%d\n", rc, id);
const char *teks = "satu dua tiga empat";
printf("hitung_kata_buruk(teks) = %zu\n", hitung_kata_buruk(teks));
printf("hitung_kata_baik(teks, 100) = %zu\n",
hitung_kata_baik(teks, 100));
printf("hitung_kata_baik(NULL, 100) = %zu\n",
hitung_kata_baik(NULL, 100));
return 0;
}Output yang Dihasilkan:
parse_id_buruk("123abc") = 123
parse_id_baik("123abc") -> rc=-2
parse_id_baik("42") -> rc=0, id=42
hitung_kata_buruk(teks) = 4
hitung_kata_baik(teks, 100) = 4
hitung_kata_baik(NULL, 100) = 0Mari kita telaah baris-baris kode di atas:
- Baris 10 (
atoi):atoi("123abc")mengembalikan123tanpa memberi tahu pemanggil bahwa ada karakter non-digit tersisa. Versiparse_id_baikmemakaistrtolyang mengisiendptrke pointer setelah digit terakhir—jika*end != '\0', ada karakter sisa dan input tidak valid. - Baris 27 (
hitung_kata_buruk): Loopwhile (*s)akan crash jikas == NULL(dereferensi NULL). Versi aman menambah cekif (s == NULL) return 0;di awal. - Baris 28-31 (loop tanpa batas):
while (*s)berhenti hanya jika menemukan\0. Jika string tidak diakhiri\0(mis. dari buffer yang tidak terinisialisasi), loop membaca tak hingga. Versi aman memakaifor (i = 0; i < max_len && s[i] != '\0'; i++)yang membatasi iterasi kemax_len. - Baris 36-44 (
hitung_kata_baik): Parametermax_leneksplisit. Pemanggil harus menyatakan batas—mis.hitung_kata_baik(input, 1024)jikainputdibaca dari socket dengan batas 1KB. Tanpa batas eksplisit, attacker bisa mengirim string 100MB dan memaksa loop membaca semua.
Lapisan 3: Performa #
Bug performa sering luput dari review karena kode “berhasil” dan tes lulus. Namun kompleksitas asimptotik yang buruk (O(n^2) vs O(n)) menjadi masalah saat data tumbuh.
/* File: test_review3.c - Checklist performance: O(n^2) vs O(n) */
#include <stdio.h>
#include <time.h>
#define N 5000
/* Buruk: nested loop O(n^2) untuk menjumlahkan array */
static long total_buruk(const int *arr, int n)
{
long sum = 0;
for (int i = 0; i < n; i++) {
/* BUG: loop dalam yang sama sekali tidak perlu */
long partial = 0;
for (int j = 0; j <= i; j++) {
partial += arr[j];
}
sum = partial; /* hanya simpan partial terbesar */
}
return sum;
}
/* Baik: single pass O(n) */
static long total_baik(const int *arr, int n)
{
long sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
int main(void)
{
static int arr[N];
for (int i = 0; i < N; i++) arr[i] = i + 1;
clock_t t1 = clock();
long s1 = total_baik(arr, N);
clock_t t2 = clock();
long s2 = total_buruk(arr, N);
clock_t t3 = clock();
double ms_baik = (double)(t2 - t1) * 1000.0 / CLOCKS_PER_SEC;
double ms_buruk = (double)(t3 - t2) * 1000.0 / CLOCKS_PER_SEC;
printf("total_baik = %ld dalam %.3f ms\n", s1, ms_baik);
printf("total_buruk = %ld dalam %.3f ms\n", s2, ms_buruk);
printf("ratio buruk/baik = %.1fx\n", ms_buruk / ms_baik);
return 0;
}Compile dengan optimisasi -O2:
gcc -std=c11 -Wall -Wextra -O2 -o test_review3 test_review3.c
./test_review3Output yang Dihasilkan:
total_baik = 12502500 dalam 0.001 ms
total_buruk = 12502500 dalam 3.674 ms
ratio buruk/baik = 3674.0xKedua fungsi menghasilkan total yang sama, tetapi total_buruk 3.674 milidetik—3.674 kali lebih lambat dibanding total_baik. Bug ini tidak terlihat saat test dengan array 10 elemen (kedua versi selesai <1ms), tetapi saat data 5.000 elemen, perbedaannya dramatis. Reviewer yang menanyakan “apakah loop dalam ini perlu?” akan langsung menangkap masalah ini.
Lapisan 4: Style dan Konsistensi #
Bug style bukan bug runtime, tetapi mempengaruhi keterbacaan dan maintainability. Hal-hal yang diperiksa di lapisan ini:
- Konsistensi nama: jika file memakai
snake_case, jangan ada fungsicamelCasedi tengah. - Konsistensi indentasi: campur tab dan space di satu file menyebabkan diff noisy.
- Komentar yang menjelaskan why, bukan what. Lihat bab Komentar dan Documentation.
- Tidak ada kode mati: hapus fungsi yang tidak dipanggil, variabel yang tidak dipakai, atau
#if 0block. - Sertakan test: setiap fitur baru idealnya disertai test yang memvalidasi behavior.
Etika Code Review #
Code review melibatkan manusia di kedua sisi—penulis kode dan reviewer. Beberapa etika yang membuat review produktif:
- Komentar fokus pada kode, bukan orang. “Fungsi ini bisa dipecah menjadi dua” bukan “Kamu terlalu malas memecah fungsi”.
- Jelaskan why, bukan hanya what. “Pindahkan ke helper karena dipakai juga di modul X” lebih bermanfaat daripada “Pindahkan ke helper”.
- Tawarkan solusi, bukan hanya kritik. “Pakai
strtolkarenaatoitidak mendeteksi overflow” lebih baik daripada “Jangan pakaiatoi”. - Acknowledge hal yang baik. Reviewer yang hanya komentar ketika ada masalah membuat penulis kode merasa tidak dihargai. Beri pujian untuk refactor yang baik atau test yang komprehensif.
- Bedakan blocking dan non-blocking. “Blocking: bug overflow wajib diperbaiki sebelum merge. Non-blocking: nama variabel bisa diperbaiki di commit berikutnya.”