IBM mengirimkan sistem kontrol satelit NASA dengan 0,1 defect per seribu baris kode. Rata-rata industri pada saat itu berada di antara 10 dan 50. Mereka tidak mencapainya dengan merekrut engineer yang lebih pintar atau bekerja lebih lama. Mereka melakukannya dengan melarang developer menjalankan kode mereka sendiri.
Ini adalah Cleanroom software engineering, dikembangkan oleh Harlan Mills di IBM pada tahun 1970-an. Namanya berasal dari manufaktur semikonduktor, di mana tujuannya adalah mencegah kondisi yang menciptakan defect. Anda tidak menguji kualitas ke dalamnya. Anda mendesainnya ke dalamnya.
Masalahnya: testing menemukan defect, tidak mencegahnya
Sebagian besar rekayasa perangkat lunak mengasumsikan siklus tulis-uji-perbaiki. Anda menulis kode, menjalankannya, menemukan bug, lalu memperbaikinya. Rasanya produktif. Ini juga secara matematis dijamin meninggalkan defect.
Testing hanya dapat membuktikan kehadiran bug, bukan ketiadaannya. Dijkstra mengatakan ini pada tahun 1969. Jika kode Anda memiliki seribu jalur eksekusi yang mungkin dan test suite Anda mencakup lima puluh di antaranya, Anda memiliki 950 jalur yang belum diuji. Tingkat defect rata-rata industri sebesar 10-50 per KLOC bukanlah kegagalan dari ketelitian testing. Ini adalah output yang dapat diprediksi dari sebuah proses yang memperbolehkan defect ada.
Cleanroom membalikkan ini. Alih-alih menulis kode lalu mengujinya, Anda menulis kode yang benar secara konstruksi.
Bagaimana Cleanroom sebenarnya bekerja
Metode ini memiliki tiga praktik kaku yang bekerja sama.
Pertama, pengembangan inkremental di bawah statistical process control. Proyek dipecah menjadi inkrement kecil, masing-masing menambahkan subset fungsionalitas yang terdefinisi dengan baik. Setiap inkrement melalui spesifikasi, desain, verifikasi, dan testing sebagai satu unit lengkap. Kuncinya adalah tingkat defect diukur per inkrement. Jika sebuah inkrement melebihi target defectnya, proses dihentikan dan tim mencari tahu apa yang salah dengan metodenya, bukan dengan individu.
Kedua, desain fungsi-teoretik menggunakan box structures. Setiap komponen perangkat lunak didefinisikan pada tiga level:
- Black box: Perilaku eksternal, didefinisikan murni sebagai fungsi matematika dari input ke output. Tidak ada state, tidak ada detail implementasi.
- State box: Fungsi transisi state internal. State apa yang dipertahankan komponen, dan bagaimana state itu berubah?
- Clear box: Implementasi aktual, dibangun dari komponen yang telah diverifikasi.
Setiap level diverifikasi terhadap level di atasnya sebelum melanjutkan. Anda tidak menulis Clear box sampai State box terbukti benar. Anda tidak menulis State box sampai Black box terbukti benar. Ini terdengar lambat. Tidak, karena Anda tidak sedang debugging. Anda tidak menghabiskan sore di debugger. Kode berfungsi pada saat pertama kali dikompilasi.
Ketiga, dan ini bagian yang membuat otak pusing: tidak ada execution testing oleh developer. Orang yang menulis kode tidak mengompilasi, menjalankan, atau melakukan unit test terhadapnya. Testing dilakukan oleh tim terpisah menggunakan metode statistik. Mereka memperlakukan perangkat lunak sebagai Black box dan menghasilkan test case berdasarkan usage profile, bukan berdasarkan struktur kode.
Pemisahan ini bukan kekejaman birokratis. Ini adalah mekanisme inti yang memaksa kebenaran secara konstruksi. Jika Anda tahu bahwa Anda tidak dapat menjalankan kode untuk “melihat apakah itu berfungsi”, Anda dipaksa untuk memikirkan setiap kasus sebelum mengetiknya. Anda tidak dapat mengandalkan compiler untuk menangkap kesalahan Anda, atau pada python script.py yang cepat untuk mengungkap bug yang jelas. Anda harus benar.
Angka-angka yang penting
IBM Federal Systems Division menggunakan Cleanroom pada banyak proyek pada tahun 1980-an dan 1990-an. Hasilnya konsisten lintas tim, bahasa, dan domain aplikasi.
Sistem kontrol satelit NASA: 0,1 defect per KLOC dalam final test. Sistem billing COBOL: 0,3 defect per KLOC. Sistem real-time Ada: 0,4 defect per KLOC. Bandingkan dengan rata-rata industri pada saat itu: 10-50 defect per KLOC pada unit test, 5-10 masih tersisa saat pengiriman.
Tim Cleanroom juga mengirimkan lebih cepat. Data industri pada saat itu menunjukkan bahwa 50-70 % upaya perangkat lunak dialokasikan untuk testing dan debugging. Tim Cleanroom menghabiskan waktu itu untuk desain, dan kode berfungsi pada saat pertama kali.
Mengapa hampir tidak ada yang menggunakannya
Jika Cleanroom seefektif ini, mengapa tidak semua orang melakukannya?
Anda tidak bisa “hanya mencobanya” dalam satu sprint. Metode ini saling bergantung. Statistical process control hanya berfungsi jika Anda mengukur setiap inkrement. Box structures hanya berfungsi jika Anda melakukan verifikasi formal. Aturan no-execution hanya berfungsi jika bersifat absolut. Adopsi parsial tidak memberikan Anda manfaat apa pun tetapi semua overhead.
Pasar juga berubah. Pada tahun 1970-an dan 1980-an, perangkat lunak dikirimkan pada pita. Bug di production mahal untuk diperbaiki. Hari ini kita mengirimkan over the wire, dan kita bisa patch dalam hitungan menit. Insentif ekonomi untuk perangkat lunak zero-defect melemah.
Sebagian besar dari kita juga suka menjalankan kode kita. Feedback loop langsung dari tulis-jalankan-perbaiki itu memuaskan. Cleanroom meminta Anda untuk menunda kepuasan itu sampai Anda memikirkan masalah sepenuhnya. Bagi banyak developer, ini terasa seperti mencoba menulis prosa tanpa diizinkan untuk membacanya kembali.
Apa yang bisa Anda tiru tanpa mengadopsi seluruh agama
Anda mungkin tidak dapat mengimplementasikan Cleanroom penuh di perusahaan Anda. Manajer Anda akan memandang Anda dengan aneh. Tetapi Anda dapat mengadopsi sebagian darinya dan mendapatkan manfaat nyata.
Tulis antarmuka sebelum implementasi. Definisikan input, output, dan precondition sebelum Anda menulis body. Nyatakan apa yang dijanjikan fungsi tersebut, bukan hanya apa yang dilakukannya.
# precondition: items is a non-empty list of comparable elements
# postcondition: returns the smallest element in items
# raises: ValueError if items is empty
def min_item(items: list) -> any:
if not items:
raise ValueError("items must not be empty")
smallest = items[0]
for item in items[1:]:
if item < smallest:
smallest = item
return smallest
Ini adalah fungsi yang trivial, tetapi disiplinnya berskala. Untuk contoh yang lebih kompleks, definisikan state machine secara eksplisit sebelum mengimplementasikannya.
from enum import Enum, auto
class ConnectionState(Enum):
DISCONNECTED = auto()
CONNECTING = auto()
CONNECTED = auto()
CLOSING = auto()
# Allowed transitions:
# DISCONNECTED -> CONNECTING (on connect())
# CONNECTING -> CONNECTED (on handshake complete)
# CONNECTING -> DISCONNECTED (on timeout/error)
# CONNECTED -> CLOSING (on close())
# CLOSING -> DISCONNECTED (on ack received)
# Any other transition is illegal and raises StateError
class StateMachine:
_transitions = {
ConnectionState.DISCONNECTED: {ConnectionState.CONNECTING},
ConnectionState.CONNECTING: {ConnectionState.CONNECTED, ConnectionState.DISCONNECTED},
ConnectionState.CONNECTED: {ConnectionState.CLOSING},
ConnectionState.CLOSING: {ConnectionState.DISCONNECTED},
}
def __init__(self):
self.state = ConnectionState.DISCONNECTED
def transition(self, new_state: ConnectionState) -> None:
if new_state not in self._transitions.get(self.state, set()):
raise StateError(f"Illegal transition: {self.state.name} -> {new_state.name}")
self.state = new_state
Dengan mengenkode transisi yang valid secara eksplisit, Anda membuat keadaan ilegal menjadi unrepresentable. Kode tidak dapat memasuki keadaan yang invalid karena state machine mencegahnya. Ini adalah filosofi box structures dalam skala mini.
Mintalah orang lain untuk menguji kode Anda. Pemisahan antara authorship dan verifikasi adalah ide Cleanroom yang paling radikal dan paling dapat dipindahkan. Ketika Anda menguji kode Anda sendiri, Anda menguji asumsi Anda sendiri. Orang lain akan mencoba kasus yang tidak Anda pertimbangkan karena terasa “jelas” bahwa hal itu tidak akan terjadi.
Ukur defect density per unit kerja. Lacak berapa banyak bug yang lolos dari setiap fase proses Anda. Jika sprint Anda secara konsisten dikirimkan dengan integration bug, masalahnya bukan developer yang ceroboh. Masalahnya adalah proses Anda memperbolehkan integration bug untuk ada. Perbaiki prosesnya.
Di mana ini gagal
Cleanroom bukan solusi universal. Ini bekerja paling baik ketika requirement stabil dan kebenaran lebih penting daripada time-to-market. Ini bekerja buruk untuk pengembangan eksploratori dan rapid prototyping, di mana tujuannya adalah menemukan apa yang diinginkan pengguna daripada mengimplementasikan spesifikasi yang diketahui dengan benar.
Ini juga memerlukan management buy-in. Anda tidak dapat melakukan Cleanroom secara rahasia. Statistical process control memerlukan pengumpulan data di seluruh tim. Aturan no-execution mengharuskan manajemen benar-benar menegakkannya, bahkan ketika deadline mendekat dan developer ingin “cepat-cepat memeriksa apakah ini berfungsi”.
Kesimpulan
0,1 defect per KLOC dari IBM bukanlah keajaiban. Itu adalah output dari sebuah proses yang dirancang untuk mencegah defect daripada mendeteksinya. Praktik spesifiknya – box structures, statistical testing, dan isolasi developer – mungkin terasa asing bagi kultur perangkat lunak modern. Tetapi prinsip dasarnya abadi: lebih murah untuk berpikir sebelum mengetik daripada debug setelahnya.
Anda tidak perlu mengadopsi Cleanroom secara keseluruhan. Mulailah dengan satu fungsi. Tulis contract-nya sebelum body-nya. Mintalah kolega untuk meninjau logikanya sebelum Anda menjalankannya. Lacak dari mana bug Anda berasal dan perbaiki proses yang menghasilkannya. Zero defects per KLOC mungkin bukan tujuan Anda. Tetapi berpindah dari 50 ke 5 adalah dapat dicapai, dan metodenya sama. Pikirkan dulu. Ketik kedua. Jalankan terakhir.