Praktik Terbaik Pemrograman Defensif Favorit (Pandai) [ditutup]

148

Jika Anda harus memilih teknik Favorit (pandai) Anda untuk pengkodean defensif, akan seperti apa mereka? Meskipun bahasa saya saat ini adalah Java dan Objective-C (dengan latar belakang dalam C ++), jangan ragu untuk menjawab dalam bahasa apa pun. Penekanan di sini adalah pada teknik pertahanan pintar selain dari yang sudah kita ketahui 70% lebih dari kita. Jadi sekarang saatnya untuk menggali lebih dalam ke dalam tas trik Anda.

Dengan kata lain cobalah untuk memikirkan selain dari contoh yang tidak menarik ini :

  • if(5 == x) alih-alih if(x == 5) : untuk menghindari penugasan yang tidak diinginkan

Berikut adalah beberapa contoh beberapa praktik pemrograman defensif terbaik yang menarik (contoh spesifik bahasa ada di Jawa):

- Kunci variabel Anda sampai Anda tahu bahwa Anda perlu mengubahnya

Artinya, Anda dapat mendeklarasikan semua variabel finalhingga Anda tahu bahwa Anda perlu mengubahnya, pada titik mana Anda dapat menghapus final. Satu fakta yang umumnya tidak diketahui adalah bahwa ini juga valid untuk params metode:

public void foo(final int arg) { /* Stuff Here */ }

- Ketika sesuatu yang buruk terjadi, tinggalkan jejak bukti

Ada beberapa hal yang dapat Anda lakukan ketika Anda memiliki pengecualian: jelas mencatatnya dan melakukan pembersihan akan sedikit. Tetapi Anda juga dapat meninggalkan jejak bukti (mis. Menyetel variabel ke nilai sentinel seperti "UNABLE TO LOAD FILE" atau 99999 akan berguna dalam debugger, jika Anda kebetulan melewati pengecualian catch-block).

- Ketika sampai pada konsistensi: iblis ada dalam perinciannya

Konsisten dengan perpustakaan lain yang Anda gunakan. Misalnya, di Jawa, jika Anda membuat metode yang mengekstrak rentang nilai, buat inklusif batas bawah dan eksklusif batas atas . Ini akan membuatnya konsisten dengan metode seperti String.substring(start, end)yang beroperasi dengan cara yang sama. Anda akan menemukan semua jenis metode ini di Sun JDK untuk berperilaku seperti ini karena membuat berbagai operasi termasuk iterasi elemen yang konsisten dengan array, di mana indeks mulai dari Nol ( inklusif ) hingga panjang array ( eksklusif ).

Jadi, apa saja praktik defensif favorit Anda?

Pembaruan: Jika Anda belum melakukannya, jangan ragu untuk berpadu. Saya memberikan kesempatan untuk mendapat lebih banyak tanggapan sebelum saya memilih jawaban resmi .

Ryan Delucchi
sumber
Saya ingin mewakili 30% dari kita yang bodoh di sini yang mungkin tidak tahu teknik sederhana . Adakah yang memiliki tautan ke teknik "jelas" yang harus diketahui setiap orang sebagai sebuah yayasan?
elliot42
Terkait: stackoverflow.com/questions/163026/…
Bill the Lizard
Mengapa Anda memilih jawaban resmi? Itu terdengar sangat tidak paham.
bzlm
Nah, ketika datang ke pemrograman, bahkan kepintaran harus mengambil kursi belakang untuk menghormati "aturan paling takjub". Bagi saya untuk memutuskan dengan semangat Stack Overflow menandai jawaban terbaik akan melanggar protokol Stack Overflow (karenanya melanggar aturan tersebut). Selain itu: Saya suka penutupan :-)
Ryan Delucchi

Jawaban:

103

Dalam c ++, saya pernah suka mendefinisikan ulang baru sehingga memberikan beberapa memori tambahan untuk menangkap kesalahan posting pagar.

Saat ini, saya lebih suka menghindari pemrograman defensif yang mendukung Test Driven Development . Jika Anda menangkap kesalahan dengan cepat dan eksternal, Anda tidak perlu memperkeruh kode Anda dengan manuver defensif, kode Anda KERING -an dan Anda berakhir dengan lebih sedikit kesalahan yang harus Anda pertahankan.

Seperti Tulisan WikiKnowledge :

Hindari Pemrograman Defensif, Gagal Cepat.

Dengan pemrograman defensif yang saya maksud adalah kebiasaan menulis kode yang mencoba untuk mengkompensasi beberapa kegagalan dalam data, menulis kode yang mengasumsikan bahwa penelepon dapat memberikan data yang tidak sesuai dengan kontrak antara penelepon dan subrutin dan bahwa subrutin entah bagaimana harus mengatasi dengan itu.

