Kita mulai proyek pengelola inventaris dari fondasi paling dasar: bagaimana satu baris data produk direpresentasikan di dalam program. Tanpa model data yang jelas, operasi CRUD, pencarian, dan persistensi akan sulit ditulis dengan benar. Pada bagian ini, kita definisikan struct Product lengkap dengan field id, nama, harga, dan stok, lalu bungkus dengan fungsi product_create() yang memvalidasi field sebelum mengisi struct.

Konsep #

struct di C adalah kumpulan field yang ditempatkan berdampingan di memori. Kita pakai struct untuk merepresentasikan satu entitas domain (di sini: produk) sehingga data yang berhubungan tidak terpisah-pisah menjadi banyak variabel lepas.

Aturan yang kita pegang pada model Product:

  1. Field id positif dan unik; nilai 0 atau di atas PRODUCT_ID_MAX ditolak.
  2. Field name adalah string C klasik (null-terminated) dengan panjang maksimum PRODUCT_NAME_MAX - 1.
  3. Field price harus non-negatif dan bukan NaN/Infinity.
  4. Field stock tidak boleh negatif.
  5. Fungsi product_create() adalah satu-satunya pintu masuk untuk membangun Product valid. Kode lain tidak boleh mengisi field secara langsung tanpa validasi.

Mekanisme kerja di balik layar: saat product_create() dipanggil, fungsi memeriksa setiap argumen satu per satu. Bila ada yang gagal, fungsi mengembalikan kode error negatif tanpa mengubah struct (kontrak “all-or-nothing”). Bila semua validasi lulus, fungsi menyalin nama dengan strncpy dan selalu menulis terminator di akhir buffer untuk menjamin string tetap null-terminated walau input terpotong.

Implementasi #

Berikut definisi header lengkap untuk model Product:

c
/* product.h - Model data produk untuk sistem inventaris */
#ifndef PRODUCT_H
#define PRODUCT_H

#include <stdint.h>

#define PRODUCT_NAME_MAX  64
#define PRODUCT_ID_MAX    999999

typedef struct {
    int  id;
    char name[PRODUCT_NAME_MAX];
    double price;
    int  stock;
} Product;

int product_create(Product *p, int id, const char *name,
                   double price, int stock);
void product_print(const Product *p);
int product_is_valid(const Product *p);

#endif /* PRODUCT_H */

Implementasi product_create() melakukan validasi lalu pengisian field:

c
/* product.c - Implementasi model Product */
#include "product.h"
#include <stdio.h>
#include <string.h>
#include <math.h>

int product_is_valid(const Product *p) {
    if (p == NULL) {
        return 0;
    }
    if (p->id <= 0 || p->id > PRODUCT_ID_MAX) {
        return 0;
    }
    if (p->name[0] == '\0') {
        return 0;
    }
    if (p->price < 0.0 || isnan(p->price) || isinf(p->price)) {
        return 0;
    }
    if (p->stock < 0) {
        return 0;
    }
    return 1;
}

int product_create(Product *p, int id, const char *name,
                   double price, int stock) {
    if (p == NULL || name == NULL) {
        return -1;
    }
    if (id <= 0 || id > PRODUCT_ID_MAX) {
        return -4;
    }
    if (price < 0.0 || isnan(price) || isinf(price)) {
        return -2;
    }
    if (stock < 0) {
        return -3;
    }
    p->id = id;
    strncpy(p->name, name, PRODUCT_NAME_MAX - 1);
    p->name[PRODUCT_NAME_MAX - 1] = '\0';
    p->price = price;
    p->stock = stock;
    if (!product_is_valid(p)) {
        return -1;
    }
    return 0;
}

void product_print(const Product *p) {
    if (p == NULL) {
        printf("(null product)\n");
        return;
    }
    printf("[%06d] %-30s  Rp%10.2f  stok=%4d\n",
           p->id, p->name, p->price, p->stock);
}

Mari kita uji model ini dengan program kecil:

c
/* demo_product.c - Uji coba model Product */
#include <stdio.h>
#include "product.h"

int main(void) {
    Product p1;
    int rc = product_create(&p1, 1, "Beras 5kg", 65000.0, 10);
    printf("rc=%d, valid=%d\n", rc, product_is_valid(&p1));
    product_print(&p1);

    /* Contoh data invalid: id negatif */
    Product p2;
    rc = product_create(&p2, -5, "Gula", 15000.0, 5);
    printf("rc=%d (id negatif ditolak)\n", rc);

    /* Contoh data invalid: harga NaN */
    Product p3;
    rc = product_create(&p3, 2, "Teh", 0.0/0.0, 1);
    printf("rc=%d (NaN ditolak)\n", rc);
    return 0;
}

Output yang Dihasilkan:

text
rc=0, valid=1
[000001] Beras 5kg                        Rp  65000.00  stok=  10
rc=-4 (id negatif ditolak)
rc=-2 (NaN ditolak)

Mari kita telaah baris-baris kode di atas:

  1. Baris 13-17 (PRODUCT_NAME_MAX/PRODUCT_ID_MAX): Konstanta batas didefinisikan sebagai macro. Angka 64 dipilih agar produk dengan nama panjang seperti “Beras Premium Organik 5kg Gold Label” tetap muat tanpa terpotong.
  2. Baris 23-26 (product_is_valid): Fungsi ini melakukan double-check setelah pengisian. Memvalidasi isnan dan isinf penting karena strtod dapat menghasilkan NaN saat parsing input pengguna gagal.
  3. Baris 33-46 (product_create): Validasi dilakukan sebelum pengisian. Bila ada field invalid, fungsi langsung return tanpa menyentuh struct, menjaga kontrak “all-or-nothing”.
  4. Baris 47 (strncpy): Salin nama dengan batas PRODUCT_NAME_MAX - 1. Selalu tulis terminator di baris berikutnya agar string tetap null-terminated walau sumber lebih panjang dari buffer.
  5. Baris 65 (printf dengan %06d): Format %06d menghasilkan id dengan leading zero, misal 000001, sehingga output mudah dibaca sebagai tabel.

Kode Lengkap #

Kode lengkap untuk bagian ini ada pada berkas product.h dan product.c di direktori proyek. Kode tersebut menjadi fondasi untuk semua bagian berikutnya. Setiap kali kita menambah operasi baru (CRUD, pencarian, persistensi), kita akan mengoper objek Product lewat pointer const Product * agar jelas mana fungsi yang hanya membaca dan mana yang memodifikasi.

Pengujian #

Pengujian model Product dilakukan dengan dua jalur. Pertama, program demo_product.c di atas yang memverifikasi kasus sukses dan dua kasus gagal. Kedua, unit test formal pada bagian 7 akan memeriksa lebih banyak edge case: nama kosong, harga negatif, stok negatif, id di luar rentang.

Compile dan jalankan demo:

bash
gcc -std=c11 -Wall -Wextra -o /tmp/demo_product demo_product.c product.c -lm
/tmp/demo_product