Asumsi yang Terbang Terlalu Dekat dengan Matahari
Pada awal 1980-an, NASA menghadapi pertanyaan yang masih menghantui engineering safety-critical hingga hari ini: bagaimana Anda mentolerir bug yang belum Anda temukan? Jawaban mereka adalah N-version programming. Berikan spesifikasi yang sama kepada tiga tim independen. Jalankan ketiga program secara paralel. Pilih output berdasarkan suara terbanyak. Jika satu tim menulis bug, dua tim lainnya akan mengalahkannya.
Kedengarannya seperti akal sehat matematis. Tapi itu juga salah.
Pada tahun 1986, John Knight dan Nancy Leveson mempublikasikan hasil eksperimen berskala besar yang didanai NASA. Dua puluh tujuh mahasiswa pascasarjana di dua universitas menulis implementasi independen dari spesifikasi interceptor rudal balistik yang sama. Setiap program secara individual sangat andal. Enam versi sama sekali tidak pernah gagal. Dua puluh tiga lolos lebih dari 99,9% dari satu juta kasus uji yang dihasilkan secara acak.
Tetapi ketika beberapa versi gagal pada input yang sama, mereka gagal bersama jauh lebih sering daripada yang diprediksi oleh statistical independence. Z-score-nya adalah 100,55. Pada confidence interval 99%, null hypothesis independensi dihancurkan. NASA bertaruh bahwa human error bersifat acak. Knight dan Leveson membuktikan bahwa human error berkelompok.
Apa yang Sebenarnya Dijanjikan oleh N-Version Programming
Logika di balik N-version programming dipinjam dari hardware redundancy. Jika Anda memasang tiga gyroscope identik pada sebuah roket dan satu melenceng, dua lainnya mengalahkan penyimpangan tersebut. Kegagalan-kegagalan bersifat independen karena gyroscope adalah objek fisik yang dikenai manufacturing variation independen, temperature gradients, dan vibration modes.
Software berbeda. Ketika Anda meminta tiga tim untuk menyelesaikan masalah yang sama, Anda tidak mendapatkan tiga kali lempar dadu independen. Anda mendapatkan tiga manusia yang membaca spesifikasi ambigu yang sama, mempelajari algoritme yang sama dari buku teks yang sama, dan menulis kode dalam bahasa yang sama dengan standard library yang sama. Kesalahan mereka berkorelasi karena input mereka berkorelasi.
Arsitektur N-version terlihat seperti ini dalam praktiknya:
from typing import Callable, List, TypeVar
T = TypeVar('T')
def n_version_vote(
implementations: List[Callable[[float], T]],
input_value: float
) -> T:
"""Run N versions and return the majority output."""
results = [impl(input_value) for impl in implementations]
# Simple majority vote
from collections import Counter
counts = Counter(str(r) for r in results)
most_common = counts.most_common(1)[0][0]
# Return the actual value that matched the winning string
for r in results:
if str(r) == most_common:
return r
raise RuntimeError("No consensus")
Voter ini adalah bagian yang mudah. Bagian yang sulit adalah asumsi yang tersembunyi di dalamnya: bahwa implementations[0], implementations[1], dan implementations[2] gagal pada subset input space yang tidak berkorelasi. Knight dan Leveson menunjukkan bahwa asumsi itulah cacatnya.
Dari Mana Datangnya Correlated Failures
Eksperimen Knight-Leveson mengungkapkan dua mekanisme berbeda untuk common-mode failure, dan keduanya tidak dapat diperbaiki dengan meminta tim untuk “berusaha lebih keras.”
Yang pertama adalah specification ambiguity. Spesifikasi interceptor rudal berisi edge cases yang memang sulit. Delapan dari dua puluh tujuh programmer salah menangani kasus di mana tiga titik radar collinear. Kesalahannya berbeda-beda. Satu memiliki off-by-one pada array subscript. Satu menggunakan algoritme dengan numerical stability yang buruk. Satu sama sekali lupa boundary case. Tetapi kegagalan-kegagalan berkelompok pada input sulit yang sama karena masalahnya sendiri sulit, bukan karena programmernya ceroboh.
Ini adalah “difficulty factor.” Ketika sebuah input berada pada boundary condition, membutuhkan floating-point math yang rumit, atau melibatkan state transition yang kurang dispesifikasikan, tim independen cenderung kesulitan di tempat yang sama. Solusi mereka berbeda. Failure region mereka tumpang tindih.
Mekanisme kedua adalah shared mental models. Programmer yang dilatih dengan kurikulum yang sama menerapkan heuristik yang sama. Mereka meraih sorting algorithm yang sama, defensive copy pattern yang sama, epsilon comparison untuk floating-point equality yang sama. Ketika default bersama itu salah untuk masalah yang dihadapi, setiap tim berjalan menjauh dari tebing yang sama.
Software Safety Guidebook milik NASA sendiri pada akhirnya mengakui ini secara eksplisit. Runbook itu mencatat bahwa “many professionals regard N-Version programming as ineffective, or even counter productive.” Dalam satu studi NASA tentang pesawat eksperimental, setiap masalah software yang ditemukan selama pengujian berasal dari redundancy management system. Primary flight control software tidak memiliki cacat. N-version layer adalah satu-satunya hal yang rusak.
Pajak Kompleksitas yang Tidak Dibicarakan Siapapun
Bahkan jika N-version programming bekerja seperti yang diiklankan, ia membebankan harga yang mahal. Anda membayar untuk tiga implementasi penuh. Anda membayar untuk voter yang sendiri mengandung logika. Anda membayar untuk operational overhead menjalankan tiga proses dan menyelaraskan outputnya.
Voter tersebut bukanlah bagian kode yang sepele. Ia harus menangani ties, timeouts, output formats yang berbeda, dan kasus di mana mayoritas salah. Brunelle dan Eckhardt mendemonstrasikan ini pada tahun 1985 dengan SIFT operating system. Dua N-version baru mengalahkan implementasi asli yang benar dan menghasilkan jawaban yang salah. Sistem redundansi menciptakan kesalahan yang seharusnya dicegah.
Kompleksitas meningkat dengan cara yang sulit diukur sampai menggigit Anda. Anda kini memiliki tiga deployables untuk di-version, tiga test matrices untuk dipelihara, dan tiga tim yang akan menafsirkan perubahan spesifikasi secara berbeda ketika requirement tak terhindari berubah. Surface area untuk kesalahan manusia tumbuh lebih cepat daripada reliabilitas yang meningkat.
Apa yang Sebenarnya Berfungsi sebagai Alternatif
NASA tidak meninggalkan fault tolerance. Mereka meninggalkan asumsi spesifik bahwa diversitas berasal dari independensi. Sistem high-assurance modern menggunakan kombinasi teknik yang mengatasi failure modes aktual yang diidentifikasi Knight dan Leveson.
Formal methods untuk critical path. Daripada berharap tiga tim menafsirkan spec dengan benar, tulis spec dalam bentuk yang machine-checkable. Alat seperti TLA+, Coq, dan SPIN memverifikasi bahwa sebuah desain memenuhi invariants-nya sebelum siapapun menulis satu baris kode implementasi. Remote Agent Experiment milik NASA sendiri menggunakan SPIN untuk menemukan concurrency bugs yang lolos dari pengujian ekstensif.
Diversitas melalui technology stack yang berbeda. Jika Anda membutuhkan redundansi, buatlah mendalam. Sistem flight control Airbus A330 menggunakan hardware architectures yang berbeda, programming languages yang berbeda, dan compilers independen untuk saluran utama dan cadangannya. Tujuannya bukan hanya tim independen tetapi failure modes independen di setiap lapisan stack.
Penyederhanaan daripada duplikasi. Software Safety Guidebook NASA pada akhirnya merekomendasikan N-version programming hanya untuk “small simple functions.” Pelajarannya tidak glamor tetapi efektif: sistem paling andal adalah yang cukup sederhana untuk dipikirkan secara langsung. Setiap baris kode redundancy management adalah baris yang bisa gagal.
Berikut adalah tampilan pendekatan yang disederhanakan dan specification-driven dalam praktiknya. Alih-alih tiga implementasi opaque yang memilih output, enkodekan invariant kritis secara eksplisit dan periksa pada runtime:
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True)
class LaunchCommand:
thrust_level: float # 0.0 to 1.0
abort_flag: bool
def is_valid(self) -> bool:
"""Runtime invariant check derived from the formal spec."""
if not (0.0 <= self.thrust_level <= 1.0):
return False
if self.abort_flag and self.thrust_level > 0.0:
return False
return True
def execute_command(cmd: LaunchCommand) -> Optional[str]:
if not cmd.is_valid():
raise ValueError("Invariant violation: command violates safety spec")
# Execute only after the single, explicit check passes.
return f"Executing thrust={cmd.thrust_level}, abort={cmd.abort_flag}"
Ini bukan N-version programming. Ini adalah satu implementasi dengan satu safety boundary yang eksplisit dan dapat diuji. Boundary adalah tempat usaha Anda diarahkan. Bukan dengan berharap dua dari tiga tim melakukannya dengan benar.
Gema Modern
Makalah Knight dan Leveson tahun 1986 membawa peringatan yang menjadi semakin relevan setiap tahun. Correlated failures tidak membutuhkan tim yang berkorelasi. Mereka membutuhkan input yang berkorelasi dan kesulitan yang berkorelasi. Ketika AI-assisted coding tools semakin berkembang, kita menjalankan eksperimen N-version baru dalam skala planet. Model yang dilatih pada corpora yang tumpang tindih, diprompt dengan pola yang serupa, menghasilkan kode dengan shared failure modes.
Penelitian terbaru tentang LLM code generation menunjukkan co-error rates antara 15% dan 30% untuk komponen AI-generated. Beta factor, fraksi kegagalan yang dapat diatribusikan pada common cause, mungkin sudah melampaui nilai programmer manusia yang diukur Knight dan Leveson. Kita mengulangi eksperimen dengan lebih banyak partisipan dan asumsi cacat yang sama.
NASA mempelajari pelajaran ini dengan cara yang mahal. Anda tidak membutuhkan tiga implementasi. Anda membutuhkan satu implementasi dengan invariant yang dapat Anda nyatakan, periksa, dan percayai. Segala sesuatu yang lain adalah optimisme yang dibalut sebagai rekayasa.
FAQ
Apa itu N-version programming?
N-version programming adalah teknik software fault tolerance di mana beberapa tim independen mengimplementasikan spesifikasi yang sama. Program-program berjalan secara paralel dan voter memilih output mayoritas, dengan asumsi bahwa tim independen akan membuat kesalahan independen.
Apakah NASA benar-benar menggunakan N-version programming?
Ya. NASA mendanai penelitian awal tentang multi-version software dan pada suatu saat beberapa dokumen kebijakan secara efektif mewajibkan N-version programming untuk sistem fault-tolerant. Urutan power-up mesin Space Shuttle menggunakannya, meskipun NASA kemudian membatasi penggunaannya untuk fungsi kecil dan sederhana setelah studi empiris mengungkapkan keterbatasannya.
Apa itu eksperimen Knight-Leveson?
Pada tahun 1986, John Knight dan Nancy Leveson menyuruh dua puluh tujuh programmer menulis implementasi independen dari spesifikasi interceptor rudal. Dalam lebih dari satu juta pengujian, program-program menunjukkan significantly more coincident failures daripada yang diprediksi oleh statistical independence, menolak asumsi inti yang mendasari N-version programming.
Apakah N-version programming pernah layak digunakan?
Dalam kasus terbatas. Panduan NASA saat ini menunjukkan bahwa itu mungkin sesuai untuk fungsi kecil yang terdefinisi dengan baik di mana true diversity dapat diberlakukan. Untuk pengembangan aplikasi secara umum, biaya kompleksitas dan risiko correlated failure melebihi manfaatnya. Formal methods, runtime invariant checking, dan penyederhanaan biasanya merupakan investasi yang lebih baik.