การเสริมความแข็งแกร่งโค้ดด้วยการจัดการข้อยกเว้นที่ไร้ประโยชน์อาจเป็นไปได้


12

เป็นวิธีปฏิบัติที่ดีที่จะใช้การจัดการข้อยกเว้นที่ไร้ประโยชน์หรือไม่ในกรณีที่ส่วนอื่นของรหัสไม่ได้เข้ารหัสอย่างถูกต้อง?

ตัวอย่างพื้นฐาน

คนที่เรียบง่ายดังนั้นฉันจึงไม่สูญเสียทุกคน :)

สมมติว่าฉันกำลังเขียนแอพที่จะแสดงข้อมูลของบุคคล (ชื่อที่อยู่ ฯลฯ ) ข้อมูลที่ถูกแยกออกจากฐานข้อมูล สมมติว่าฉันเป็นผู้เขียนส่วน UI และคนอื่นกำลังเขียนรหัสแบบสอบถาม DB

ทีนี้ลองจินตนาการว่าข้อมูลจำเพาะของแอพของคุณบอกว่าถ้าข้อมูลของบุคคลนั้นไม่สมบูรณ์ (สมมติว่าชื่อหายไปในฐานข้อมูล) ผู้ที่เข้ารหัสแบบสอบถามควรจัดการสิ่งนี้โดยส่งกลับ "NA" สำหรับฟิลด์ที่หายไป

เกิดอะไรขึ้นถ้าแบบสอบถามมีการเข้ารหัสไม่ดีและไม่จัดการกรณีนี้ จะเป็นอย่างไรถ้าคนที่เขียนข้อความค้นหาจัดการกับผลลัพธ์ที่ไม่สมบูรณ์และเมื่อคุณพยายามที่จะแสดงข้อมูลทุกอย่างก็ขัดข้องเพราะรหัสของคุณไม่ได้เตรียมที่จะแสดงสิ่งที่ว่างเปล่า?

ตัวอย่างนี้เป็นพื้นฐานมาก ฉันเชื่อว่าพวกคุณส่วนใหญ่จะพูดว่า "ไม่ใช่ปัญหาของคุณคุณไม่ต้องรับผิดชอบต่อความผิดพลาดนี้" แต่ก็ยังคงเป็นส่วนหนึ่งของรหัสของคุณที่ขัดข้อง

ตัวอย่างอื่น

สมมติว่าตอนนี้ฉันเป็นคนเขียนแบบสอบถาม ข้อกำหนดไม่ได้พูดเหมือนกับข้างต้น แต่คนที่เขียนแบบสอบถาม "แทรก" ควรตรวจสอบให้แน่ใจว่าเขตข้อมูลทั้งหมดจะเสร็จสมบูรณ์เมื่อเพิ่มบุคคลในฐานข้อมูลเพื่อหลีกเลี่ยงการแทรกข้อมูลที่ไม่สมบูรณ์ ฉันควรจะปกป้องแบบสอบถาม "เลือก" ของฉันเพื่อให้แน่ใจว่าฉันให้ข้อมูลที่สมบูรณ์เกี่ยวกับ UI หรือไม่

คำถาม

ถ้าข้อมูลจำเพาะไม่ได้พูดอย่างชัดเจนว่า "คนนี้เป็นคนที่รับผิดชอบในการจัดการสถานการณ์นี้"? ถ้าบุคคลที่สามใช้แบบสอบถามอื่น (คล้ายกับแบบสอบถามแรก แต่ใช้ฐานข้อมูลอื่น) และใช้รหัส UI ของคุณเพื่อแสดง แต่ไม่จัดการกรณีนี้ในรหัสของเขา

ฉันควรทำสิ่งที่จำเป็นเพื่อป้องกันความผิดพลาดที่อาจเกิดขึ้นแม้ว่าฉันจะไม่ใช่คนที่ควรจัดการกับคดีที่ไม่ดี?

ฉันไม่ได้มองหาคำตอบเช่น "(s) เขาเป็นคนรับผิดชอบต่อความผิดพลาด" เนื่องจากฉันไม่ได้แก้ไขข้อขัดแย้งที่นี่ฉันอยากรู้ว่าฉันควรป้องกันโค้ดของฉันจากสถานการณ์ที่ไม่ใช่ความรับผิดชอบของฉันหรือไม่ ที่จะจัดการ? ที่นี่ง่าย ๆ "ถ้าว่างทำอะไร" จะพอเพียง

