Mengapa Anda harus menggunakan praktik pengembangan berbasis standar (bahkan jika tidak perlu)
Seluruh industri telah berkembang seputar praktik verifikasi dan validasi yang diperjuangkan oleh keselamatan fungsional, keamanan, dan standar pengkodean seperti IEC 61508, ISO 26262, IEC 62304, MISRA C, dan CWE. Tentu saja, tidak semua orang diwajibkan untuk mengikuti proses dan metodologi formal yang dipromosikan oleh standar ini, terutama jika perangkat lunak mereka tidak perlu memenuhi standar ini. Namun standar mendukung praktik terbaik karena pengalaman mengatakan bahwa standar tersebut mewakili cara paling efektif untuk mencapai perangkat lunak berkualitas tinggi, andal, dan tangguh.
Teknik pengembangan praktik terbaik yang mengikuti standar ini membantu memastikan bahwa kesalahan tidak dimasukkan ke dalam kode sejak awal, mengurangi kebutuhan akan aktivitas debug ekstensif yang dapat memperlambat waktu pemasaran dan menambah biaya. Tentu saja, tidak semua pengembang memiliki kemewahan waktu dan anggaran yang diberikan untuk aplikasi yang terlihat di industri kedirgantaraan, otomotif, atau perangkat medis. Namun, teknik yang mereka terapkan mewakili kotak alat dengan potensi manfaat yang sangat besar bagi tim pengembangan mana pun, baik kekritisan memaksa penggunaannya atau tidak.
Jenis Kesalahan dan Alat untuk Mengatasinya
Dua jenis kesalahan utama dapat ditemukan dalam perangkat lunak dan diatasi dengan menggunakan alat untuk mencegah terjadinya kesalahan:
Kesalahan pengkodean. Contohnya adalah kode yang mencoba mengakses di luar batas array. Masalah seperti ini dapat dideteksi dengan melakukan analisis statis.
Kesalahan aplikasi. Ini hanya dapat dideteksi dengan mengetahui secara pasti apa yang seharusnya dilakukan aplikasi, yang berarti pengujian terhadap persyaratan.
Kesalahan Pengkodean dan Tinjauan Kode
Analisis statis adalah teknik yang efektif untuk mendeteksi bug pengkodean, terutama ketika digunakan sejak awal proyek. Setelah kode dianalisis, ada berbagai jenis hasil yang dapat dilihat. Peninjauan kode adalah tempat kode diperiksa terhadap standar pengkodean seperti MISRA C:2012, yang akan kami fokuskan dalam artikel ini.
Idealnya bahasa yang aman seperti Ada akan digunakan untuk semua proyek yang disematkan. Ada mencakup banyak karakteristik untuk menegakkan proses berpikir yang secara alami mengurangi kesalahan (seperti pengetikan yang ketat, misalnya). Sayangnya, sulit untuk menemukan programmer dengan pengetahuan dan pengalaman Ada, sehingga sebagian besar perusahaan menggunakan C dan/atau C++. Bahasa-bahasa ini, bagaimanapun, menghadirkan jebakan bahkan untuk pengembang berpengalaman. Untungnya, dengan melakukan tinjauan kode, sebagian besar potensi jebakan ini dapat dihindari.
Cara terbaik untuk menghindari cacat dalam kode adalah dengan menghindari meletakkannya di sana. Ini terdengar jelas, tetapi inilah yang dilakukan standar pengkodean. Di dunia C dan C++, sekitar 80% dari cacat perangkat lunak disebabkan oleh penggunaan yang salah dari sekitar 20% bahasa. Jika penggunaan bahasa dibatasi untuk menghindari bagian bahasa yang diketahui bermasalah, maka kerusakan dapat dihindari dan kualitas perangkat lunak meningkat pesat.
Penyebab kegagalan mendasar terkait bahasa dengan bahasa pemrograman C/C++ adalah perilaku yang tidak ditentukan, perilaku yang ditentukan implementasi, dan perilaku yang tidak ditentukan. Perilaku ini menyebabkan bug perangkat lunak dan masalah keamanan.
Sebagai contoh perilaku yang ditentukan implementasi, pertimbangkan propagasi bit orde tinggi ketika bilangan bulat bertanda digeser ke kanan. Apakah hasilnya 0x40000000 atau 0xC0000000?
Gambar 1:Perilaku beberapa konstruksi C dan C++ bergantung pada kompiler yang digunakan. (Sumber:LDRA)
Jawabannya tergantung pada kompiler yang Anda gunakan (Gambar 1). Bisa juga. Urutan di mana argumen ke fungsi dievaluasi tidak ditentukan dalam bahasa C. Dalam kode yang ditunjukkan pada Gambar 2—di mana rollDice() fungsi hanya membaca nilai berikutnya dari buffer melingkar yang memegang nilai "1, 2, 3 dan 4"—nilai yang dikembalikan yang diharapkan adalah 1234. Namun, tidak ada jaminan untuk itu dan setidaknya satu kompiler akan menghasilkan kode yang mengembalikan nilainya 3412.
Gambar 2:Perilaku beberapa konstruksi C dan C++ tidak ditentukan oleh bahasa. (Sumber:LDRA)
Bahasa C/C++ menghadirkan banyak jebakan seperti ini, tetapi dengan penggunaan standar pengkodean, perilaku yang tidak ditentukan, tidak ditentukan, dan ditentukan implementasi ini dapat dihindari. Demikian pula, penggunaan konstruksi seperti goto atau malloc dapat menyebabkan cacat, sehingga standar pengkodean dapat digunakan untuk mencegah konstruksi ini digunakan. Banyak masalah terjadi saat mencampur nilai yang ditandatangani dan tidak ditandatangani, yang hampir selalu tidak menimbulkan masalah, tetapi terkadang ada kasus sudut di mana nilai yang ditandatangani meluap dan menjadi negatif.
Standar pengkodean juga dapat memeriksa bahwa kode ditulis dengan gaya tertentu; misalnya memverifikasi bahwa karakter tab tidak digunakan, bahwa lekukan adalah ukuran tertentu, atau tanda kurung diposisikan pada posisi tertentu. Hal ini penting karena beberapa tinjauan kode manual akan diperlukan dan ketika kode tersebut dilihat di editor yang berbeda di mana karakter tab memiliki ukuran yang berbeda, maka tata letak yang aneh mengalihkan perhatian pengulas untuk berkonsentrasi pada peninjauan kode.
Beberapa pengembang bersalah karena menulis kode "pintar" yang mungkin sangat efisien dan ringkas, tetapi mungkin juga samar dan rumit, sehingga sulit dipahami oleh orang lain. Jauh lebih baik untuk membuatnya tetap sederhana dan membiarkan kompiler mengurus pembuatan biner yang efisien. Sekali lagi, penggunaan standar pengkodean dapat membantu mencegah pengembang membuat kode yang tidak berdokumen dan terlalu rumit.
Standar pemrograman yang paling terkenal mungkin adalah standar MISRA, yang pertama kali diterbitkan pada tahun 1998 untuk industri otomotif. Popularitas standar ini tercermin dalam jumlah kompiler tertanam yang menawarkan beberapa tingkat pemeriksaan MISRA. Versi terbaru dari MISRA adalah MISRA C:2012, yang memiliki jumlah halaman hampir dua kali lipat dari pendahulunya. Sebagian besar dokumentasi tambahan ini terdiri dari penjelasan yang berguna tentang mengapa setiap aturan ada, bersama dengan rincian berbagai pengecualian untuk aturan itu. MISRA memiliki beberapa pedoman dan bila berlaku, pedoman tersebut berisi referensi ke standar atau perilaku yang tidak ditentukan, tidak ditentukan, dan ditentukan implementasi. Contohnya dapat dilihat pada Gambar 3.
Gambar 3:MISRA C mengacu pada perilaku yang tidak ditentukan, tidak ditentukan, dan ditentukan implementasi. (Sumber:LDRA)
Mayoritas pedoman MISRA adalah “Decidable”, yang berarti bahwa suatu alat harus dapat mengidentifikasi apakah ada pelanggaran atau tidak. Namun, beberapa pedoman bersifat “Undecidable”, artinya tidak selalu mungkin bagi suatu alat untuk menyimpulkan apakah ada pelanggaran atau tidak. Contohnya adalah ketika variabel yang tidak diinisialisasi dilewatkan sebagai parameter output ke fungsi sistem yang harus menginisialisasinya. Namun, kecuali analisis statis memiliki akses ke kode sumber untuk fungsi sistem, maka ia tidak dapat mengetahui apakah fungsi tersebut menggunakan variabel sebelum menginisialisasinya. Jika pemeriksa MISRA sederhana digunakan, maka mungkin tidak melaporkan pelanggaran ini, mungkin mengarah ke negatif palsu. Atau, jika pemeriksa MISRA tidak yakin maka ia dapat melaporkan pelanggaran, yang mungkin mengarah ke positif palsu. Apa yang terbaik? Tidak mengetahui bahwa mungkin ada masalah? Atau tahu persis di mana harus menghabiskan waktu untuk memastikan bahwa pasti tidak ada masalah? Tentunya lebih baik memiliki hasil positif palsu daripada negatif palsu.
Pada bulan April 2016, Komite MISRA mengeluarkan amandemen terhadap MISRA C:2012 yang menambahkan 14 pedoman tambahan untuk membantu memastikan bahwa MISRA dapat diterapkan tidak hanya untuk perangkat lunak yang kritis terhadap keselamatan tetapi juga untuk perangkat lunak yang kritis terhadap keamanan. Salah satu pedoman ini adalah Directive 4.14, yang, seperti dapat dilihat pada Gambar 4, membantu mencegah jebakan karena perilaku yang tidak ditentukan.
Gambar 4:MISRA dan pertimbangan keamanan. (Sumber:LDRA)
Kesalahan Aplikasi dan Pengujian Persyaratan
Bug aplikasi hanya dapat ditemukan dengan menguji bahwa produk melakukan apa yang seharusnya dilakukan, dan itu berarti memiliki persyaratan. Menghindari bug aplikasi membutuhkan desain produk yang tepat, dan desain produk yang tepat.
Merancang produk yang tepat berarti menetapkan persyaratan di awal dan memastikan ketertelusuran dua arah antara persyaratan dan kode sumber sehingga setiap persyaratan telah diterapkan dan setiap fungsi perangkat lunak menelusuri kembali ke persyaratan. Fungsionalitas yang hilang atau tidak diperlukan (yang tidak memenuhi persyaratan) juga merupakan bug aplikasi. Merancang produk dengan benar adalah proses konfirmasi bahwa kode sistem yang dikembangkan memenuhi persyaratan proyek, yang dapat dicapai dengan melakukan pengujian berbasis persyaratan.
Gambar 5 menunjukkan contoh ketertelusuran dua arah. Dalam contoh sederhana ini, satu fungsi telah dipilih, dan ketertelusuran hulu disorot dari fungsi ke persyaratan tingkat rendah, lalu ke persyaratan tingkat tinggi, dan terakhir ke persyaratan tingkat sistem.
Gambar 5:Ketertelusuran dua arah, dengan fungsi yang dipilih. (Sumber:LDRA)
Pada Gambar 6, persyaratan tingkat tinggi telah dipilih, dan penyorotan menunjukkan ketertelusuran hulu ke persyaratan tingkat sistem dan ketertelusuran hilir ke persyaratan tingkat rendah dan ke fungsi kode sumber.
klik untuk gambar lebih besar
Gambar 6:Ketertelusuran dua arah, dengan kebutuhan yang dipilih. (Sumber:LDRA)
Kemampuan untuk memvisualisasikan ketertelusuran ini dapat mengarah pada deteksi masalah ketertelusuran (bug aplikasi) di awal siklus hidup.
Menguji fungsionalitas kode menuntut kesadaran tentang apa yang seharusnya dilakukan, dan itu berarti memiliki persyaratan tingkat rendah untuk menyatakan apa yang dilakukan setiap fungsi. Gambar 7 menunjukkan contoh persyaratan tingkat rendah, yang, dalam hal ini, sepenuhnya menggambarkan satu fungsi.
Gambar 7:Contoh kebutuhan tingkat rendah. (Sumber:LDRA)
Kasus uji diturunkan dari persyaratan tingkat rendah seperti yang diilustrasikan pada Tabel 1.
Tabel 1:Kasus uji diturunkan dari persyaratan tingkat rendah. (Sumber:LDRA)
Menggunakan alat uji unit, kasus uji ini kemudian dapat dijalankan pada host atau target untuk memastikan bahwa kode berperilaku sesuai dengan persyaratan. Gambar 8 menunjukkan bahwa semua kasus uji telah diregresi dan lulus.
Gambar 8:Melakukan pengujian unit. (Sumber:LDRA)
Ketika kasus uji telah berjalan, maka cakupan struktural harus diukur untuk memastikan bahwa semua kode telah dilaksanakan. Jika cakupannya tidak 100 persen maka mungkin diperlukan lebih banyak kasus uji atau ada kode yang berlebihan yang harus dihapus.
Kesimpulan
Dengan meningkatnya kompleksitas perangkat lunak, potensi kesalahan perangkat lunak juga meningkat. Teknik pengembangan praktik terbaik membantu mencegah kesalahan ini terjadi. Pengembangan praktik terbaik terdiri dari penggunaan standar pengkodean mutakhir seperti MISRA C:2012, mengukur metrik pada kode, menelusuri persyaratan, dan menerapkan pengujian berbasis persyaratan. Sejauh mana teknik ini diterapkan di mana tidak ada kewajiban untuk memenuhi standar jelas merupakan kebijaksanaan tim pengembangan. Namun, standar mendukung praktik ini karena pengalaman mengatakan bahwa standar tersebut mewakili cara paling efektif untuk mencapai perangkat lunak yang berkualitas, andal, dan tangguh. Dan apakah suatu produk kritis terhadap keselamatan atau tidak, itu pasti hasil yang hanya dapat bermanfaat bagi tim pengembangannya.