Joe Soul-bringer
sumber
8
Pemrograman defensif berusaha untuk menangani kondisi ilegal yang diperkenalkan oleh bagian lain dari suatu program. Menangani input pengguna yang tidak benar adalah hal yang sangat berbeda.
Joe Soul-bringer
5
Errr ... perhatikan bahwa definisi pemrograman defensif ini bahkan tidak mendekati definisi yang secara implisit digunakan dalam pertanyaan.
Sol
15
Jangan pernah menghindari Pemrograman Defensif. Masalahnya bukan "kompensasi" untuk kegagalan dalam data tetapi melindungi diri Anda dari data jahat yang dirancang untuk membuat kode Anda melakukan hal-hal yang seharusnya tidak dilakukan. Lihat Buffer Overflow, SQL Injection. Tidak ada yang gagal lebih cepat dari halaman web di bawah XSS tetapi tidak cantik
Jorge Córdoba
6
Saya berpendapat bahwa prosedur "gagal cepat" adalah bentuk pemrograman defensif.
Ryan Delucchi
6
@ryan benar, gagal cepat adalah konsep pertahanan yang bagus. Jika keadaan Anda tidak memungkinkan, jangan mencoba untuk tetap berjalan pincang, GAGAL CEPAT DAN KERAS! Sangat penting jika Anda didorong oleh meta-data. Pemrograman defensif tidak hanya memeriksa parameter Anda ...
Bill K
75

SQL

Ketika saya harus menghapus data, saya menulis

select *    
--delete    
From mytable    
Where ...

Ketika saya menjalankannya, saya akan tahu jika saya lupa atau merusak klausa mana. Saya memiliki keamanan. Jika semuanya baik-baik saja, saya sorot semuanya setelah tanda komentar '-', dan jalankan.

Sunting: jika saya menghapus banyak data, saya akan menggunakan count (*) alih-alih hanya *

John MacIntyre
sumber
Saya menulis ini di iPhone saya, di mana saya tidak dapat memilih teks. Jika seseorang dapat menandai kode saya sebagai kode, itu akan sangat dihargai. Terima kasih.
John MacIntyre
6
Aku hanya bungkus dalam transaksi sehingga saya bisa memutar kembali jika saya mengacaukan ...
rmeador
36
TRANSAKSI AWAL | TRANSAKSI ROLLBACK
Dalin Seivewright
3
+1 Ya, saya kira ini bisa diganti dengan transaksi, tapi saya suka kesederhanaan melakukannya dengan cara ini.
Ryan Delucchi
3
Menggunakan transaksi lebih baik; itu berarti Anda dapat menjalankannya puluhan kali, dan melihat efek yang sebenarnya , sampai Anda yakin Anda sudah melakukannya dengan benar dan dapat melakukan.
Ryan Lundy
48

Mengalokasikan sepotong memori yang masuk akal ketika aplikasi dimulai - saya pikir Steve McConnell menyebutnya sebagai parasut memori dalam Kode Lengkap.

Ini dapat digunakan jika terjadi kesalahan serius dan Anda diharuskan untuk mengakhiri.

Mengalokasikan memori ini di muka memberi Anda jaring pengaman, karena Anda dapat membebaskannya dan kemudian menggunakan memori yang tersedia untuk melakukan hal berikut:

  • Simpan semua data persisten
  • Tutup semua file yang sesuai
  • Tulis pesan kesalahan ke file log
  • Menampilkan kesalahan yang berarti bagi pengguna
LeopardSkinPillBoxHat
sumber
Saya pernah melihat ini disebut dana hari hujan.
plinth
42

Di setiap pernyataan beralih yang tidak memiliki case default, saya menambahkan case yang membatalkan program dengan pesan kesalahan.

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}
Diomidis Spinellis
sumber
2
Atau lemparan dalam bahasa modern yang ketinggalan jaman.
Tom Hawtin - tackline
2
Assert memiliki keuntungan bahwa efeknya dapat dinonaktifkan secara global pada waktu kompilasi. Namun, dalam beberapa situasi lemparan bisa lebih tepat, jika bahasa Anda mendukungnya.
Diomidis Spinellis
2
Assert memiliki keuntungan lain yang membuatnya berguna untuk kode produksi: Ketika terjadi kesalahan, ia memberi tahu Anda apa yang gagal dan dari baris program mana kesalahan itu berasal. Laporan bug seperti itu luar biasa!
Mason Wheeler
2
@Diomidis: Sudut lain adalah: Assert memiliki kelemahan bahwa efeknya dapat dinonaktifkan secara global pada waktu kompilasi.
Disillusioned
1
melempar memang lebih baik. Dan kemudian Anda memastikan bahwa cakupan tes Anda memadai.
Dominic Cronin
41

