Memperkuat kode dengan penanganan pengecualian yang mungkin tidak berguna

12

Apakah ini praktik yang baik untuk menerapkan penanganan pengecualian yang tidak berguna, kalau-kalau bagian lain dari kode tidak dikodekan dengan benar?

Contoh dasar

Yang sederhana, jadi saya tidak kehilangan semuanya :).

Katakanlah saya sedang menulis aplikasi yang akan menampilkan informasi seseorang (nama, alamat, dll.), Data yang diekstrak dari basis data. Katakanlah saya yang mengkode bagian UI, dan orang lain sedang menulis kode permintaan DB.

Sekarang bayangkan bahwa spesifikasi aplikasi Anda mengatakan bahwa jika informasi orang tersebut tidak lengkap (katakanlah, nama tersebut tidak ada dalam database), orang yang mengkode kueri harus menangani ini dengan mengembalikan "NA" untuk bidang yang hilang.

Bagaimana jika kueri memiliki kode yang buruk dan tidak menangani kasus ini? Bagaimana jika orang yang menulis kueri menangani Anda dengan hasil yang tidak lengkap, dan ketika Anda mencoba untuk menampilkan informasi, semuanya macet, karena kode Anda tidak siap untuk menampilkan barang kosong?

Contoh ini sangat mendasar. Saya percaya sebagian besar dari Anda akan berkata "itu bukan masalah Anda, Anda tidak bertanggung jawab atas kecelakaan ini". Tapi, itu masih bagian dari kode Anda yang sedang crash.

Contoh lain

Katakanlah sekarang akulah yang menulis kueri. Spesifikasi tidak mengatakan hal yang sama seperti di atas, tetapi orang yang menulis kueri "sisipkan" harus memastikan semua bidang sudah lengkap saat menambahkan seseorang ke database untuk menghindari memasukkan informasi yang tidak lengkap. Haruskah saya melindungi permintaan "pilih" untuk memastikan saya memberikan informasi lengkap kepada orang UI?

Pertanyaan-pertanyaan

Bagaimana jika spesifikasinya tidak secara jelas mengatakan "orang ini yang bertanggung jawab menangani situasi ini"? Bagaimana jika orang ketiga mengimplementasikan kueri lain (mirip dengan yang pertama, tetapi pada DB lain) dan menggunakan kode UI Anda untuk menampilkannya, tetapi tidak menangani kasus ini dalam kodenya?

Haruskah saya melakukan apa yang perlu untuk mencegah kemungkinan tabrakan, bahkan jika saya bukan orang yang seharusnya menangani kasus buruk?

Saya tidak mencari jawaban seperti "(dia) yang bertanggung jawab atas kecelakaan itu", karena saya tidak menyelesaikan konflik di sini, saya ingin tahu, haruskah saya melindungi kode saya terhadap situasi itu bukan tanggung jawab saya untuk menangani? Di sini, cukup "jika kosong melakukan sesuatu" sudah cukup.

Secara umum, pertanyaan ini menangani penanganan pengecualian yang berlebihan. Saya menanyakannya karena ketika saya bekerja sendirian di sebuah proyek, saya dapat mengkodekan 2-3 kali pengecualian yang sama yang menangani fungsi berturut-turut, "untuk berjaga-jaga" Saya melakukan sesuatu yang salah dan membiarkan kasus buruk masuk.

randur
sumber
4
Anda berbicara tentang "tes", tetapi sejauh yang saya mengerti masalah Anda maksud Anda "tes yang diterapkan dalam produksi", ini lebih baik disebut "validasi" atau "penanganan pengecualian".
Doc Brown
1
Ya, kata yang tepat adalah "penanganan pengecualian".
rdurand
kemudian mengubah tag yang salah
Doc Brown
Saya merujuk Anda ke The DailyWTF - Anda yakin ingin melakukan pengujian semacam ini?
gbjbaanb
@ gbjbaanb: Jika saya memahami tautan Anda dengan benar, itu sama sekali bukan yang saya bicarakan. Saya tidak berbicara tentang "tes bodoh", saya berbicara tentang menggandakan penanganan pengecualian.
rdurand

Jawaban:

14

Yang Anda bicarakan di sini adalah batas kepercayaan . Apakah Anda mempercayai batas antara aplikasi Anda dan basis data? Apakah database percaya bahwa data dari aplikasi selalu divalidasi sebelumnya?

