Setiap fungsi memiliki kontrak: precondition yang harus dipenuhi pemanggil sebelum memanggil, dan postcondition yang dijanjikan fungsi bila precondition terpenuhi. Sayangnya, pemanggil sering melanggar kontrak—baik karena lupa, salah ketik, atau karena data berasal dari pengguna yang tidak prediksi. Pola fail-fast validation adalah strategi memeriksa semua parameter di awal fungsi dan langsung mengembalikan error bila ada yang tidak valid, sebelum logika bisnis apapun dijalankan. Pada chapter ini kita akan mempraktikkan tiga level validasi parameter: parameter primitif, parameter berstruktur (struct), dan parameter command-line argv.

Pola Fail-Fast: Validasi Semua di Awal #

Filosofi fail-fast sederhana: bila ada parameter yang tidak valid, kembalikan error segera. Jangan menunda pengecekan, jangan mencoba “memperbaiki” input, jangan menjalankan logika bisnis dengan asumsi mungkin-mungkin saja benar. Strategi ini memberi tiga manfaat: (1) bug terdeteksi lebih awal saat stack trace masih dekat dengan sumber masalah, (2) kode logika bisnis menjadi lebih bersih karena tidak perlu dipenuhi cabang error, (3) pesan error lebih spesifik karena setiap parameter punya kode error sendiri.

c
#include <stdio.h>
#include <stddef.h>

/* Pola fail-fast: validasi SEMUA parameter di awal, kembali lebih awal bila ada yang tidak valid.
   Tidak ada logika bisnis yang berjalan sebelum semua parameter dipastikan sah. */
typedef enum {
    V_OK = 0,
    V_BUF_NULL = 1,
    V_DATA_NULL = 2,
    V_SIZE_ZERO = 3,
    V_OFFSET_NEG = 4,
    V_OFFSET_TOO_LARGE = 5,
} v_status;

v_status isi_buffer(int *buf, size_t ukuran, int offset, int nilai) {
    /* Pintu masuk: periksa SEMUA parameter sebelum melakukan apapun */
    if (buf == NULL) return V_BUF_NULL;
    if (ukuran == 0) return V_SIZE_ZERO;
    if (offset < 0)  return V_OFFSET_NEG;

    size_t abs_offset = (size_t)offset;
    if (abs_offset >= ukuran) return V_OFFSET_TOO_LARGE;

    /* Hanya sampai sini jika SEMUA parameter valid */
    buf[abs_offset] = nilai;
    return V_OK;
}

int main(void) {
    int data[5] = {0};

    struct {
        int offset;
        int nilai;
        const char *deskripsi;
    } kasus[] = {
        {2,  100, "valid"},
        {-1, 200, "offset negatif"},
        {7,  300, "offset terlalu besar"},
        {3,  400, "valid lagi"},
    };
    int n = sizeof(kasus) / sizeof(kasus[0]);

    for (int i = 0; i < n; i++) {
        v_status s = isi_buffer(data, 5, kasus[i].offset, kasus[i].nilai);
        printf("isi_buffer(offset=%d, nilai=%d) [%s] -> status %d\n",
               kasus[i].offset, kasus[i].nilai, kasus[i].deskripsi, s);
    }

    /* Cek isi buffer yang berhasil dimodifikasi */
    printf("\nIsi data: ");
    for (int i = 0; i < 5; i++) printf("%d ", data[i]);
    printf("\n");

    return 0;
}

Output yang Dihasilkan:

text
isi_buffer(offset=2, nilai=100) [valid] -> status 0
isi_buffer(offset=-1, nilai=200) [offset negatif] -> status 4
isi_buffer(offset=7, nilai=300) [offset terlalu besar] -> status 5
isi_buffer(offset=3, nilai=400) [valid lagi] -> status 0

Isi data: 0 0 100 400 0 

Output membuktikan pola fail-fast bekerja: dua pemanggilan dengan parameter valid (offset 2 dan 3) berhasil menulis ke data[2] = 100 dan data[3] = 400. Dua pemanggilan lain dengan parameter tidak valid (offset -1 dan 7) ditolak dengan kode status 4 dan 5, tanpa memodifikasi buffer.

