Haruskah saya menambahkan kode berlebihan sekarang kalau-kalau mungkin diperlukan di masa depan?

114

Benar atau salah, saya saat ini berkeyakinan bahwa saya harus selalu berusaha membuat kode saya sekuat mungkin, bahkan jika ini berarti menambahkan kode berlebihan / memeriksa yang saya tahu tidak akan ada gunanya saat ini, tetapi mereka mungkin x jumlah tahun ke depan.

Misalnya, saya sedang mengerjakan aplikasi seluler yang memiliki kode ini:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
    }

    //2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
    if(rows.Count == 0)
    {
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

Melihat secara khusus pada bagian dua, saya tahu bahwa jika bagian satu benar, maka bagian dua juga akan benar. Saya tidak dapat memikirkan alasan mengapa bagian satu akan salah dan bagian dua mengevaluasi benar, yang membuat ifpernyataan kedua berlebihan.

Namun, mungkin ada kasus di masa depan di mana ifpernyataan kedua ini sebenarnya dibutuhkan, dan untuk alasan yang diketahui.

Beberapa orang mungkin melihat ini pada awalnya dan berpikir saya pemrograman dengan masa depan dalam pikiran, yang jelas merupakan hal yang baik. Tapi saya tahu beberapa contoh di mana kode semacam ini memiliki bug "tersembunyi" dari saya. Berarti saya perlu waktu lebih lama untuk mencari tahu mengapa fungsi xyzdilakukan abcpadahal seharusnya itu dilakukan def.

Di sisi lain, ada juga banyak contoh di mana kode semacam ini telah membuatnya jauh, lebih mudah untuk meningkatkan kode dengan perilaku baru, karena saya tidak harus kembali dan memastikan bahwa semua cek yang relevan sudah ada.

Apakah ada umum aturan-of-thumb pedoman untuk jenis kode? (Saya juga tertarik mendengar apakah ini akan dianggap praktik yang baik atau buruk?)

NB: Ini bisa dianggap mirip dengan pertanyaan ini, namun tidak seperti pertanyaan itu, saya ingin jawaban dengan asumsi tidak ada tenggat waktu.

TLDR: Haruskah saya sejauh menambahkan kode berlebihan untuk membuatnya berpotensi lebih kuat di masa depan?

KidCode
sumber
95
Dalam pengembangan perangkat lunak, hal-hal yang seharusnya tidak pernah terjadi terjadi setiap saat.
Roman Reiner
34
Jika Anda tahu if(rows.Count == 0)tidak akan pernah terjadi, maka Anda bisa mengajukan pengecualian ketika itu terjadi - dan periksa mengapa asumsi Anda menjadi salah.
Knut
9
Tidak relevan dengan pertanyaan, tetapi saya menduga kode Anda memiliki bug. Ketika baris nol, daftar baru dibuat dan (saya tebak) dibuang. Tetapi ketika baris tidak nol, daftar yang ada diubah. Desain yang lebih baik mungkin untuk menegaskan bahwa klien memasukkan daftar yang mungkin atau mungkin tidak kosong.
Theodore Norvell
9
Kenapa rowsbisa nol? Tidak pernah ada alasan yang bagus, setidaknya di .NET, untuk koleksi menjadi nol. Kosong , tentu, tetapi tidak nol . Saya akan melempar pengecualian jika rowsnol, karena itu berarti ada kesalahan dalam logika oleh pemanggil.
Kyralessa

Jawaban:

176

Sebagai latihan, pertama-tama mari kita verifikasi logika Anda. Meskipun seperti yang akan kita lihat, Anda memiliki masalah lebih besar daripada masalah logis apa pun.

Sebut kondisi pertama A dan kondisi kedua B.

Anda pertama kali mengatakan:

Melihat secara khusus pada bagian dua, saya tahu bahwa jika bagian satu benar, maka bagian dua juga akan benar.

Yaitu: A menyiratkan B, atau, dalam istilah yang lebih mendasar (NOT A) OR B

Lalu:

Saya tidak dapat memikirkan alasan mengapa bagian satu akan salah dan bagian dua menilai benar, yang membuat pernyataan kedua jika berlebihan.

Yaitu: NOT((NOT A) AND B). Terapkan hukum Demorgan untuk mendapatkan (NOT B) OR AB yang menyiratkan A.

Karena itu, jika kedua pernyataan Anda benar maka A menyiratkan B dan B menyiratkan A, yang berarti keduanya harus sama.

Karena itu ya, ceknya berlebihan. Anda tampaknya memiliki empat jalur kode melalui program ini tetapi pada kenyataannya Anda hanya memiliki dua jalur.

Jadi sekarang pertanyaannya adalah: bagaimana cara menulis kode? Pertanyaan sebenarnya adalah: apa kontrak metode yang dinyatakan ? Pertanyaan apakah kondisinya berlebihan adalah ikan haring merah. Pertanyaan sebenarnya adalah "sudahkah saya merancang kontrak yang masuk akal, dan apakah metode saya jelas menerapkan kontrak itu?"

Mari kita lihat deklarasi:

public static CalendarRow AssignAppointmentToRow(
    Appointment app,    
    List<CalendarRow> rows)

Ini publik, jadi harus kuat terhadap data buruk dari penelepon yang sewenang-wenang.

Ini mengembalikan nilai, jadi itu harus berguna untuk nilai pengembaliannya, bukan untuk efek sampingnya.

Namun nama metode ini adalah kata kerja, menunjukkan bahwa itu berguna untuk efek sampingnya.

Kontrak dari parameter daftar adalah:

  • Daftar nol adalah OK
  • Daftar dengan satu atau lebih elemen di dalamnya OK
  • Daftar tanpa elemen di dalamnya salah dan tidak mungkin dilakukan.

Kontrak ini gila . Bayangkan menulis dokumentasi untuk ini! Bayangkan menulis test case!

Saran saya: mulai dari awal. API ini memiliki antarmuka mesin permen yang ditulis di atasnya. (Ungkapan ini berasal dari cerita lama tentang mesin permen di Microsoft, di mana harga dan pilihannya adalah angka dua digit, dan sangat mudah untuk mengetik "85" yang merupakan harga item 75, dan Anda mendapatkan item yang salah. Fakta menyenangkan: ya, saya benar-benar melakukannya secara tidak sengaja ketika saya mencoba mengeluarkan permen karet dari mesin penjual otomatis di Microsoft!)

Inilah cara merancang kontrak yang masuk akal:

Jadikan metode Anda bermanfaat untuk efek samping atau nilai kembalinya, bukan keduanya.

Jangan terima jenis yang bisa berubah sebagai input, seperti daftar; jika Anda memerlukan urutan informasi, ambil nomor IEnumerable. Hanya baca urutannya; jangan menulis ke koleksi yang lewat kecuali sangat jelas bahwa ini adalah kontrak dari metode ini. Dengan mengambil IEnumerable Anda mengirim pesan ke pemanggil bahwa Anda tidak akan mengubah koleksi mereka.

Jangan pernah menerima nol; urutan nol adalah kekejian. Mengharuskan pemanggil untuk melewati urutan kosong jika itu masuk akal, tidak pernah nol.

Hancurkan segera jika penelepon melanggar kontrak Anda, untuk mengajari mereka bahwa Anda serius, dan agar mereka menangkap bug dalam pengujian, bukan produksi.

Rancang kontrak terlebih dahulu agar masuk akal mungkin, dan kemudian jelas menerapkan kontrak. Itulah cara untuk membuktikan desain masa depan.

Sekarang, saya hanya berbicara tentang kasus spesifik Anda, dan Anda mengajukan pertanyaan umum. Jadi, inilah beberapa saran umum tambahan:

  • Jika ada fakta bahwa Anda sebagai pengembang dapat menyimpulkan tetapi kompiler tidak bisa, maka gunakan pernyataan untuk mendokumentasikan fakta itu. Jika pengembang lain, seperti Anda di masa depan atau salah satu rekan kerja Anda, melanggar asumsi itu maka pernyataan itu akan memberi tahu Anda.

  • Dapatkan alat cakupan tes. Pastikan tes Anda mencakup setiap baris kode. Jika ada kode yang tidak terbuka maka Anda memiliki tes yang hilang, atau Anda memiliki kode mati. Kode mati secara mengejutkan berbahaya karena biasanya tidak dimaksudkan untuk mati! Kelemahan keamanan "kegagalan goto" Apple yang mengerikan beberapa tahun yang lalu segera muncul di benak.

  • Dapatkan alat analisis statis. Heck, dapatkan beberapa; setiap alat memiliki kekhususannya sendiri dan tidak ada yang superset dari yang lain. Perhatikan ketika memberitahu Anda bahwa ada kode yang tidak dapat dijangkau atau berlebihan. Sekali lagi, itu kemungkinan bug.

Jika kedengarannya seperti yang saya katakan: pertama, desain kodenya dengan baik, dan kedua, ujilah itu untuk memastikan sudah benar hari ini, well, itulah yang saya katakan. Melakukan hal-hal itu akan membuat berurusan dengan masa depan jauh lebih mudah; bagian tersulit tentang masa depan adalah berurusan dengan semua kode mesin permen kereta yang ditulis orang di masa lalu. Lakukan dengan benar hari ini dan biayanya akan lebih rendah di masa depan.

Eric Lippert
sumber
24
Saya tidak pernah benar-benar memikirkan metode seperti ini sebelumnya, memikirkannya sekarang saya sepertinya selalu mencoba dan menutupi setiap kemungkinan, ketika pada kenyataannya jika orang / metode yang memanggil metode saya tidak memberikan apa yang saya butuhkan, maka saya tidak boleh t mencoba untuk memperbaiki kesalahan mereka (ketika saya tidak benar-benar tahu apa yang mereka maksudkan), saya hanya harus crash. Terima kasih untuk ini, pelajaran berharga telah dipelajari!
KidCode
4
Fakta bahwa suatu metode mengembalikan nilai tidak menyiratkannya juga tidak boleh berguna untuk efek sampingnya. Dalam kode konkuren, kemampuan fungsi untuk mengembalikan keduanya sering kali penting (bayangkan CompareExchangetanpa kemampuan seperti itu!), Dan bahkan dalam skenario non-konkuren, sesuatu seperti "tambahkan catatan jika tidak ada, dan kembalikan baik yang sudah lewat catatan atau yang ada "bisa lebih nyaman daripada pendekatan apa pun yang tidak menggunakan efek samping dan nilai pengembalian.
supercat
5
@KidCode Ya, Eric sangat pandai menjelaskan berbagai hal dengan jelas, bahkan beberapa topik yang sangat rumit. :)
Mason Wheeler
3
@ supercat Tentu, tetapi juga lebih sulit untuk dipikirkan. Pertama, Anda mungkin ingin melihat sesuatu yang tidak mengubah negara global, sehingga menghindari masalah konkurensi dan korupsi negara. Ketika ini tidak masuk akal, Anda masih ingin mengisolasi keduanya - yang memberi Anda tempat yang jelas di mana konkurensi menjadi masalah (dan karenanya dianggap lebih berbahaya), dan tempat-tempat di mana hal itu ditangani. Ini adalah salah satu ide inti dalam makalah OOP asli, dan merupakan inti dari kegunaan para aktor. Tidak ada aturan agama - lebih baik memisahkan keduanya ketika itu masuk akal. Biasanya begitu.
Luaan
5
Ini adalah posting yang sangat berguna untuk hampir semua orang!
Adrian Buzea
89