โดยทั่วไปแล้วคำถามนี้จะจัดการกับข้อยกเว้นที่ซ้ำซ้อน ฉันถามเพราะเมื่อฉันทำงานคนเดียวในโครงการฉันอาจเขียนโค้ดข้อยกเว้นที่คล้ายกัน 2-3 ครั้งในฟังก์ชั่นที่ต่อเนื่องกัน "ในกรณี" ฉันทำอะไรผิดพลาดและปล่อยให้คดีแย่ ๆ


4
คุณกำลังพูดถึง "การทดสอบ" แต่เท่าที่ฉันเข้าใจปัญหาของคุณคุณหมายถึง "การทดสอบที่ใช้ในการผลิต" นี้เรียกว่า "การตรวจสอบความถูกต้อง" หรือ "การจัดการข้อยกเว้น" ที่ดีกว่า
Doc Brown

1
ใช่คำที่เหมาะสมคือ "การจัดการข้อยกเว้น"
rdurand

เปลี่ยนแท็กที่ไม่ถูกต้องแล้ว
Doc Brown

ฉันแนะนำคุณให้รู้จักกับ The DailyWTF - คุณแน่ใจหรือว่าต้องการทำการทดสอบประเภทนี้?
gbjbaanb

@gbjbaanb: ถ้าฉันเข้าใจลิงก์ของคุณถูกต้องนั่นไม่ใช่สิ่งที่ฉันกำลังพูดถึง ฉันไม่ได้พูดถึง "การทดสอบโง่ ๆ " ฉันกำลังพูดถึงการทำข้อยกเว้นซ้ำ
rdurand

คำตอบ:


14

สิ่งที่คุณกำลังพูดถึงนี่คือขอบเขตของความไว้วางใจ คุณเชื่อถือขอบเขตระหว่างแอปพลิเคชันของคุณกับฐานข้อมูลหรือไม่? ฐานข้อมูลเชื่อถือได้หรือไม่ว่าข้อมูลจากแอปพลิเคชันนั้นได้รับการตรวจสอบล่วงหน้าหรือไม่

นั่นเป็นการตัดสินใจที่จะต้องทำในทุกแอปพลิเคชันและไม่มีคำตอบที่ถูกและผิด ฉันมักจะทำผิดพลาดในด้านของการเรียกขอบเขตที่ไว้ใจมากเกินไปนักพัฒนาอื่น ๆ จะไว้วางใจ API ของบุคคลที่สามอย่างมีความสุขเพื่อทำสิ่งที่คุณคาดหวังให้พวกเขาทำตลอดเวลาทุกครั้ง


5

หลักการความแข็งแกร่ง"จงระมัดระวังในสิ่งที่คุณส่งให้มีอิสระในสิ่งที่คุณยอมรับ"คือสิ่งที่คุณเป็น มันเป็นหลักการที่ดี - แก้ไข: ตราบใดที่แอปพลิเคชันไม่ได้ซ่อนข้อผิดพลาดร้ายแรงใด ๆ - แต่ฉันเห็นด้วยกับ @pdr ว่ามันจะขึ้นอยู่กับสถานการณ์เสมอถ้าคุณควรใช้มันหรือไม่


บางคนคิดว่า "หลักการความแข็งแกร่ง" เป็นเรื่องไร้สาระ บทความแสดงตัวอย่าง

@ MattFenwick: ขอบคุณที่ชี้ให้เห็นว่ามันเป็นจุดที่ถูกต้องฉันได้เปลี่ยนคำตอบของฉันเล็กน้อย
Doc Brown

2
นี่เป็นบทความที่ดียิ่งขึ้นชี้ให้เห็นถึงปัญหากับ "หลักการความทนทาน": joelonsoftware.com/items/2008/03/17.html
hakoja

1
@hakoja: โดยสุจริตฉันรู้ว่าบทความนี้เป็นเรื่องเกี่ยวกับปัญหาที่คุณได้รับเมื่อคุณเริ่มที่จะไม่ปฏิบัติตามหลักการความแข็งแกร่ง (เช่น MS บางคนพยายามกับ IE รุ่นใหม่กว่า) อย่างไรก็ตามสิ่งนี้อยู่ไกลจากคำถามเดิมเล็กน้อย
Doc Brown

1
@DocBrown: ซึ่งเป็นเหตุผลว่าทำไมคุณไม่ควรมีแนวคิดเสรีในสิ่งที่คุณยอมรับ ความทนทานไม่ได้หมายความว่าคุณต้องยอมรับทุกสิ่งที่คุณโยนโดยไม่มีการร้องเรียนเพียงว่าคุณต้องยอมรับทุกสิ่งที่คุณโยนโดยไม่กระแทก
Marjan Venema

