Inilah bagian yang menjadi alasan seluruh proyek ini ada: meminjam dan mengembalikan buku. Operasi pinjam tampak sederhana — turunkan stok, catat peminjaman — tetapi justru karena sederhananya itu, mudah ditulis dengan salah. Sebuah pinjaman hanya boleh terjadi bila bukunya ada, anggotanya terdaftar, anggota belum meminjam judul yang sama, jumlah pinjaman aktifnya belum menyentuh batas, dan stok masih tersisa. Kelima syarat itu kita susun sebagai gerbang berurutan, dan perubahan state hanya terjadi setelah semuanya lolos. Sebaliknya, kembalikan membalik arahnya: mengubah status catatan lalu memulihkan stok tepat satu. Setelah bagian ini, semua invariant dari Bagian 1 yang menyebut Bagian 3 akhirnya punya penjaganya.
Lima Gerbang dalam method pinjam #
Method pinjam mengembalikan std::expected<void, std::string> — kosong berarti sukses, sedangkan kegagalan berupa pesan yang siap ditampilkan:
std::expected<void, std::string> pinjam(const std::string& isbn, int idAnggota) {
const Buku* buku = cariBuku(isbn);
if (buku == nullptr) {
return std::unexpected("buku dengan ISBN " + isbn + " tidak ditemukan");
}
const Anggota* anggota = cariAnggota(idAnggota);
if (anggota == nullptr) {
return std::unexpected("anggota dengan id " + std::to_string(idAnggota)
+ " tidak terdaftar");
}
if (temukanPeminjamanAktif(isbn, idAnggota) != nullptr) {
return std::unexpected(anggota->nama + " masih meminjam buku yang sama");
}
if (hitungPinjamanAktif(idAnggota) >= BATAS_PINJAM) {
return std::unexpected(anggota->nama + " sudah mencapai batas "
+ std::to_string(BATAS_PINJAM) + " buku");
}
if (buku->stok <= 0) {
return std::unexpected("stok \"" + buku->judul + "\" habis");
}
// Semua aturan lolos — baru state diubah.
Buku* bukuMut = temukanBuku(isbn);
--bukuMut->stok;
daftarPeminjaman_.push_back(
Peminjaman{isbn, idAnggota, StatusPeminjaman::Dipinjam});
return {};
}Mari kita bedah gerbang demi gerbang:
cariBuku(isbn)— buku harus ada dulu; tanpa ISBN yang sah, sisa pemeriksaan tidak bermakna. Pointerconstyang dikembalikan dipakai untuk membaca judul di pesan-pesan berikutnya.cariAnggota(idAnggota)— peminjam harus terdaftar. Pesan galatnya membawastd::to_string(idAnggota)agar pengguna tahu id mana yang tidak dikenali.temukanPeminjamanAktif(isbn, idAnggota)— satu pasangan (ISBN, anggota) tidak boleh punya dua peminjaman aktif. Tanpa gerbang ini, anggota yang sama bisa mengosongkan stok judul favoritnya sendiri lewat peminjaman berulang.hitungPinjamanAktif(idAnggota) >= BATAS_PINJAM— batas tiga buku dihitung dari jumlah catatan berstatusDipinjam, bukan disimpan sebagai counter terpisah; tidak ada angka kedua yang bisa berbeda dari catatan sebenarnya.buku->stok <= 0— pemeriksaan terakhir sebelum mutasi. Hanya setelah kelima gerbang lolos,--bukuMut->stokdanpush_backdijalankan sebagai satu blok yang tidak bisa terpisah.
Perhatikan bahwa tidak ada jalur yang mengubah sebagian state lalu gagal: setiap return std::unexpected terjadi sebelum satu byte pun dari state disentuh. Konsekuensinya, invariant “stok tidak pernah negatif” tidak ditegakkan dengan memeriksa sesudah mengurangi, melainkan dengan menjamin pengurangan hanya terjadi bila stok sudah terbukti lebih besar dari nol.
Urutan Gerbang Menentukan Pesan Galat #
Lima gerbang itu tidak berurutan sembarangan. Buku dan anggota diperiksa lebih dulu karena keduanya menjadi bahan pesan galat untuk gerbang berikutnya — pesan “Budi masih meminjam buku yang sama” hanya bisa disusun setelah anggota ditemukan. Gerbang dobel-pinjam mendahului gerbang batas, dan gerbang batas mendahului gerbang stok, sehingga untuk setiap masukan ada tepat satu pesan yang tercetak: ketika Budi dengan tiga pinjaman aktif mencoba buku yang stoknya nol, yang dilaporkan adalah batas, bukan stok — konsisten untuk setiap pemanggilan, dan karena itu bisa dikunci oleh test di Bagian 5.
Mengembalikan Buku dan Memulihkan Stok #
Pengembalian bekerja pada catatan yang sudah ada, bukan membuat catatan baru:
std::expected<void, std::string> kembalikan(const std::string& isbn, int idAnggota) {
Peminjaman* catatan = temukanPeminjamanAktif(isbn, idAnggota);
if (catatan == nullptr) {
return std::unexpected("tidak ada peminjaman aktif untuk ISBN " + isbn
+ " oleh anggota id " + std::to_string(idAnggota));
}
catatan->status = StatusPeminjaman::Kembali;
if (Buku* buku = temukanBuku(isbn)) {
++buku->stok;
}
return {};
}temukanPeminjamanAktif versi dua-argumen mencari catatan yang cocok dengan ISBN dan id anggota berstatus Dipinjam. Tidak ada catatan berarti tidak ada yang bisa dikembalikan — pesan galatnya menunjuk kedua kunci agar pengguna tahu persis kombinasi mana yang tidak ditemukan. Bila catatan ada, statusnya berubah menjadi Kembali dan stok buku ditambah satu. Catatan tidak dihapus: riwayat peminjaman tetap hidup di daftarPeminjaman_, dan hitungPinjamanAktif otomatis berhenti menghitungnya karena statusnya sudah berganti. Inilah buah dari keputusan Bagian 1 — status berupa enum, bukan keberadaan elemen — yang kini terbayar tanpa satu baris kode tambahan.
Menampilkan Peminjaman Aktif #
Untuk LAPOR, class menyediakan tampilan khusus yang menerjemahkan catatan menjadi kalimat yang bisa dibaca manusia:
void tampilkanPeminjamanAktif() const {
const std::vector<const Peminjaman*> aktif = peminjamanAktif();
std::cout << "Peminjaman aktif: " << aktif.size() << '\n';
for (const Peminjaman* p : aktif) {
const Buku* buku = cariBuku(p->isbn);
const Anggota* anggota = cariAnggota(p->idAnggota);
std::cout << " \"" << (buku ? buku->judul : p->isbn)
<< "\" dipinjam oleh " << (anggota ? anggota->nama : "(tidak dikenal)")
<< '\n';
}
}Catatan peminjaman hanya menyimpan kunci — ISBN dan id — sehingga tampilan harus mencari judul dan nama saat mencetak. Ekspresi ternary buku ? buku->judul : p->isbn adalah penjaga keamanan terakhir: bila data tidak konsisten (misalnya hasil memuat berkas lama), tampilan memakai kunci mentah daripada menghentikan program. Method ini const, dan seharusnya begitu: menampilkan state tidak boleh mengubahnya.
Sesi Lengkap Alur Peminjaman #
Kompilasi ulang, lalu jalankan skenario yang memicu setiap gerbang, pengembalian yang gagal, dan pengembalian yang sukses:
g++ -std=c++23 -Wall -Wextra perpustakaan.cpp -o perpustakaan
printf 'BUKU|978-6021|Pemrograman C++ Modern|Budi Santoso|2\nBUKU|978-6022|Algoritma dan Struktur Data|Siti Rahmawati|1\nBUKU|978-6023|Clean Code|Dewi Lestari|3\nBUKU|978-6024|Struktur Data Dasar|Budi Santoso|1\nANGGOTA|1|Budi\nANGGOTA|2|Siti\nPINJAM|978-6025|1\nPINJAM|978-6021|9\nPINJAM|978-6021|1\nPINJAM|978-6021|1\nPINJAM|978-6022|2\nPINJAM|978-6022|1\nPINJAM|978-6023|1\nPINJAM|978-6024|1\nPINJAM|978-6022|1\nKEMBALI|978-6022|1\nKEMBALI|978-6021|1\nPINJAM|978-6021|2\nLAPOR\n' | ./perpustakaanOutput yang Dihasilkan:
ok: buku "Pemrograman C++ Modern" masuk koleksi
ok: buku "Algoritma dan Struktur Data" masuk koleksi
ok: buku "Clean Code" masuk koleksi
ok: buku "Struktur Data Dasar" masuk koleksi
ok: anggota Budi (id 1) terdaftar
ok: anggota Siti (id 2) terdaftar
gagal: buku dengan ISBN 978-6025 tidak ditemukan
gagal: anggota dengan id 9 tidak terdaftar
ok: Budi meminjam "Pemrograman C++ Modern"
gagal: Budi masih meminjam buku yang sama
ok: Siti meminjam "Algoritma dan Struktur Data"
gagal: stok "Algoritma dan Struktur Data" habis
ok: Budi meminjam "Clean Code"
ok: Budi meminjam "Struktur Data Dasar"
gagal: Budi sudah mencapai batas 3 buku
gagal: tidak ada peminjaman aktif untuk ISBN 978-6022 oleh anggota id 1
ok: Budi mengembalikan "Pemrograman C++ Modern"
ok: Siti meminjam "Pemrograman C++ Modern"
ISBN Judul Penulis Stok
----------------------------------------------------------------------
978-6021 Pemrograman C++ Modern Budi Santoso 1
978-6022 Algoritma dan Struktur Data Siti Rahmawati 0
978-6023 Clean Code Dewi Lestari 2
978-6024 Struktur Data Dasar Budi Santoso 0
Peminjaman aktif: 4
"Algoritma dan Struktur Data" dipinjam oleh Siti
"Clean Code" dipinjam oleh Budi
"Struktur Data Dasar" dipinjam oleh Budi
"Pemrograman C++ Modern" dipinjam oleh SitiBaca keluarannya berdampingan dengan skenarionya. Dua pinjaman pertama tertahan di gerbang keberadaan — ISBN tidak dikenal, id 9 tidak terdaftar. Peminjaman 978-6021 oleh Budi berhasil sekali, lalu ditolak pada percobaan kedua karena gerbang dobel-pinjam. Stok 978-6022 yang hanya satu eksemplar habis oleh Siti, dan percobaan Budi atas buku yang sama dilaporkan sebagai stok habis — pada saat itu Budi baru memegang satu buku, jadi gerbang batas belum bersuara. Dua pinjaman berikutnya membawa Budi ke batas tiga, dan percobaan keempatnya ditolak dengan pesan batas. Setelah Budi mengembalikan 978-6021, stok judul itu naik kembali dan Siti berhasil meminjamnya — siklus penuh pinjam-kembali-pinjam lagi dalam satu sesi. LAPOR di akhir memverifikasi semuanya: empat baris tabel dengan stok 1, 0, 2, 0, dan empat peminjaman aktif yang rinciannya cocok dengan sejarah skenario.
Alur peminjaman kini lengkap dan setiap invariant-nya dijaga, tetapi seluruh data masih hidup di memori — menutup terminal berarti kehilangan semuanya. Bagian 4 memberi sistem ini ingatan jangka panjang: menyimpan seluruh state ke berkas teks dan memuatnya kembali, bahkan oleh proses yang sama sekali baru.
Rujukan resmi: std::expected (cppreference) , std::to_string (cppreference) .