Apa yang Anda lakukan dalam kode yang Anda tunjukkan di atas tidak begitu banyak pemeriksaan di masa depan seperti halnya coding defensif .

Kedua ifpernyataan menguji untuk hal-hal yang berbeda. Keduanya adalah tes yang tepat tergantung pada kebutuhan Anda.

Bagian 1 menguji dan mengoreksi nullobjek. Catatan samping: Membuat daftar tidak membuat item anak (misalnya, CalendarRow).

Bagian 2 menguji dan memperbaiki kesalahan pengguna dan / atau implementasi. Hanya karena Anda memiliki List<CalendarRow>, bukan berarti Anda memiliki item dalam daftar. Pengguna dan pelaksana akan melakukan hal-hal yang tidak dapat Anda bayangkan hanya karena mereka diizinkan melakukannya, apakah itu masuk akal bagi Anda atau tidak.

Adam Zuckerman
sumber
1
Sebenarnya saya menggunakan 'trik pengguna bodoh' sebagai input. YA Anda seharusnya tidak pernah mempercayai input. Bahkan dari luar kelas Anda. Mengesahkan! Jika Anda pikir ini hanya masalah masa depan, saya akan senang meretas Anda hari ini.
candied_orange
1
@CandiedOrange, itu maksudnya, tapi kata-katanya tidak menunjukkan humor yang dicoba. Saya telah mengubah kata-katanya.
Adam Zuckerman
3
Hanya pertanyaan singkat di sini, jika ini merupakan kesalahan implementasi / data buruk, bukankah seharusnya saya hanya crash saja, alih-alih mencoba memperbaiki kesalahan mereka?
KidCode
4
@KidCode Setiap kali Anda mencoba untuk memulihkan, "memperbaiki kesalahan mereka", Anda perlu melakukan dua hal, kembali ke kondisi baik yang diketahui dan tidak kehilangan input berharga diam-diam. Mengikuti aturan dalam hal ini menimbulkan pertanyaan: apakah input baris nol bernilai?
candied_orange
7
Sangat tidak setuju dengan jawaban ini. Jika suatu fungsi menerima input yang tidak valid itu berarti secara definisi ada bug dalam program. Pendekatan yang benar adalah dengan melemparkan pengecualian pada input yang tidak valid, sehingga Anda menemukan masalah dan memperbaiki bug. Pendekatan yang Anda jelaskan hanya akan menyembunyikan bug yang membuatnya lebih berbahaya dan lebih sulit dilacak. Pengkodean defensif berarti Anda tidak secara otomatis memercayai input tetapi memvalidasinya, tetapi itu tidak berarti Anda harus membuat tebakan acak untuk "memperbaiki" input yang tidak valid atau tidak terduga.
JacquesB
35

