Tăng cường mã với khả năng xử lý ngoại lệ vô dụng


12

Đó có phải là một thực tiễn tốt để thực hiện xử lý ngoại lệ vô dụng, chỉ trong trường hợp một phần khác của mã không được mã hóa chính xác?

Ví dụ cơ bản

Một điều đơn giản, vì vậy tôi không mất tất cả mọi người :).

Giả sử tôi đang viết một ứng dụng sẽ hiển thị thông tin của một người (tên, địa chỉ, v.v.), dữ liệu được trích xuất từ ​​cơ sở dữ liệu. Giả sử tôi là người mã hóa phần UI và ai đó đang viết mã truy vấn DB.

Bây giờ hãy tưởng tượng rằng các thông số kỹ thuật của ứng dụng của bạn nói rằng nếu thông tin của người đó không đầy đủ (giả sử, tên bị thiếu trong cơ sở dữ liệu), người mã hóa truy vấn sẽ xử lý việc này bằng cách trả về "NA" cho trường bị thiếu.

Điều gì xảy ra nếu truy vấn được mã hóa kém và không xử lý trường hợp này? Điều gì sẽ xảy ra nếu anh chàng viết truy vấn xử lý cho bạn một kết quả không đầy đủ và khi bạn cố gắng hiển thị thông tin, mọi thứ sẽ gặp sự cố, vì mã của bạn không được chuẩn bị để hiển thị nội dung trống?

Ví dụ này rất cơ bản. Tôi tin rằng hầu hết các bạn sẽ nói "đó không phải là vấn đề của bạn, bạn không chịu trách nhiệm cho vụ tai nạn này". Nhưng, nó vẫn là một phần của mã bị lỗi.

Một vi dụ khac

Hãy nói bây giờ tôi là người viết truy vấn. Các thông số kỹ thuật không nói giống như trên, nhưng anh chàng viết truy vấn "chèn" phải đảm bảo tất cả các trường đã hoàn tất khi thêm một người vào cơ sở dữ liệu để tránh chèn thông tin không đầy đủ. Tôi có nên bảo vệ truy vấn "chọn" của mình để đảm bảo rằng tôi cung cấp cho anh chàng UI thông tin đầy đủ không?

Những câu hỏi

Điều gì xảy ra nếu các thông số kỹ thuật không nói rõ ràng "anh chàng này là người chịu trách nhiệm xử lý tình huống này"? Điều gì sẽ xảy ra nếu người thứ ba thực hiện một truy vấn khác (tương tự như truy vấn đầu tiên, nhưng trên DB khác) và sử dụng mã UI của bạn để hiển thị nó, nhưng không xử lý trường hợp này trong mã của anh ta?

Tôi có nên làm những gì cần thiết để ngăn chặn sự cố có thể xảy ra, ngay cả khi tôi không phải là người phải xử lý trường hợp xấu?

Tôi không tìm kiếm câu trả lời như "(anh ấy) là người chịu trách nhiệm cho vụ tai nạn", vì tôi không giải quyết xung đột ở đây, tôi muốn biết, liệu tôi có nên bảo vệ mã của mình trước các tình huống không phải là trách nhiệm của tôi giải quyết? Ở đây, một "nếu trống làm một cái gì đó" đơn giản sẽ đủ.

Nói chung, câu hỏi này giải quyết xử lý ngoại lệ dư thừa. Tôi đang hỏi nó bởi vì khi tôi làm việc một mình trong một dự án, tôi có thể mã hóa 2-3 lần một xử lý ngoại lệ tương tự trong các hàm liên tiếp, "chỉ trong trường hợp" tôi đã làm gì đó sai và để một trường hợp xấu xảy ra.


4
Bạn đang nói về "các thử nghiệm", nhưng theo tôi hiểu vấn đề của bạn, bạn có nghĩa là "các thử nghiệm được áp dụng trong sản xuất", điều này tốt hơn được gọi là "xác nhận" hoặc "xử lý ngoại lệ".
Doc Brown

1
Có, từ thích hợp là "xử lý ngoại lệ".
lần thứ

sau đó đã thay đổi thẻ sai
Doc Brown

Tôi giới thiệu bạn đến The DailyWTF - bạn có chắc chắn muốn thực hiện loại thử nghiệm này không?
gbjbaanb

@gbjbaanb: Nếu tôi hiểu chính xác liên kết của bạn, đó không phải là tất cả những gì tôi đang nói. Tôi không nói về "các bài kiểm tra ngu ngốc", tôi đang nói về việc sao chép xử lý ngoại lệ.
ndurand

Câu trả lời:


14