1

ขึ้นอยู่กับว่าคุณกำลังทดสอบอะไร แต่สมมติว่าขอบเขตการทดสอบของคุณเป็นรหัสของคุณเท่านั้น ในกรณีนั้นคุณควรทดสอบ:

  • "กรณีที่มีความสุข": ฟีดอินพุตที่ถูกต้องของแอปพลิเคชันของคุณและตรวจสอบให้แน่ใจว่ามันสร้างผลลัพธ์ที่ถูกต้อง
  • กรณีล้มเหลว: ป้อนแอปพลิเคชันของคุณให้ป้อนข้อมูลไม่ถูกต้องและตรวจสอบให้แน่ใจว่าจัดการได้อย่างถูกต้อง

ในการทำเช่นนี้คุณไม่สามารถใช้องค์ประกอบของเพื่อนร่วมงานของคุณได้: ใช้การเยาะเย้ยแทนการใช้ส่วนที่เหลือของแอปพลิเคชันด้วยโมดูล "ปลอม" ที่คุณสามารถควบคุมได้จากกรอบการทดสอบ วิธีการที่คุณทำสิ่งนี้ขึ้นอยู่กับวิธีการที่โมดูลอินเตอร์เฟซ; อาจเพียงพอที่จะเรียกวิธีการของโมดูลของคุณด้วยอาร์กิวเมนต์ที่กำหนดรหัสยากและอาจซับซ้อนได้เช่นเดียวกับการเขียนกรอบงานทั้งหมดที่เชื่อมต่อส่วนต่อประสานสาธารณะของโมดูลอื่นกับสภาพแวดล้อมการทดสอบ

นั่นเป็นเพียงกรณีทดสอบหน่วย นอกจากนี้คุณยังต้องการทดสอบการรวมที่คุณทดสอบโมดูลทั้งหมดในคอนเสิร์ต อีกครั้งคุณต้องการทดสอบทั้งกรณีที่มีความสุขและความล้มเหลว

ในกรณี "ตัวอย่างพื้นฐาน" ของคุณในการทดสอบหน่วยของคุณให้เขียนคลาสจำลองที่จำลองเลเยอร์ฐานข้อมูล คลาสจำลองของคุณไม่ได้ไปที่ฐานข้อมูลจริงๆ: คุณเพียงโหลดล่วงหน้าด้วยอินพุตที่คาดหวังและเอาต์พุตคงที่ ใน 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();
}

และนี่คือวิธีที่คุณจะทดสอบฟิลด์ที่หายไปที่รายงานอย่างถูกต้อง :

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();
}

ตอนนี้สิ่งต่าง ๆ น่าสนใจ เกิดอะไรขึ้นถ้าคลาส DB ที่แท้จริงทำงานผิดปกติ? ตัวอย่างเช่นอาจทำให้เกิดข้อยกเว้นด้วยเหตุผลที่ไม่ชัดเจน เราไม่รู้ว่ามันเป็นเช่นนั้นหรือไม่ แต่เราต้องการให้โค้ดของเราจัดการได้อย่างสง่างาม ไม่มีปัญหาเราแค่ทำให้ MockDB ของเรามีข้อยกเว้นเช่นโดยการเพิ่มวิธีดังนี้:

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

จากนั้นกรณีทดสอบของเราจะเป็นดังนี้:

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;
}

นี่คือการทดสอบหน่วยของคุณ สำหรับการทดสอบการรวมคุณจะไม่ใช้คลาส MockDB คุณโยงทั้งคลาสจริงเข้าด้วยกันแทน คุณยังต้องติดตั้ง ตัวอย่างเช่นคุณควรเริ่มต้นฐานข้อมูลการทดสอบเป็นสถานะที่รู้จักก่อนที่จะทำการทดสอบ

ตอนนี้ตราบใดที่ความรับผิดชอบ: รหัสของคุณควรคาดหวังว่าส่วนที่เหลือของ codebase จะดำเนินการตามข้อกำหนด แต่ก็ควรเตรียมที่จะจัดการสิ่งต่าง ๆ อย่างสง่างามเมื่อส่วนที่เหลือกรูขึ้น คุณจะไม่รับผิดชอบสำหรับการทดสอบรหัสอื่นที่ไม่ใช่ของตัวเอง แต่คุณมีความรับผิดชอบในการทำให้รหัสของคุณมีความยืดหยุ่นในการทำงานไม่รหัสในส่วนอื่น ๆ และคุณยังมีความรับผิดชอบสำหรับการทดสอบความยืดหยุ่นรหัสของคุณ นั่นคือสิ่งที่การทดสอบที่สามด้านบนทำ