Saya kira, pertanyaan ini pada dasarnya tidak sesuai selera. Ya, itu ide yang baik untuk menulis kode yang kuat, namun kode dalam contoh Anda adalah sedikit pelanggaran terhadap prinsip KISS (karena banyak kode "bukti masa depan" akan seperti itu).

Saya pribadi kemungkinan tidak akan repot membuat kode anti peluru untuk masa depan. Saya tidak tahu masa depan, dan karena itu, kode "masa depan-bukti" seperti itu pasti gagal total ketika masa depan tiba.

Sebagai gantinya, saya lebih suka pendekatan yang berbeda: Buatlah asumsi yang Anda buat secara eksplisit menggunakan fasilitas assert()makro atau serupa. Dengan begitu, ketika masa depan datang, ia akan memberi tahu Anda dengan tepat di mana asumsi Anda tidak berlaku lagi.

cmaster
sumber
4
Saya suka poin Anda tentang tidak tahu apa yang akan terjadi di masa depan. Sekarang yang saya lakukan hanyalah menebak apa yang bisa menjadi masalah, lalu menebak lagi untuk solusinya.
KidCode
3
@KidCode: Pengamatan yang baik. Pemikiran Anda sendiri di sini sebenarnya jauh lebih pintar daripada banyak jawaban di sini, termasuk yang Anda terima.
JacquesB
1
Saya suka jawaban ini. Simpan kode seminimal mungkin sehingga mudah bagi pembaca di masa depan untuk memahami mengapa segala pemeriksaan diperlukan. Jika pembaca masa depan melihat cek untuk hal-hal yang terlihat tidak perlu, mereka mungkin membuang-buang waktu untuk mencoba memahami mengapa cek itu ada. Manusia masa depan mungkin memodifikasi kelas ini, daripada orang lain yang menggunakannya. Selain itu, jangan menulis kode yang tidak dapat Anda debug , yang akan menjadi kasus jika Anda mencoba menangani kasus yang saat ini tidak dapat terjadi. (Kecuali jika Anda menulis tes unit yang menggunakan jalur kode yang tidak dimiliki program utama.)
Peter Cordes
23

Prinsip lain yang mungkin ingin Anda pikirkan adalah ide gagal cepat . Idenya adalah ketika ada sesuatu yang salah dalam program Anda, Anda ingin segera menghentikan program tersebut, setidaknya saat Anda sedang mengembangkannya sebelum merilisnya. Di bawah prinsipal ini, Anda ingin menulis banyak cek untuk memastikan asumsi Anda berlaku, tetapi pertimbangkan dengan serius agar program Anda berhenti berjalan di jalurnya setiap kali asumsi dilanggar.

Singkatnya, jika ada kesalahan kecil di program Anda, Anda ingin crash sepenuhnya saat Anda menonton!

Ini mungkin terdengar berlawanan dengan intuisi, tetapi memungkinkan Anda menemukan bug secepat mungkin selama pengembangan rutin. Jika Anda menulis sepotong kode, dan Anda pikir sudah selesai, tetapi crash ketika Anda mengujinya, tidak ada pertanyaan bahwa Anda belum selesai. Selain itu, sebagian besar bahasa pemrograman memberi Anda alat debugging luar biasa yang paling mudah digunakan ketika program Anda benar-benar macet daripada mencoba melakukan yang terbaik setelah kesalahan. Contoh terbesar dan paling umum adalah bahwa jika Anda menghentikan program dengan melemparkan pengecualian yang tidak ditangani, pesan pengecualian akan memberi tahu Anda sejumlah besar informasi tentang bug, termasuk baris kode mana yang gagal dan jalur melalui kode Anda yang diambil oleh program jalannya ke baris kode (jejak stack).

Untuk pemikiran lebih lanjut, baca esai singkat ini: Jangan Memaku Program Anda di Posisi Tegak


Ini relevan bagi Anda karena ada kemungkinan bahwa kadang-kadang, cek yang Anda tulis ada di sana karena Anda ingin program Anda mencoba untuk tetap berjalan bahkan setelah ada kesalahan. Sebagai contoh, pertimbangkan implementasi singkat dari deret Fibonacci ini:

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

Ini berfungsi, tetapi bagaimana jika seseorang memberikan angka negatif ke fungsi Anda? Itu tidak akan berhasil kalau begitu! Jadi, Anda ingin menambahkan tanda centang untuk memastikan bahwa fungsi tersebut dipanggil dengan input non-negatif.

Mungkin tergoda untuk menulis fungsi seperti ini:

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    // Make sure the input is nonnegative
    if(n < 0) {
        n = 1; // Replace the negative input with an input that will work
    }

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

Namun, jika Anda melakukan ini, maka kemudian secara tidak sengaja memanggil fungsi Fibonacci Anda dengan input negatif, Anda tidak akan pernah menyadarinya! Lebih buruk lagi, program Anda mungkin akan terus berjalan tetapi mulai menghasilkan hasil yang tidak masuk akal tanpa memberi Anda petunjuk di mana ada sesuatu yang salah. Ini adalah jenis bug yang paling sulit untuk diperbaiki.

Sebagai gantinya, lebih baik menulis cek seperti ini:

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    // Make sure the input is nonnegative
    if(n < 0) {
        throw new ArgumentException("Can't have negative inputs to Fibonacci");
    }

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