Những gì bạn đang nói ở đây là ranh giới tin cậy . Bạn có tin tưởng ranh giới giữa ứng dụng của bạn và cơ sở dữ liệu không? Cơ sở dữ liệu có tin tưởng rằng dữ liệu từ ứng dụng luôn được xác thực trước không?

Đó là một quyết định phải được đưa ra trong mọi ứng dụng và không có câu trả lời đúng và sai. Tôi có xu hướng sai lầm khi gọi quá nhiều ranh giới là ranh giới tin cậy, các nhà phát triển khác sẽ vui vẻ tin tưởng API của bên thứ ba để làm những gì bạn mong muốn họ làm mọi lúc, mọi lúc.


5

Nguyên tắc mạnh mẽ "Hãy thận trọng trong những gì bạn gửi, hãy tự do trong những gì bạn chấp nhận" là những gì bạn đang theo đuổi. Đó là một nguyên tắc tốt - EDIT: miễn là ứng dụng của nó không ẩn bất kỳ lỗi nghiêm trọng nào - nhưng tôi đồng ý với @pdr rằng nó luôn phụ thuộc vào tình huống bạn có nên áp dụng hay không.


Một số người nghĩ rằng "nguyên tắc mạnh mẽ" là tào lao. Bài báo đưa ra một ví dụ.

@MattFenwick: cảm ơn bạn đã chỉ ra rằng, đó là một điểm hợp lệ, tôi đã thay đổi câu trả lời của mình một chút.
Doc Brown

2
Đây là một bài viết thậm chí còn tốt hơn chỉ ra các vấn đề với "nguyên tắc mạnh mẽ": joelonsoftware.com/items/2008/03/17.html
hakoja

1
@hakoja: Thành thật mà nói, tôi biết rõ bài viết này, đó là về những vấn đề bạn gặp phải khi bạn bắt đầu không tuân theo nguyên tắc mạnh mẽ (như một số người MS đã thử với các phiên bản IE mới hơn). Tuy nhiên, điều này hơi xa câu hỏi ban đầu.
Doc Brown

1
@DocBrown: đó chính xác là lý do tại sao bạn không bao giờ nên tự do trong những gì bạn chấp nhận. Mạnh mẽ không có nghĩa là bạn cần chấp nhận mọi thứ ném vào bạn mà không phàn nàn, chỉ là bạn cần chấp nhận mọi thứ ném vào bạn mà không gặp sự cố.
Marjan Venema

1

Nó phụ thuộc vào những gì bạn đang thử nghiệm; nhưng hãy giả sử phạm vi kiểm tra của bạn chỉ là mã của riêng bạn. Trong trường hợp đó, bạn nên kiểm tra:

  • "Trường hợp hạnh phúc": cung cấp cho ứng dụng của bạn đầu vào hợp lệ và đảm bảo rằng nó tạo ra đầu ra chính xác.
  • Các trường hợp thất bại: cung cấp cho ứng dụng của bạn đầu vào không hợp lệ và đảm bảo rằng nó xử lý chúng chính xác.

Để thực hiện việc này, bạn không thể sử dụng thành phần của đồng nghiệp: thay vào đó, hãy sử dụng chế độ chế nhạo , nghĩa là thay thế phần còn lại của ứng dụng bằng các mô-đun "giả" mà bạn có thể kiểm soát từ khung kiểm tra. Làm thế nào chính xác bạn làm điều này phụ thuộc vào cách giao diện mô-đun; Chỉ cần gọi các phương thức của mô-đun của bạn bằng các đối số được mã hóa cứng và nó có thể trở nên phức tạp như việc viết toàn bộ khung kết nối các giao diện chung của các mô-đun khác với môi trường thử nghiệm.

Đó chỉ là trường hợp thử nghiệm đơn vị. Bạn cũng muốn kiểm tra tích hợp, nơi bạn kiểm tra tất cả các mô-đun trong buổi hòa nhạc. Một lần nữa, bạn muốn kiểm tra cả trường hợp hạnh phúc và thất bại.

Trong trường hợp "Ví dụ cơ bản" của bạn, để kiểm tra đơn vị mã của bạn, hãy viết một lớp giả mô phỏng lớp cơ sở dữ liệu. Lớp giả của bạn không thực sự đi đến cơ sở dữ liệu: bạn chỉ tải trước nó với đầu vào dự kiến ​​và đầu ra cố định. Trong mã giả:

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

Và đây là cách bạn kiểm tra các trường bị thiếu được báo cáo chính xác :

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

Bây giờ mọi thứ trở nên thú vị. Điều gì xảy ra nếu lớp DB thực sự hoạt động sai? Ví dụ, nó có thể ném một ngoại lệ vì những lý do không rõ ràng. Chúng tôi không biết nếu có, nhưng chúng tôi muốn mã riêng của chúng tôi xử lý nó một cách duyên dáng. Không có vấn đề gì, chúng ta chỉ cần làm cho MockDB của mình trở thành một ngoại lệ, ví dụ: bằng cách thêm một phương thức như thế này:

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