Saat Anda menangani berbagai status enum (C #):

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Kemudian, di dalam beberapa rutinitas ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

Pada titik tertentu saya akan menambahkan jenis akun lain ke enum ini. Dan ketika saya melakukannya, saya akan lupa untuk memperbaiki pernyataan switch ini. Jadi Debug.Failcrash mengerikan (dalam mode Debug) untuk menarik perhatian saya pada fakta ini. Ketika saya menambahkan case AccountType.MyNewAccountType:, crash mengerikan berhenti ... sampai saya menambahkan jenis akun lain dan lupa untuk memperbarui kasus di sini.

(Ya, polimorfisme mungkin lebih baik di sini, tetapi ini hanya contoh dari atas kepala saya.)

Kyralessa
sumber
4
Kebanyakan kompiler cukup pintar untuk mengeluarkan peringatan jika Anda tidak menangani beberapa enum di blok kasus. Tetapi pengaturan default untuk gagal masih dalam bentuk yang baik - enum hanyalah angka dan jika Anda mendapatkan kerusakan memori Anda dapat memiliki nilai yang tidak valid muncul.
Adam Hawes
di c, saya digunakan untuk menambahkan invalidAccountType di akhir. ini terkadang bermanfaat.
Ray Tayek
1
@Adam - kompiler akan mengeluarkan peringatan jika Anda mengkompilasi ulang semuanya . Jika Anda menambahkan kelas baru dan hanya mengkompilasi ulang sebagian, Anda mungkin tidak melihat sesuatu seperti di atas, dan kasing standar akan menyelamatkan Anda. Itu tidak akan gagal secara diam-diam.
Eddie
4
Istirahat tersirat dalam komentar, Slashene. : P
Ryan Lundy
2
Setelah debug harus menjadi 'membuang NotSupportedException ()' baru untuk kode produksi.
user7116
35

Saat mencetak pesan kesalahan dengan string (terutama yang tergantung pada input pengguna), saya selalu menggunakan tanda kutip tunggal ''. Sebagai contoh:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

Kurangnya kutipan di sekitar %sini benar-benar buruk, karena katakan nama file adalah string kosong atau hanya spasi putih atau sesuatu. Pesan yang dicetak tentu saja adalah:

ERROR: Could not open file

Jadi, selalu lebih baik untuk dilakukan:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

Maka setidaknya pengguna melihat ini:

ERROR: Could not open file ''

Saya menemukan bahwa ini membuat perbedaan besar dalam hal kualitas laporan bug yang dikirimkan oleh pengguna akhir. Jika ada pesan kesalahan yang tampak lucu seperti ini alih-alih sesuatu yang terdengar umum, maka mereka lebih mungkin untuk menyalin / menempelkannya daripada hanya menulis "itu tidak akan membuka file saya".

Nik Reiman
sumber
4
bagus, saya telah melihat masalah ini juga
Alex Baranosky
28

Keamanan SQL

Sebelum menulis SQL apa pun yang akan mengubah data, saya membungkus semuanya dalam transaksi yang dibatalkan:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

Ini mencegah Anda dari melakukan penghapusan / pembaruan buruk secara permanen. Dan, Anda dapat menjalankan semuanya dan memverifikasi jumlah catatan yang masuk akal atau menambahkan SELECTpernyataan antara SQL Anda dan ROLLBACK TRANSACTIONuntuk memastikan semuanya terlihat benar.

Ketika Anda benar-benar yakin itu melakukan apa yang Anda harapkan, ubah ROLLBACKto COMMITdan jalankan nyata.

amsimmon
sumber
Anda mengalahkan saya dengan pukulan yang satu ini! :-)
Howard Pinsley
2
Saya biasa melakukan ini sepanjang waktu, tapi itu menyebabkan overhead ekstra pada cluster DB sehingga saya harus berhenti.
Zan Lynx
25

Untuk semua bahasa:

Kurangi cakupan variabel hingga seminimal mungkin diperlukan. Hindari variabel yang hanya disediakan untuk membawanya ke pernyataan berikutnya. Variabel yang tidak ada adalah variabel yang tidak perlu Anda pahami, dan Anda tidak dapat dianggap bertanggung jawab. Gunakan Lambdas jika memungkinkan untuk alasan yang sama.

le dorfier
sumber
5
Apa sebenarnya yang dimaksud dengan bagian menghindari? Saya kadang-kadang memperkenalkan variabel yang hanya hidup sampai baris berikutnya. Mereka berfungsi sebagai nama untuk ekspresi, yang membuat kode lebih mudah dibaca.
zoul
4
Ya saya juga tidak setuju. Dengan ekspresi yang sangat kompleks, sering kali ide yang bagus untuk memecahnya menjadi dua atau kadang-kadang lebih pendek dan lebih sederhana menggunakan variabel sementara. Ini kurang rawan kesalahan dalam pemeliharaan dan compiler akan mengoptimalkan keluar temps
Cruachan
1
Saya setuju bahwa ada kasus luar biasa di mana saya mendeklarasikan variabel untuk kejelasan (dan kadang-kadang untuk membuat target untuk debugging). Pengalaman saya adalah bahwa praktik umum adalah berbuat salah arah.
dkretz
Saya setuju dengan kedua sudut pandang; meskipun ketika saya memecah ekspresi menjadi variabel sementara saya mencoba melakukannya dalam lingkup yang terpisah. Lambdas bagus untuk ini, seperti juga metode pembantu.
Erik Forbes
19

Jika ragu, bom aplikasi!

Periksa masing-masing dan setiap parameter di awal masing - masing dan setiap metode (apakah secara eksplisit mengkode sendiri, atau menggunakan pemrograman berbasis kontrak tidak masalah di sini) dan bom dengan pengecualian yang benar dan / atau pesan kesalahan yang bermakna jika ada prasyarat untuk kode adalah tidak bertemu.

