Cari artikel di situs ini

Membuat Program yang Benar

Versi ramah cetakVersi ramah cetak

Program benar tidak jadi dengan sendirinya. Ia membutuhkan perencanaan dan perhatian kepada detail untuk mencegah kesalahan dalam program. Ada beberapa teknik yang bisa digunakan oleh programmer untuk meningkatkan kebenaran suatu program.

Dalam beberapa kasus, kita bisa membuktikan bahwa program tersebut benar. Yaitu dengan menggunakan pembuktian secara matematis bahwa urutan penghitungan yang dilakukan program akan selalu menghasilkan hasil yang benar. Pembuktian yang komprehensive sangat sulit dibuat karena secara praktek pembuktian semacam ini hanya bisa dilakukan pada program yang cukup kecil.

Seperti telah disebutkan sebelumnya, program yang benar menurut spesifikasi tidak berguna apabila spesifikasinya salah. Artinya, bahkan dalam pemrograman sehari-hari pun, kita harus terus mencari ide dan teknik yang bisa kita gunakan untuk membuktikan bahwa program yang kita tulis adalah benar.

Ide dasarnya adalah proses (process) dan keadaan (state). Suatu keadaan terdiri dari semua informasi yang terkait dengan eksekusi suatu program pada saat tertentu. Keadaan mencakup, misalnya, nilai semua variabel pada program, keluaran yang telah diproduksi, input yang sedang diambil, dan posisi dalam di mana program tersebut sedang dieksekusi. Proses adalah urutan keadaan yang harus dilalui oleh komputer ketika menjalankan suatu program.

Dari sudut pandang ini, arti suatu pernyataan dalam suatu program dapat diekspresikan dalam akibat apa yang dihasilkan dari eksekusi suatu perintah terhadap keadaan saat itu. Sebagai contoh sederhana, arti dari pernyataan "x = 7;" adalah setelah pernyataan ini dieksekusi, nilai dari variabel x adalah 7. Kita bisa yakin tentang fakta ini, sehingga fakta ini bisa dijadikan salah satu bukti matematis.

Sebenarnya, kita juga seringkali bisa menilai suatu program dan menyimpulkan bahwa suatu fakta adalah benar pada saat tertentu pada eksekusi suatu program. Misalnya, perhatikan perulangan berikut :

do {
    N = Math.random();
} while (N <= 0.5);

Setelah perulangan selesai, kita yakin betul bahwa nilai variabel N pasti lebih besar dari 0.5. Perulangan tidak bisa berhenti jika kondisi ini tidak tercapai. Bukti ini merupakan bagian dari arti perulangan while. Lebih umum, jika perulangan while menggunakan pengujian "while (kondisi)" maka bisa dipastikan bahwa setelah perulangan selesai kondisi bernilai false. Kemudian kita bisa menggunakan fakta ini untuk menyimpulkan apa yang akan terjadi ketika eksekusi program berlanjut. (Dengan perulangan, kita juga harus memastikan kapan perulangan tersebut akan berakhir. Hal ini harus dipikirkan lebih lanjut secara terpisah).

Suatu fakta yang pasti benar setelah bagian program dieksekusi disebut kondisi akhir dari bagian program tersebut. Kondisi akhir adalah fakta yang bisa kita gunakan untuk menyimpulkan tentang perilaku suatu program. Kondisi akhir suatu program secara keseluruan adalah fakta yang bisa dibuktikan ketika program selesai dieksekusi. Suatu program bisa dibuktikan bahwa ia melakukan fungsinya dengan benar jika kondisi akhirnya sesuai dengan spesifikasi program.

Misalnya, kita lihat potongan program berikut, di mana semua variabelnya memiliki tipe double:

det = B*B - 4*A*C;
x = (-B + Math.sqrt(det)) / (2*A);

Persamaan kuadrat (darti matematika di SMU) menyatakan bahwa nilai x adalah solusi persamaan A*x2 + B*x + C = 0 jika det bernilai 0 atau lebih. Jika kita menganggap atau menjamin bahwa B*B - 4*A*C >= 0 dan A != 0, maka x yaitu solusi persamaan kuadrat merupakan kondisi akhir.

Kita sebut B*B - 4*A*C >= 0 sebagai kondisi awal potongan program tersebut. Kondisi A != 0 adalah kondisi awal lainnya. Kondisi awal adalah kondisi yang harus bernilai benar pada suatu waktu di tengah eksekusi program untuk menjamin bahwa program akan dapat terus dieksekusi tanpa kesalahan. Kondisi awal adalah sesuatu yang kita ingin selalu benar. Kondisi awal harus kita cek agar program kita benar.

Mari kita lihat potongan program yang lebih panjang berikut ini. Program ini menggunakan kelas KonsolInput yang dibahas pada bagian sebelumnya.

