Begitu library Anda dipakai orang lain — rekan tim, proyek lain, atau publik — setiap detail yang Anda ubah berpotensi memutus kode mereka. Function yang dipindah nama, exception baru yang terbang, atau nilai kembali yang berubah bentuk semuanya adalah breaking change: pembaruan yang memaksa pengguna menulis ulang kode mereka. Chapter ini membahas cara bergerak maju tanpa meninggalkan pengguna lama: mengenali apa yang terhitung patah, memberi tahu lewat proses deprecasi, dan menjaga kontrak lewat semver serta changelog.
Apa yang Terhitung Breaking Change #
Tidak semua perubahan setara. Empat jenis perubahan berikut memutus pemanggil lama:
| Jenis perubahan | Contoh | Dampak pada pemanggil |
|---|---|---|
| Signature | total(harga, jumlah) menjadi total(items) | TypeError saat memanggil cara lama |
| Nilai kembali | float menjadi (float, str) | unpacking dan perhitungan salah |
| Exception | kini melempar KeyError untuk input kosong | blok except lama tak menangkap |
| Perilaku | pembulatan naik-turun berubah | hasil diam-diam berbeda — bug paling licik |
Perhatikan baris terakhir: perubahan perilaku tanpa crash justru paling berbahaya karena tidak ada traceback yang mengarah ke penyebabnya.
Semver sebagai Janji Publik #
Skema semantic versioning — dibahas pada Chapter Dependency Pinning dari sisi konsumen — di sini dipandang dari sisi penulis library:
2.4.1
│ │ └── PATCH: perbaikan bug, perilaku tidak berubah
│ └──── MINOR: fitur baru, semua kode pengguna lama tetap jalan
└────── MAJOR: boleh ada breaking changeJanjinya tegas: selama angka MAJOR tidak bergerak, pemanggil boleh menaikkan versi tanpa membaca apa pun. Konsekuensnya, setiap breaking change menunggu jendela MAJOR berikutnya — dan di antara dua jendela itu, kita butuh teknik jembatan.
Proses Deprecasi Bertahap #
Menghapus function lama secara mendadak itu kasar. Jalur hormatnya bertahap: fungsi baru diperkenalkan, fungsi lama diberi peringatan, peringatan itu didokumentasikan dengan tenggat, barulah dihapus pada MAJOR berikutnya. File toko_lama.py:
"""Module dengan API lama yang sedang dideprecasi."""
import warnings
def hitung_total(harga, jumlah):
"""Hitung total belanja (versi lama)."""
warnings.warn(
"hitung_total sudah usang; gunakan hitung_tagihan",
DeprecationWarning,
stacklevel=2,
)
return harga * jumlah
def hitung_tagihan(harga, jumlah):
"""Hitung total belanja (versi baru)."""
return harga * jumlahFile pemanggil pemanggil.py:
from toko_lama import hitung_total
print(hitung_total(15000, 3))Hasil eksekusi python3 pemanggil.py:
45000Kode lama tetap jalan — tetapi diam-diam sudah bersuara. DeprecationWarning secara bawaan disembunyikan Python; jalankan dengan filter agar terlihat:
python3 -W default pemanggil.pyOutput yang Dihasilkan:
45000pemanggil.py:3: DeprecationWarning: hitung_total sudah usang; gunakan hitung_tagihan
print(hitung_total(15000, 3))Python menampilkan path lengkap file sebelum nama file; dipangkas di sini. Argumen stacklevel=2 yang penting: peringatan menunjuk baris pemanggil (pemanggil.py:3), bukan baris definisi — pengguna perlu tahu kode mana milik mereka yang harus berubah. Untuk CI, -W error::DeprecationWarning mengubah peringatan menjadi error sehingga penggunaan API usang gagal build sejak hari pertama.
Teknik Menjaga Kompatibilitas #
Selama belum tiba waktunya MAJOR, beberapa teknik menjaga agar API tetap ramah:
| Teknik | Situasinya | Bentuknya |
|---|---|---|
| Parameter default | menambah argumen baru | def bukti(nominal, metode="tunai") |
| Nama baru + alias | mengganti nama function | hitung_tagihan = hitung_total lalu deprecate yang lama |
| Konstanta penampung | mengubah struktur | simbol lama tetap hidup sebagai property yang mendelegasikan |
TYPE_CHECKING | mengetik tanpa kopling runtime | import anotasi hanya untuk mypy |
Contoh teknik pertama — pemanggil lama dan baru hidup berdampingan:
def bukti_transaksi(nominal, metode="tunai", mata_uang="IDR"):
"""Cetak bukti transaksi.
Versi lama hanya punya `nominal`; versi baru menambahkan
`metode` dan `mata_uang` dengan nilai default.
"""
print(f"Rp{nominal:,.0f} via {metode} ({mata_uang})")
bukti_transaksi(150_000)
bukti_transaksi(200_000, metode="qris")Output yang Dihasilkan:
Rp150,000 via tunai (IDR)
Rp200,000 via qris (IDR)Perhatikan kejujurannya: nilai default "tunai" dipilih agar identik dengan perilaku lama — parameter baru yang mengubah hasil bawaan tetap terhitung breaking.
Kontrak API: __all__ dan Garis Bawah #
Kontrak kompatibilitas hanya berlaku pada API yang Anda umumkan. Dua penanda membuat batas itu eksplisit. Nama diawali garis bawah (_bulatkan_rupiah) adalah wilayah internal: boleh berubah kapan pun. Daftar __all__ pada __init__.py menetapkan yang dianggap publik:
"""API publik toko: kontrak untuk pengguna package ini."""
from toko_paket.diskon import BATAS_DISKON, hitung_diskon, _bulatkan_rupiah
__all__ = ["BATAS_DISKON", "hitung_diskon"]Pemanggil yang menulis from toko_paket import * hanya menerima dua nama itu:
from toko_paket import *
print([n for n in dir() if not n.startswith("_")])Output yang Dihasilkan:
['BATAS_DISKON', 'hitung_diskon']_bulatkan_rupiah bisa dihapus atau dirombak esok hari tanpa rasa bersalah — garis bawah sudah menandakannya bukan janji.
Changelog: Keep a Changelog #
Kontrak versi tidak berguna bila tidak terbaca. Format Keep a Changelog memberi struktur baku pada file CHANGELOG.md:
# Changelog
## [1.2.0] - 2025-06-15
### Added
- Fungsi `hitung_tagihan` sebagai pengganti `hitung_total`.
### Deprecated
- `hitung_total` kini mengeluarkan `DeprecationWarning` dan akan
dihapus pada versi 2.0.0.
## [1.1.0] - 2025-04-02
### Added
- Fungsi `bukti_transaksi` dengan parameter opsional `metode`.Empat kategori tetapnya: Added, Changed, Deprecated, Removed (plus Fixed untuk perbaikan). Tanggal menggunakan format ISO; entri Deprecated selalu menyebut versi penghapusan.
Kapan Boleh Melanggar #
Menjaga kompatibilitas pun harga. Setiap jembatan alias dan parameter default menambah permukaan yang harus dites; lama-lama API lama menghalangi desain yang benar. Momen yang tepat untuk melanggar adalah rilis MAJOR — dengan syarat alasannya kuat: API yang salah desainnya akan terus menghasilkan bug, atau pemeliharaan jembatannya lebih mahal daripada biaya migrasi pengguna. Saat waktunya tiba, pastikan rilis itu disertai panduan migrasi yang menunjukkan pasangan “cara lama → cara baru” untuk setiap perubahan. Pengguna bisa menerima kerja migrasi; yang tidak bisa diterima adalah kerja migrasi tanpa peta.