Kita semua tahu tentang prasyarat implisit ini ketika kita menulis kode , tetapi jika mereka tidak dicek secara eksplisit, kita menciptakan labirin untuk diri kita sendiri ketika ada yang tidak beres kemudian dan tumpukan lusinan pemanggilan metode memisahkan kejadian gejala dan lokasi sebenarnya. di mana prasyarat tidak terpenuhi (= di mana sebenarnya masalah / bug).

peSHIr
sumber
Dan tentu saja: Gunakan kode generik (perpustakaan kecil, metode ekstensi dalam C #, apa pun) untuk membuatnya mudah. Sehingga Anda dapat menulis sesuatu glike param.NotNull("param")bukanif ( param == null ) throw new ArgumentNullException("param");
peSHIr
2
Atau gunakan pemrograman berbasis kontrak, seperti Spec #!
bzlm
Tergantung aplikasi apa itu. Saya harap orang-orang yang menulis kode untuk pesawat terbang dan kawat pacu tidak berpikir seperti peSHIr.
MarkJ
6
@ MarkJ: Anda tidak benar-benar mengerti, bukan? Jika bom awal (= selama pengembangan dan pengujian) itu tidak boleh bom ketika sedang dalam produksi. Jadi saya sangat berharap mereka melakukan program seperti ini!
RASHIr
Saya harus mengakui bahwa saya tidak terlalu menyukainya, terutama untuk metode pribadi dan terlindungi. Alasan: a) Anda mengacaukan kode dengan cek yang sama sekali tidak menyerupai persyaratan bisnis b) cek itu sulit diuji untuk metode non-publik c) tidak berguna dalam banyak kasus, misalnya karena nilai nol akan menyebabkan metode gagal lagi pula dua baris kemudian
Erich Kitzmueller
18

Di Jawa, terutama dengan koleksi, gunakan API, jadi jika metode Anda mengembalikan tipe daftar (misalnya), coba yang berikut ini:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

Jangan biarkan apa pun keluar dari kelas Anda yang tidak perlu Anda lakukan!

David Grant
sumber
+1 Di C # ada koleksi hanya baca untuk ini.
tobsen
1
+1 untuk Java. Saya menggunakan ini sepanjang waktu
Fortyrunner
+1 ... Saya sering melakukan ini (walaupun saya berharap lebih banyak orang akan ingat untuk melakukan ini).
Ryan Delucchi
Pastikan tidak ada yang lain yang memiliki instance dari variabel daftar yang mendasarinya, atau ini tidak akan banyak membantu Anda ...
GreenieMeanie
5
FYI, Collections.unmodifiableList mengembalikan tampilan daftar yang tidak berubah, bukan salinan yang tidak dapat diubah . Jadi, jika daftar asli diubah, maka tampilan juga!
Zarkonnen
17

di Perl, semua orang melakukannya

use warnings;

saya suka

use warnings FATAL => 'all';

Ini menyebabkan kode mati untuk peringatan kompiler / runtime. Ini sebagian besar berguna dalam menangkap string yang tidak diinisialisasi.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here
Eric Johnson
sumber
Berharap ini sedikit lebih diiklankan ...
DJG
16

C #:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}
CMS
sumber
Sama di Jawa, dan mungkin sebagian besar bahasa OO.
MiniQuark
Ini bagus di Objective-C, di mana dimungkinkan untuk mengirim pesan ke nil ("metode panggilan objek nol", dengan lisensi tertentu). Memanggil [nil isEqualToString: @ "Moo"] mengembalikan false.
Zoul
Saya tidak setuju dengan contoh C #. Solusi yang lebih baik adalah dengan menggunakan "if (myString ==" someValue ")". Juga tidak ada pengecualian referensi nol, dan tentu saja lebih mudah dibaca.
Dan C.
13
contoh ini hanya memungkinkan Anda untuk menyembunyikan situasi yang berpotensi berbahaya dalam program Anda. Jika Anda tidak mengharapkan itu menjadi nol, Anda INGIN itu untuk melemparkan pengecualian, dan jika Anda mengharapkannya menjadi nol, Anda harus menanganinya seperti itu. Ini praktik buruk.
rmeador
2
Di C #, saya selalu menggunakan string.Equals (<string1>, <string2>). Tidak masalah jika keduanya nol.
Darcy Casselman
15

Dalam c # memeriksa string.IsNullOrEmpty sebelum melakukan operasi pada string seperti panjang, indexOf, pertengahan dll

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[Sunting]
Sekarang saya juga bisa melakukan hal berikut dalam. NET 4.0, yang juga memeriksa apakah nilainya hanya spasi kosong

string.IsNullOrWhiteSpace(myString)
Binoj Antony
sumber
7
Itu bukan defensif, itu mengabaikan masalah. Seharusnya if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
enashnash
14