Itu keputusan yang harus dibuat dalam setiap aplikasi dan tidak ada jawaban benar dan salah. Saya cenderung berbuat salah dengan menyebut terlalu banyak batas sebagai batas kepercayaan, pengembang lain akan dengan senang hati mempercayai API pihak ketiga untuk melakukan apa yang Anda harapkan, setiap saat, setiap saat.

pdr
sumber
5

Prinsip kekokohan "Jadilah konservatif dalam apa yang Anda kirim, menjadi liberal dalam apa yang Anda terima" adalah apa yang Anda cari. Ini adalah prinsip yang baik - EDIT: selama aplikasinya tidak menyembunyikan kesalahan serius - tapi saya setuju dengan @ pdr bahwa itu selalu tergantung pada situasi apakah Anda harus menerapkannya atau tidak.

Doc Brown
sumber
Beberapa orang berpikir bahwa "prinsip ketahanan" adalah omong kosong. Artikel itu memberi contoh.
@ MattFenwick: terima kasih telah menunjukkan itu, ini adalah poin yang valid, saya telah sedikit mengubah jawaban saya.
Doc Brown
2
Ini adalah artikel yang lebih baik lagi menunjukkan masalah dengan "prinsip ketahanan": joelonsoftware.com/items/2008/03/17.html
hakoja
1
@akoako: jujur, saya tahu artikel ini dengan baik, ini tentang masalah yang Anda dapatkan ketika Anda mulai tidak mengikuti prinsip ketahanan (seperti beberapa orang MS mencoba dengan versi IE yang lebih baru). Namun demikian, ini agak jauh dari pertanyaan awal.
Doc Brown
1
@DocBrown: itulah sebabnya Anda seharusnya tidak pernah liberal dalam apa yang Anda terima. Ketegaran tidak berarti bahwa Anda harus menerima semua yang dilemparkan kepada Anda tanpa keluhan, hanya saja Anda harus menerima semua yang dilemparkan kepada Anda tanpa menabrak.
Marjan Venema
1

Itu tergantung pada apa yang Anda uji; tapi mari kita asumsikan ruang lingkup pengujian Anda hanya kode Anda sendiri. Dalam hal ini, Anda harus menguji:

  • "Happy case": beri makan input aplikasi Anda yang valid dan pastikan itu menghasilkan output yang benar.
  • Kasus kegagalan: beri makan input aplikasi Anda tidak valid dan pastikan itu menanganinya dengan benar.

Untuk melakukan ini, Anda tidak dapat menggunakan komponen rekan Anda: alih-alih, gunakan mengejek , yaitu mengganti sisa aplikasi dengan modul "palsu" yang dapat Anda kontrol dari kerangka pengujian. Bagaimana tepatnya Anda melakukan ini tergantung pada cara antarmuka modul; cukup dengan memanggil metode-metode modul Anda dengan argumen hard-coded, dan dapat menjadi serumit menulis seluruh kerangka kerja yang menghubungkan antarmuka publik modul-modul lain dengan lingkungan pengujian.

Itu hanya kasus uji unit. Anda juga ingin tes integrasi, tempat Anda menguji semua modul dalam konser. Sekali lagi, Anda ingin menguji baik kasus bahagia maupun kegagalan.

Dalam kasus "Contoh Dasar" Anda, untuk menguji unit kode Anda, tulis kelas tiruan yang mensimulasikan lapisan basis data. Kelas tiruan Anda tidak benar-benar pergi ke database: Anda hanya memuatnya dengan input yang diharapkan dan output tetap. Dalam pseudocode:

function test_ValidUser() {
    // set up mocking and fixtures
    userid = 23;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "Doe" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);
    expectedResult = "John Doe";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

Dan inilah cara Anda menguji bidang yang hilang yang dilaporkan dengan benar :

function test_IncompleteUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "NA" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // let's say the user controller is specified to leave "NA" fields 
    // blank
    expectedResult = "John";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

Sekarang semuanya menjadi menarik. Bagaimana jika kelas DB yang sebenarnya berkelakuan buruk? Sebagai contoh, itu bisa melempar pengecualian untuk alasan yang tidak jelas. Kami tidak tahu apakah itu benar, tetapi kami ingin kode kami sendiri menanganinya dengan baik. Tidak masalah, kita hanya perlu membuat pengecualian MockDB kita, misalnya dengan menambahkan metode seperti ini:

class MockDB {
    // ... snip
    function getUser(userid) {
        if (this.fixedException) {
            throw this.fixedException;
        }
        else {
            return this.fixedResult;
        }
    }
}

Dan kemudian uji kasus kami terlihat seperti ini:

function test_MisbehavingUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedException = new SQLException("You have an error in your SQL syntax");
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // run the actual test
    try {
        userController.displayUserAsString(userid);
    }
    catch (DatabaseException ex) {
        // This is good: our userController has caught the raw exception
        // from the database layer and wrapped it in a DatabaseException.
        return TEST_PASSED;
    }
    catch (Exception ex) {
        // This is not good: we have an exception, but it's the wrong kind.
        testLog.log("Found the wrong exception: " + ex);
        return TEST_FAILED;
    }
    // This is bad, too: either our mocking class didn't throw even when it
    // should have, or our userController swallowed the exception and
    // discarded it
    testLog.log("Expected an exception to be thrown, but nothing happened.");
    return TEST_FAILED;
}

Ini adalah unit test Anda. Untuk tes integrasi, Anda tidak menggunakan kelas MockDB; sebagai gantinya, Anda menghubungkan kedua kelas yang sebenarnya menjadi satu. Anda masih membutuhkan perlengkapan; misalnya, Anda harus menginisialisasi database uji ke kondisi yang diketahui sebelum menjalankan tes.

Sekarang, sejauh tanggung jawab berjalan: Kode Anda harus mengharapkan sisa basis kode diimplementasikan sesuai spesifikasi, tetapi juga harus siap untuk menangani hal-hal dengan anggun ketika sisanya rusak. Anda tidak bertanggung jawab untuk menguji kode lain selain Anda sendiri, tetapi Anda yang bertanggung jawab untuk membuat kode Anda tahan terhadap nakal kode pada ujung yang lain, dan Anda juga bertanggung jawab untuk menguji ketahanan kode Anda. Itulah yang dilakukan tes ketiga di atas.

tammmer
sumber
apakah Anda membaca komentar di bawah pertanyaan? OP menulis "tes", tetapi ia memaksudkannya dalam arti "pemeriksaan validasi" dan / atau "penanganan pengecualian"
Doc Brown
1
@tdammers: maaf atas kesalahpahaman, maksud saya sebenarnya penanganan pengecualian .. Terima kasih atas jawaban lengkapnya, paragraf terakhir adalah apa yang saya cari.
rdurand
1

Ada 3 prinsip utama yang saya coba kode dengan:

  • KERING

  • CIUMAN

  • YAGNI

Gosok semua ini adalah bahwa Anda berisiko menulis kode validasi yang digandakan di tempat lain. Jika aturan validasi berubah, ini perlu diperbarui di banyak tempat.

Tentu saja, di beberapa titik di masa depan, Anda mungkin mengganti platform database Anda (itu terjadi) dalam hal ini Anda mungkin berpikir memiliki kode di lebih dari satu tempat akan menguntungkan. Tapi ... Anda sedang mengkode sesuatu yang mungkin tidak terjadi.

Kode tambahan apa pun (meskipun tidak pernah berubah) adalah overhead karena perlu ditulis, dibaca, disimpan, dan diuji.

Semua hal di atas benar, Anda tidak boleh melakukan validasi sama sekali. Untuk menampilkan nama lengkap dalam aplikasi, Anda akan memerlukan beberapa data dasar - bahkan jika Anda tidak memvalidasi data itu sendiri.

Robbie Dee
sumber
1

Dalam kata-kata awam.

Tidak ada yang namanya "database" atau "aplikasi" .

  1. Database dapat digunakan oleh lebih dari satu aplikasi.
  2. Aplikasi dapat menggunakan lebih dari satu basis data.
  3. Model database harus menegakkan integritas data, yang termasuk melempar kesalahan ketika bidang yang diperlukan tidak dimasukkan dalam operasi penyisipan, kecuali nilai default didefinisikan dalam definisi tabel. Ini harus dilakukan bahkan jika Anda memasukkan baris langsung ke basis data melewati aplikasi. Biarkan sistem database melakukannya untuk Anda.
  4. Database harus menjaga integritas data dan membuat kesalahan .
  5. Logika bisnis harus menangkap kesalahan itu dan melemparkan pengecualian ke lapisan presentasi.
  6. Lapisan presentasi harus memvalidasi input, menangani pengecualian atau menunjukkan hamster yang sedih kepada pengguna.

Lagi:

  • Database-> kesalahan melempar
  • Logika Bisnis-> menangkap kesalahan dan melempar pengecualian
  • Layer Presentasi-> memvalidasi, melempar pengecualian atau menampilkan pesan sedih.
Tulains Córdova
sumber