Sekarang, jika Anda secara tidak sengaja memanggil fungsi Fibonacci dengan input negatif, program Anda akan segera berhenti dan memberi tahu Anda bahwa ada sesuatu yang salah. Selanjutnya, dengan memberi Anda jejak tumpukan, program akan memberi tahu Anda bagian mana dari program Anda yang mencoba mengeksekusi fungsi Fibonacci secara tidak benar, memberi Anda titik awal yang sangat baik untuk men-debug apa yang salah.

Kevin
sumber
1
Bukankah c # memiliki jenis pengecualian tertentu untuk menunjukkan argumen yang tidak valid atau argumen di luar jangkauan?
JDługosz
@ JDługosz Yap! C # memiliki ArgumentException , dan Java memiliki IllegalArgumentException .
Kevin
Pertanyaannya menggunakan c #. Inilah C ++ (tautan ke ringkasan) untuk kelengkapan.
JDługosz
3
"Gagal cepat ke kondisi aman terdekat" lebih masuk akal. Jika Anda membuat aplikasi sehingga macet ketika terjadi sesuatu yang tidak terduga, Anda berisiko kehilangan data pengguna. Tempat-tempat di mana data pengguna dalam bahaya adalah poin bagus untuk penanganan pengecualian "kedua dari belakang" (selalu ada kasus di mana Anda tidak dapat melakukan apa pun selain mengangkat tangan Anda - kecelakaan utama). Hanya membuatnya macet di debug hanya membuka kaleng cacing lagi - Anda harus menguji apa yang Anda sebarkan kepada pengguna, dan sekarang Anda menghabiskan setengah dari waktu pengujian Anda pada versi yang tidak akan pernah dilihat pengguna.
Luaan
2
@Luaan: itu bukan tanggung jawab fungsi contoh.
whatsisname
11

Haruskah Anda menambahkan kode yang berlebihan? Tidak.

Tapi, apa yang Anda gambarkan bukan kode yang mubazir .

Apa yang Anda gambarkan adalah pemrograman secara defensif terhadap kode panggilan yang melanggar prasyarat fungsi Anda. Apakah Anda melakukan ini atau hanya menyerahkannya kepada pengguna untuk membaca dokumentasi dan menghindari pelanggaran itu sendiri, sepenuhnya subjektif.

Secara pribadi, saya sangat percaya pada metodologi ini, tetapi seperti semua yang Anda harus berhati-hati. Ambil, misalnya, C ++ std::vector::operator[]. Mengesampingkan implementasi mode debug VS untuk sesaat, fungsi ini tidak melakukan pemeriksaan batas. Jika Anda meminta elemen yang tidak ada, hasilnya tidak ditentukan. Terserah kepada pengguna untuk memberikan indeks vektor yang valid. Ini cukup disengaja: Anda dapat "memilih" untuk memeriksa batas dengan menambahkannya di callsite, tetapi jika operator[]implementasinya melakukannya maka Anda tidak akan bisa "memilih keluar". Sebagai fungsi tingkat rendah, ini masuk akal.

Tetapi jika Anda sedang menulis sebuah AddEmployee(string name)fungsi untuk beberapa antarmuka tingkat yang lebih tinggi, saya sepenuhnya berharap fungsi ini setidaknya melemparkan pengecualian jika Anda menyediakan kosong name, serta prasyarat ini didokumentasikan segera di atas deklarasi fungsi. Anda mungkin tidak memberikan input pengguna yang tidak didiagnosis untuk fungsi ini hari ini, tetapi menjadikannya "aman" dengan cara ini berarti segala pelanggaran prasyarat yang muncul di masa depan dapat dengan mudah didiagnosis, daripada berpotensi memicu rantai domino kartu sulit yang harus dikerjakan. mendeteksi bug. Ini bukan redundansi: ini adalah ketekunan.

Jika saya harus datang dengan aturan umum (walaupun, sebagai aturan umum, saya mencoba untuk menghindari itu), saya akan mengatakan bahwa fungsi yang memenuhi salah satu dari berikut ini:

  • hidup dalam bahasa tingkat super tinggi (katakanlah, JavaScript daripada C)
  • duduk di batas antarmuka
  • tidak kritis terhadap kinerja
  • langsung menerima input pengguna

... dapat mengambil manfaat dari pemrograman defensif. Dalam kasus lain, Anda masih dapat menulis assertion yang menyala selama pengujian tetapi dinonaktifkan di build rilis, untuk lebih meningkatkan kemampuan Anda menemukan bug.