Di Jawa dan C #, beri setiap utas nama yang bermakna. Ini termasuk utas kumpulan benang. Itu membuat tumpukan dump lebih bermakna. Dibutuhkan sedikit usaha lebih untuk memberikan nama yang berarti bahkan untuk utas utas, tetapi jika satu utas utas memiliki masalah dalam aplikasi yang berjalan lama, saya dapat menyebabkan tumpukan tumpukan terjadi (Anda tahu tentang SendSignal.exe , kan? ), ambil log, dan tanpa harus mengganggu sistem yang sedang berjalan saya dapat mengetahui utas mana ... apa pun. Jalan buntu, bocor, tumbuh, apa pun masalahnya.

Eddie
sumber
Dan - di Windows - untuk C ++ juga! (diaktifkan dengan lemparan pengecualian SEH khusus dan tangkapan berikut).
Brian Haak
12

Dengan VB.NET, aktifkan Option Explicit dan Option Strict secara default untuk seluruh Visual Studio.

ChrisA
sumber
Walaupun saya bekerja dengan kode lama, jadi saya tidak memiliki opsi ketat dihidupkan (menyebabkan terlalu banyak kesalahan kompiler untuk memperbaikinya), kedua opsi ini diaktifkan dapat (dan akan ada dalam kasus saya) menyelamatkan banyak hati sakit.
Kibbee
1
Ngomong-ngomong, bagi siapa saja yang mengonversi kode lama yang tidak menggunakan Opsi Ketat untuk menggunakannya, perhatikan bahwa dalam kasus item yang secara otomatis dikonversi ke String, mereka tidak menggunakan ToString (); mereka menggunakan gips untuk string. Pada hari-hari awal saya .NET saya akan mengubah ini untuk menggunakan ToString (), dan itu akan merusak banyak hal, terutama dengan enum.
Ryan Lundy
10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

lalu ganti semua panggilan ' hapus pPtr ' dan ' hapus [] pPtr ' Anda dengan SAFE_DELETE (pPtr) dan SAFE_DELETE_ARRAY (pPtr)

Sekarang karena kesalahan jika Anda menggunakan pointer 'pPtr' setelah menghapusnya, Anda akan mendapatkan kesalahan 'akses pelanggaran'. Jauh lebih mudah untuk memperbaikinya daripada kerusakan memori acak.

Nitin Bhide
sumber
1
Lebih baik gunakan templat. Ini scoped dan overloadable.
Aku baru saja akan mengatakan itu. Gunakan templat alih-alih makro. Dengan begitu, jika Anda perlu menelusuri kode, Anda bisa.
Steve Rowe
Saya belajar mana yang lebih mudah untuk debug dengan cara yang sulit di sekolah. Itu kesalahan yang belum pernah saya lakukan sejak itu. :)
Greg D
3
Atau gunakan Smart Pointers ... Atau heck, hindari yang baru / hapus sebanyak yang Anda bisa.
Arafangion
1
@Arafangion: hindari saja delete. Menggunakan newtidak apa-apa, asalkan objek baru dimiliki oleh pointer pintar.
Alexandre C.
10

Dengan Java, akan sangat berguna untuk menggunakan kata kunci yang menegaskan, bahkan jika Anda menjalankan kode produksi dengan pernyataan yang dimatikan:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

Bahkan dengan pernyataan tidak aktif, setidaknya kode mendokumentasikan fakta bahwa param diharapkan akan ditetapkan di suatu tempat. Perhatikan bahwa ini adalah fungsi pembantu pribadi dan bukan anggota API publik. Metode ini hanya bisa dipanggil oleh Anda, jadi tidak apa-apa untuk membuat asumsi tertentu tentang bagaimana itu akan digunakan. Untuk metode publik, mungkin lebih baik untuk melemparkan pengecualian nyata untuk input yang tidak valid.

Outlaw Programmer
sumber
Di .NET, Debug.Assert melakukan hal yang sama. Setiap kali Anda memiliki tempat di mana Anda berpikir "Referensi ini tidak boleh nol di sini, kan?", Anda dapat meletakkan Debug. Masukkan di sana, sehingga jika itu bisa nol, Anda dapat memperbaiki bug atau mengubah asumsi Anda.
Ryan Lundy
1
+1, metode publik harus
didasarkan
9

Saya tidak menemukan readonlykata kunci sampai saya menemukan ReSharper, tetapi sekarang saya menggunakannya secara naluriah, terutama untuk kelas layanan.

readonly var prodSVC = new ProductService();
JMS
sumber
Saya menggunakan kata kunci 'final' yang setara dengan Java di semua bidang yang saya bisa. Ini benar-benar menyelamatkan Anda dari membuat bonehead bergerak seperti tidak mengatur bidang / penulisan switch yang membingungkan yang dapat mengatur bidang beberapa kali. Saya cenderung menandai variabel / parameter lokal tapi saya rasa tidak ada salahnya.
Outlaw Programmer
C # tidak memungkinkan Anda untuk menandai variabel lokal sebagai dibaca, sehingga kita bahkan tidak memiliki pilihan ...
Jason Punyon
Saya tidak yakin apa artinya hanya baca di sini, tetapi final Java tidak cukup untuk saya. Ini hanya berarti bahwa pointer ke objek tidak berubah, tetapi tidak ada cara untuk mencegah perubahan pada objek itu sendiri.
Slartibartfast
Dalam C # sebuah readonly struct akan mencegahnya agar tidak pernah diubah.
Samuel
9

