Penulis kode C tidak hanya berkomunikasi dengan compiler, tetapi juga dengan manusia yang akan membaca kode enam bulan ke depan—termasuk diri sendiri. Compiler mengabaikan indentasi, spasi, dan posisi kurung kurawal ({}); manusia tidak. Bab ini membandingkan tiga brace style yang paling banyak dipakai di komunitas C (K&R, Allman, dan Linux kernel), menunjukkan kapan tab vs space menjadi pertimbangan penting, lalu memperkenalkan alat otomatis seperti clang-format yang menjaga konsistensi format di seluruh tim. Pada akhir bab ini kita akan mampu memilih satu gaya, mempertahankannya secara konsisten, dan tahu kapan harus mengikuti gaya proyek yang sudah ada.

Mengapa Formatting Penting #

Compiler C mengabaikan whitespace. Tiga fragmen berikut menghasilkan bytecode identik:

c
int hitung(int x){return x*2;}
c
int hitung(int x) {
    return x * 2;
}
c
int hitung(int x)
{
    return x * 2;
}

Meskipun compiler memperlakukan ketiganya sama, mata manusia membutuhkan struktur visual yang konsisten untuk memindai logika dengan cepat. Studi mata (eye-tracking) yang dirilis oleh Software Quality Journal menunjukkan bahwa developer membaca kode terformat rata kiri-kanan ~30% lebih cepat daripada kode yang indentasinya tidak konsisten. Inilah mengapa standar seperti Linux kernel coding style, GNU Coding Standards, dan CERT C memiliki aturan eksplisit tentang indentasi dan brace placement.

Tiga Brace Style Utama #

K&R / 1TBS (One True Brace Style) #