Topik ini dieksplorasi lebih lanjut di Wikipedia ( https://en.wikipedia.org/wiki/Defensive_programming ).

Lightness Races di Orbit
sumber
9

Dua dari sepuluh perintah pemrograman relevan di sini:

  • Anda tidak akan berasumsi bahwa input benar

  • Jangan membuat kode untuk penggunaan di masa mendatang

Di sini, memeriksa null bukan "membuat kode untuk digunakan di masa mendatang". Membuat kode untuk digunakan di masa depan adalah hal-hal seperti menambahkan antarmuka karena Anda pikir mereka mungkin berguna "suatu hari". Dengan kata lain, perintahnya adalah untuk tidak menambahkan lapisan abstraksi kecuali dibutuhkan saat ini.

Memeriksa null tidak ada hubungannya dengan penggunaan di masa mendatang. Itu ada hubungannya dengan Perintah # 1: jangan menganggap input akan benar. Jangan pernah menganggap fungsi Anda akan menerima beberapa subset input. Suatu fungsi harus merespons dengan cara yang logis tidak peduli seberapa palsu dan kacau inputnya.

Tyler Durden
sumber
5
Di mana perintah-perintah pemrograman ini. Apakah Anda memiliki tautan? Saya ingin tahu karena saya telah bekerja pada beberapa program yang berlangganan yang kedua dari perintah-perintah itu, dan beberapa yang tidak. Selalu, mereka yang berlangganan perintah tersebut Thou shall not make code for future usemengalami masalah pemeliharaan lebih cepat , terlepas dari logika intuitif perintah tersebut. Saya telah menemukan, dalam pengkodean kehidupan nyata, bahwa perintah hanya efektif dalam kode di mana Anda mengontrol daftar fitur dan tenggat waktu, dan pastikan Anda tidak memerlukan kode yang kelihatan di masa depan untuk mencapainya ... artinya, tidak pernah.
Cort Ammon
1
Terbukti sepele: jika seseorang dapat memperkirakan kemungkinan penggunaan di masa depan, dan nilai yang diproyeksikan dari kode "penggunaan di masa depan", dan produk dari keduanya lebih besar daripada biaya menambahkan kode "penggunaan di masa depan", secara statistik optimal untuk tambahkan kode. Saya pikir perintah itu muncul dalam situasi di mana pengembang (atau manajer) dipaksa untuk mengakui bahwa keterampilan estimasi mereka tidak dapat diandalkan seperti yang mereka inginkan, sehingga sebagai ukuran pertahanan mereka hanya memilih untuk tidak memperkirakan tugas di masa depan sama sekali.
Cort Ammon
2
@CortAmmon Tidak ada tempat untuk perintah agama dalam pemrograman - "praktik terbaik" hanya masuk akal dalam konteks, dan mempelajari "praktik terbaik" tanpa alasan membuat Anda tidak dapat beradaptasi. Saya menemukan YAGNI sangat berguna, tetapi itu tidak berarti bahwa saya tidak memikirkan tempat-tempat di mana menambahkan titik ekstensi nanti akan mahal - itu hanya berarti bahwa saya tidak harus memikirkan kasus sederhana sebelumnya. Tentu saja, ini juga berubah seiring waktu, karena semakin banyak asumsi yang dilesat pada kode Anda, secara efektif meningkatkan antarmuka kode Anda - ini adalah tindakan penyeimbang.
Luaan
2
@CortAmmon Kasing Anda yang "terbukti sepele" mengabaikan dua biaya yang sangat penting - biaya estimasi itu sendiri, dan biaya pemeliharaan titik ekstensi (mungkin tidak perlu). Di situlah orang mendapatkan estimasi yang sangat tidak dapat diandalkan - dengan meremehkan estimasi tersebut. Untuk fitur yang sangat sederhana, mungkin cukup untuk berpikir selama beberapa detik, tetapi sangat mungkin bahwa Anda akan menemukan seluruh kaleng cacing yang mengikuti dari fitur awalnya "sederhana". Komunikasi adalah kuncinya - saat segalanya menjadi besar, Anda perlu berbicara dengan pemimpin / pelanggan Anda.
Luaan
1
@Luaan Saya mencoba memperdebatkan maksud Anda, tidak ada tempat untuk perintah agama dalam pemrograman. Selama ada satu kasus bisnis di mana biaya estimasi dan pemeliharaan titik ekstensi cukup terikat, ada kasus di mana "perintah" tersebut dipertanyakan. Dari pengalaman saya dengan kode, pertanyaan apakah meninggalkan titik ekstensi seperti itu atau tidak tidak pernah cocok dengan baik dalam satu perintah baris satu atau lain cara.
Cort Ammon
7

Definisi 'redundant code' dan 'YAGNI' sering tergantung saya temukan pada seberapa jauh Anda melihat ke depan.

Jika Anda mengalami masalah, maka Anda cenderung menulis kode di masa depan sedemikian rupa untuk menghindari masalah itu. Programmer lain yang tidak mengalami masalah tertentu mungkin menganggap kode Anda berlebihan berlebihan.

Saran saya adalah untuk melacak berapa banyak waktu yang Anda habiskan untuk 'hal-hal yang belum keliru' jika bebannya dan Anda rekan-rekannya mengeluarkan fitur lebih cepat daripada Anda, kemudian menguranginya.

Namun, jika Anda seperti saya, saya berharap Anda akan mengetik semuanya 'secara default' dan itu tidak benar-benar membawa Anda lagi.

Ewan
sumber
6

Merupakan ide bagus untuk mendokumentasikan asumsi tentang parameter. Dan itu ide yang baik untuk memeriksa bahwa kode klien Anda tidak melanggar asumsi tersebut. Saya akan melakukan ini:

/** ...
*   Precondition: rows is null or nonempty
*/
public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    Assert( rows==null || rows.Count > 0 )
    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

[Dengan asumsi ini adalah C #, Pernyataan mungkin bukan alat terbaik untuk pekerjaan itu, karena tidak dikompilasi dalam kode yang dirilis. Tapi itu debat untuk hari lain.]

Mengapa ini lebih baik dari apa yang Anda tulis? Kode Anda masuk akal jika, di masa depan di mana klien Anda telah berubah, ketika klien melewati daftar kosong, hal yang benar untuk dilakukan adalah menambahkan baris pertama dan menambahkan aplikasi ke janji temu. Tetapi bagaimana Anda tahu bahwa itu akan terjadi? Lebih baik membuat asumsi sekarang tentang masa depan yang lebih sedikit.

Theodore Norvell
sumber
5

Perkirakan biaya penambahan kode itu sekarang . Ini akan relatif murah karena semuanya segar di pikiran Anda, sehingga Anda dapat melakukan ini dengan cepat. Menambahkan tes unit diperlukan - tidak ada yang lebih buruk daripada menggunakan beberapa metode setahun kemudian, itu tidak berhasil, dan Anda tahu itu rusak sejak awal dan tidak pernah benar-benar bekerja.

Perkirakan biaya penambahan kode itu saat dibutuhkan. Ini akan lebih mahal, karena Anda harus kembali ke kode, mengingat semuanya, dan itu jauh lebih sulit.

Perkirakan probabilitas bahwa kode tambahan akan benar-benar dibutuhkan. Lalu lakukan matematika.

Di sisi lain, kode penuh dengan asumsi "X tidak akan pernah terjadi" sangat buruk untuk debugging. Jika sesuatu tidak berfungsi sebagaimana dimaksud, itu berarti kesalahan bodoh, atau asumsi yang salah. "X Anda tidak akan pernah terjadi" adalah asumsi, dan di hadapan bug itu mencurigakan. Yang memaksa pengembang berikutnya untuk membuang waktu di atasnya. Biasanya lebih baik tidak mengandalkan asumsi seperti itu.

gnasher729
sumber
4
Di paragraf pertama Anda, Anda lupa menyebutkan biaya pemeliharaan kode dari waktu ke waktu, ketika ternyata fitur yang benar - benar diperlukan adalah saling eksklusif dengan fitur yang ditambahkan secara tidak perlu. . .
ruakh
Anda juga perlu memperkirakan biaya bug yang mungkin masuk ke program karena Anda tidak gagal pada input yang tidak valid. Tetapi Anda tidak dapat memperkirakan biaya bug karena menurut definisi mereka tidak terduga. Jadi keseluruhan "lakukan matematika" berantakan.
JacquesB
3

Pertanyaan utama di sini adalah "apa yang akan terjadi jika Anda lakukan / tidak"?

Seperti yang telah ditunjukkan orang lain, pemrograman defensif semacam ini bagus, tetapi terkadang juga berbahaya.

Misalnya, jika Anda memberikan nilai default, maka Anda mempertahankan program. Tetapi program sekarang mungkin tidak melakukan apa yang Anda inginkan. Misalnya, jika ia menulis array kosong itu ke sebuah file, Anda sekarang mungkin telah mengubah bug Anda dari "crash karena saya memasok null secara tidak sengaja" menjadi "menghapus baris kalender karena saya memasok null secara tidak sengaja". (misalnya jika Anda mulai menghapus hal-hal yang tidak muncul dalam daftar di bagian yang bertuliskan "// blah")

Kuncinya bagi saya adalah Jangan pernah merusak data . Saya ulangi lagi; TIDAK PERNAH. KORUP. DATA. Jika pengecualian program Anda keluar, Anda mendapatkan laporan bug yang dapat Anda tambal; jika ia menulis data buruk ke file yang nantinya, Anda harus menabur garam.

Semua keputusan "tidak perlu" Anda harus dibuat dengan pemikiran itu.

deworde
sumber
2

Apa yang Anda hadapi di sini pada dasarnya adalah sebuah antarmuka. Dengan menambahkan perilaku "saat input null, inisialisasi input", Anda telah secara efektif memperluas antarmuka metode - sekarang alih-alih selalu beroperasi pada daftar yang valid, Anda telah membuatnya "memperbaiki" input. Apakah ini bagian resmi atau tidak resmi dari antarmuka, Anda dapat bertaruh seseorang (kemungkinan besar termasuk Anda) akan menggunakan perilaku ini.

Antarmuka harus dijaga tetap sederhana, dan harus relatif stabil - terutama dalam hal seperti public staticmetode. Anda mendapatkan sedikit kelonggaran dalam metode pribadi, terutama metode contoh pribadi. Dengan memperluas antarmuka secara implisit, Anda telah membuat kode Anda lebih rumit dalam praktiknya. Sekarang, bayangkan Anda tidak benar - benar ingin menggunakan jalur kode itu - jadi Anda menghindarinya. Sekarang Anda punya sedikit kode yang belum diuji yang berpura - pura itu bagian dari perilaku metode. Dan saya dapat memberitahu Anda sekarang bahwa itu mungkin memiliki bug: ketika Anda melewati daftar, daftar itu dimutasi oleh metode. Namun, jika tidak, Anda membuat lokaldaftar, dan membuangnya nanti. Ini adalah jenis perilaku tidak konsisten yang akan membuat Anda menangis dalam waktu setengah tahun, ketika Anda mencoba melacak bug yang tidak jelas.

Secara umum, pemrograman defensif adalah hal yang sangat berguna. Tetapi jalur kode untuk pemeriksaan defensif harus diuji, sama seperti kode lainnya. Dalam kasus seperti ini, mereka memperumit kode Anda tanpa alasan, dan saya akan memilih alternatif seperti ini sebagai gantinya:

if (rows == null) throw new ArgumentNullException(nameof(rows));

Anda tidak ingin input rowsyang null, dan Anda ingin membuat kesalahan jelas untuk semua penelepon Anda sesegera mungkin .

Ada banyak nilai yang Anda butuhkan untuk menangani saat mengembangkan perangkat lunak. Bahkan ketahanan itu sendiri adalah kualitas yang sangat kompleks - misalnya, saya tidak akan menganggap pemeriksaan defensif Anda lebih kuat daripada melemparkan pengecualian. Pengecualian cukup berguna untuk memberi Anda tempat yang aman untuk mencoba lagi dari tempat yang aman - masalah korupsi data biasanya jauh lebih sulit dilacak daripada mengenali masalah sejak dini dan menanganinya dengan aman. Pada akhirnya, mereka hanya cenderung memberi Anda ilusi kekokohan, dan kemudian sebulan kemudian Anda melihat bahwa sepersepuluh janji Anda hilang, karena Anda tidak pernah memperhatikan bahwa daftar yang berbeda diperbarui. Aduh.

Pastikan untuk membedakan keduanya. Pemrograman defensif adalah teknik yang berguna untuk menangkap kesalahan di tempat yang paling relevan, sangat membantu upaya debugging Anda, dan dengan penanganan pengecualian yang baik, mencegah "korupsi licik". Gagal lebih awal, gagal cepat. Di sisi lain, apa yang Anda lakukan lebih seperti "menyembunyikan kesalahan" - Anda menyulap input dan membuat asumsi tentang apa yang dimaksud pemanggil. Ini sangat penting untuk kode yang menghadap pengguna (mis. Pengecekan ejaan), tetapi Anda harus waspada ketika melihat ini dengan kode yang menghadap pengembang.

Masalah utama adalah bahwa apa pun abstraksi yang Anda buat, itu akan bocor ("Saya ingin mengetik ulang, jangan kedepan! Pemeriksa ejaan bodoh!"), Dan kode untuk menangani semua kasus khusus dan perbaikan masih menjadi kode Anda perlu mempertahankan dan memahami, dan kode yang perlu Anda uji. Bandingkan upaya untuk memastikan daftar yang tidak nol dilewatkan dengan memperbaiki bug yang Anda miliki setahun kemudian dalam produksi - itu bukan trade-off yang baik. Di dunia yang ideal, Anda ingin setiap metode bekerja secara eksklusif dengan inputnya sendiri, mengembalikan hasilnya dan tidak mengubah keadaan global. Tentu saja, di dunia nyata, Anda akan menemukan banyak kasus di mana itu tidaksolusi paling sederhana dan paling jelas (misalnya saat menyimpan file), tetapi saya menemukan bahwa menjaga metode "murni" ketika tidak ada alasan bagi mereka untuk membaca atau memanipulasi keadaan global membuat kode lebih mudah untuk dipikirkan. Ini juga cenderung memberi Anda lebih banyak poin alami untuk memisahkan metode Anda :)

Ini tidak berarti bahwa segala sesuatu yang tidak terduga akan membuat aplikasi Anda mogok, sebaliknya. Jika Anda menggunakan pengecualian dengan baik, mereka secara alami membentuk titik penanganan kesalahan yang aman, di mana Anda dapat memulihkan keadaan aplikasi yang stabil dan memungkinkan pengguna untuk melanjutkan apa yang mereka lakukan (idealnya sambil menghindari kehilangan data bagi pengguna). Pada titik-titik penanganan itu, Anda akan melihat peluang untuk memperbaiki masalah ("Tidak ditemukan nomor urut 2212. Apakah maksud Anda 2212b?") Atau memberikan kontrol pengguna ("Kesalahan menyambung ke basis data. Coba lagi?"). Bahkan ketika tidak ada pilihan seperti itu tersedia, setidaknya itu akan memberi Anda kesempatan bahwa tidak ada yang rusak - Saya sudah mulai menghargai kode yang menggunakan usingdan try... finallylebih dari try...catch, ini memberi Anda banyak peluang untuk mempertahankan invarian bahkan dalam kondisi luar biasa.

Pengguna seharusnya tidak kehilangan data dan pekerjaan mereka. Ini masih harus diimbangi dengan biaya pengembangan dll., Tetapi itu adalah pedoman umum yang cukup bagus (jika pengguna memutuskan apakah akan membeli perangkat lunak Anda atau tidak - perangkat lunak internal biasanya tidak memiliki kemewahan itu). Bahkan seluruh aplikasi mogok menjadi jauh lebih sedikit dari masalah jika pengguna hanya dapat me-restart dan kembali ke apa yang mereka lakukan. Ini adalah kekuatan yang nyata - Word menyimpan pekerjaan Anda sepanjang waktu tanpa merusak dokumen Anda pada disk, dan memberi Anda opsiuntuk mengembalikan perubahan itu setelah me-restart Word setelah crash. Apakah lebih baik daripada tidak ada bug di tempat pertama? Mungkin tidak - meskipun jangan lupa bahwa pekerjaan yang dihabiskan untuk menangkap bug langka mungkin lebih baik dihabiskan di mana-mana. Tapi itu jauh lebih baik daripada alternatif - misalnya dokumen yang rusak pada disk, semua pekerjaan sejak terakhir disimpan hilang, dokumen otomatis diganti dengan perubahan sebelum crash yang kebetulan adalah Ctrl + A dan Delete.

Luaan
sumber
1

Saya akan menjawab ini berdasarkan asumsi Anda bahwa kode yang kuat akan menguntungkan Anda "tahun" dari sekarang. Jika manfaat jangka panjang adalah tujuan Anda, saya akan memprioritaskan desain dan pemeliharaan dibandingkan ketahanan.

Pertukaran antara desain dan ketahanan, adalah waktu dan fokus. Sebagian besar pengembang lebih suka memiliki satu set kode yang dirancang dengan baik bahkan jika itu berarti melalui beberapa titik masalah dan melakukan beberapa kondisi tambahan atau penanganan kesalahan. Setelah beberapa tahun digunakan, tempat-tempat yang benar-benar Anda butuhkan mungkin telah diidentifikasi oleh pengguna.

Dengan asumsi desain memiliki kualitas yang sama, kode yang lebih sedikit lebih mudah dipelihara. Ini tidak berarti kita lebih baik jika Anda membiarkan masalah yang diketahui pergi selama beberapa tahun, tetapi menambahkan hal-hal yang Anda tahu tidak perlu menyulitkan. Kita semua telah melihat kode lama dan menemukan bagian yang tidak perlu. Anda harus memiliki kode perubahan kepercayaan tingkat tinggi yang telah bekerja selama bertahun-tahun.

Jadi, jika Anda merasa aplikasi Anda dirancang sebaik mungkin, mudah dirawat dan tidak memiliki bug, temukan sesuatu yang lebih baik untuk dilakukan daripada menambahkan kode yang tidak Anda butuhkan. Paling tidak itu bisa Anda lakukan untuk menghormati semua pengembang lain yang bekerja berjam-jam pada fitur yang tidak berguna.

JeffO
sumber
1

Tidak seharusnya tidak. Dan Anda benar-benar menjawab pertanyaan Anda sendiri ketika Anda menyatakan bahwa cara pengkodean ini dapat menyembunyikan bug . Ini tidak akan membuat kode lebih kuat - melainkan akan membuatnya lebih rentan terhadap bug dan membuat debugging lebih sulit.

Anda menyatakan harapan Anda saat ini tentang rowsargumen: Entah itu nol atau memiliki setidaknya satu item. Jadi pertanyaannya adalah: Apakah ide yang baik untuk menulis kode untuk menangani kasus ketiga yang tidak terduga di mana rowsnol item?

Jawabannya adalah tidak. Anda harus selalu memberikan pengecualian dalam hal input yang tidak terduga. Pertimbangkan ini: Jika beberapa bagian lain dari kode melanggar harapan (yaitu kontrak) dari metode Anda itu berarti ada bug . Jika ada bug yang ingin Anda ketahui sedini mungkin sehingga Anda dapat memperbaikinya, dan pengecualian akan membantu Anda melakukannya.

Apa yang dilakukan kode saat ini adalah menebak bagaimana memulihkan dari bug yang mungkin ada atau tidak ada dalam kode tersebut. Tetapi bahkan jika ada bug, Anda tidak tahu bagaimana memulihkan sepenuhnya dari itu. Bug menurut definisi memiliki konsekuensi yang tidak diketahui. Mungkin beberapa kode inisialisasi tidak berjalan seperti yang diharapkan mungkin memiliki banyak konsekuensi selain dari baris yang hilang.

Jadi kode Anda akan terlihat seperti ini:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    if (rows != null && rows.Count == 0) throw new ArgumentException("Rows is empty."); 

    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

Catatan: Ada beberapa kasus khusus di mana masuk akal untuk "menebak" bagaimana menangani input yang tidak valid daripada hanya melemparkan pengecualian. Misalnya, jika Anda menangani input eksternal, Anda tidak dapat mengendalikannya. Browser web adalah contoh yang terkenal karena mereka mencoba untuk menangani segala jenis input yang salah dan tidak valid. Tetapi ini hanya masuk akal dengan input eksternal, bukan dengan panggilan dari bagian lain dari program.


Sunting: Beberapa jawaban lain menyatakan bahwa Anda melakukan pemrograman defensif . Saya tidak setuju. Pemrograman defensif berarti Anda tidak secara otomatis memercayai input yang valid. Jadi validasi parameter (seperti di atas) adalah teknik pemrograman defensif, tetapi itu tidak berarti Anda harus mengubah input yang tidak terduga atau tidak valid dengan menebak. Pendekatan defensif yang kuat adalah untuk memvalidasi input dan kemudian melemparkan pengecualian jika input yang tidak terduga atau tidak valid.

JacquesB
sumber
1

Haruskah saya menambahkan kode berlebihan sekarang kalau-kalau mungkin diperlukan di masa depan?

Anda tidak boleh menambahkan kode berlebihan kapan saja.

Anda tidak boleh menambahkan kode yang hanya dibutuhkan di masa mendatang.

Anda harus memastikan bahwa kode Anda berperilaku baik apa pun yang terjadi.

Definisi "berperilaku baik" sesuai dengan kebutuhan Anda. Salah satu teknik yang saya suka gunakan adalah pengecualian "paranoia". Jika saya 100% yakin bahwa kasus tertentu tidak akan pernah terjadi, saya masih memprogram pengecualian, tetapi saya melakukannya dengan cara yang a) dengan jelas memberi tahu semua orang bahwa saya tidak pernah berharap ini terjadi, dan b) ditampilkan dengan jelas dan dicatat dengan demikian tidak menyebabkan korupsi yang menjalar di kemudian hari.