Di Jawa, ketika sesuatu terjadi dan saya tidak tahu mengapa, saya kadang-kadang akan menggunakan Log4J seperti ini:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

dengan cara ini saya mendapatkan jejak stack yang menunjukkan kepada saya bagaimana saya masuk ke situasi yang tidak terduga, katakan kunci yang tidak pernah dibuka, sesuatu yang nol yang tidak boleh nol, dan sebagainya. Jelas, jika Pengecualian nyata dilemparkan, saya tidak perlu melakukan ini. Inilah saatnya saya perlu melihat apa yang terjadi dalam kode produksi tanpa benar-benar mengganggu yang lain. Saya tidak ingin membuang Exception dan saya tidak menangkapnya. Saya hanya ingin jejak stack dicatat dengan pesan yang sesuai untuk menandai saya apa yang terjadi.

Eddie
sumber
Hmm, ini agak rapi tapi saya khawatir akan menyebabkan beberapa pencampuran kode fungsional dan penanganan kesalahan. Sementara mekanisme coba-tangkap dapat berada di sisi yang canggung, ia memaksa seseorang untuk membuat eksekusinya mengeksekusi pengalihan rute dari satu blok (coba) ke yang lain (menangkap), yang merupakan tempat semua penanganan kesalahan adalah
29880 Ryan Delucchi
1
Ini tidak digunakan untuk penanganan kesalahan, tetapi hanya untuk diagnostik . Ini memungkinkan Anda untuk melihat jalur di mana kode Anda masuk ke situasi yang tidak terduga tanpa mengganggu aliran kode Anda. Saya menggunakan ini ketika metode itu sendiri dapat menangani situasi yang tidak terduga, tetapi tetap saja itu tidak boleh terjadi.
Eddie
2
Anda juga dapat menggunakan Pengecualian ("pesan") baru. printStackTrace (); Tidak perlu melempar atau menangkap, tetapi Anda masih mendapatkan stacktrace yang bagus di log. Jelas, ini seharusnya tidak ada dalam kode produksi, tetapi ini bisa sangat berguna untuk debugging.
Jorn
9

Jika Anda menggunakan Visual C ++, gunakan kata kunci override setiap kali Anda naik metode kelas dasar. Dengan cara ini jika ada orang yang mengubah tanda tangan kelas dasar, itu akan menimbulkan kesalahan kompiler daripada metode yang salah dipanggil secara diam-diam. Ini akan menyelamatkan saya beberapa kali jika sudah ada sebelumnya.

Contoh:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}
Steve Rowe
sumber
untuk Java, ini adalah kelas Foo {void doSomething () {}} class Bar {@Override void doSomething () {}} Akan memberikan peringatan jika tidak ada anotasi (jadi Anda tidak bisa diam-diam menimpa metode yang tidak Anda lakukan secara diam-diam) berarti) dan ketika anotasi ada tetapi tidak menimpa apa pun.
Jorn
Bagus ... Saya tidak tahu tentang yang satu ini ...
sayang
8

Saya telah belajar di Jawa untuk hampir tidak pernah menunggu untuk membuka kunci tanpa batas waktu, kecuali jika saya benar-benar berharap bahwa itu akan memakan waktu yang lama. Jika secara realistis, kunci akan terbuka dalam hitungan detik, maka saya hanya akan menunggu untuk jangka waktu tertentu. Jika kunci tidak membuka, maka saya mengeluh dan membuang tumpukan ke log, dan tergantung pada apa yang terbaik untuk stabilitas sistem, baik melanjutkan seolah-olah kunci tidak terkunci, atau melanjutkan seolah-olah kunci tidak pernah membuka.

Ini telah membantu mengisolasi beberapa kondisi balapan dan kondisi buntu pseudo yang misterius sebelum saya mulai melakukan ini.

Eddie
sumber
1
menunggu dengan timeout yang digunakan seperti ini adalah tanda masalah lain yang lebih buruk.
dicroce
2
Dalam sistem yang harus memiliki waktu aktif yang tinggi, lebih baik untuk setidaknya mendapatkan informasi diagnostik sehingga Anda dapat menemukan masalahnya. Kadang-kadang Anda lebih suka untuk keluar dari utas atau mengembalikan respons ke penelepon ketika sistem dalam kondisi yang diketahui, jadi Anda menunggu semafor. Tetapi Anda tidak ingin menggantung selamanya
Eddie
8

C #

  • Verifikasi nilai bukan nol untuk parameter tipe referensi dalam metode publik.
  • Saya menggunakan sealedbanyak untuk kelas untuk menghindari memperkenalkan dependensi di mana saya tidak menginginkannya. Mengizinkan pewarisan harus dilakukan secara eksplisit dan bukan karena kecelakaan.