คุณอ่านความคิดเห็นด้านล่างคำถามหรือไม่ OP เขียนว่า "การทดสอบ" แต่เขาหมายถึงมันในแง่ของ "การตรวจสอบความถูกต้อง" และ / หรือ "การจัดการข้อยกเว้น"
Doc Brown

1
@ ผู้ส่งอีเมล: ขออภัยในความเข้าใจผิดฉันหมายถึงการจัดการข้อยกเว้นจริง .. ขอบคุณสำหรับคำตอบที่สมบูรณ์ย่อหน้าสุดท้ายคือสิ่งที่ฉันกำลังมองหา
rdurand

1

มีหลักการ 3 ข้อที่ฉันพยายามเขียนรหัสด้วย:

  • แห้ง

  • จูบ

  • YAGNI

การลบสิ่งเหล่านี้คือคุณเสี่ยงที่จะเขียนรหัสการตรวจสอบความถูกต้องที่ทำซ้ำที่อื่น หากกฎการตรวจสอบมีการเปลี่ยนแปลงจะต้องมีการอัปเดตสิ่งเหล่านี้ในหลาย ๆ ที่

แน่นอนในบางจุดในอนาคตคุณอาจจำลองฐานข้อมูลของคุณ (เกิดขึ้น) ซึ่งในกรณีนี้คุณอาจคิดว่าการมีรหัสในที่มากกว่าหนึ่งแห่งจะเป็นประโยชน์ แต่ ... คุณกำลังเขียนโค้ดเพื่อสิ่งที่อาจไม่เกิดขึ้น

รหัสเพิ่มเติมใด ๆ (แม้ว่าจะไม่เคยเปลี่ยนแปลง) เป็นค่าใช้จ่ายเพราะจะต้องมีการเขียนอ่านจัดเก็บและทดสอบ

จากทั้งหมดที่กล่าวมาเป็นความจริงคุณจะไม่ได้รับการตรวจสอบ แต่อย่างใด ในการแสดงชื่อเต็มในแอปพลิเคชันคุณจะต้องมีข้อมูลพื้นฐานบางอย่างแม้ว่าคุณจะไม่ได้ตรวจสอบความถูกต้องของข้อมูลก็ตาม


1

ในคำพูดของคนธรรมดา

ไม่มีสิ่งดังกล่าวเป็น"ฐานข้อมูล"หรือ"การประยุกต์ใช้"

  1. สามารถใช้ฐานข้อมูลได้มากกว่าหนึ่งแอปพลิเคชั่น
  2. แอปพลิเคชันสามารถใช้ฐานข้อมูลมากกว่าหนึ่ง
  3. โมเดลฐานข้อมูลควรบังคับใช้ความถูกต้องของข้อมูลซึ่งรวมถึงการขว้างข้อผิดพลาดเมื่อฟิลด์ที่จำเป็นต้องใช้ไม่รวมอยู่ในการดำเนินการแทรกเว้นแต่จะมีการกำหนดค่าเริ่มต้นในนิยามของตาราง สิ่งนี้จะต้องทำแม้ว่าคุณจะแทรกแถวลงในฐานข้อมูลโดยตรงผ่านแอพ ปล่อยให้ระบบฐานข้อมูลทำเพื่อคุณ
  4. ฐานข้อมูลควรป้องกันความสมบูรณ์ของข้อมูลและข้อผิดพลาดจากเส้นข้าง
  5. ตรรกะทางธุรกิจต้องตรวจจับข้อผิดพลาดเหล่านั้นและส่งข้อยกเว้นไปยังเลเยอร์การนำเสนอ
  6. เลเยอร์การนำเสนอต้องตรวจสอบความถูกต้องของอินพุตจัดการข้อยกเว้นหรือแสดงแฮมสเตอร์ที่น่าเศร้าแก่ผู้ใช้

อีกครั้ง:

  • ฐานข้อมูล -> โยนข้อผิดพลาด
  • ตรรกะทางธุรกิจ -> ตรวจจับข้อผิดพลาดและส่งข้อยกเว้น
  • เลเยอร์การนำเสนอ -> ตรวจสอบโยนข้อยกเว้นหรือแสดงข้อความที่น่าเศร้า
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.