Contoh pseudo-code:

file = File.open(">", "bla")  or raise "Paranoia: cannot open file 'bla'"

file.write("xytz") or raise "Paranoia: disk full?"

file.close()  or raise "Paranoia: huh?!?!?"

Ini dengan jelas mengomunikasikan bahwa saya 100% yakin bahwa saya selalu dapat membuka, menulis, atau menutup file, yaitu, saya tidak pergi sampai batas membuat penanganan kesalahan yang rumit. Tetapi jika (tidak: kapan) saya tidak dapat membuka file, program saya masih akan gagal secara terkendali.

Antarmuka pengguna tentu saja tidak akan menampilkan pesan tersebut kepada pengguna, mereka akan dicatat secara internal bersama dengan jejak tumpukan. Sekali lagi, ini adalah pengecualian "Paranoia" internal yang hanya memastikan bahwa kode "berhenti" ketika sesuatu yang tidak terduga terjadi. Contoh ini agak dirundung, dalam praktiknya saya tentu saja akan menerapkan penanganan kesalahan nyata untuk kesalahan saat membuka file, karena ini terjadi secara teratur (nama file yang salah, USB stick mount read-only, apa pun).

Istilah pencarian terkait yang sangat penting adalah "gagal cepat", sebagaimana dicatat dalam jawaban lain dan sangat berguna untuk membuat perangkat lunak yang tangguh.