Brian Rasmussen
sumber
8

Saat Anda mengeluarkan pesan kesalahan, setidaknya cobalah memberikan informasi yang sama dengan yang dimiliki program ketika membuat keputusan untuk membuang kesalahan.

"Izin ditolak" memberi tahu Anda ada masalah izin, tetapi Anda tidak tahu mengapa atau di mana masalah terjadi. "Tidak dapat menulis log transaksi / file saya: Sistem file read-only" setidaknya memberi tahu Anda dasar pengambilan keputusan, bahkan jika itu salah - terutama jika itu salah: nama file yang salah? salah dibuka? kesalahan tak terduga lainnya? - dan memberi tahu Anda di mana Anda berada saat Anda memiliki masalah.

Joe McMahon
sumber
1
Saat menulis kode untuk pesan kesalahan, baca pesannya dan tanyakan apa yang ingin Anda ketahui selanjutnya , kemudian tambahkan. Ulangi sampai tidak masuk akal. Misalnya: "Di luar jangkauan." Apa yang di luar jangkauan? "Foo menghitung di luar jangkauan." Apa nilainya? "Foo menghitung (42) di luar jangkauan." Berapa kisarannya? "Hitungan Foo (42) di luar jangkauan (549 hingga 666)."
HABO
7

Di C #, gunakan askata kunci untuk melakukan cast.

string a = (string)obj

akan melempar pengecualian jika obj bukan string

string a = obj as string

akan meninggalkan sebagai null jika obj bukan string

Anda masih perlu mempertimbangkan null, tetapi itu biasanya lebih lurus ke depan daripada mencari pengecualian pemain. Kadang-kadang Anda ingin "melemparkan atau meledakkan" tipe perilaku, dalam hal ini (string)objsintaksis lebih disukai.

Dalam kode saya sendiri, saya menemukan saya menggunakan assintaksis sekitar 75% dari waktu, dan (cast)sintaksis sekitar 25%.

Matt Briggs
sumber
Benar, tetapi sekarang Anda harus memeriksa nol sebelum menggunakan referensi.
Brian Rasmussen
7
Tidak mengerti Tampaknya keputusan yang buruk bagi saya, untuk memilih yang nol. Anda akan mendapatkan masalah di suatu tempat selama runtime tanpa ada petunjuk tentang alasan aslinya.
Kai Huppmann
Iya. Ini hanya berguna jika Anda benar-benar ingin null ketika itu bukan tipe yang benar. Berguna dalam beberapa kasus: di Silverlight di mana logika kontrol dan desain sering dipisahkan, logika ingin menggunakan kontrol "Atas" hanya jika itu adalah tombol. Jika tidak, seolah-olah itu tidak ada (= null).
Sander
2
Tampaknya ini sama defensifnya dengan jendela ballroom besar di sebuah benteng. Tapi itu pemandangan yang indah!
Pontus Gagge
1
Ini mengingatkan saya pada seseorang yang tidak menggunakan pengecualian karena 'mereka merusak program'. Jika Anda menginginkan perilaku tertentu ketika suatu objek bukan kelas yang Anda harapkan, saya pikir saya akan mengkodekan ini dengan cara lain. is/instanceofmuncul dalam pikiran.
Kirschstein
6

Bersiaplah untuk input apa pun , dan input apa pun yang Anda dapatkan yang tidak terduga, buang ke log. (Sesuai alasan. Jika Anda membaca kata sandi dari pengguna, jangan membuangnya ke log! Dan jangan mencatat ribuan jenis pesan ini ke log per detik. Alasan tentang konten dan kemungkinan serta frekuensi sebelum Anda login) .)

Saya tidak hanya berbicara tentang validasi input pengguna. Misalnya, jika Anda membaca permintaan HTTP yang Anda harapkan mengandung XML, bersiaplah untuk format data lainnya. Saya terkejut melihat respons HTML di mana saya hanya mengharapkan XML - sampai saya melihat dan melihat bahwa permintaan saya melalui proxy transparan yang tidak saya sadari dan bahwa pelanggan mengklaim ketidaktahuan tentang - dan proxy kehabisan waktu mencoba menyelesaikan permintaan. Dengan demikian proxy mengembalikan halaman kesalahan HTML ke klien saya, membingungkan heck out dari klien yang hanya mengharapkan data XML.

Dengan demikian, bahkan ketika Anda berpikir Anda mengontrol kedua ujung kabel, Anda bisa mendapatkan format data yang tidak terduga tanpa terlibat kejahatan. Bersiaplah, buat kode secara defensif, dan berikan hasil diagnostik jika input tidak terduga.

Eddie
sumber
6

Saya mencoba menggunakan pendekatan Design by Contract. Itu dapat ditiru waktu menjalankan oleh bahasa apa pun. Setiap bahasa mendukung "menegaskan", tetapi mudah dan covenient untuk menulis implementasi yang lebih baik yang memungkinkan Anda mengelola kesalahan dengan cara yang lebih bermanfaat.

