Kualitas Kode

Panduan gaya, komentar dokumentasi, dasar pengujian unit, dan version control.

Daftar isi

Kode yang berjalan benar belum tentu kode yang berkualitas. Kualitas diukur dari kemudahan pembacaan, pengujian, dan perubahan pada masa mendatang, bukan dari keberhasilannya sekali dijalankan. Empat kebiasaan menopang ukuran itu, yaitu panduan gaya yang konsisten, komentar dokumentasi pada antarmuka, pengujian unit yang berjalan otomatis, dan version control yang merekam setiap langkah. Topik ini membahas keempatnya pada skala satu proyek kecil, sehingga kebiasaan tersebut dapat dipraktikkan sejak sekarang tanpa menunggu proyek yang besar.

Panduan gaya yang konsisten#

Panduan gaya (style guide) menetapkan pilihan yang tidak memengaruhi perilaku program, seperti penamaan, indentasi, dan penempatan kurung kurawal. Nilai utama panduan gaya bukan pada pilihan tertentu, melainkan pada keseragamannya, karena pembaca dapat menyimpan perhatian untuk logika program alih-alih berpindah-pindah antargaya. Dua penulis pada satu proyek akan memilih nama dan tata letak yang berbeda tanpa panduan, dan perbedaan semacam itu menumpuk menjadi penghalang saat kode ditinjau ulang beberapa bulan kemudian.

Konvensi yang mapan pada komunitas C dapat dijadikan titik awal tanpa perlu menyusunnya dari nol. Tabel berikut merangkum konvensi yang paling sering ditemui pada proyek C.

ElemenKonvensi umumContoh
Fungsisnake_case, kata kerja atau frasa deskriptifhitung_rata_rata
Variabel lokalsnake_case, deskriptif, singkat yang wajarjumlah_nilai
Konstanta dan makrohuruf besar dengan garis bawahBATAS_MAKSIMUM
Tipe bentukandiakhiri kata umum, misalnya _t pada typedefnilai_t

Selain penamaan, dua kebiasaan kecil berdampak besar pada keterbacaan, yaitu indentasi yang konsisten, berupa empat spasi atau satu tab di seluruh proyek, dan satu tugas per baris. Pedoman gaya kernel Linux yang dirujuk pada bagian Referensi membahas alasan di balik aturan serupa secara panjang, dan cocok dibaca sebagai pembanding setelah konvensi dasarnya diterima.

Komentar dokumentasi#

Komentar yang baik menjelaskan maksud yang tidak tampak dari kodenya, sedangkan kode yang sudah terbaca sendiri tidak perlu diulang dengan kata lain. Komentar /* menambah satu */ di atas i = i + 1 tidak menambah apa pun. Sebaliknya, komentar yang menjelaskan mengapa sebuah batas dipilih atau mengapa sebuah urutan wajib dipertahankan menyimpan pengetahuan yang hilang begitu penulisnya pergi. Karena itu, penempatan dan isi komentar dipilah menurut sasarannya, yaitu antarmuka untuk pembaca luar dan keputusan internal untuk pemelihara kode.

Komentar dokumentasi pada antarmuka biasanya ditulis dengan blok /** ... */ gaya Doxygen, yaitu alat yang membaca komentar tersebut menjadi halaman dokumentasi. Bentuk lengkapnya diperlihatkan pada potongan berikut.

c Contoh: komentar dokumentasi gaya Doxygen
// tidak-dijalankan
/**
 * Mengonversi suhu dari derajat Celcius ke Fahrenheit.
 *
 * @param c  suhu dalam derajat Celcius
 * @return   suhu setara dalam derajat Fahrenheit
 */
double celcius_ke_fahrenheit(double c);

Komentar semacam ini ditempatkan pada header, tepat di atas prototipe yang didokumentasikan, sehingga pembaca satuan.h mendapat kontrak lengkap tanpa membuka implementasinya. Tag @param dan @return masing-masing mendeskripsikan masukan dan hasilnya, sedangkan kalimat pertamanya berfungsi sebagai ringkasan singkat. Komentar pada tubuh fungsi tetap berguna untuk keputusan yang tidak jelas alasannya, dan kedua jenis komentar itu tidak saling menggantikan.