AnoE
sumber
-1

Ada banyak jawaban yang terlalu rumit di sini. Anda mungkin bertanya pertanyaan ini karena Anda merasa tidak benar tentang kode bagian itu tetapi tidak yakin mengapa atau bagaimana cara memperbaikinya. Jadi jawaban saya adalah bahwa masalahnya sangat mungkin dalam struktur kode (seperti biasa).

Pertama, header metode:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)

Tetapkan janji temu untuk baris apa ? Itu harus segera dihapus dari daftar parameter. Tanpa pengetahuan lebih lanjut, saya harapkan metode params terlihat seperti ini: (Appointment app, CalendarRow row).

Selanjutnya, "input check":

//1. Is rows equal to null? - This will be the case if this is the first appointment.
if (rows == null) {
    rows = new List<CalendarRow> ();
}

//2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
if(rows.Count == 0)
{
    rows.Add (new CalendarRow (0));
    rows [0].Appointments.Add (app);
}

Ini omong kosong.

  1. check) Pemanggil metode harus memastikan tidak melewati nilai yang tidak diinisialisasi di dalam metode. Ini adalah tanggung jawab programmer (untuk mencoba) untuk tidak menjadi bodoh.
  2. check) Jika saya tidak memperhitungkan bahwa meneruskan rowske metode mungkin salah (lihat komentar di atas), maka seharusnya tidak menjadi tanggung jawab dari metode yang dipanggil AssignAppointmentToRowuntuk memanipulasi baris dengan cara lain selain menugaskan janji di suatu tempat.