Dalam 25 Kesalahan Pemrograman Paling Berbahaya Top , "Validasi Input Tidak Benar" adalah kesalahan paling berbahaya di bagian "Interaksi Tidak Aman Antar Komponen".

Menambahkan pernyataan prasyarat di awal metode adalah cara yang baik untuk memastikan bahwa parameter konsisten. Pada akhir metode saya menulis postconditions , yang memeriksa output apa yang diinginkan.

Untuk mengimplementasikan invarian , saya menulis sebuah metode di kelas mana saja yang memeriksa "konsistensi kelas", yang harus disebut secara otomatis oleh makro prekondisi dan postkondisi.

Saya mengevaluasi Perpustakaan Kontrak Kode .

Zen
sumber
6

Jawa

Java api tidak memiliki konsep objek yang tidak dapat diubah, yang buruk! Final dapat membantu Anda dalam hal itu. Tag setiap kelas yang berubah dengan akhir dan mempersiapkan kelas sesuai .

Terkadang berguna untuk menggunakan final pada variabel lokal untuk memastikan mereka tidak pernah mengubah nilainya. Saya menemukan ini berguna dalam jelek, tetapi diperlukan konstruksi lingkaran. Itu hanya untuk mudah menggunakan kembali variabel secara tidak sengaja meskipun itu menjadi konstan.

Gunakan menyalin pertahanan di getter Anda. Kecuali Anda mengembalikan tipe primitif atau objek yang tidak dapat diubah, pastikan Anda menyalin objek tersebut agar tidak melanggar enkapsulasi.

Jangan pernah menggunakan klon, gunakan copy constructor .

Pelajari kontrak antara equals dan kode hash. Ini sangat sering dilanggar. Masalahnya adalah itu tidak mempengaruhi kode Anda dalam 99% kasus. Orang menimpa sama, tetapi tidak peduli kode hash. Ada beberapa contoh di mana kode Anda dapat merusak atau berperilaku aneh, misalnya menggunakan objek yang bisa berubah sebagai kunci di peta.

user45947
sumber
5

Saya lupa menulis echodalam PHP terlalu sering:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

Butuh waktu selamanya untuk mencoba dan mencari tahu mengapa -> baz () tidak mengembalikan apa-apa padahal sebenarnya aku tidak menggemakannya! : -S Jadi saya membuat EchoMekelas yang dapat melilit nilai apa pun yang harus digaungkan:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

Dan kemudian untuk lingkungan pengembangan, saya menggunakan EchoMe untuk membungkus hal-hal yang harus dicetak:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

Menggunakan teknik itu, contoh pertama yang hilang echosekarang akan melempar pengecualian ...

terlalu banyak php
sumber
Bukankah seharusnya destruktor memeriksa $ this-> printed! == true?
Karsten
Anda harus mempertimbangkan menggunakan sistem template, menanamkan php dalam html kurang optimal di hampir semua sistem.
Cruachan
Mungkin saya melewatkan sesuatu? Tetapi bagi saya sepertinya ini sedang mencoba mengkompensasi untuk pengkodean: AnObject.ToStringbukan Writeln(AnObject.ToString)?
Disillusioned
Ya, tetapi kesalahannya jauh lebih mudah dilakukan di PHP
terlalu banyak php
4

C ++

Saat saya mengetik baru, saya harus segera mengetik hapus. Khusus untuk array.

C #

Periksa nol sebelum mengakses properti, terutama ketika menggunakan pola Mediator. Objek dilewatkan (dan kemudian harus dilemparkan menggunakan seperti, sebagaimana telah dicatat), dan kemudian periksa terhadap nol. Bahkan jika Anda berpikir itu tidak akan menjadi nol, tanyakan pula. Saya terkejut.

mmr
sumber
Saya suka poin pertama Anda. Saya melakukan hal serupa ketika, misalnya, menulis metode yang mengembalikan koleksi sesuatu. Saya membuat koleksi di baris pertama, dan segera menulis pernyataan pengembalian. Yang tersisa hanyalah mengisi bagaimana koleksi dihuni.
Outlaw Programmer
5
Di C ++, ketika Anda mengetik baru, Anda harus segera menetapkan pointer itu ke AutoPtr atau wadah yang dihitung referensi. C ++ memiliki destruktor dan templat; gunakan dengan bijak untuk menangani penghapusan secara otomatis.
An̲̳̳drew
4

Gunakan sistem pencatatan yang memungkinkan penyesuaian tingkat log waktu berjalan yang dinamis. Seringkali jika Anda harus menghentikan program untuk mengaktifkan pencatatan, Anda akan kehilangan kondisi langka apa pun yang terjadi. Anda harus dapat mengaktifkan lebih banyak informasi pencatatan tanpa menghentikan prosesnya.

Juga, 'strace -p [pid]' di linux akan menunjukkan Anda menginginkan pemanggilan sistem yang sedang diproses (atau utas linux). Awalnya mungkin terlihat aneh, tetapi begitu Anda terbiasa dengan panggilan sistem apa yang biasanya dilakukan oleh panggilan libc, Anda akan menemukan ini sangat berharga untuk diagnosis lapangan.

dicroce
sumber