Pengujian unit dengan assert#

Pengujian unit (unit testing) menguji unit terkecil yang dapat diuji, yaitu fungsi, dengan membandingkan hasilnya terhadap harapan yang dituliskan eksplisit. Cara paling ringan di C menggunakan makro assert dari assert.h: ekspresi yang bernilai benar membuat program melanjutkan, sedangkan yang bernilai salah menghentikan program dengan pesan yang menunjuk barisnya. Berkas berikut menguji fungsi konversi nilai angka menjadi nilai huruf pada batas-batasnya.

c uji-nilai-huruf.c
#include <assert.h>
#include <stdio.h>

/* Fungsi yang diuji: konversi nilai angka menjadi nilai huruf. */
char nilai_huruf(int nilai)
{
    if (nilai >= 85) {
        return 'A';
    }
    if (nilai >= 70) {
        return 'B';
    }
    if (nilai >= 55) {
        return 'C';
    }
    if (nilai >= 40) {
        return 'D';
    }
    return 'E';
}

/* Satu fungsi uji untuk satu perilaku yang diharapkan. */
void uji_tepat_pada_batas(void)
{
    assert(nilai_huruf(85) == 'A');
    assert(nilai_huruf(70) == 'B');
    assert(nilai_huruf(55) == 'C');
    assert(nilai_huruf(40) == 'D');
}

void uji_satu_tingkat_di_bawah_batas(void)
{
    assert(nilai_huruf(84) == 'B');
    assert(nilai_huruf(69) == 'C');
    assert(nilai_huruf(54) == 'D');
    assert(nilai_huruf(39) == 'E');
}

int main(void)
{
    uji_tepat_pada_batas();
    uji_satu_tingkat_di_bawah_batas();
    printf("Semua uji lolos\n");
    return 0;
}
text Keluaran
Semua uji lolos

Struktur berkasnya mengikuti pola yang mudah diperluas, yaitu fungsi yang diuji di bagian atas, satu fungsi uji per perilaku, dan main sebagai pemanggil seluruh uji sekaligus pencetak pesan akhirnya. Batas antara dua keluaran, seperti 85 dan 70, sengaja diuji dari dua sisi, karena kesalahan tanda >= paling sering tersembunyi tepat di situ. Kumpulan uji semacam ini dijalankan ulang setiap kali kode berubah, sehingga cacat yang baru muncul diketahui dalam hitungan detik.

Uji yang gagal memberikan pesan yang jauh lebih informatif daripada keluaran yang salah diam-diam. Berkas berikut sengaja memuat satu harapan yang keliru. Berkas ini tidak dijalankan langsung karena pesan yang dihasilkan memuat jalur berkas yang bergantung pada tempat kompilasi.

c uji-gagal.c
// tidak-dijalankan
#include <assert.h>

char nilai_huruf(int nilai)
{
    if (nilai >= 85) {
        return 'A';
    }
    return 'B';
}

void uji_nilai_rendah(void)
{
    assert(nilai_huruf(30) == 'E');
}

int main(void)
{
    uji_nilai_rendah();
    return 0;
}

Menjalankan versi gagal ini menghasilkan pesan assertion beserta kode keluar 134. Pesan tersebut diambil pada blok berikut dengan menangkap stderr ke berkas sementara agar keluarannya murni. Penangkapan itu diperlukan karena pesan bawaan shell untuk sinyal memuat nomor proses yang berubah pada setiap eksekusi.

