Bab 19 — Kualitas Kode: Rapi, Tercatat, dan Terdokumentasi
Seri I1 — Kode yang Rapi dan Terawat
Semua skrip bab ini ada di folder kode/bab19/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).
Kode lebih sering dibaca daripada ditulis: oleh rekan kerja, dan oleh Anda sendiri enam bulan lagi. Bayangkan buku catatan lapangan. Bila tiap petugas menulis dengan tata letak sendiri-sendiri, mencari satu angka jadi menyiksa. Kode yang seragam dan rapi mudah dibaca, dan kesalahan bodoh lebih cepat ketahuan. Bab 18 menjaga agar kode benar; bab ini menjaga agar kode rapi, tercatat, dan bisa dipahami.

PEP 8: tata bahasa kode Python
PEP 8 adalah panduan gaya resmi Python (PEP = Python Enhancement Proposal, usulan peningkatan Python). Ringkasannya, dari dokumen resminya (peps.python.org/pep-0008, dibaca 2026-10-10):
| Hal | Aturan PEP 8 |
|---|---|
| Menjorok | 4 spasi per tingkat |
| Panjang baris | maksimal 79 karakter (docstring dan komentar 72) |
| Nama fungsi dan variabel | huruf kecil dengan garis bawah: hitung_biomassa |
| Nama kelas | tiap kata berawalan huruf besar (CapWords): KolamIkan |
| Konstanta | huruf besar semua: BATAS_PH |
| Impor | di atas berkas, satu per baris; kelompokkan: pustaka standar, paket luar, kode sendiri |
| Spasi | satu spasi di sekitar =, ==, and; dua baris kosong antar fungsi dan kelas |
Hafalkan prinsipnya, bukan angkanya: konsisten dan mudah dibaca. Untungnya, Anda tidak perlu menghafal. Ada alat yang memeriksanya.
ruff: pemeriksa sekaligus perapi
ruff adalah alat Python (dibuat Astral, pembuat uv) yang menggabungkan dua tugas: linter (pemeriksa yang mencari kesalahan dan kebiasaan buruk) dan formatter (perapi yang menata tampilan). Ia sudah terpasang di environment buku ini; di komputer Anda: python -m pip install ruff. Mari beri makan ruff berkas yang sengaja dibuat berantakan:
01_ruff_periksa.py
# Bab 19 - ruff check: mencari masalah di kode (linter)
from bantu import FOLDER, ruff
contoh = FOLDER / "contoh"
print((contoh / "berantakan.py").read_text(encoding="utf-8"))
print("--- ruff check ---")
teks, kode = ruff("check", "--output-format", "concise", "berantakan.py", folder=contoh)
print(teks)
print("kode keluar:", kode)import os, sys
import json
def hitung_biomassa(jumlah,bobot_g,riwayat=[]):
l = jumlah*bobot_g/1000
tidak_dipakai = 5
if jumlah == True:
print( "jumlah aneh" )
riwayat.append(l)
print("Biomassa: %s kg" % l)
return l
class kolam_ikan:
def __init__(self,nama): self.nama=nama
--- ruff check ---
berantakan.py:1:1: E401 [*] Multiple imports on one line
berantakan.py:1:1: I001 [*] Import block is un-sorted or un-formatted
berantakan.py:1:8: F401 [*] `os` imported but unused
berantakan.py:1:12: F401 [*] `sys` imported but unused
berantakan.py:2:8: F401 [*] `json` imported but unused
berantakan.py:4:44: B006 Do not use mutable data structures for argument defaults
berantakan.py:5:5: E741 Ambiguous variable name: `l`
berantakan.py:6:5: F841 Local variable `tidak_dipakai` is assigned to but never used
berantakan.py:7:8: E712 Avoid equality comparisons to `True`; use `jumlah:` for truth checks
berantakan.py:10:11: UP031 Use format specifiers instead of percent format
berantakan.py:13:7: N801 Class name `kolam_ikan` should use CapWords convention
Found 11 errors.
[*] 5 fixable with `--fix` (4 unsafe fixes available with `--fix --unsafe-fixes`).
kode keluar: 1
Setiap baris laporan berbentuk berkas:baris:kolom: KODE pesan. Beberapa yang muncul:
| Kode | Arti |
|---|---|
E401 |
beberapa impor dalam satu baris (import os, sys) |
I001 |
urutan impor belum rapi |
F401 |
import yang tidak pernah dipakai |
F841 |
variabel diisi tetapi tidak pernah dipakai |
E712 |
membandingkan dengan True memakai == |
E741 |
nama yang membingungkan: huruf l mirip angka 1 |
B006 |
nilai bawaan parameter berupa list kosong (jebakan: list-nya dipakai bersama oleh semua pemanggilan) |
UP031 |
format teks gaya lama dengan %; gaya baru f-string |
N801 |
nama kelas kolam_ikan seharusnya KolamIkan |
Tanda [*] berarti ruff bisa memperbaikinya sendiri. Kode keluar 1 menandakan ada masalah, jadi cocok dipakai di skrip otomatis.
Aturan mana yang diperiksa ditentukan di pyproject.toml. Di proyek kita: select = ["E", "F", "I", "UP", "B", "N"] (kelompok aturan pycodestyle, pyflakes, impor, pembaruan sintaks, kesalahan umum, dan penamaan) dengan line-length = 88. Bawaan ruff memeriksa banyak aturan, dan himpunannya berubah antarversi, jadi menuliskannya sendiri membuat hasil stabil.
ruff format: merapikan tampilan
Sekarang bagian perapi. Opsi --diff memperlihatkan perubahan yang akan dibuat tanpa mengubah berkas:
02_ruff_format_diff.py
# Bab 19 - ruff format --diff: melihat apa yang akan dirapikan, tanpa mengubah berkas
from bantu import FOLDER, ruff
teks, kode = ruff("format", "--diff", "berantakan.py", folder=FOLDER / "contoh")
print(teks)
print("kode keluar:", kode)--- berantakan.py
+++ berantakan.py
@@ -1,14 +1,17 @@
import os, sys
import json
-def hitung_biomassa(jumlah,bobot_g,riwayat=[]):
- l = jumlah*bobot_g/1000
+
+def hitung_biomassa(jumlah, bobot_g, riwayat=[]):
+ l = jumlah * bobot_g / 1000
tidak_dipakai = 5
if jumlah == True:
- print( "jumlah aneh" )
+ print("jumlah aneh")
riwayat.append(l)
print("Biomassa: %s kg" % l)
return l
+
class kolam_ikan:
- def __init__(self,nama): self.nama=nama
+ def __init__(self, nama):
+ self.nama = nama
1 file would be reformatted
kode keluar: 1
Tanda - adalah baris lama, + baris baru. Perubahannya murni tampilan: spasi setelah koma, dua baris kosong antar fungsi, isi __init__ dipindah ke baris sendiri. Artinya program tidak berubah, hanya cara menulisnya. Kode keluar 1 di sini berarti “ada yang akan diubah”.
ruff check –fix: perbaikan otomatis
03_ruff_perbaiki_otomatis.py
# Bab 19 - ruff check --fix lalu ruff format, pada salinan berkas
import shutil
from bantu import FOLDER, ruff
kerja = FOLDER / "_tmp" / "perbaiki"
shutil.rmtree(kerja, ignore_errors=True)
shutil.copytree(FOLDER / "contoh", kerja)
teks, _ = ruff("check", "--fix", "--output-format", "concise", "berantakan.py", folder=kerja)
print(teks)
print("--- ruff format ---")
teks, _ = ruff("format", "berantakan.py", folder=kerja)
print(teks)
print("--- berkas setelah dirapikan ---")
print((kerja / "berantakan.py").read_text(encoding="utf-8"))berantakan.py:2:44: B006 Do not use mutable data structures for argument defaults
berantakan.py:3:5: E741 Ambiguous variable name: `l`
berantakan.py:4:5: F841 Local variable `tidak_dipakai` is assigned to but never used
berantakan.py:5:8: E712 Avoid equality comparisons to `True`; use `jumlah:` for truth checks
berantakan.py:8:11: UP031 Use format specifiers instead of percent format
berantakan.py:11:7: N801 Class name `kolam_ikan` should use CapWords convention
Found 10 errors (4 fixed, 6 remaining).
4 unsafe fixes available with `--fix --unsafe-fixes`.
--- ruff format ---
1 file reformatted
--- berkas setelah dirapikan ---
def hitung_biomassa(jumlah, bobot_g, riwayat=[]):
l = jumlah * bobot_g / 1000
tidak_dipakai = 5
if jumlah == True:
print("jumlah aneh")
riwayat.append(l)
print("Biomassa: %s kg" % l)
return l
class kolam_ikan:
def __init__(self, nama):
self.nama = nama
Perhatikan apa yang terjadi. --fix hanya menjalankan perbaikan aman: impor yang tak terpakai dibuang, dan urutan impor dirapikan (pada berkas hasil, baris impor di atas hilang). Enam masalah tersisa, dan ruff mencatat bahwa ada empat perbaikan “tidak aman” (unsafe fixes) yang hanya dijalankan bila Anda memintanya dengan --unsafe-fixes. Alasannya: ubah riwayat=[] atau == True akan mengubah perilaku program, dan keputusan itu ada di tangan Anda, bukan alat. Lalu ruff format merapikan tampilan.
Sisanya diperbaiki dengan tangan. Berikut hasil akhirnya, diperiksa ulang:
04_ruff_hasil_tangan.py
# Bab 19 - Sisa masalah diperbaiki dengan tangan (rapi.py), lalu diperiksa ulang
from bantu import FOLDER, ruff
contoh = FOLDER / "contoh"
print((contoh / "rapi.py").read_text(encoding="utf-8"))
print("--- ruff check ---")
print(ruff("check", "rapi.py", folder=contoh)[0])
print("--- ruff format --check ---")
print(ruff("format", "--check", "rapi.py", folder=contoh)[0])def hitung_biomassa(jumlah, bobot_g, riwayat=None):
"""Menghitung biomassa ikan (kg) dan mencatatnya di riwayat."""
if riwayat is None:
riwayat = []
biomassa_kg = jumlah * bobot_g / 1000
if jumlah <= 0:
print("jumlah tidak masuk akal")
riwayat.append(biomassa_kg)
print(f"Biomassa: {biomassa_kg} kg")
return biomassa_kg
class KolamIkan:
def __init__(self, nama):
self.nama = nama
--- ruff check ---
All checks passed!
--- ruff format --check ---
1 file already formatted
Apa yang kita putuskan sendiri: riwayat=None (list dibuat baru di dalam fungsi), l menjadi biomassa_kg (nama yang bercerita), variabel tak terpakai dihapus, == True diganti pemeriksaan yang masuk akal (jumlah <= 0; maksud kode aslinya memang tidak jelas), % menjadi f-string, dan kolam_ikan menjadi KolamIkan. Semua pemeriksaan kini lulus (“All checks passed!”).
Biasakan dua perintah ini sebelum menyimpan pekerjaan: ruff check --fix . lalu ruff format ..
pre-commit: penjaga pintu otomatis
Anda pasti suatu saat lupa menjalankan ruff. pre-commit adalah alat yang memasang “penjaga” di git: setiap kali Anda git commit (menyimpan perubahan; dibahas di Bab 20), serangkaian pemeriksaan berjalan otomatis. Bila ada yang gagal, commit dibatalkan. Pasang dengan python -m pip install pre-commit.
Pengaturannya di berkas .pre-commit-config.yaml. Pada dokumentasi resminya (pre-commit.com, dibaca 2026-10-10), kebanyakan contoh mengambil hook dari repo di internet (repo: https://... dengan rev:). Agar skrip kita tidak bergantung pada internet, di sini dipakai hook lokal (repo: local) yang menjalankan ruff yang sudah ada di komputer. Skrip berikut membuat repo git sementara di _tmp/, memasang penjaga, lalu mencoba dua commit.
07_pre_commit.py
# Bab 19 - pre-commit: pemeriksaan otomatis setiap kali git commit (di repo sementara)
import os
import shutil
import subprocess
import sys
from pathlib import Path
from bantu import FOLDER, hapus_folder
repo = FOLDER / "_tmp" / "repo_pre_commit"
hapus_folder(repo)
repo.mkdir(parents=True)
config_kosong = repo.parent / "gitconfig_kosong"
config_kosong.write_text("", encoding="utf-8")
# Git sementara: tidak membaca konfigurasi git pengguna; identitas lewat variabel lingkungan
folder_python = str(Path(sys.executable).parent)
env = dict(
os.environ,
GIT_CONFIG_GLOBAL=str(config_kosong), GIT_CONFIG_NOSYSTEM="1",
GIT_AUTHOR_NAME="Uji", GIT_AUTHOR_EMAIL="uji@contoh.invalid",
GIT_COMMITTER_NAME="Uji", GIT_COMMITTER_EMAIL="uji@contoh.invalid",
PATH=folder_python + os.pathsep + os.environ["PATH"], # supaya perintah 'ruff' ditemukan
PYTHONUTF8="1", NO_COLOR="1",
)
def jalan(*perintah):
hasil = subprocess.run(perintah, cwd=repo, env=env, text=True, encoding="utf-8",
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return hasil.stdout.strip(), hasil.returncode
(repo / ".pre-commit-config.yaml").write_text("""\
repos:
- repo: local
hooks:
- id: ruff-periksa
name: ruff periksa
entry: ruff check
language: unsupported
types: [python]
- id: ruff-format
name: ruff format
entry: ruff format --check
language: unsupported
types: [python]
""", encoding="utf-8")
shutil.copy(FOLDER / "contoh" / "berantakan.py", repo / "berantakan.py")
shutil.copy(FOLDER / "contoh" / "pyproject.toml", repo / "pyproject.toml")
jalan("git", "init", "--quiet", "-b", "main")
print(jalan(sys.executable, "-m", "pre_commit", "install")[0].replace(str(repo), "<repo>"))
jalan("git", "add", ".")
teks, kode = jalan("git", "commit", "-m", "tambah berantakan.py")
print("--- commit pertama (kode berantakan) ---")
print("\n".join(b for b in teks.splitlines() if b.startswith(("ruff", "- hook id", "- exit code"))))
print("commit berhasil?", kode == 0)
shutil.copy(FOLDER / "contoh" / "rapi.py", repo / "berantakan.py")
jalan("git", "add", ".")
teks, kode = jalan("git", "commit", "-m", "perbaiki berantakan.py")
print("--- commit kedua (kode sudah rapi) ---")
print("\n".join(b for b in teks.splitlines() if b.startswith("ruff")))
print("commit berhasil?", kode == 0)
print("--- riwayat ---")
print(jalan("git", "log", "--format=%s")[0])pre-commit installed at .git\hooks\pre-commit
--- commit pertama (kode berantakan) ---
ruff periksa.............................................................Failed
- hook id: ruff-periksa
- exit code: 1
ruff format..............................................................Failed
- hook id: ruff-format
- exit code: 1
commit berhasil? False
--- commit kedua (kode sudah rapi) ---
ruff periksa.............................................................Passed
ruff format..............................................................Passed
commit berhasil? True
--- riwayat ---
perbaiki berantakan.py
Jalan ceritanya:
pre-commit installmenaruh skrip penjaga di.git/hooks/pre-commit.- Commit pertama (kode berantakan): kedua hook menjawab
Failed, dan commit tidak terjadi (False). - Berkas diganti dengan versi rapi, lalu commit kedua: keduanya
Passed, commit berhasil, dan riwayat git berisi satu entri.
Dokumentasi pre-commit mencatat bahwa nilai language: system kini hanya alias bagi unsupported (sejak versi 4.4.0) dan akan dihapus; konfigurasi baru sebaiknya memakai unsupported, seperti di skrip ini. Untuk pre-commit versi lebih lama, ganti ke system. Repo sementara ini memakai git yang dipisahkan dari konfigurasi git Anda (lihat Bab 20).
Logging: catatan harian program
print() cocok untuk percobaan, tetapi di program sungguhan Anda butuh yang lebih teratur: logging, modul bawaan untuk mencatat peristiwa dengan tingkat kepentingan. Menurut dokumentasi Python (HOWTO Logging, dibaca 2026-10-10):
| Tingkat | Nilai | Pemakaian |
|---|---|---|
DEBUG |
10 | detail teknis untuk mencari bug |
INFO |
20 | penegasan bahwa semuanya berjalan wajar |
WARNING |
30 | sesuatu yang tak terduga, program masih jalan |
ERROR |
40 | ada fungsi yang gagal dikerjakan |
CRITICAL |
50 | kesalahan serius |
Pencatat hanya meneruskan pesan yang tingkatnya sama atau lebih tinggi dari tingkat yang berlaku. Tingkat bawaan pencatat induk (root) adalah WARNING, sehingga tanpa pengaturan INFO dan DEBUG tidak tampil.
05_logging_dasar.py
# Bab 19 - logging: catatan harian program dengan tingkat kepentingan
import logging
import sys
logging.basicConfig(
level=logging.INFO,
format="%(levelname)-8s | %(name)s | %(message)s",
stream=sys.stdout,
)
catat = logging.getLogger("kolam")
catat.debug("detail teknis (tidak tampil pada level INFO)")
catat.info("kolam A: 800 ikan ditebar")
catat.warning("pH 5.9 di bawah batas 6.5")
catat.error("sensor suhu tidak menjawab")
try:
rata_rata = 540.5 / 0
except ZeroDivisionError:
catat.exception("hitungan rata-rata gagal") # sekaligus mencetak traceback
print("level yang berlaku:", logging.getLevelName(catat.getEffectiveLevel()))INFO | kolam | kolam A: 800 ikan ditebar
WARNING | kolam | pH 5.9 di bawah batas 6.5
ERROR | kolam | sensor suhu tidak menjawab
ERROR | kolam | hitungan rata-rata gagal
Traceback (most recent call last):
File "05_logging_dasar.py", line 18, in <module>
rata_rata = 540.5 / 0
~~~~~~^~~
ZeroDivisionError: division by zero
level yang berlaku: INFO
basicConfig(level=..., format=..., stream=...)mengatur tingkat dan bentuk catatan. Bentuk%(levelname)-8sartinya nama tingkat, rata kiri, selebar 8 huruf.- Pesan
DEBUGtidak tampil karena tingkat diaturINFO. Tiga lainnya tampil. catat.exception(...)dipanggil di dalamexcept: ia mencatat pesan pada tingkatERRORsekaligus traceback error-nya. Inilah penggantiprintyang paling berguna.getEffectiveLevel()menunjukkan tingkat yang berlaku:INFO.
Logging di paket inventaris
Dokumentasi Python menyarankan tiap modul memakai logger = logging.getLogger(__name__). Nama pencatat lalu mengikuti hierarki paket (inventaris.unit adalah anak inventaris), dan pesan naik ke pencatat induknya. Dokumentasi juga menyarankan pustaka tidak memasang pengatur keluaran sendiri: itu urusan aplikasi pemakai. Itulah yang kita ikuti: paket inventaris hanya mencatat (logger.debug di Unit.tambah, logger.info di simpan_ringkasan), dan skrip memutuskan ke mana catatan pergi.
06_logging_proyek.py
# Bab 19 - logging di paket inventaris: layar menampilkan INFO, berkas menyimpan DEBUG
import logging
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "proyek"))
from inventaris.contoh import buat_contoh # noqa: E402
from inventaris.laporan import simpan_ringkasan # noqa: E402
kerja = Path(__file__).parent / "_tmp"
kerja.mkdir(exist_ok=True)
berkas_log = kerja / "inventaris.log"
berkas_log.unlink(missing_ok=True)
format_log = "%(levelname)s %(name)s: %(message)s"
layar = logging.StreamHandler(sys.stdout)
layar.setLevel(logging.INFO)
berkas = logging.FileHandler(berkas_log, encoding="utf-8")
berkas.setLevel(logging.DEBUG)
for pencatat in (layar, berkas):
pencatat.setFormatter(logging.Formatter(format_log))
akar = logging.getLogger("inventaris") # induk dari inventaris.unit dan inventaris.laporan
akar.setLevel(logging.DEBUG)
akar.addHandler(layar)
akar.addHandler(berkas)
unit = buat_contoh()
simpan_ringkasan(unit, kerja / "ringkasan.txt")
berkas.close()
isi = berkas_log.read_text(encoding="utf-8").splitlines()
print(f"Isi berkas log: {len(isi)} baris")
print("Baris pertama :", isi[0])
print("Baris terakhir:", isi[-1])INFO inventaris.laporan: menulis 5 ringkasan ke ringkasan.txt
Isi berkas log: 11 baris
Baris pertama : DEBUG inventaris.unit: Plot hutan A menerima Pohon
Baris terakhir: INFO inventaris.laporan: menulis 5 ringkasan ke ringkasan.txt
Dua penampung (handler) dipasang pada pencatat inventaris: layar menerima tingkat INFO ke atas, dan berkas menerima semuanya sampai DEBUG. Hasilnya: layar hanya menampilkan satu baris (menulis 5 ringkasan ...), sedangkan berkas log berisi 11 baris: 10 catatan DEBUG (tiap kali tambah dipanggil: 3 pohon + 2 + 1 + 1 + 3 = 10) ditambah 1 catatan INFO.
Debugging: mencari bug dengan teliti
Debugging adalah menjalankan program sambil berhenti di titik tertentu untuk melihat isi variabel. Cara paling dasar sudah ada di Python: fungsi bawaan breakpoint() menghentikan program dan membuka debugger pdb di terminal.
10_debug_contoh.py
# Bab 19 - Tempat memasang titik henti (breakpoint) saat mencari bug
def biomassa_kg(jumlah, bobot_g):
total_g = jumlah * bobot_g
breakpoint() # program berhenti di sini; ketik "p total_g" lalu "c" untuk lanjut
return total_g / 1000
print(biomassa_kg(800, 540.5))Kode ini tidak dijalankan saat buku dibuat karena breakpoint() menghentikan program dan menunggu perintah dari pengguna, sehingga tidak cocok untuk build otomatis. Penulis mencobanya sekali dengan masukan p total_g lalu c: program berhenti di baris breakpoint(), p total_g mencetak 432400.0 (gram), dan c (continue) melanjutkan hingga mencetak 432.4.
Di VS Code, cara yang lebih nyaman (deskripsi dari dokumentasi resmi VS Code, dibaca 2026-10-10; tidak dijalankan oleh penulis):
- Pasang ekstensi Python (ekstensi Python Debugger ikut terpasang bersamanya).
- Klik di margin kiri editor untuk memasang breakpoint (titik henti). Breakpoint bisa diberi syarat atau hitungan, misalnya hanya berhenti setelah lima kali.
- Tekan F5 (Start Debugging) atau pilih Debug Python File. Program berhenti di breakpoint, dan Anda bisa memeriksa variabel.
- Pengaturan lebih rinci disimpan di
.vscode/launch.json(nama,programyang dijalankan,args, dan lain-lain).
Di notebook Jupyter dalam VS Code (juga dari dokumentasi resmi, tidak dijalankan): tersedia Run by Line untuk menjalankan sel satu pernyataan demi satu pernyataan, dan Debug Cell untuk debugger penuh dengan breakpoint. Keduanya membutuhkan ipykernel versi 6 ke atas.
Debugger menjawab pertanyaan “mengapa nilainya begini di titik ini?”. Jangan memakainya sebagai pengganti tes. Tes (Bab 18) mencegah bug yang sama muncul lagi; debugger hanya membantu menemukannya sekali.
Dokumentasi proyek: MkDocs dan Sphinx
Kode tanpa petunjuk pemakaian cepat dilupakan. Dua alat populer mengubah berkas teks menjadi situs dokumentasi:
- MkDocs (
mkdocs.org): menulis halaman dalam Markdown, lalumkdocs buildmenghasilkan situs HTML statis di foldersite/.mkdocs servemembuka pratinjau lokal. Satu-satunya pengaturan wajib dimkdocs.ymladalahsite_name;navmengatur urutan menu (dokumentasi resmi, dibaca 2026-10-10). - Sphinx (
sphinx-doc.org): alat yang lebih tua dan lebih kaya, memakai reStructuredText.sphinx-quickstartmembuat kerangka (conf.py,index.rst),make htmlmembangun situs, dan ekstensi autodoc menarik dokumentasi langsung dari docstring (dokumentasi resmi, dibaca 2026-10-10). Sphinx tidak dipasang atau dijalankan di bab ini.
Skrip berikut membangun situs MkDocs mini sungguhan:
08_mkdocs.py
# Bab 19 - MkDocs: halaman dokumentasi dari berkas Markdown (membangun situs sungguhan di _tmp)
import shutil
import sys
from pathlib import Path
from bantu import FOLDER, jalankan
situs = FOLDER / "_tmp" / "situs_dokumentasi"
shutil.rmtree(situs, ignore_errors=True)
(situs / "docs").mkdir(parents=True)
(situs / "mkdocs.yml").write_text("""\
site_name: Dokumentasi Inventaris
nav:
- Beranda: index.md
- Cara memakai: cara-memakai.md
""", encoding="utf-8")
(situs / "docs" / "index.md").write_text(
"# Inventaris\n\nPaket contoh untuk mencatat hutan, kebun, sawah, kolam, dan kandang.\n",
encoding="utf-8")
(situs / "docs" / "cara-memakai.md").write_text(
"# Cara memakai\n\n```python\nfrom inventaris.contoh import buat_contoh\n```\n",
encoding="utf-8")
teks, kode = jalankan([sys.executable, "-m", "mkdocs", "build", "--quiet"], situs)
print("kode keluar:", kode, "| pesan:", teks or "(tidak ada)")
hasil = sorted(p.relative_to(situs / "site").as_posix() for p in (situs / "site").rglob("*.html"))
print("Halaman HTML yang dihasilkan:", hasil)
html = (situs / "site" / "index.html").read_text(encoding="utf-8")
print("Judul situs ada di halaman?", "Dokumentasi Inventaris" in html)kode keluar: 0 | pesan: (tidak ada)
Halaman HTML yang dihasilkan: ['404.html', 'cara-memakai/index.html', 'index.html']
Judul situs ada di halaman? True
Kode keluar 0 dan tanpa pesan berarti pembangunan lancar. Tiga halaman HTML dihasilkan: index.html dan cara-memakai/index.html dari dua berkas Markdown kita, ditambah 404.html yang dibuat MkDocs sendiri. Judul situs dari mkdocs.yml memang tertulis di halaman.
Docstring yang Anda tulis sejak Bab 10 adalah dokumentasi paling murah: ia ikut terbaca oleh help(), editor, dan alat seperti Sphinx autodoc. Menulis docstring satu kalimat di tiap fungsi dan kelas sudah banyak membantu.
Proyek Bersambung I1: merapikan inventaris
Sekarang semua alat dipakai pada paket inventaris, yang sudah memiliki logging dan 14 tes (satu tes baru memeriksa catatan log dengan fixture caplog). Skrip menyalin proyek ke _tmp/, mengecek, memperbaiki, dan mengujinya ulang.
09_proyek_rapi.py
# Bab 19 - Proyek bersambung I1: merapikan paket inventaris dengan ruff, lalu menguji ulang
import re
import shutil
import sys
from bantu import FOLDER, jalankan, ruff
kerja = FOLDER / "_tmp" / "proyek_rapi"
shutil.rmtree(kerja, ignore_errors=True)
shutil.copytree(FOLDER / "proyek", kerja, ignore=shutil.ignore_patterns("__pycache__"))
def periksa(judul):
teks, _ = ruff("check", "--output-format", "concise", ".", folder=kerja)
print(f"--- {judul} ---")
print(teks)
periksa("ruff check, sebelum")
ruff("check", "--fix", "--quiet", ".", folder=kerja) # perbaikan aman otomatis (urutan impor)
print("--- ruff format ---")
print(ruff("format", ".", folder=kerja)[0])
periksa("ruff check, setelah perbaikan otomatis")
# Dua docstring terlalu panjang: ruff tidak mau menebak kalimat Anda, jadi dipersingkat dengan tangan
perbaikan = {
"inventaris/__init__.py": (
"Paket inventaris: sistem informasi hutan, kebun, sawah, kolam, dan kandang (data fiktif).",
"Paket inventaris: data fiktif hutan, kebun, sawah, kolam, dan kandang.",
),
"inventaris/barang.py": (
"Besar bila diameter 40 cm ke atas, sedang bila 30 cm ke atas, selain itu kecil.",
"Besar mulai 40 cm, sedang mulai 30 cm, selain itu kecil.",
),
}
for jalur, (lama, baru) in perbaikan.items():
berkas = kerja / jalur
berkas.write_text(berkas.read_text(encoding="utf-8").replace(lama, baru), encoding="utf-8")
periksa("ruff check, setelah perbaikan tangan")
print(ruff("format", "--check", ".", folder=kerja)[0])
teks, kode = jalankan([sys.executable, "-m", "pytest", "-q", "-p", "no:cacheprovider"], kerja)
print("--- pytest ---")
print(re.sub(r" in \d+\.\d+s", "", teks.splitlines()[-1]))--- ruff check, sebelum ---
inventaris\__init__.py:1:89: E501 Line too long (95 > 88)
inventaris\barang.py:14:89: E501 Line too long (93 > 88)
inventaris\contoh.py:3:1: I001 [*] Import block is un-sorted or un-formatted
inventaris\contoh.py:3:89: E501 Line too long (92 > 88)
inventaris\unit.py:22:89: E501 Line too long (92 > 88)
Found 5 errors.
[*] 1 fixable with `--fix`.
--- ruff format ---
3 files reformatted, 6 files left unchanged
--- ruff check, setelah perbaikan otomatis ---
inventaris\__init__.py:1:89: E501 Line too long (95 > 88)
inventaris\barang.py:14:89: E501 Line too long (93 > 88)
Found 2 errors.
--- ruff check, setelah perbaikan tangan ---
All checks passed!
9 files already formatted
--- pytest ---
14 passed
Telusuri:
- Sebelum: 5 temuan, hampir semuanya
E501(baris lebih dari 88 karakter) dan satuI001(urutan impor). --fixdanformatmenyelesaikan tiga temuan (impor dirapikan, baris panjang dipecah per baris). Sisa 2 adalah docstring yang terlalu panjang: ruff tidak mau menebak kalimat Anda, jadi kita persingkat dengan tangan.- Sesudahnya semua pemeriksaan lulus, 9 berkas rapi, dan 14 tes tetap lulus: merapikan tidak merusak perilaku. Itulah gunanya tes di Bab 18.
Latihan
- Jalankan
ruff checkpada skrip lama Anda (misalnya dari Bab 10). Berapa temuan? Pilih satu kode dan cari artinya denganpython -m ruff rule F841. - Ubah
05_logging_dasar.pyagar tingkatnyaDEBUG. Pesan apa yang bertambah? - Tambahkan
logger.warning(...)padaKolambilaph_airdi luar 6,5 sampai 8,5, lalu amati lewat06_logging_proyek.py. (Batas ini hanya angka contoh.) - Tantangan: ubah
07_pre_commit.pysupaya berkas pertama hanya berisiimport osdan satu fungsi rapi. Hook mana yang gagal, dan hook mana yang lolos? Jelaskan alasannya.
Ringkasan Bab
- PEP 8 adalah panduan gaya resmi Python: konsisten dan mudah dibaca.
- ruff check memeriksa (kode
E,F,I,B, …),--fixhanya memperbaiki yang aman, ruff format menata tampilan tanpa mengubah perilaku. - pre-commit menjalankan pemeriksaan otomatis tiap
git commitdan membatalkan commit bila gagal. - logging mencatat dengan tingkat DEBUG, INFO, WARNING, ERROR, CRITICAL; modul memakai
getLogger(__name__), aplikasi yang memilih tujuan catatan. - Debugging:
breakpoint()(pdb) atau fitur VS Code dan Jupyter; mencari bug, bukan pengganti tes. - MkDocs (Markdown) dan Sphinx (reStructuredText) membuat situs dokumentasi dari berkas teks dan docstring.
Selanjutnya, di Bab 20: git, GitHub, dan alur kerja proyek: menyimpan riwayat, bercabang, bekerja bersama, dan menutup Seri I1.