Program hingga bagian ini bisa saja ditulis sebagai satu file main.c sepanjang 1000 baris. Tapi kode seperti itu sulit dipelihara: setiap perubahan di salah satu fungsi berisiko merusak yang lain, dan test menjadi berat karena seluruh kode harus di-compile bersama. Pada bagian ini, kita pecah program menjadi modul .h/.c yang terpisah dengan tanggung jawab tunggal.
Konsep #
Pemisahan modul di C mengikuti prinsip Single Responsibility. Setiap modul memiliki:
- Header file (
.h) — berisi deklarasi publik: tipe, konstanta, dan prototype fungsi yang dipakai modul lain. - Implementasi (
.c) — berisi definisi fungsi dan static helpers yang tidak keluar dari modul. - Header guard —
#ifndef PRODUCT_H/#define PRODUCT_H/#endifmencegah include ganda yang menyebabkan redefinition error.
Untuk pengelola inventaris, kita kelompokkan menjadi tiga modul:
| Modul | Tanggung Jawab |
|---|---|
product | Model data Product + validasi field |
inventory | Koleksi Inventory + operasi CRUD |
storage | Persistensi CSV (load/save) |
Plus main.c sebagai entry point dan tests.c sebagai unit test runner.
Implementasi #
Struktur file setelah pemisahan:
proyek-07-pengelola-inventaris/
├── product.h # Model Product
├── product.c # Implementasi model
├── inventory.h # Koleksi + CRUD
├── inventory.c # Implementasi koleksi
├── storage.h # Load/save CSV
├── storage.c # Implementasi storage
├── main.c # Entry point + menu interaktif
├── tests.c # Unit test runner
├── Makefile # Build automation
└── README.md # DokumentasiSetiap header punya header guard yang unik. Contoh product.h:
#ifndef PRODUCT_H
#define PRODUCT_H
#include <stdint.h>
/* ... deklarasi tipe dan fungsi ... */
#endif /* PRODUCT_H */Pattern #ifndef/#define/#endif bekerja sebagai berikut: pada include pertama, PRODUCT_H belum terdefinisi, jadi #ifndef lolos dan #define mendefinisikannya. Pada include kedua (mis. bila dua header berbeda sama-sama meng-include product.h), PRODUCT_H sudah ada, jadi seluruh blok di-skip.
Hubungan dependency antar modul:
main.c ──> inventory.h ──> product.h
│ │
│ └──> storage.h ──> inventory.h
│
└──> storage.hstorage.h butuh tipe Inventory (untuk load/save signature), jadi storage.h meng-include inventory.h yang pada gilirannya meng-include product.h. Tidak ada circular dependency.
Setiap file .c hanya meng-include header yang dibutuhkannya. product.c hanya meng-include product.h. inventory.c meng-include inventory.h (yang otomatis dapat product.h). storage.c meng-include storage.h (yang otomatis dapat inventory.h dan product.h). main.c meng-include inventory.h dan storage.h karena memanggil fungsi dari kedua modul itu.
Verifikasi struktur modular:
/* demo_modular.c - Buktikan setiap modul bisa dipakai terpisah */
#include <stdio.h>
#include "product.h" /* Bisa dipakai tanpa inventory.h */
int main(void) {
Product p;
int rc = product_create(&p, 1, "Standalone Product",
25000.0, 4);
printf("rc=%d\n", rc);
product_print(&p);
return 0;
}Build dengan satu modul saja:
gcc -std=c11 -Wall -Wextra -o /tmp/demo_modular \
demo_modular.c product.c -lm
/tmp/demo_modularOutput yang Dihasilkan:
rc=0
[000001] Standalone Product Rp 25000.00 stok= 4Mari kita telaah pola modular di atas:
- Header guard di setiap
.hmencegah redefinition bila header di-include lebih dari sekali dalam satu unit kompilasi. Tanpa guard, compiler error “redefinition of struct Product”. #include "product.h"(tanda kutip): tanda kutip memberi tahu preprocessor untuk mencari header di direktori yang sama dengan file.clebih dulu, baru kemudian ke path sistem.#include <stdio.h>(kurung siku) mencari header di path sistem (/usr/include/dan sejenisnya) tanpa mencari di direktori lokal.- Static helper di
.c: Fungsistaticseperticontains_ci()diinventory.ctidak terlihat dari modul lain. Ini menyembunyikan detail implementasi dan mencegah tabrakan nama dengan modul lain yang mungkin punya fungsi serupa. - Separation of concerns:
product.ctidak tahu tentangInventory,inventory.ctidak tahu tentang CSV,storage.ctidak tahu tentang menu interaktif. Setiap modul bisa diganti tanpa menyentuh yang lain, selama signature fungsi publik tetap sama. - Dependency direction: dependency mengalir satu arah.
storage.cbutuhinventory.h(untuk tipeInventory), tetapiinventory.ctidak tahu tentangstorage.h. Bila kelak kita mengganti format CSV ke JSON, hanyastorage.cyang berubah.
Kode Lengkap #
Kode lengkap modul-modul terpisah ada pada direktori proyek: product.h/.c, inventory.h/.c, storage.h/.c, main.c, tests.c. Setiap file memiliki tanggung jawab tunggal dan dokumentasi singkat di komentar header.
Pengujian #
Verifikasi pemisahan modul dengan dua cara:
- Compile parsial — pastikan setiap modul bisa di-compile sendiri (tidak ada include yang missing). Contoh:
gcc -std=c11 -Wall -Wextra -c product.charus menghasilkanproduct.otanpa error. - Link multi-file — bangun executable akhir dengan menyertakan semua file
.cyang relevan. Pastikan tidak adamultiple definitionerror.
# Compile parsial (object files)
gcc -std=c11 -Wall -Wextra -c product.c -o /tmp/product.o
gcc -std=c11 -Wall -Wextra -c inventory.c -o /tmp/inventory.o
gcc -std=c11 -Wall -Wextra -c storage.c -o /tmp/storage.o
gcc -std=c11 -Wall -Wextra -c main.c -o /tmp/main.o
# Link bersama-sama
gcc /tmp/main.o /tmp/inventory.o /tmp/product.o /tmp/storage.o \
-o /tmp/inventory_app -lm
/tmp/inventory_app