bash
# mengompilasi versi uji yang sengaja gagal
gcc uji-gagal.c -o uji-gagal
# pesan bawaan shell disembunyikan sementara agar keluarannya murni
exec 2>/dev/null
./uji-gagal 2> pesan-uji.txt
status=$?
exec 2>&1
cat pesan-uji.txt
echo "kode keluar: $status"
text Keluaran
uji-gagal: uji-gagal.c:14: uji_nilai_rendah: Assertion `nilai_huruf(30) == 'E'' failed.
kode keluar: 134

Pesan itu memuat nama program, berkas, baris, fungsi, dan ekspresi yang gagal, sehingga pemetaan dari gejala ke baris penyebabnya hampir tidak menuntut pencarian. Kode keluar 134 mengikuti rumus 128 + 6 untuk SIGABRT, sebagaimana dibahas pada topik bug pointer. Kebiasaan yang menyertai alat ini adalah menambahkan satu uji baru setiap bug ditemukan, sehingga cacat yang sama tidak pernah lolos dua kali.

Version control dengan git#

Version control merekam setiap keadaan proyek sebagai commit, yaitu titik simpan berlabel yang dapat dikunjungi kembali, sehingga perubahan yang merusak dapat dilacak sumbernya atau dibatalkan. git menandai folder mana pun sebagai repository melalui git init, lalu perubahan dicatat setelah berkasnya dipilih dengan git add. Urutan berikut menyiapkan repository latihan pada folder sementara dan memeriksa keadaannya.

bash
# menyiapkan folder latihan yang selalu baru
rm -rf /tmp/repo-latihan && mkdir -p /tmp/repo-latihan && cd /tmp/repo-latihan
git init -b main
printf 'char nilai_huruf(int nilai);\n' > statistik.c
git add statistik.c
git status --short
text Keluaran
Initialized empty Git repository in /tmp/repo-latihan/.git/
A  statistik.c

Huruf A pada keluaran git status --short menandai berkas yang sudah dipilih untuk dicatat, sedangkan berkas yang baru dibuat tanpa dipilih ditandai ??. Tahap git add ini memisahkan pemilihan berkas dari pencatatan, sehingga satu commit dapat berisi beberapa berkas yang benar-benar satu pekerjaan.

Pencatatan dilakukan dengan git commit -m diikuti pesan singkat yang mendeskripsikan isinya. Keluaran commit memuat nomor hash dan nama cabang yang berbeda pada setiap repository. Karena itu, keluarannya dialihkan ke /dev/null, dan yang ditampilkan hanyalah riwayat pesannya.

bash
# mencatat commit pertama lalu menampilkan daftar pesannya
cd /tmp/repo-latihan
git commit -m "Tambah kerangka fungsi statistik" > /dev/null
git log --pretty=format:%s
text Keluaran
Tambah kerangka fungsi statistik

Pesan commit yang baik menyatakan isi perubahan dalam satu kalimat imperatif, sehingga riwayat git log dapat dibaca seperti daftar perubahan yang bermakna. Berkas hasil build seperti *.o dan program hasil pengaitan tidak pernah dicatat, melainkan didaftarkan pada berkas .gitignore agar git status tetap bersih.

Kesalahan umum#

  • Mengubah gaya di tengah proyek tanpa alasan. Campuran dua indentasi atau dua konvensi penamaan membuat perbedaan penting sulit terlihat di antara perbedaan yang tidak berarti. Tetapkan panduannya sekali di awal, lalu terapkan pada seluruh berkas.
  • Menulis komentar yang mengulang kode. Komentar /* menambah i dengan satu */ di atas i = i + 1 hanya menambah baris yang harus dibaca. Arahkan komentar pada alasan dan kontrak, bukan pada apa yang sudah tampak.
  • Menghapus atau melonggarkan uji yang gagal alih-alih memperbaiki kodenya. Uji yang gagal adalah sinyal, bukan hambatan. Perbaiki fungsi yang diuji, atau ubah uji tersebut hanya apabila harapannya memang perlu disesuaikan, dan catat alasan perubahan itu pada commit.
  • Menulis pesan commit yang tidak mendeskripsikan isi. Pesan seperti perbaikan atau update membuat riwayat kehilangan gunanya saat dicari kembali. Gunakan satu kalimat imperatif yang menyebut apa yang berubah.

Ringkasan#

  • Panduan gaya menyeragamkan penamaan dan tata letak agar perhatian pembaca tersisa untuk logika program.
  • Komentar dokumentasi /** ... */ gaya Doxygen menempatkan kontrak fungsi pada header, sedangkan komentar internal menyimpan alasan keputusan.
  • Pengujian unit berbasis assert membandingkan hasil fungsi dengan harapan eksplisit, dan uji yang gagal menunjuk berkas, baris, serta ekspresinya.
  • git merekam keadaan proyek sebagai commit berlabel, dengan git add memilih berkas dan pesan commit yang imperatif menjaga riwayat tetap terbaca.

Referensi#