Gaya K&R dipopulerkan oleh buku The C Programming Language oleh Kernighan dan Ritchie. Ciri khasnya: kurung kurawal pembuka ({) berada di akhir baris yang sama dengan keyword if, for, while, atau deklarasi fungsi. Single-statement if tanpa brace kadang dibiarkan tanpa brace, tetapi CERT C dan MISRA C menyarankan selalu memakai brace.

c
/* File: format1.c - K&R / 1TBS style */
#include <stdio.h>

static int hitung_diskon(int harga, int persen) {
    if (persen < 0 || persen > 100) {
        return -1;
    }
    int potongan = harga * persen / 100;
    return harga - potongan;
}

int main(void) {
    int harga = 200000;
    int persen = 25;
    int akhir = hitung_diskon(harga, persen);
    if (akhir < 0) {
        fprintf(stderr, "persen tidak valid\n");
        return 1;
    }
    printf("Harga awal : %d\n", harga);
    printf("Diskon %d%%: %d\n", persen, harga - akhir);
    printf("Harga akhir: %d\n", akhir);
    return 0;
}

Output yang Dihasilkan:

text
Harga awal : 200000
Diskon 25%: 50000
Harga akhir: 150000

Allman Style #

Gaya Allman (juga disebut BSD style) menempatkan kurung kurawal pembuka di baris baru. Keuntungannya: brace pembuka dan penutup berada di kolom yang sama, sehingga mudah melihat pasangannya. Kerugiannya: setiap blok memakai satu baris ekstra.

c
/* File: format2.c - Allman style (brace on new line) */
#include <stdio.h>

static int hitung_diskon(int harga, int persen)
{
    if (persen < 0 || persen > 100)
    {
        return -1;
    }
    int potongan = harga * persen / 100;
    return harga - potongan;
}

int main(void)
{
    int harga = 200000;
    int persen = 25;
    int akhir = hitung_diskon(harga, persen);
    if (akhir < 0)
    {
        fprintf(stderr, "persen tidak valid\n");
        return 1;
    }
    printf("Harga awal : %d\n", harga);
    printf("Diskon %d%%: %d\n", persen, harga - akhir);
    printf("Harga akhir: %d\n", akhir);
    return 0;
}

Output yang Dihasilkan:

text
Harga awal : 200000
Diskon 25%: 50000
Harga akhir: 150000

Linux Kernel Style #

Linux kernel coding style dijabarkan oleh Linus Torvalds di kernel.org/doc/html/latest/process/coding-style.html. Ciri khasnya: indentasi dengan tab (lebar 8 karakter, bukan 4), switch tanpa indentasi pada case, single-statement tanpa brace diperbolehkan untuk kondisi sederhana, dan brace pembuka fungsi di baris baru (sedangkan brace pembuka if/for/while di akhir baris). Tab 8-karakter sengaja dipilih untuk memaksa programmer memecah fungsi yang terlalu dalam (indentation is a hint that you should refactor).

c
/* File: format3.c - Linux kernel style (tabs, 8-wide, brace same line if/for,
   but new line for function. Spaces around binary ops. */
#include <stdio.h>

static int hitung_diskon(int harga, int persen)
{
	if (persen < 0 || persen > 100)
		return -1;

	int potongan = harga * persen / 100;
	return harga - potongan;
}

int main(void)
{
	int harga = 200000;
	int persen = 25;
	int akhir = hitung_diskon(harga, persen);

	if (akhir < 0) {
		fprintf(stderr, "persen tidak valid\n");
		return 1;
	}

	printf("Harga awal : %d\n", harga);
	printf("Diskon %d%%: %d\n", persen, harga - akhir);
	printf("Harga akhir: %d\n", akhir);
	return 0;
}

Output yang Dihasilkan:

text
Harga awal : 200000
Diskon 25%: 50000
Harga akhir: 150000

Ketiga versi tersebut menghasilkan output identik—compiler melihat kode yang sama. Yang berbeda adalah cara mata manusia membaca struktur blok.

Indentasi dan Spasi yang Konsisten #

Selain brace style, dua hal yang sering menjadi perdebatan adalah tab vs space dan letak spasi di sekitar operator. Tabel berikut merangkum konvensi yang paling banyak dipakai:

AspekKonvensi umumAlasan
Indentasi4 space atau 1 tab (lebar 4 atau 8)Konsistensi
Spasi sebelum ( fungsiTanpa spasi: foo(a)Membedakan dari keyword if (a)
Spasi sebelum ( keywordDengan spasi: if (a), for (i)Membedakan dari pemanggilan fungsi
Spasi sebelum ( pointer derefTanpa spasi: (*p)Mengikuti syntax operator
Spasi di sekitar binary opa + b, c * dKeterbacaan
Spasi di sekitar unary opTanpa spasi: -a, *p, i++Mengikuti syntax operator
Trailing whitespaceDihapusMenghindari noise diff
Baris terakhir fileSelalu newlinePOSIX text file

Mari kita lihat contoh kode yang mengikuti konvensi di atas:

c
/* File: format4.c - Demonstrasikan indentasi yang konsisten dengan clang-format */
#include <stdio.h>

typedef enum {
    STATUS_OK = 0,
    STATUS_INVALID = -1,
    STATUS_OVERFLOW = -2
} status_t;

static status_t proses_data(int *out, const int *in, size_t n, int threshold)
{
    int total = 0;
    for (size_t i = 0; i < n; i++) {
        if (in[i] < 0) {
            return STATUS_INVALID;
        }
        if (in[i] > threshold) {
            total += threshold;
        } else {
            total += in[i];
        }
    }
    *out = total;
    return STATUS_OK;
}

int main(void)
{
    int data[] = {5, 10, 3, 8, 12};
    int hasil;
    status_t s = proses_data(&hasil, data, 5, 8);
    printf("status=%d hasil=%d\n", s, hasil);
    return 0;
}

Output yang Dihasilkan:

text
status=0 hasil=32

Mari kita telaah baris-baris kode di atas:

  1. Baris 5-9 (typedef enum { ... } status_t): Enumerator STATUS_OK, STATUS_INVALID, STATUS_OVERFLOW ditulis dalam ALL_CAPS untuk menandakan konstanta compile-time. Brace pembuka enum berada di akhir baris typedef enum (gaya K&R).
  2. Baris 11 (static status_t proses_data(int *out, const int *in, size_t n, int threshold)): Tanda * menempel ke nama variabel (int *out), bukan ke tipe (int* out). Konvensi C mengikuti posisi * ke variabel karena int *out, *in; adalah dua pointer—bukan int* out, in; yang akan menghasilkan int* dan int.
  3. Baris 14 (for (size_t i = 0; i < n; i++) {): Spasi setelah keyword for, tanpa spasi setelah nama fungsi. Loop body selalu diapit brace meski hanya satu statement—ini aturan CERT C EXP15-C untuk mencegah bug ketika nanti ditambahkan statement.
  4. Baris 19-23 (if/else berantai): else ditulis pada baris yang sama dengan } penutup if—ini idiom K&R. Brace pembuka else { juga di akhir baris yang sama.
  5. Baris 25 (*out = total;): Tanpa spasi antara * dan out karena keduanya adalah satu kesatuan operasi dereferensi.

Otomatisasi dengan clang-format #

Manual formatting tidak realistis untuk proyek besar. Standar industri adalah menggunakan clang-format, sebuah tool dari proyek LLVM yang mengubah format kode sesuai aturan yang didefinisikan di file .clang-format. File ini bisa dipakai bersama oleh seluruh tim melalui version control, sehingga setiap commit memiliki format identik.

Konfigurasi umum untuk proyek C modern:

yaml
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 100
AllowShortIfStatementsOnASingleLine: Never
BreakBeforeBraces: Attach
AlignConsecutiveAssignments: true
PointerAlignment: Right

Setelah konfigurasi disimpan, jalankan clang-format -i *.c *.h untuk memformat semua file secara in-place. Editor modern seperti VS Code, Vim (dengan plugin), dan CLion terintegrasi langsung dengan clang-format dan memformat kode saat men-save.