Mari kita telaah baris-baris kode di atas:

  1. Baris 17-22 (typedef enum { V_OK, V_BUF_NULL, ... } v_status;): setiap parameter yang mungkin invalid punya kode error sendiri. V_BUF_NULL untuk buffer NULL, V_OFFSET_NEG untuk offset negatif, dst. Enum membuat kode mudah dibaca di pemanggil: if (s == V_OFFSET_NEG) jauh lebih jelas daripada if (s == 4).
  2. Baris 24 (v_status isi_buffer(int *buf, size_t ukuran, int offset, int nilai)): signature mengembalikan v_status, bukan void. Karena fungsi memodifikasi buffer lewat pointer, return value dapat dipakai untuk status.
  3. Baris 26-30 (if (buf == NULL) return V_BUF_NULL; ...): lima pengecekan berurutan, masing-masing mengembalikan kode berbeda. Urutan pengecekan penting: buf == NULL dulu karena bila buf NULL, pengecekan ukuran tidak ada gunanya. Begitu satu pengecekan gagal, return langsung—tidak ada yang membahayakan data.
  4. Baris 33 (size_t abs_offset = (size_t)offset;): konversi setelah offset >= 0 dipastikan. Tanpa pengecekan di baris 28, konversi int negatif ke size_t (unsigned) akan menghasilkan angka sangat besar, lolos dari pengecekan >= ukuran, dan menulis ke alamat tak terduga.
  5. Baris 34 (if (abs_offset >= ukuran) return V_OFFSET_TOO_LARGE;): pengecekan batas. Indeks terakhir yang sah adalah ukuran - 1, jadi >= ukuran adalah kondisi error.
  6. Baris 37 (buf[abs_offset] = nilai;): baru di jalur sukses, modifikasi dilakukan. Inilah inti fail-fast: data hanya disentuh bila semua valid.

Validasi Parameter Berstruktur #

Saat fungsi menerima struct, kita memvalidasi setiap field secara terpisah. Strateginya sama—fail-fast di setiap field—tetapi dengan keuntungan tambahan: pesan error bisa langsung menunjuk field mana yang bermasalah.

c
#include <stdio.h>
#include <string.h>

/* Validasi parameter berstruktur - validasi bertahap dengan pesan spesifik.
   Setiap parameter diperiksa terpisah agar pesan error bisa menunjuk parameter mana. */
typedef struct {
    const char *nama;
    int umur;
    const char *email;
} user_input;

typedef enum {
    USER_OK = 0,
    USER_NAMA_NULL,
    USER_NAMA_KOSONG,
    USER_NAMA_PANJANG,
    USER_UMUR_NEGATIF,
    USER_UMUR_KELEBIHAN,
    USER_EMAIL_NULL,
    USER_EMAIL_TANPA_AT,
} user_status;

#define MAX_NAMA 50

user_status validasi_user(const user_input *u) {
    /* Periksa pointer struct utama dulu */
    if (u == NULL) return USER_NAMA_NULL;   /* fallback */

    /* Validasi nama */
    if (u->nama == NULL) return USER_NAMA_NULL;
    if (u->nama[0] == '\0') return USER_NAMA_KOSONG;
    if (strlen(u->nama) > MAX_NAMA) return USER_NAMA_PANJANG;

    /* Validasi umur */
    if (u->umur < 0)   return USER_UMUR_NEGATIF;
    if (u->umur > 150) return USER_UMUR_KELEBIHAN;

    /* Validasi email */
    if (u->email == NULL) return USER_EMAIL_NULL;
    if (strchr(u->email, '@') == NULL) return USER_EMAIL_TANPA_AT;

    return USER_OK;
}

const char *status_ke_pesan(user_status s) {
    switch (s) {
        case USER_OK:              return "valid";
        case USER_NAMA_NULL:       return "nama NULL";
        case USER_NAMA_KOSONG:     return "nama kosong";
        case USER_NAMA_PANJANG:    return "nama terlalu panjang (>50)";
        case USER_UMUR_NEGATIF:    return "umur negatif";
        case USER_UMUR_KELEBIHAN:  return "umur tidak masuk akal (>150)";
        case USER_EMAIL_NULL:      return "email NULL";
        case USER_EMAIL_TANPA_AT:  return "email tanpa karakter @";
        default:                   return "tidak dikenal";
    }
}