do {
    System.out.println("Masukkan A, B, dan C. B*B-4*A*C harus >= 0.");
 
    System.out.print("A = ");
    A = KonsolInput.ambilDouble();
 
    System.out.print("B = ");
    B = KonsolInput.ambilDouble();
 
    System.out.print("C = ");
    C = KonsolInput.ambilDouble();
 
    if (A == 0 || B*B - 4*A*C < 0)
        System.out.println("Input Anda tidak benar, masukkan lagi.");
 
} while (A == 0 || B*B - 4*A*C < 0);
 
det = B*B - 4*A*C;
x = (-B + Math.sqrt(det)) / (2*A);

Setelah perulangan berakhir, kita yakin bahwa B*B-4*A*C >= 0 dan juga A != 0. Kondisi awal untuk dua baris terakhir sudah dipenuhi, sehingga kondisi akhir bahwa x merupakan solusi persamaan A*x2 + B*x + C = 0 juga benar. Potongan progam ini menghitung solusi suatu persamaan dengan benar dan bisa dibuktikan secara matematis (Sebetulnya karena ada masalah utama dalam merepresentasi angka terutama bilangan real pada komputer, hal ini tidak 100% benar. Algoritma ini benar, akan tetapi programnya bukan implementasi sempurna dari algoritma ini.)

Berikut ini adalah contoh lain, di mana kondisi awal diuji dengan suatu perintah. Di bagian awal pernyataan if, di mana solusi dihitung kemudian dicetak ke layar, kita yakin bahwa kondisi awal telah dipenuhi. Di bagian lain, kita tahu bahwa salah satu kondisi awal tidak bisa dipenuhi. Bagaimana pun kondisinya, program akan tetap benar.

System.out.println("Masukkan nilai A, B, dan C.");
 
System.out.print("A = ");
A = KonsolInput.ambilDouble();
 
System.out.print("B = ");
B = KonsolInput.ambilDouble();
 
System.out.print("C = ");
C = KonsolInput.ambilDouble();
 
if (A != 0 && B*B - 4*A*C >= 0) {
    det = B*B - 4*A*C;
    x = (-B + Math.sqrt(disc)) / (2*A);
    System.out.println("Solusi persamaan A*X*X + B*X + C = 0 is " + x);
} else if (A == 0) {
    System.out.println("Nilai A tidak boleh 0.");
} else {
    System.out.println("Karena B*B - 4*A*C kurang dari nol, maka");
    System.out.println("persamaan A*X*X + B*X + C = 0 tidak memiliki solusi.");
}

Ketika kita menulis suatu program, akan lebih baik jika kita mencari tahu kondisi awal suatu program dan memikirkan bagaimana program kita harus menanganinya. Sering kali, kondisi awal suatu program bisa memberi informasi tentang bagaimana cara menulis program.

Misalnya, untuk setiap referensi pada array, misalnya A[i], memiliki suatu kondisi awal. Indeksnya harus berada di dalam rentang yang diperbolehkan pada array tersebut. Untuk A[i], kondisi awalnya adalah 0 <= i < A.length. Komputer akan menguji kondisi ini ketika ia mengevaluasi A[i], dan jika kondisi tidak dipenuhi, program akan dihentikan. Untuk mencegah hal ini, kita harus menguji bahwa indeks i berada di dalam nilai yang diperbolehkan (Sebetulnya ada lagi kondisi awal yang lain, yaitu A tidak boleh null, akan tetapi mari kita abaikan untuk sementara waktu.) Misalnya kode berikut digunakan untuk mencari nilai x di dalam array A :

i = 0;
while (A[i] != x) {
    i++;
}

Dalam program ini, kita melihat bahwa program tersebut memiliki kondisi awal, yaitu x harus ada di dalam array. JIka kondisi awal ini dipenuhi, maka perulangan akan berhenti ketika A[i] == x. Akan tetapi, jika x tidak berada di dalam array, maka nilai i akan terus dinaikkan hingga nilainya sama dengan A.length. Pada saat tersebut, referensi ke A[i] menjadi ilegal sehingga program akan dihentikan. Untuk mencegah hal ini, kita bisa menambahkan pengujian untuk menjamin bahwa kondisi awal untuk merujuk pada A[i] bisa dipenuhi, yaitu :

i = 0;
while (i < A.length && A[i] != x) {
    i++;
}

Sekarang, perulangan pasti akan selesai. Setelah selesai, nilai i akan bernilai i == A.length atau A[i] == x. Pernyataan if bisa ditambahkan di akhir perulangan untuk menguji apa yang menyebabkan perulangan berhenti : 

i = 0;
while (i < A.length && A[i] != x) {
    i++;
}
 
if (i == A.length)
    System.out.println("x berada di luar array");
else
    System.out.println("x berada pada posisi " + i);