Tetapi seluruh konsep menugaskan suatu tempat aneh (kecuali jika ini adalah bagian dari kode GUI). Kode Anda tampaknya mengandung (atau setidaknya itu mencoba) suatu struktur data eksplisit yang mewakili kalender (yaitu List<CalendarRows><- yang harus didefinisikan sebagai Calendarsuatu tempat jika Anda ingin pergi dengan cara ini, maka Anda akan meneruskan Calendar calendarke metode Anda). Jika Anda pergi dengan cara ini, saya berharap calendarakan diawali dengan slot di mana Anda menempatkan (menetapkan) janji sesudahnya (misalnya calendar[month][day] = appointmentakan menjadi kode yang sesuai). Tapi kemudian Anda juga dapat memilih untuk membuang struktur kalender dari logika utama sekaligus dan hanya memiliki List<Appointment>tempat Appointmentobjek berisi atributdate. Dan kemudian jika Anda perlu membuat kalender di suatu tempat di GUI, Anda dapat membuat struktur 'kalender eksplisit' ini sebelum merender.

Saya tidak tahu detail aplikasi Anda sehingga beberapa di antaranya mungkin tidak berlaku untuk Anda tetapi kedua pemeriksaan ini (terutama yang kedua) memberi tahu saya bahwa ada sesuatu yang salah dengan pemisahan masalah dalam kode Anda.

iklim
sumber
-2

Untuk kesederhanaan, mari kita asumsikan bahwa Anda pada akhirnya akan membutuhkan kode ini dalam N hari (tidak lebih lambat atau lebih awal) atau tidak perlu sama sekali.

Kodesemu:

let C_now   = cost of implementing the piece of code now
let C_later = ... later
let C_maint = cost of maintaining the piece of code one day
              (note that it can be negative)
let P_need  = probability that you will need the code after N days

if C_now + C_maint * N < P_need*C_later then implement the code else omit it.

Faktor-faktor untuk C_maint:

  • Apakah ini meningkatkan kode secara umum, membuatnya lebih mendokumentasikan diri sendiri, lebih mudah untuk diuji? Jika ya, C_maintdiharapkan negatif
  • Apakah ini membuat kode lebih besar (karenanya lebih sulit dibaca, lebih lama untuk dikompilasi, melaksanakan tes, dll)?
  • Apakah ada refactor / desain ulang yang tertunda? Jika ya, benjolan C_maint. Kasus ini memerlukan rumus yang lebih kompleks, dengan variabel yang lebih sering suka N.

Begitu besar hal yang hanya memberatkan kode dan mungkin diperlukan hanya dalam 2 tahun dengan probabilitas rendah harus ditinggalkan, tetapi hal kecil yang juga menghasilkan pernyataan yang berguna dan 50% bahwa itu akan diperlukan dalam 3 bulan, itu harus dilaksanakan .

Vi0
sumber
Anda juga perlu memperhitungkan biaya bug yang mungkin masuk ke program karena Anda tidak menolak input yang tidak valid. Jadi, bagaimana Anda memperkirakan biaya potensi bug yang sulit ditemukan?
JacquesB