int main(void) {
    user_input kasus[] = {
        {"Budi",  25, "[email protected]"},            /* OK */
        {"",      30, "[email protected]"},                      /* nama kosong */
        {"Siti", -5, "[email protected]"},             /* umur negatif */
        {"Andi",  30, "bukan-email"},                 /* tanpa @ */
        {"Joko", 200, "[email protected]"},            /* umur kelebihan */
    };
    int n = sizeof(kasus) / sizeof(kasus[0]);

    for (int i = 0; i < n; i++) {
        user_status s = validasi_user(&kasus[i]);
        printf("user '%s' umur=%d email='%s' -> %s\n",
               kasus[i].nama, kasus[i].umur, kasus[i].email,
               status_ke_pesan(s));
    }
    return 0;
}

Output yang Dihasilkan:

text
user 'Budi' umur=25 email='[email protected]' -> valid
user '' umur=30 email='[email protected]' -> nama kosong
user 'Siti' umur=-5 email='[email protected]' -> umur negatif
user 'Andi' umur=30 email='bukan-email' -> email tanpa karakter @
user 'Joko' umur=200 email='[email protected]' -> umur tidak masuk akal (>150)

Fungsi validasi_user memvalidasi tiga field secara berurutan: nama, umur, email. Untuk field string (nama, email) ada tiga lapisan: NULL, empty, dan validasi format (panjang maksimum atau karakter khusus). Untuk field numerik (umur) ada rentang wajar (0-150). Fungsi pembantu status_ke_pesan() mengkonversi enum menjadi teks, sehingga pemanggil tidak perlu switch manual.

Validasi Parameter Command-Line #

main(int argc, char **argv) adalah entry point pertama yang berhadapan dengan input dari luar. Argumen argv[] adalah array string dari pengguna; tanpa validasi, akses argv[1] saat argc == 1 akan menjadi out of bounds dan berpotensi membaca memori tak terdefinisi.

c
#include <stdio.h>
#include <stdlib.h>

/* Demonstrasi: validasi parameter di entry point program (main).
   argc dan argv wajib diperiksa sebelum dipakai untuk menghindari
   out-of-bounds di argv[]. */
int main(int argc, char **argv) {
    /* Validasi jumlah argumen */
    if (argc < 3) {
        fprintf(stderr, "Penggunaan: %s <nama> <umur>\n",
                argc > 0 ? argv[0] : "./program");
        fprintf(stderr, "Contoh: %s Budi 25\n",
                argc > 0 ? argv[0] : "./program");
        return 1;
    }

    const char *nama = argv[1];
    const char *umur_str = argv[2];

    /* Validasi nama: tidak boleh kosong */
    if (nama[0] == '\0') {
        fprintf(stderr, "Error: nama tidak boleh kosong\n");
        return 2;
    }

    /* Validasi umur: harus berupa angka positif */
    char *endptr = NULL;
    long umur = strtol(umur_str, &endptr, 10);
    if (endptr == umur_str || *endptr != '\0') {
        fprintf(stderr, "Error: umur '%s' bukan angka yang valid\n", umur_str);
        return 3;
    }
    if (umur < 0 || umur > 150) {
        fprintf(stderr, "Error: umur %ld di luar rentang wajar (0-150)\n", umur);
        return 4;
    }

    /* Baru di sini semua parameter valid, lanjut ke logika bisnis */
    printf("Halo %s, umur Anda %ld tahun\n", nama, umur);
    return 0;
}

Output yang Dihasilkan (berbagai pemanggilan):

text
$ ./test_validasi3 Budi 25
Halo Budi, umur Anda 25 tahun

$ ./test_validasi3 Budi
Penggunaan: ./test_validasi3 <nama> <umur>
Contoh: ./test_validasi3 Budi 25

$ ./test_validasi3 Siti bukan_angka
Error: umur 'bukan_angka' bukan angka yang valid

$ ./test_validasi3 Joko 200
Error: umur 200 di luar rentang wajar (0-150)

Empat skenario berbeda menghasilkan empat respons berbeda. Pemanggilan dengan argumen valid (Budi 25) mencetak pesan sukses dengan exit code 0. Tiga skenario lain ditolak dengan exit code berbeda (1, 3, 4) sehingga shell script pemanggil bisa membedakan jenis kesalahan dan merespons sesuai—misalnya menampilkan help saat exit=1, atau meminta input ulang saat exit=3.