Và sau đó trường hợp thử nghiệm của chúng tôi trông như thế này:

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

Đây là những bài kiểm tra đơn vị của bạn. Đối với kiểm tra tích hợp, bạn không sử dụng lớp MockDB; thay vào đó, bạn xâu chuỗi cả các lớp thực tế với nhau. Bạn vẫn cần đồ đạc; ví dụ: bạn nên khởi tạo cơ sở dữ liệu kiểm tra đến trạng thái đã biết trước khi chạy thử nghiệm.

Bây giờ, theo trách nhiệm: Mã của bạn sẽ hy vọng phần còn lại của cơ sở mã sẽ được triển khai theo thông số kỹ thuật, nhưng nó cũng nên được chuẩn bị để xử lý mọi việc một cách duyên dáng khi phần còn lại vặn vít. Bạn không chịu trách nhiệm kiểm tra mã khác ngoài mã của mình, nhưng bạn trách nhiệm làm cho mã của mình trở nên linh hoạt để xử lý sai mã ở đầu bên kia và bạn cũng chịu trách nhiệm kiểm tra khả năng phục hồi của mã. Đó là những gì bài kiểm tra thứ ba ở trên làm.


bạn đã đọc những bình luận bên dưới câu hỏi chưa? OP đã viết "các bài kiểm tra", nhưng ông có nghĩa là "kiểm tra xác nhận" và / hoặc "xử lý ngoại lệ"
Doc Brown

1
@tdammers: xin lỗi vì sự hiểu lầm, ý tôi là trong thực tế xử lý ngoại lệ .. Dù sao cũng cảm ơn vì câu trả lời đầy đủ, đoạn cuối là thứ tôi đang tìm kiếm.
ndurand

1

Có 3 nguyên tắc chính tôi cố gắng viết mã bằng cách:

  • KHÔ

  • HÔN

  • YAGNI

Chà bỏ tất cả những điều này là bạn có nguy cơ viết mã xác nhận được sao chép ở nơi khác. Nếu các quy tắc xác nhận thay đổi, chúng sẽ cần được cập nhật ở nhiều nơi.

Tất nhiên, tại một thời điểm nào đó trong tương lai, bạn có thể phát triển lại cơ sở dữ liệu của mình (nó xảy ra) trong trường hợp bạn có thể nghĩ rằng việc có mã ở nhiều nơi sẽ là lợi thế. Nhưng ... bạn đang mã hóa cho điều gì đó có thể không xảy ra.

Bất kỳ mã bổ sung nào (ngay cả khi nó không bao giờ thay đổi) là chi phí vì nó sẽ cần phải được viết, đọc, lưu trữ và kiểm tra.

Tất cả những điều trên là đúng, sẽ là thiếu sót khi bạn không xác nhận gì cả. Để hiển thị tên đầy đủ trong ứng dụng, bạn sẽ cần một số dữ liệu cơ bản - ngay cả khi bạn không xác thực dữ liệu.


1

Theo lời của giáo dân.

Không có thứ gọi là "cơ sở dữ liệu" hay "ứng dụng" .

  1. Một cơ sở dữ liệu có thể được sử dụng bởi nhiều hơn một ứng dụng.
  2. Một ứng dụng có thể sử dụng nhiều hơn một cơ sở dữ liệu.
  3. Mô hình cơ sở dữ liệu sẽ thực thi tính toàn vẹn dữ liệu, bao gồm việc đưa ra lỗi khi không yêu cầu trường aa trong hoạt động chèn, trừ khi giá trị mặc định được xác định trong định nghĩa bảng. Điều này phải được thực hiện ngay cả khi bạn chèn hàng trực tiếp vào cơ sở dữ liệu bỏ qua ứng dụng. Hãy để hệ thống cơ sở dữ liệu làm điều đó cho bạn.
  4. Cơ sở dữ liệu nên bảo vệ tính toàn vẹn dữ liệu và ném lỗi .
  5. Logic nghiệp vụ phải nắm bắt những lỗi đó và ném ngoại lệ cho lớp trình bày.
  6. Lớp trình bày phải xác thực đầu vào, xử lý các trường hợp ngoại lệ hoặc hiển thị một hamster buồn cho người dùng.

Lần nữa:

  • Cơ sở dữ liệu-> lỗi ném
  • Business Logic-> bắt lỗi và ném ngoại lệ
  • Lớp trình bày-> xác nhận, ném ngoại lệ hoặc hiển thị thông báo buồn.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.