Setiap fungsi di examplelib mengembalikan kode error sebagai enum exlib_status. Pada bagian ini, kita perdalam strategi error handling: kapan menggunakan kode error vs return value, bagaimana menyusun enum yang konsisten, dan bagaimana membantu pengguna memahami error yang terjadi.

Konsep #

Strategi error handling di C biasanya memilih satu dari tiga pola:

  1. Kode error sebagai return value — fungsi mengembalikan int atau enum, hasil perhitungan lewat parameter output. Pola yang kita pakai di examplelib.
  2. Sentinel value — return value khusus menandai error (mis. NULL untuk pointer, -1 untuk int). Pola ini dipakai getchar() yang mengembalikan EOF.
  3. Out-of-band error (errno) — variabel global errno di <errno.h> di-set saat error terjadi. Pola yang dipakai library standar fopen, strtol.

Pilihan kita: pola 1 untuk examplelib karena paling eksplisit dan type-safe. Pengguna langsung tahu dari return value apakah sukses atau error, dan tahu jenis error apa.

Mekanisme kerja: setiap fungsi publik mengembalikan exlib_status. Pemanggil wajib memeriksa return value sebelum memakai hasil. Kontrak “all-or-nothing”: bila return != EXLIB_OK, parameter output tidak diubah. Pola ini membuat kode pemanggil lebih aman:

c
double mean;
if (exlib_mean(data, n, &mean) != EXLIB_OK) {
    /* mean tidak boleh dipakai di sini */
    return;
}
/* mean aman dipakai */

Implementasi #

Enum error yang dipakai di examplelib:

c
typedef enum {
    EXLIB_OK            = 0,
    EXLIB_ERR_NULL      = -1,
    EXLIB_ERR_RANGE     = -2,
    EXLIB_ERR_FORMAT    = -3,
    EXLIB_ERR_OVERFLOW  = -4,
    EXLIB_ERR_NOT_FOUND = -5
} exlib_status;

Fungsi exlib_strerror menerjemahkan kode ke pesan:

c
const char *exlib_strerror(exlib_status code) {
    switch (code) {
        case EXLIB_OK:            return "sukses";
        case EXLIB_ERR_NULL:      return "pointer NULL atau n=0";
        case EXLIB_ERR_RANGE:     return "nilai di luar rentang valid";
        case EXLIB_ERR_FORMAT:    return "format input tidak valid";
        case EXLIB_ERR_OVERFLOW:  return "potensi overflow";
        case EXLIB_ERR_NOT_FOUND: return "tidak ditemukan";
        default:                  return "kode tidak dikenal";
    }
}

Pola penggunaan yang baik di sisi pemanggil:

c
/* demo_error_lib.c - Pola error handling yang baik */
#include <stdio.h>
#include <stdint.h>
#include "examplelib.h"

static void report(const char *op, exlib_status s) {
    if (s == EXLIB_OK) {
        printf("[OK] %s\n", op);
    } else {
        printf("[ERR] %s: %s (code=%d)\n",
               op, exlib_strerror(s), (int)s);
    }
}

int main(void) {
    /* Kasus sukses. */
    double data[] = {2.0, 4.0, 6.0};
    double mean;
    exlib_status s = exlib_mean(data, 3, &mean);
    report("mean(data,3)", s);
    if (s == EXLIB_OK) {
        printf("  -> mean = %.2f\n", mean);
    }

    /* Kasus error: n=0. */
    s = exlib_mean(data, 0, &mean);
    report("mean(data,0)", s);

    /* Kasus error: data NULL. */
    s = exlib_mean(NULL, 5, &mean);
    report("mean(NULL,5)", s);

    /* Kasus error: buffer terlalu kecil. */
    char buf[5];
    s = exlib_format_size(1024, buf, sizeof(buf));
    report("format_size(buf=5)", s);

    /* Kasus error: substring tidak ditemukan. */
    s = exlib_contains("hello", "world");
    report("contains(hello,world)", s);

    /* Kode tidak dikenal (seharusnya tidak terjadi di API normal). */
    s = (exlib_status)999;
    report("invalid_code_999", s);

    return 0;
}

Output yang Dihasilkan:

text
[OK] mean(data,3)
  -> mean = 4.00
[ERR] mean(data,0): pointer NULL atau n=0 (code=-1)
[ERR] mean(NULL,5): pointer NULL atau n=0 (code=-1)
[ERR] format_size(buf=5): nilai di luar rentang valid (code=-2)
[ERR] contains(hello,world): tidak ditemukan (code=-5)
[ERR] invalid_code_999: kode tidak dikenal (code=999)

Mari kita telaah baris-baris kode di atas:

  1. Baris 1-7 (enum dengan nilai eksplisit): Setiap error punya nilai negatif yang unik. EXLIB_OK = 0 sehingga if (s) bekerja sebagai cek error (0 = false = sukses, non-zero = true = error). Pola ini konsisten dengan convention Unix exit code.
  2. Baris 9-19 (exlib_strerror): Switch case yang lengkap. default menangani integer cast ke enum yang tidak valid. Pola ini defensif: bila pengguna lupa update strerror saat menambah enum baru, default memberi pesan yang masuk akal.
  3. Baris 21-29 (report helper): Helper di sisi pemanggil yang mencetak hasil dengan format konsisten. Bila s == EXLIB_OK, cetak [OK]; selain itu, cetak [ERR] dengan kode dan pesan. Helper seperti ini mengurangi boilerplate di kode pemanggil.
  4. Baris 38-43 (kasus sukses): Setelah exlib_mean return OK, kita bisa memakai mean. Bila return != OK, mean tidak boleh dipakai (nilainya tidak terdefinisi). Kontrak ini harus dijaga oleh implementasi.
  5. Baris 46-53 (kasus error): Berbagai input invalid memicu error berbeda. Pemanggil bisa switch (s) untuk menangani error berbeda secara berbeda — mis. logika retry untuk EXLIB_ERR_FORMAT, abort untuk EXLIB_ERR_OVERFLOW.
  6. Baris 60-61 (invalid code): (exlib_status)999 menguji default case. Pemanggil “nakal” bisa saja melewatkan integer acak ke exlib_strerror. Default menangani dengan pesan “kode tidak dikenal” tanpa crash.

Kode Lengkap #

Kode lengkap dengan enum error, exlib_strerror, dan dokumentasi setiap kode ada di examplelib.h dan examplelib.c. README juga menyertakan tabel kode error untuk referensi cepat.

Pengujian #

Test error handling dengan berbagai skenario:

  • Input NULL → EXLIB_ERR_NULL.
  • Input dengan n=0 → EXLIB_ERR_NULL (konvensi kita: n=0 dianggap “no data”).
  • Input dengan nilai di luar rentang (mis. out_cap terlalu kecil) → EXLIB_ERR_RANGE.
  • Input dengan format yang salah → EXLIB_ERR_FORMAT.
  • Operasi yang menghasilkan substring tidak ditemukan → EXLIB_ERR_NOT_FOUND.
  • Cast integer acak ke enum → default case di strerror.
bash
gcc -std=c11 -Wall -Wextra -Iinclude -o /tmp/demo_error_lib \
    demo_error_lib.c src/examplelib.c -lm
/tmp/demo_error_lib