Có một lá cờ để cho biết nếu chúng ta nên ném lỗi


64

Gần đây tôi đã bắt đầu làm việc tại một nơi với một số nhà phát triển lớn tuổi hơn (khoảng hơn 50 tuổi). Họ đã làm việc trên các ứng dụng quan trọng liên quan đến hàng không mà hệ thống không thể ngừng hoạt động. Kết quả là các lập trình viên lớn tuổi có xu hướng mã theo cách này.

Anh ta có xu hướng đặt một boolean vào các đối tượng để cho biết liệu có nên ném ngoại lệ hay không.

Thí dụ

public class AreaCalculator
{
    AreaCalculator(bool shouldThrowExceptions) { ... }
    CalculateArea(int x, int y)
    {
        if(x < 0 || y < 0)
        {
            if(shouldThrowExceptions) 
                throwException;
            else
                return 0;
        }
    }
}

(Trong dự án của chúng tôi, phương thức có thể thất bại vì chúng tôi đang cố gắng sử dụng một thiết bị mạng không thể có mặt tại thời điểm đó. Ví dụ về khu vực chỉ là một ví dụ về cờ ngoại lệ)

Đối với tôi điều này có vẻ như một mùi mã. Viết bài kiểm tra đơn vị trở nên phức tạp hơn một chút vì bạn phải kiểm tra cờ ngoại lệ mỗi lần. Ngoài ra, nếu có sự cố xảy ra, bạn có muốn biết ngay không? Không phải là trách nhiệm của người gọi để xác định làm thế nào để tiếp tục?

Logic / lý luận của anh ấy là chương trình của chúng tôi cần làm 1 việc, hiển thị dữ liệu cho người dùng. Bất kỳ ngoại lệ nào khác không ngăn chúng tôi làm như vậy nên được bỏ qua. Tôi đồng ý rằng họ không nên bị phớt lờ, nhưng nên nổi lên và được xử lý bởi người thích hợp, và không phải đối phó với những lá cờ cho điều đó.

Đây có phải là một cách tốt để xử lý các trường hợp ngoại lệ?

Chỉnh sửa : Chỉ để cung cấp thêm ngữ cảnh cho quyết định thiết kế, tôi nghi ngờ rằng đó là vì nếu thành phần này bị lỗi, chương trình vẫn có thể hoạt động và thực hiện nhiệm vụ chính của nó. Do đó, chúng tôi sẽ không muốn ném một ngoại lệ (và không xử lý nó?) Và nó sẽ gỡ chương trình xuống khi người dùng hoạt động tốt

Chỉnh sửa 2 : Để cung cấp thêm ngữ cảnh, trong trường hợp của chúng tôi, phương thức được gọi để đặt lại card mạng. Vấn đề phát sinh khi card mạng bị ngắt kết nối và kết nối lại, nó được gán một địa chỉ IP khác, do đó Reset sẽ đưa ra một ngoại lệ vì chúng tôi sẽ cố gắng đặt lại phần cứng với ip cũ.


22
c # có một quy ước cho mẫu thử-Parse này. Thêm thông tin: docs.microsoft.com/en-us/dotnet/st Chuẩn / design-guferences / Lỗi Cờ không khớp với mẫu này.
Peter

18
Về cơ bản, đây là một tham số điều khiển và nó thay đổi cách các phương thức bên trong thực thi. Điều này là xấu, bất kể kịch bản. martinfowler.com/bliki/FlagArgument.html , softwareengineering.stackexchange.com/questions/147977/... , medium.com/@amlcurran/...
bic

1
Ngoài những nhận xét Hãy thử-Parse từ Peter, đây là một bài viết thú vị về trường hợp ngoại lệ gây nhiều tranh cãi: blogs.msdn.microsoft.com/ericlippert/2008/09/10/...
Linaith

2
"Vì vậy, chúng tôi sẽ không muốn đưa ra một ngoại lệ (và không xử lý nó?) Và nó sẽ gỡ chương trình xuống khi người dùng hoạt động tốt" - bạn biết bạn có thể bắt ngoại lệ phải không?
dùng253751

1
Tôi khá chắc chắn rằng điều này đã được đề cập trước một nơi khác nhưng với ví dụ Khu vực đơn giản, tôi sẽ có xu hướng tự hỏi những số âm đó sẽ đến từ đâu và liệu bạn có thể xử lý tình trạng lỗi đó ở nơi nào khác không (ví dụ: bất cứ điều gì đã đọc tệp chứa chiều dài và chiều rộng, ví dụ); Tuy nhiên, "chúng tôi đang cố gắng sử dụng một thiết bị mạng không thể có mặt tại thời điểm đó." điểm có thể xứng đáng với một câu trả lời hoàn toàn khác, đây có phải là API của bên thứ ba hoặc một tiêu chuẩn công nghiệp nào đó như TCP / UDP không?
3:44

Câu trả lời:


74

Vấn đề với cách tiếp cận này là trong khi các ngoại lệ không bao giờ bị ném (và do đó, ứng dụng không bao giờ gặp sự cố do ngoại lệ chưa được phát hiện), kết quả trả về không nhất thiết đúng và người dùng có thể không bao giờ biết rằng có vấn đề với dữ liệu (hoặc vấn đề đó là gì và làm thế nào để sửa nó).

Để các kết quả là chính xác và có ý nghĩa, phương thức gọi phải kiểm tra kết quả cho các số đặc biệt - tức là các giá trị trả về cụ thể được sử dụng để biểu thị các vấn đề phát sinh trong khi thực hiện phương thức. Các số âm (hoặc không) được trả về cho các đại lượng xác định dương (như diện tích) là một ví dụ điển hình cho điều này trong mã cũ. Tuy nhiên, nếu phương thức gọi không biết (hoặc quên!) Để kiểm tra các số đặc biệt này, thì quá trình xử lý có thể tiếp tục mà không bao giờ nhận ra lỗi. Dữ liệu sau đó được hiển thị cho người dùng hiển thị vùng 0, mà người dùng biết là không chính xác, nhưng họ không có dấu hiệu cho thấy điều gì đã sai, ở đâu hoặc tại sao. Sau đó, họ tự hỏi liệu có bất kỳ giá trị nào khác là sai ...

Nếu ngoại lệ được đưa ra, quá trình xử lý sẽ dừng lại, lỗi (lý tưởng) sẽ được ghi lại và người dùng có thể được thông báo theo một cách nào đó. Sau đó, người dùng có thể sửa bất cứ điều gì sai và thử lại. Xử lý ngoại lệ thích hợp (và kiểm tra!) Sẽ đảm bảo rằng các ứng dụng quan trọng không gặp sự cố hoặc ngược lại ở trạng thái không hợp lệ.


1
@Quirk Thật ấn tượng khi Chen quản lý vi phạm Nguyên tắc Trách nhiệm duy nhất chỉ trong 3 hoặc 4 dòng. Đó là vấn đề thực sự. Thêm vào đó, vấn đề mà anh ấy đang nói đến (lập trình viên không nghĩ về hậu quả của lỗi trên mỗi dòng) luôn là một khả năng với các ngoại lệ không được kiểm tra và đôi khi chỉ là một khả năng với các ngoại lệ được kiểm tra. Tôi nghĩ rằng tôi đã thấy tất cả các đối số từng được đưa ra chống lại các ngoại lệ được kiểm tra và không một đối số nào trong số chúng là hợp lệ.
TKK

Cá nhân @TKK có một số trường hợp tôi đã chạy vào nơi mà tôi thực sự thích các ngoại lệ được kiểm tra trong .NET. Sẽ thật tuyệt nếu có một số công cụ phân tích tĩnh cao cấp có thể đảm bảo những gì tài liệu API như ngoại lệ ném của nó là chính xác, mặc dù điều đó có thể sẽ không thể thực hiện được, đặc biệt là khi truy cập tài nguyên bản địa.
jrh

1
@jrh Vâng, thật tuyệt nếu có thứ gì đó ngăn chặn sự an toàn ngoại lệ vào .NET, tương tự như cách TypeScript loại bỏ sự an toàn của kiểu gõ vào JS.
TKK

47

Đây có phải là một cách tốt để xử lý các trường hợp ngoại lệ?

Không, tôi nghĩ rằng đây là thực tế khá xấu. Ném một ngoại lệ so với trả về một giá trị là một thay đổi cơ bản trong API, thay đổi chữ ký của phương thức và làm cho phương thức hoạt động hoàn toàn khác với phối cảnh giao diện.

Nói chung, khi chúng ta thiết kế các lớp và API của chúng, chúng ta nên xem xét điều đó

  1. có thể có nhiều phiên bản của lớp với các cấu hình khác nhau trôi nổi trong cùng một chương trình cùng một lúc, và,

  2. do tiêm phụ thuộc và bất kỳ số lượng thực hành lập trình nào khác, một khách hàng tiêu thụ có thể tạo các đối tượng và giao chúng cho người khác sử dụng chúng - vì vậy chúng tôi thường có sự tách biệt giữa người tạo đối tượng và người dùng đối tượng.

Bây giờ hãy xem xét những gì người gọi phương thức phải làm để sử dụng một cá thể, anh ta đã được trao, ví dụ như để gọi phương thức tính toán: người gọi sẽ phải kiểm tra cả khu vực bằng 0 cũng như bắt ngoại lệ - ouch! Việc xem xét kiểm tra không chỉ đến bản thân lớp mà còn xử lý lỗi của người gọi ...

Chúng ta nên luôn luôn làm mọi thứ dễ dàng nhất có thể cho khách hàng tiêu dùng; cấu hình boolean này trong hàm tạo thay đổi API của phương thức cá thể ngược lại với việc làm cho lập trình viên khách hàng tiêu thụ (có thể bạn hoặc đồng nghiệp của bạn) rơi vào hố thành công.

Để cung cấp cả hai API, bạn sẽ tốt hơn và bình thường hơn khi cung cấp hai lớp khác nhau - một lớp luôn luôn bị lỗi và một lớp luôn trả về 0 do lỗi hoặc cung cấp hai phương thức khác nhau cho một lớp. Bằng cách này, khách hàng tiêu thụ có thể dễ dàng biết chính xác cách kiểm tra và xử lý lỗi.

Sử dụng hai lớp khác nhau hoặc hai phương thức khác nhau, bạn có thể sử dụng IDE tìm người dùng phương thức và các tính năng tái cấu trúc, v.v. dễ dàng hơn nhiều vì hai trường hợp sử dụng không còn bị xáo trộn. Đọc mã, viết, bảo trì, đánh giá và kiểm tra cũng đơn giản hơn.


Một lưu ý khác, cá nhân tôi cảm thấy rằng chúng ta không nên lấy các tham số cấu hình boolean, trong đó tất cả những người gọi thực tế chỉ đơn giản là vượt qua một hằng số . Tham số cấu hình như vậy kết hợp hai trường hợp sử dụng riêng biệt không có lợi ích thực sự.

Hãy xem cơ sở mã của bạn và xem nếu một biến (hoặc biểu thức không cố định) đã từng được sử dụng cho tham số cấu hình boolean trong hàm tạo! Tôi nghi ngờ điều đó.


Và xem xét thêm là để hỏi tại sao điện toán khu vực có thể thất bại. Tốt nhất có thể là ném vào hàm tạo, nếu phép tính không thể được thực hiện. Tuy nhiên, nếu bạn không biết liệu phép tính có thể được thực hiện cho đến khi đối tượng được khởi tạo thêm hay không, thì có lẽ nên xem xét sử dụng các lớp khác nhau để phân biệt các trạng thái đó (không sẵn sàng tính diện tích so với sẵn sàng tính diện tích).

Tôi đọc rằng tình huống thất bại của bạn được định hướng từ xa, vì vậy có thể không áp dụng; chỉ là một số thực phẩm cho suy nghĩ.


Không phải là trách nhiệm của người gọi để xác định làm thế nào để tiếp tục?

Vâng tôi đồng ý. Có vẻ như rất sớm để callee quyết định rằng khu vực 0 là câu trả lời đúng trong các điều kiện lỗi (đặc biệt là 0 là khu vực hợp lệ nên không có cách nào để phân biệt sự khác biệt giữa lỗi và 0 thực tế, mặc dù có thể không áp dụng cho ứng dụng của bạn).


Bạn thực sự không phải kiểm tra ngoại lệ vì bạn phải kiểm tra các đối số trước khi gọi phương thức. Kiểm tra kết quả bằng 0 không phân biệt giữa các đối số pháp lý 0, 0 và các đối số phủ định bất hợp pháp. API thực sự khủng khiếp IMHO.
BlackJack

Phụ lục K MS thúc đẩy các iostream C99 và C ++ là các ví dụ về API trong đó một cái móc hoặc cờ thay đổi hoàn toàn phản ứng đối với các thất bại.
Ded repeatator

37

Họ đã làm việc trên các ứng dụng quan trọng liên quan đến hàng không mà hệ thống không thể ngừng hoạt động. Kết quả là ...

Đó là một giới thiệu thú vị, mang lại cho tôi ấn tượng động lực đằng sau thiết kế này là để tránh ném ngoại lệ trong một số bối cảnh "bởi vì hệ thống có thể đi xuống" sau đó. Nhưng nếu hệ thống "có thể ngừng hoạt động vì một ngoại lệ", thì đây là một dấu hiệu rõ ràng cho thấy

  • các ngoại lệ không được xử lý đúng cách , ít nhất cũng không cứng nhắc.

Vì vậy, nếu chương trình sử dụng AreaCalculatorlà lỗi, đồng nghiệp của bạn không muốn chương trình "bị sập sớm", mà trả lại một số giá trị sai (hy vọng không ai nhận thấy nó, hoặc hy vọng không ai làm điều gì quan trọng với nó). Điều đó thực sự che giấu một lỗi , và theo kinh nghiệm của tôi, nó sớm muộn sẽ dẫn đến các lỗi tiếp theo mà việc tìm ra nguyên nhân gốc rễ trở nên khó khăn.

IMHO viết một chương trình không bị sập trong bất kỳ trường hợp nào nhưng hiển thị dữ liệu sai hoặc kết quả tính toán thường không có cách nào tốt hơn là để chương trình bị sập. Cách tiếp cận đúng duy nhất là cung cấp cho người gọi cơ hội nhận thấy lỗi, xử lý nó, để anh ta quyết định xem người dùng có được thông báo về hành vi sai hay không và liệu có an toàn để tiếp tục xử lý hay không, nếu an toàn hơn dừng chương trình hoàn toàn Vì vậy, tôi muốn giới thiệu một trong những điều sau đây:

  • làm cho khó có thể bỏ qua thực tế một chức năng có thể ném một ngoại lệ. Các tiêu chuẩn tài liệu và mã hóa là bạn của bạn ở đây và các đánh giá mã thông thường sẽ hỗ trợ việc sử dụng đúng các thành phần và xử lý ngoại lệ phù hợp.

  • huấn luyện nhóm để mong đợi và xử lý các trường hợp ngoại lệ khi họ sử dụng các thành phần "hộp đen" và có ý định hành vi toàn cầu của chương trình.

  • nếu vì một số lý do mà bạn nghĩ rằng bạn không thể lấy mã gọi (hoặc nhà phát triển viết nó) để sử dụng xử lý ngoại lệ đúng cách, thì, như là phương sách cuối cùng, bạn có thể thiết kế API với các biến đầu ra lỗi rõ ràng và không có ngoại lệ nào, như

    CalculateArea(int x, int y, out ErrorCode err)

    vì vậy thật khó để người gọi bỏ qua chức năng có thể thất bại. Nhưng đây là IMHO cực kỳ xấu xí trong C #; đó là một kỹ thuật lập trình phòng thủ cũ từ C, nơi không có ngoại lệ, và ngày nay nó không cần thiết phải hoạt động.


3
"Viết chương trình không bị sập trong bất kỳ trường hợp nào nhưng hiển thị dữ liệu sai hoặc kết quả tính toán thường không có cách nào tốt hơn là để chương trình bị sập" Tôi hoàn toàn đồng ý mặc dù tôi có thể tưởng tượng rằng trong ngành hàng không tôi có thể thích máy bay hơn vẫn đi với các công cụ hiển thị các giá trị sai so với việc tắt máy tính. Đối với tất cả các ứng dụng ít quan trọng, tốt hơn hết là không che giấu lỗi.
Trilarion

18
@Trilarion: nếu một chương trình cho máy tính bay không chứa xử lý ngoại lệ phù hợp, "sửa lỗi này" bằng cách làm cho các thành phần không ném ngoại lệ là một cách tiếp cận rất sai lầm. Nếu chương trình gặp sự cố, cần có một số hệ thống sao lưu dự phòng có thể tiếp quản. Ví dụ, nếu chương trình không gặp sự cố và hiển thị độ cao sai, các phi công có thể nghĩ rằng "mọi thứ đều ổn" trong khi máy bay lao vào ngọn núi tiếp theo.
Doc Brown

7
@Trilarion: nếu máy tính bay có chiều cao sai và máy bay sẽ gặp sự cố vì điều này, nó sẽ không giúp bạn (đặc biệt là khi hệ thống dự phòng ở đó và không được thông báo cần phải tiếp quản). Hệ thống sao lưu cho máy tính máy bay không phải là một ý tưởng mới, google cho "hệ thống sao lưu máy tính máy bay", tôi khá chắc chắn rằng các kỹ sư trên toàn thế giới luôn xây dựng các hệ thống dự phòng vào bất kỳ hệ thống phê bình thực tế nào (và nếu chỉ vì không mất bảo hiểm).
Doc Brown

4
Điều này. Nếu bạn không đủ khả năng để chương trình bị sập, bạn cũng không thể để nó âm thầm trả lời sai. Câu trả lời đúng là có xử lý ngoại lệ phù hợp trong mọi trường hợp. Đối với một trang web, điều đó có nghĩa là trình xử lý toàn cầu chuyển đổi các lỗi không mong muốn thành 500. Bạn cũng có thể có các trình xử lý bổ sung cho các tình huống cụ thể hơn, như có một try/ catchbên trong một vòng lặp nếu bạn cần xử lý để tiếp tục nếu một yếu tố thất bại.
jpmc26

2
Nhận kết quả sai luôn là loại thất bại tồi tệ nhất; điều đó nhắc nhở tôi về quy tắc tối ưu hóa: "làm cho đúng trước khi bạn tối ưu hóa, bởi vì nhận được câu trả lời sai nhanh hơn vẫn không mang lại lợi ích cho ai."
Toby Speight

13

Viết bài kiểm tra đơn vị trở nên phức tạp hơn một chút vì bạn phải kiểm tra cờ ngoại lệ mỗi lần.

Bất kỳ hàm nào có n tham số sẽ khó kiểm tra hơn hàm có tham số n-1 . Mở rộng điều đó ra vô lý và đối số trở thành các hàm không nên có tham số nào cả vì điều đó làm cho chúng dễ kiểm tra nhất.

Mặc dù đó là một ý tưởng tuyệt vời để viết mã dễ kiểm tra, nhưng đó là một ý tưởng khủng khiếp để đặt sự đơn giản thử nghiệm lên trên việc viết mã hữu ích cho những người phải gọi nó. Nếu ví dụ trong câu hỏi có một công tắc xác định xem có ném ngoại lệ hay không, thì có thể người gọi số muốn hành vi đó xứng đáng thêm nó vào hàm. Trường hợp ranh giới giữa phức tạp và quá phức tạp là một lời kêu gọi phán xét; Bất cứ ai đang cố gắng nói với bạn rằng có một đường sáng áp dụng trong mọi tình huống nên bị nghi ngờ.

Ngoài ra, nếu có sự cố xảy ra, bạn có muốn biết ngay không?

Điều đó phụ thuộc vào định nghĩa của bạn về sai. Ví dụ trong câu hỏi định nghĩa sai là "được cho kích thước nhỏ hơn 0 và shouldThrowExceptionslà đúng". Được cung cấp một thứ nguyên nếu nhỏ hơn 0 không sai khi shouldThrowExceptionssai vì công tắc gây ra hành vi khác nhau. Đó là, khá đơn giản, không phải là một tình huống đặc biệt.

Vấn đề thực sự ở đây là công tắc được đặt tên kém vì nó không mô tả về những gì nó làm cho chức năng này làm. Nếu nó được đặt một cái tên tốt hơn như thế treatInvalidDimensionsAsZero, bạn có hỏi câu hỏi này không?

Không phải là trách nhiệm của người gọi để xác định làm thế nào để tiếp tục?

Người gọi không xác định làm thế nào để tiếp tục. Trong trường hợp này, nó làm như vậy trước thời hạn bằng cách thiết lập hoặc xóa shouldThrowExceptionsvà hàm hoạt động theo trạng thái của nó.

Ví dụ này là một ví dụ đơn giản về mặt bệnh lý bởi vì nó thực hiện một phép tính và trả về. Nếu bạn làm cho nó phức tạp hơn một chút, chẳng hạn như tính tổng các căn bậc hai của một danh sách các số, việc ném ngoại lệ có thể gây ra các vấn đề cho người gọi mà họ không thể giải quyết. Nếu tôi chuyển vào một danh sách [5, 6, -1, 8, 12]và hàm ném một ngoại lệ trên -1, tôi không có cách nào để nói hàm tiếp tục hoạt động vì nó sẽ bị hủy bỏ và vứt bỏ tổng. Nếu danh sách là một tập dữ liệu khổng lồ, việc tạo một bản sao không có bất kỳ số âm nào trước khi gọi hàm có thể không thực tế, vì vậy tôi buộc phải nói trước cách xử lý các số không hợp lệ, dưới dạng "chỉ cần bỏ qua chúng "Chuyển đổi hoặc có thể cung cấp một lambda được gọi để đưa ra quyết định.

Logic / lý luận của anh ấy là chương trình của chúng tôi cần làm 1 việc, hiển thị dữ liệu cho người dùng. Bất kỳ ngoại lệ nào khác không ngăn chúng tôi làm như vậy nên được bỏ qua. Tôi đồng ý rằng họ không nên bị phớt lờ, nhưng nên nổi lên và được xử lý bởi người thích hợp, và không phải đối phó với những lá cờ cho điều đó.

Một lần nữa, không có giải pháp một kích cỡ phù hợp cho tất cả. Trong ví dụ này, có lẽ, chức năng này được viết cho một thông số kỹ thuật cho biết cách xử lý các kích thước âm. Điều cuối cùng bạn muốn làm là hạ thấp tỷ lệ tín hiệu trên tạp âm của các bản ghi của bạn bằng cách điền chúng vào các thông báo "thông thường, một ngoại lệ sẽ được ném vào đây, nhưng người gọi nói không làm phiền".

Và là một trong những lập trình viên lớn tuổi hơn, tôi sẽ yêu cầu bạn vui lòng rời khỏi bãi cỏ của tôi. ;-)


Tôi đồng ý rằng việc đặt tên và mục đích là cực kỳ quan trọng và trong trường hợp này, tên tham số thích hợp thực sự có thể biến các bảng, vì vậy +1, nhưng 1. our program needs to do 1 thing, show data to user. Any other exception that doesn't stop us from doing so should be ignoredsuy nghĩ có thể dẫn đến việc người dùng đưa ra quyết định dựa trên dữ liệu sai (vì thực sự chương trình cần phải làm 1 điều - để giúp người dùng đưa ra quyết định sáng suốt) 2. các trường hợp tương tự như bool ExecuteJob(bool throwOnError = false)thường cực kỳ lỗi và dẫn đến mã khó lý do chỉ bằng cách đọc nó.
Eugene Podskal

@EugenePodskal Tôi nghĩ rằng giả định là "hiển thị dữ liệu" có nghĩa là "hiển thị dữ liệu chính xác." Người hỏi không nói thành phẩm không hoạt động, chỉ là nó có thể được viết "sai". Tôi phải xem một số dữ liệu cứng về điểm thứ hai. Tôi có một số ít các chức năng được sử dụng nhiều trong dự án hiện tại của tôi có công tắc ném / không ném và không khó để lý do hơn bất kỳ chức năng nào khác, nhưng đó là một điểm dữ liệu.
Blrfl

Câu trả lời hay, tôi nghĩ logic này áp dụng cho phạm vi hoàn cảnh rộng hơn nhiều so với chỉ OP. BTW, mới của cpp có phiên bản ném / không ném vì chính xác những lý do này. Tôi biết có một số khác biệt nhỏ nhưng ...
drjpizzle

8

An toàn quan trọng và mã 'bình thường' có thể dẫn đến những ý tưởng rất khác nhau về 'thực hành tốt' trông như thế nào. Có rất nhiều sự chồng chéo - một số thứ có rủi ro và nên tránh trong cả hai - nhưng vẫn có những khác biệt đáng kể. Nếu bạn thêm một yêu cầu để được đảm bảo đáp ứng, những sai lệch sẽ khá lớn.

Những điều này thường liên quan đến những điều bạn mong đợi:

  • Đối với git, câu trả lời sai có thể rất tệ liên quan đến: mất nhiều thời gian / hủy bỏ / treo hoặc thậm chí sụp đổ (đó là những vấn đề không liên quan đến việc thay đổi mã đăng ký một cách vô tình).

    Tuy nhiên: Đối với bảng điều khiển có tính toán lực g bị đình trệ và ngăn việc tính toán tốc độ không khí được thực hiện có thể không được chấp nhận.

Một số ít rõ ràng hơn:

  • Nếu bạn đã kiểm tra rất nhiều , kết quả đặt hàng đầu tiên (như câu trả lời đúng) không phải là vấn đề đáng lo ngại tương đối. Bạn biết thử nghiệm của bạn sẽ bao gồm điều này. Tuy nhiên, nếu có trạng thái ẩn hoặc luồng điều khiển, bạn không biết điều này sẽ không phải là nguyên nhân của thứ gì đó tinh tế hơn nhiều. Điều này là khó để loại trừ với thử nghiệm.

  • An toàn tuyệt đối là tương đối quan trọng. Không có nhiều khách hàng sẽ ngồi xuống để lý do về việc nguồn họ mua có an toàn hay không. Nếu bạn đang ở trong thị trường hàng không thì ...

Làm thế nào điều này áp dụng cho ví dụ của bạn:

Tôi không biết. Có một số quy trình suy nghĩ có thể có các quy tắc chính như "Mã sản xuất không ai ném" được áp dụng trong mã quan trọng an toàn sẽ khá ngớ ngẩn trong các tình huống thông thường hơn.

Một số sẽ liên quan đến việc được nhúng, một số an toàn và có thể một số khác ... Một số là tốt (cần có hiệu suất chặt chẽ / giới hạn bộ nhớ) một số là xấu (chúng tôi không xử lý các trường hợp ngoại lệ đúng cách để tốt nhất không mạo hiểm). Hầu hết thời gian thậm chí biết tại sao họ làm điều đó sẽ không thực sự trả lời câu hỏi. Ví dụ, nếu nó là để làm cho việc kiểm tra mã dễ dàng hơn mà thực sự làm cho nó tốt hơn, thì nó có tốt không? Bạn thực sự không thể nói. Chúng là những động vật khác nhau, và cần đối xử khác nhau.

Tất cả những gì đã nói, có vẻ như tôi nghi ngờ NHƯNG :

Những quyết định thiết kế phần mềm và phần mềm quan trọng có lẽ không nên được đưa ra bởi những người lạ trên stackexchange. Cũng có thể có một lý do tốt để làm điều này ngay cả khi đó là một phần của hệ thống xấu. Đừng đọc nhiều về bất kỳ thứ gì khác ngoài "thức ăn cho suy nghĩ".


7

Đôi khi ném một ngoại lệ không phải là phương pháp tốt nhất. Không ít nhất là do ngăn xếp ngăn xếp, nhưng đôi khi vì bắt một ngoại lệ là vấn đề, đặc biệt là dọc theo ngôn ngữ hoặc đường nối giao diện.

Một cách tốt để xử lý việc này là trả về kiểu dữ liệu được làm giàu. Kiểu dữ liệu này có đủ trạng thái để mô tả tất cả các đường dẫn hạnh phúc và tất cả các đường dẫn không vui. Vấn đề là, nếu bạn tương tác với chức năng này (thành viên / toàn cầu / nếu không), bạn sẽ buộc phải xử lý kết quả.

Điều đó được nói rằng kiểu dữ liệu phong phú này không nên ép buộc hành động. Hãy tưởng tượng trong khu vực của bạn ví dụ như thế nào var area_calc = new AreaCalculator(); var volume = area_calc.CalculateArea(x, y) * z;. Có vẻ hữu ích volumenên chứa diện tích nhân với độ sâu - đó có thể là hình khối, hình trụ, v.v ...

Nhưng nếu dịch vụ area_calc không hoạt động thì sao? Sau đó area_calc .CalculateArea(x, y)trả về một kiểu dữ liệu phong phú có lỗi. Có hợp pháp để nhân nó với z? Đó là một câu hỏi hay. Bạn có thể buộc người dùng xử lý việc kiểm tra ngay lập tức. Điều này tuy nhiên phá vỡ logic với xử lý lỗi.

var area_calc = new AreaCalculator();
var area_result = area_calc.CalculateArea(x, y);
if (area_result.bad())
{
    //handle unhappy path
}
var volume = area_result.value() * z;

đấu với

var area_calc = new AreaCalculator();
var volume = area_calc.CalculateArea(x, y) * z;
if (volume.bad())
{
    //handle unhappy path
}

Logic cơ bản được trải rộng trên hai dòng và chia cho xử lý lỗi trong trường hợp đầu tiên, trong khi trường hợp thứ hai có tất cả logic liên quan trên một dòng tiếp theo là xử lý lỗi.

Trong trường hợp thứ hai đó volumelà một loại dữ liệu phong phú. Nó không chỉ là một con số. Điều này làm cho dung lượng lưu trữ lớn hơn và volumevẫn cần được điều tra cho tình trạng lỗi. Ngoài ra, volumecó thể cung cấp các tính toán khác trước khi người dùng chọn xử lý lỗi, cho phép nó xuất hiện ở một số vị trí khác nhau. Điều này có thể là tốt, hoặc xấu tùy thuộc vào chi tiết cụ thể của tình huống.

Thay thế volumecó thể chỉ là một loại dữ liệu đơn giản - chỉ là một số, nhưng sau đó điều gì xảy ra với tình trạng lỗi? Nó có thể là giá trị hoàn toàn chuyển đổi nếu nó ở trong một điều kiện hạnh phúc. Nếu nó ở trong một điều kiện không hài lòng, nó có thể trả về giá trị mặc định / lỗi (đối với khu vực 0 hoặc -1 có vẻ hợp lý). Thay phiên, nó có thể ném một ngoại lệ ở bên này của ranh giới giao diện / ngôn ngữ.

... foo() {
   var area_calc = new AreaCalculator();
   return area_calc.CalculateArea(x, y) * z;
}
var volume = foo();
if (volume <= 0)
{
    //handle error
}

so với

... foo() {
   var area_calc = new AreaCalculator();
   return area_calc.CalculateArea(x, y) * z;
}

try { var volume = foo(); }
catch(...)
{
    //handle error
}

Bằng cách đưa ra một giá trị xấu, hoặc có thể xấu, nó đặt rất nhiều trách nhiệm cho người dùng để xác thực dữ liệu. Đây là một nguồn lỗi, vì theo như trình biên dịch có liên quan, giá trị trả về là một số nguyên hợp pháp. Nếu một cái gì đó không được kiểm tra, bạn sẽ phát hiện ra nó khi có sự cố. Trường hợp thứ hai pha trộn tốt nhất của cả hai thế giới bằng cách cho phép các ngoại lệ xử lý các đường dẫn không vui, trong khi các đường dẫn hạnh phúc tuân theo quy trình xử lý thông thường. Thật không may, nó buộc người dùng phải xử lý các trường hợp ngoại lệ một cách khôn ngoan, điều này thật khó.

Rõ ràng, một đường dẫn không vui là một trường hợp không xác định đối với logic nghiệp vụ (miền ngoại lệ), không xác thực là một đường dẫn hạnh phúc vì bạn biết cách xử lý điều đó theo quy tắc kinh doanh (miền quy tắc).

Giải pháp cuối cùng sẽ là một giải pháp cho phép tất cả các kịch bản (trong lý do).

  • Người dùng sẽ có thể truy vấn cho một tình trạng xấu và xử lý nó ngay lập tức
  • Người dùng sẽ có thể hoạt động trên loại được làm giàu như thể đường dẫn hạnh phúc đã được theo dõi và truyền các chi tiết lỗi.
  • Người dùng sẽ có thể trích xuất giá trị đường dẫn hạnh phúc thông qua việc truyền (ẩn / rõ ràng là hợp lý), tạo ra một ngoại lệ cho các đường dẫn không hài lòng.
  • Người dùng sẽ có thể trích xuất giá trị đường dẫn hạnh phúc hoặc sử dụng mặc định (được cung cấp hoặc không)

Cái gì đó như:

Rich::value_type value_or_default(Rich&, Rich::value_type default_value = ...);
bool bad(Rich&);
...unhappy path report... bad_state(Rich&);
Rich& assert_not_bad(Rich&);
class Rich
{
public:
   typedef ... value_type;

   operator value_type() { assert_not_bad(*this); return ...value...; }
   operator X(...) { if (bad(*this)) return ...propagate badness to new value...; /*operate and generate new value*/; }
}

//check
if (bad(x))
{
    var report = bad_state(x);
    //handle error
}

//rethrow
assert_not_bad(x);
var result = (assert_not_bad(x) + 23) / 45;

//propogate
var y = x * 23;

//implicit throw
Rich::value_type val = x;
var val = ((Rich::value_type)x) + 34;
var val2 = static_cast<Rich::value_type>(x) % 3;

//default value
var defaulted = value_or_default(x);
var defaulted_to = value_or_default(x, 55);

@TobySpeight Đủ công bằng, những điều này nhạy cảm với bối cảnh và có phạm vi phạm vi của chúng.
Kain0_0

Tôi nghĩ vấn đề ở đây là các khối 'assert_not_bad'. Tôi nghĩ rằng những điều này sẽ kết thúc như ở cùng một nơi với mã ban đầu đã cố gắng giải quyết. Trong thử nghiệm, những điều này cần được chú ý, tuy nhiên, nếu chúng thực sự là khẳng định, chúng nên bị tước trước khi sản xuất trên một chiếc máy bay thực sự. Nếu không thì một số điểm tuyệt vời.
drjpizzle

@drjpizzle Tôi sẽ lập luận rằng nếu nó đủ quan trọng để thêm một người bảo vệ để thử nghiệm, thì điều quan trọng là phải để người bảo vệ tại chỗ khi chạy trong sản xuất. Sự hiện diện của người bảo vệ ngụ ý nghi ngờ. Nếu bạn nghi ngờ mã đủ để bảo vệ nó trong quá trình thử nghiệm, bạn đang nghi ngờ nó vì lý do kỹ thuật. tức là Điều kiện có thể / không thực tế xảy ra. Các thử nghiệm thực hiện không chứng minh rằng điều kiện sẽ không bao giờ đạt được trong sản xuất. Điều đó có nghĩa là có một điều kiện đã biết, có thể xảy ra, cần phải được xử lý ở đâu đó. Tôi nghĩ làm thế nào nó được xử lý, là vấn đề.
Kain0_0

3

Tôi sẽ trả lời từ quan điểm của C ++. Tôi khá chắc chắn rằng tất cả các khái niệm cốt lõi đều có thể chuyển sang C #.

Có vẻ như phong cách ưa thích của bạn là "luôn ném ngoại lệ":

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
        throw Exception("negative side lengths");
    }
    return x * y;
}

Đây có thể là một vấn đề đối với mã C ++ vì việc xử lý ngoại lệ rất nặng nề - nó làm cho trường hợp thất bại chạy chậm và làm cho trường hợp thất bại phân bổ bộ nhớ (đôi khi thậm chí không khả dụng) và nói chung làm cho mọi thứ trở nên khó đoán hơn. Sự nặng nề của EH là một lý do bạn nghe thấy mọi người nói những điều như "Đừng sử dụng ngoại lệ cho luồng kiểm soát."

Vì vậy, một số thư viện (chẳng hạn như <filesystem>) sử dụng cái mà C ++ gọi là "API kép" hoặc cái mà C # gọi là Try-Parsemẫu (cảm ơn Peter vì tiền boa!)

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
        throw Exception("negative side lengths");
    }
    return x * y;
}

bool TryCalculateArea(int x, int y, int& result) {
    if (x < 0 || y < 0) {
        return false;
    }
    result = x * y;
    return true;
}

int a1 = CalculateArea(x, y);
int a2;
if (TryCalculateArea(x, y, a2)) {
    // use a2
}

Bạn có thể thấy vấn đề với "API kép" ngay lập tức: rất nhiều sự trùng lặp mã, không có hướng dẫn nào cho người dùng về việc API nào là "quyền" để sử dụng và người dùng phải đưa ra lựa chọn khó khăn giữa các thông báo lỗi hữu ích ( CalculateArea) và speed ( TryCalculateArea) bởi vì phiên bản nhanh hơn lấy "negative side lengths"ngoại lệ hữu ích của chúng tôi và làm phẳng nó thành vô dụng false- "có gì đó không ổn, đừng hỏi tôi cái gì hoặc ở đâu." (Một số API kép sử dụng một loại lỗi biểu cảm hơn, chẳng hạn như int errnohay C ++ 's std::error_code, nhưng điều đó vẫn không cho bạn biết nơi xảy ra lỗi - chỉ là nó đã xảy ra ở đâu đó.)

Nếu bạn không thể quyết định cách mã của bạn nên hoạt động, bạn luôn có thể đưa ra quyết định cho người gọi!

template<class F>
int CalculateArea(int x, int y, F errorCallback) {
    if (x < 0 || y < 0) {
        return errorCallback(x, y, "negative side lengths");
    }
    return x * y;
}

int a1 = CalculateArea(x, y, [](auto...) { return 0; });
int a2 = CalculateArea(x, y, [](int, int, auto msg) { throw Exception(msg); });
int a3 = CalculateArea(x, y, [](int, int, auto) { return x * y; });

Đây thực chất là những gì đồng nghiệp của bạn đang làm; ngoại trừ việc anh ấy bao gồm "trình xử lý lỗi" thành một biến toàn cục:

std::function<int(const char *)> g_errorCallback;

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
        return g_errorCallback("negative side lengths");
    }
    return x * y;
}

g_errorCallback = [](auto) { return 0; };
int a1 = CalculateArea(x, y);
g_errorCallback = [](const char *msg) { throw Exception(msg); };
int a2 = CalculateArea(x, y);

Chuyển các tham số quan trọng từ các tham số chức năng rõ ràng sang trạng thái toàn cầu hầu như luôn là một ý tưởng tồi. Tôi không khuyên bạn nên nó. (Thực tế đó không phải là trạng thái toàn cầu trong trường hợp của bạn mà chỉ đơn giản là quốc gia thành viên toàn thể giảm nhẹ tính xấu một chút, nhưng không nhiều.)

Hơn nữa, đồng nghiệp của bạn không cần thiết phải giới hạn số lượng các hành vi xử lý lỗi có thể xảy ra. Thay vì cho phép bất kỳ lambda xử lý lỗi, anh ấy đã quyết định chỉ hai:

bool g_errorViaException;

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
        return g_errorViaException ? throw Exception("negative side lengths") : 0;
    }
    return x * y;
}

g_errorViaException = false;
int a1 = CalculateArea(x, y);
g_errorViaException = true;
int a2 = CalculateArea(x, y);

Đây có lẽ là "điểm chua" trong số các chiến lược có thể xảy ra. Bạn đã lấy tất cả sự linh hoạt khỏi người dùng cuối bằng cách buộc họ sử dụng một trong hai cuộc gọi xử lý lỗi chính xác của bạn ; bạn đã có tất cả các vấn đề của nhà nước toàn cầu chia sẻ; bạn vẫn đang trả tiền cho chi nhánh có điều kiện ở khắp mọi nơi.

Cuối cùng, một giải pháp phổ biến trong C ++ (hoặc bất kỳ ngôn ngữ nào có biên dịch có điều kiện) sẽ buộc người dùng đưa ra quyết định cho toàn bộ chương trình của họ, trên toàn cầu, vào thời gian biên dịch, để có thể tối ưu hóa hoàn toàn bộ mã hóa:

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
#ifdef NEXCEPTIONS
        return 0;
#else
        throw Exception("negative side lengths");
#endif
    }
    return x * y;
}

// Now these two function calls *must* have the same behavior,
// which is a nice property for a program to have.
// Improves understandability.
//
int a1 = CalculateArea(x, y);
int a2 = CalculateArea(x, y);

Một ví dụ về một cái gì đó hoạt động theo cách này là assertmacro trong C và C ++, điều kiện hành vi của nó trên macro tiền xử lý NDEBUG.


Nếu một trả về std::optionaltừ TryCalculateArea()thay vào đó, thật đơn giản để thống nhất việc thực hiện cả hai phần của giao diện kép trong một mẫu chức năng duy nhất với cờ biên dịch thời gian.
Ded repeatator

@Ded repeatator: Có thể với a std::expected. Chỉ với điều đó std::optional, trừ khi tôi hiểu nhầm giải pháp được đề xuất của bạn, nó vẫn phải chịu những gì tôi đã nói: người dùng phải đưa ra lựa chọn khó khăn giữa các thông báo lỗi hữu ích và tốc độ, bởi vì phiên bản nhanh hơn lấy "negative side lengths"ngoại lệ hữu ích của chúng tôi và làm phẳng nó thành vô dụng false- " có gì đó không ổn, đừng hỏi tôi cái gì hay ở đâu. "
Quuxplusone

Đó là lý do tại sao libc ++ <filesystem> thực sự làm một cái gì đó rất gần với mô hình đồng nghiệp của OP: nó dẫn std::error_code *ecđường xuyên suốt mọi cấp độ của API, và ở phía dưới thực hiện tương đương đạo đức if (ec == nullptr) throw something; else *ec = some error code. (Nó trừu tượng hóa thực tế ifthành một thứ gọi là ErrorHandler, nhưng đó là cùng một ý tưởng cơ bản.)
Quuxplusone

Vâng, đó sẽ là một tùy chọn để giữ thông tin lỗi mở rộng mà không cần ném. Có thể phù hợp, hoặc không xứng đáng với chi phí bổ sung tiềm năng.
Ded repeatator

1
Vì vậy, nhiều suy nghĩ tốt có trong câu trả lời này ... Chắc chắn cần nhiều sự
ủng hộ

1

Tôi cảm thấy nó nên được đề cập đến nơi đồng nghiệp của bạn có mô hình của họ từ đó.

Ngày nay, C # có mẫu TryGet public bool TryThing(out result). Điều này cho phép bạn nhận được kết quả của mình, trong khi vẫn cho bạn biết nếu kết quả đó thậm chí là một giá trị hợp lệ. (Ví dụ: tất cả các intgiá trị là kết quả hợp lệ cho Math.sum(int, int), nhưng nếu giá trị bị tràn, kết quả cụ thể này có thể là rác). Đây là một mô hình tương đối mới mặc dù.

Trước outtừ khóa, bạn phải đưa ra một ngoại lệ (đắt tiền và người gọi phải bắt hoặc hủy toàn bộ chương trình), tạo một cấu trúc đặc biệt (lớp trước khi lớp hoặc tướng thực sự là một thứ) cho mỗi kết quả để thể hiện giá trị và các lỗi có thể xảy ra (tốn thời gian để tạo và làm phình phần mềm) hoặc trả về giá trị "lỗi" mặc định (đó có thể không phải là lỗi).

Cách tiếp cận mà đồng nghiệp của bạn sử dụng mang lại cho họ những lỗi ngoại lệ sớm trong khi kiểm tra / gỡ lỗi các tính năng mới, đồng thời mang lại cho họ sự an toàn và hiệu suất thời gian chạy (hiệu suất là vấn đề quan trọng mọi lúc ~ 30 năm trước) khi chỉ trả về giá trị lỗi mặc định. Bây giờ đây là mẫu mà phần mềm đã được viết và mô hình dự kiến ​​sẽ tiến lên phía trước, vì vậy việc tiếp tục thực hiện theo cách này mặc dù có nhiều cách tốt hơn bây giờ là điều tự nhiên. Nhiều khả năng mô hình này được kế thừa từ thời đại của phần mềm, hoặc một mô hình trường đại học của bạn không bao giờ phát triển (thói quen cũ rất khó phá vỡ).

Các câu trả lời khác đã giải thích lý do tại sao điều này được coi là thực tiễn xấu, vì vậy tôi sẽ kết thúc bằng cách khuyên bạn nên đọc mô hình TryGet (cũng có thể gói gọn cho những gì hứa hẹn một đối tượng sẽ thực hiện cho người gọi).


Trước outtừ khóa, bạn sẽ viết một boolhàm đưa con trỏ đến kết quả, tức là một reftham số. Bạn có thể làm điều đó trong VB6 vào năm 1998. outTừ khóa chỉ mua cho bạn sự chắc chắn về thời gian biên dịch rằng tham số được gán khi hàm trả về, đó là tất cả những gì có trong đó. Đó một mô hình tốt đẹp và hữu ích mặc dù.
Mathieu Guindon

@MathieuGuindon Yeay, nhưng GetTry chưa phải là một mẫu được biết đến / thành lập và ngay cả khi nó là, tôi không hoàn toàn chắc chắn rằng nó sẽ được sử dụng. Rốt cuộc, một phần của sự dẫn đến Y2K là việc lưu trữ bất cứ thứ gì lớn hơn 0-99 là không thể chấp nhận được.
Tezra

0

Có những lúc bạn muốn làm theo cách của anh ấy, nhưng tôi sẽ không coi đó là tình huống "bình thường". Chìa khóa để xác định trường hợp bạn đang ở là:

Logic / lý luận của anh ấy là chương trình của chúng tôi cần làm 1 việc, hiển thị dữ liệu cho người dùng. Bất kỳ ngoại lệ nào khác không ngăn chúng tôi làm như vậy nên được bỏ qua.

Kiểm tra các yêu cầu. Nếu yêu cầu của bạn thực sự nói rằng bạn có một công việc, đó là hiển thị dữ liệu cho người dùng, thì anh ta đã đúng. Tuy nhiên, theo kinh nghiệm của tôi, phần lớn người dùng cũng quan tâm dữ liệu nào được hiển thị. Họ muốn dữ liệu chính xác. Một số hệ thống chỉ muốn thất bại lặng lẽ và để người dùng nhận ra rằng đã xảy ra sự cố, nhưng tôi coi chúng là ngoại lệ của quy tắc.

Câu hỏi chính tôi sẽ hỏi sau khi thất bại là "Hệ thống có ở trạng thái mà kỳ vọng của người dùng và bất biến của phần mềm là hợp lệ không?" Nếu vậy, bằng mọi cách chỉ cần quay lại và tiếp tục đi. Thực tế nói đây không phải là những gì xảy ra trong hầu hết các chương trình.

Đối với bản thân cờ, cờ ngoại lệ thường được coi là mùi mã vì người dùng cần biết bằng cách nào đó mô-đun đang ở chế độ nào để hiểu cách thức hoạt động của chức năng. Nếu ở !shouldThrowExceptionschế độ, người dùng cần biết rằng họ có trách nhiệm phát hiện lỗi và duy trì các kỳ vọng và bất biến khi chúng xảy ra. Họ cũng chịu trách nhiệm ngay lúc đó, tại dòng nơi hàm được gọi. Một lá cờ như thế này thường rất khó hiểu.

Tuy nhiên, nó đã xảy ra. Xem xét rằng nhiều bộ xử lý cho phép thay đổi hành vi dấu phẩy động trong chương trình. Một chương trình muốn có các tiêu chuẩn thoải mái hơn có thể làm như vậy chỉ bằng cách thay đổi một thanh ghi (đó thực sự là một cờ). Bí quyết là bạn nên hết sức thận trọng, để tránh vô tình giẫm phải ngón chân người khác. Mã thường sẽ kiểm tra cờ hiện tại, đặt nó vào cài đặt mong muốn, thực hiện các thao tác, sau đó đặt lại. Bằng cách đó, không ai bị bất ngờ bởi sự thay đổi.


0

Ví dụ cụ thể này có một tính năng thú vị có thể ảnh hưởng đến các quy tắc ...

CalculateArea(int x, int y)
{
    if(x < 0 || y < 0)
    {
        if(shouldThrowExceptions) 
            throwException;
        else
            return 0;
    }
}

Những gì tôi thấy ở đây là một kiểm tra điều kiện tiên quyết . Kiểm tra điều kiện tiên quyết thất bại ngụ ý một lỗi cao hơn trong ngăn xếp cuộc gọi. Vì vậy, câu hỏi trở thành là mã này chịu trách nhiệm báo cáo lỗi nằm ở nơi khác?

Một số căng thẳng ở đây là do thực tế là giao diện này thể hiện nỗi ám ảnh nguyên thủy - xycó lẽ được cho là đại diện cho các phép đo thực tế về chiều dài. Trong ngữ cảnh lập trình, trong đó các loại cụ thể của miền là một lựa chọn hợp lý, chúng tôi sẽ thực hiện di chuyển kiểm tra điều kiện tiên quyết đến gần nguồn dữ liệu - nói cách khác, chịu trách nhiệm về tính toàn vẹn dữ liệu trong ngăn xếp cuộc gọi, nơi chúng tôi có ý nghĩa tốt hơn cho bối cảnh.

Điều đó nói rằng, tôi không thấy bất cứ điều gì sai về cơ bản khi có hai chiến lược khác nhau để quản lý séc thất bại. Sở thích của tôi sẽ là sử dụng thành phần để xác định chiến lược nào được sử dụng; cờ tính năng sẽ được sử dụng trong thư mục gốc, thay vì thực hiện phương thức thư viện.

// Configurable dependencies
AreaCalculator(PreconditionFailureStrategy strategy)

CalculateArea(int x, int y)
{
    if (x < 0 || y < 0) {
        return this.strategy.fail(0);
    }
    // ...
}

Họ đã làm việc trên các ứng dụng quan trọng liên quan đến hàng không mà hệ thống không thể ngừng hoạt động.

Ủy ban An toàn và Giao thông Quốc gia thực sự tốt; Tôi có thể đề xuất các kỹ thuật triển khai thay thế cho các con rái cá, nhưng tôi không có khuynh hướng tranh luận với chúng về việc thiết kế các vách ngăn trong hệ thống con báo cáo lỗi.

Rộng hơn: chi phí cho doanh nghiệp là gì? Nó rẻ hơn rất nhiều khi đánh sập một trang web so với nó là một hệ thống quan trọng trong cuộc sống.


Tôi thích đề xuất của một cách thay thế để giữ lại sự linh hoạt mong muốn trong khi vẫn hiện đại hóa.
drjpizzle

-1

Các phương thức xử lý các trường hợp ngoại lệ hoặc chúng không có, không cần cờ trong các ngôn ngữ như C #.

public int Method1()
{
  ...code

 return 0;
}

Nếu có gì đó không ổn trong ... mã thì ngoại lệ đó sẽ cần được xử lý bởi người gọi. Nếu không ai xử lý lỗi, chương trình sẽ chấm dứt.

public int Method1()
{
try {  
...code
}
catch {}
 ...Handle error 
}
return 0;
}

Trong trường hợp này, nếu có điều gì đó xấu xảy ra trong ... mã, Phương thức 1 đang xử lý sự cố và chương trình sẽ tiếp tục.

Nơi bạn xử lý các trường hợp ngoại lệ là tùy thuộc vào bạn. Chắc chắn bạn có thể bỏ qua chúng bằng cách bắt và không làm gì cả. Nhưng, tôi sẽ đảm bảo rằng bạn chỉ bỏ qua một số loại ngoại lệ cụ thể mà bạn có thể mong đợi xảy ra. Bỏ qua ( exception ex) là nguy hiểm vì một số trường hợp ngoại lệ bạn không muốn bỏ qua như ngoại lệ hệ thống liên quan đến bộ nhớ ngoài và như vậy.


3
Thiết lập hiện tại mà OP đăng tải liên quan đến việc quyết định có sẵn sàng ném ngoại lệ hay không. Mã của OP không dẫn đến việc nuốt những thứ không mong muốn như ngoại lệ trong bộ nhớ. Nếu bất cứ điều gì, khẳng định rằng các ngoại lệ sụp đổ hệ thống ngụ ý rằng cơ sở mã không bắt được ngoại lệ và do đó sẽ không nuốt bất kỳ ngoại lệ nào; cả những thứ đã và không bị ném bởi logic kinh doanh của OP có chủ ý.
Flater

-1

Cách tiếp cận này phá vỡ triết lý "thất bại nhanh, thất bại khó khăn".

Tại sao bạn muốn thất bại nhanh chóng:

  • Bạn càng thất bại nhanh hơn, các triệu chứng rõ ràng của sự thất bại càng gần với nguyên nhân thực sự của sự thất bại. Điều này làm cho việc gỡ lỗi dễ dàng hơn nhiều - trong trường hợp tốt nhất, bạn có dòng lỗi ngay trong dòng đầu tiên của dấu vết ngăn xếp của bạn.
  • Bạn càng thất bại nhanh hơn (và bắt lỗi một cách thích hợp), thì càng ít có khả năng là bạn nhầm lẫn phần còn lại của chương trình.
  • Bạn càng khó thất bại (nghĩa là ném ngoại lệ thay vì chỉ trả lại mã "-1" hoặc đại loại như vậy), người gọi thực sự quan tâm đến lỗi và không tiếp tục làm việc với các giá trị sai.

Hạn chế của việc không thất bại nhanh và khó:

  • Nếu bạn tránh được những thất bại có thể nhìn thấy, tức là giả vờ rằng mọi thứ đều ổn, bạn có xu hướng làm cho nó cực kỳ khó tìm ra lỗi thực tế. Hãy tưởng tượng rằng giá trị trả về của ví dụ của bạn là một phần của thói quen tính toán tổng của 100 khu vực; tức là gọi hàm đó 100 lần và tính tổng các giá trị trả về. Nếu bạn âm thầm khắc phục lỗi, không có cách nào để tìm ra lỗi thực sự xảy ra ở đâu; và tất cả các tính toán sau đây sẽ âm thầm sai.
  • Nếu bạn trì hoãn thất bại (bằng cách trả về giá trị trả về không thể như "-1" cho một khu vực), bạn sẽ tăng khả năng người gọi hàm của bạn không bận tâm về điều đó và quên xử lý lỗi; mặc dù họ có thông tin về sự thất bại trong tầm tay.

Cuối cùng, việc xử lý lỗi dựa trên ngoại lệ thực tế có lợi ích là bạn có thể đưa ra thông báo hoặc đối tượng lỗi "ngoài băng", bạn có thể dễ dàng ghi nhật ký lỗi, cảnh báo, v.v. mà không cần viết thêm một dòng trong mã miền của mình .

Vì vậy, không chỉ có những lý do kỹ thuật đơn giản, mà còn có những lý do "hệ thống" khiến nó rất hữu ích để thất bại nhanh chóng.

Vào cuối ngày, không thất bại nặng nề và nhanh chóng trong thời đại hiện tại của chúng ta, nơi xử lý ngoại lệ là nhẹ và rất ổn định chỉ là một nửa tội phạm. Tôi hiểu hoàn toàn suy nghĩ rằng việc loại bỏ các ngoại lệ xuất phát từ đâu, nhưng nó không còn áp dụng được nữa.

Đặc biệt trong trường hợp cụ thể của bạn, nơi bạn thậm chí còn đưa ra một tùy chọn về việc có nên ném ngoại lệ hay không: điều này có nghĩa là người gọi phải quyết định dù sao đi nữa . Vì vậy, không có nhược điểm nào để người gọi bắt ngoại lệ và xử lý nó một cách thích hợp.

Một điểm đến trong một bình luận:

Nó đã được chỉ ra trong các câu trả lời khác rằng thất bại nhanh và khó là không mong muốn trong một ứng dụng quan trọng khi phần cứng bạn đang chạy là một chiếc máy bay.

Thất bại nhanh và khó không có nghĩa là toàn bộ ứng dụng của bạn gặp sự cố. Nó có nghĩa là tại điểm xảy ra lỗi, nó bị lỗi cục bộ. Trong ví dụ của OP, phương pháp cấp thấp tính toán một số khu vực không nên âm thầm thay thế một lỗi bằng một giá trị sai. Nó nên thất bại rõ ràng.

Một số người gọi lên chuỗi rõ ràng phải bắt lỗi / ngoại lệ đó và xử lý nó một cách thích hợp. Nếu phương pháp này được sử dụng trong máy bay, điều này có thể sẽ dẫn đến một số lỗi đèn LED sáng lên hoặc ít nhất là để hiển thị "khu vực tính toán lỗi" thay vì một khu vực sai.


3
Nó đã được chỉ ra trong các câu trả lời khác rằng thất bại nhanh và khó là không mong muốn trong một ứng dụng quan trọng khi phần cứng bạn đang chạy là một chiếc máy bay.
HAEM

1
@HAEM, đó là một sự hiểu lầm về những gì thất bại nhanh và khó có nghĩa là gì. Tôi đã thêm một đoạn về điều này vào câu trả lời.
AnoE

Ngay cả khi ý định không thất bại 'khó đến thế', nó vẫn được coi là nguy hiểm khi chơi với loại lửa đó.
drjpizzle

Đó là loại điểm, @drjpizzle. Nếu bạn đã quen với thất bại khó thất bại nhanh chóng, thì đó không phải là "rủi ro" hay "chơi với loại lửa đó" theo kinh nghiệm của tôi. Au chống lại. Điều đó có nghĩa là bạn đã quen với việc suy nghĩ về "điều gì sẽ xảy ra nếu tôi có một ngoại lệ ở đây", trong đó "ở đây" có nghĩa là ở khắp mọi nơi , và bạn có xu hướng nhận thức được liệu vị trí mà bạn hiện đang lập trình có gặp vấn đề lớn không ( tai nạn máy bay, bất cứ điều gì trong trường hợp đó). Chơi với lửa sẽ mong mọi thứ sẽ ổn, và mọi thành phần, nghi ngờ, giả vờ rằng mọi thứ đều ổn ...
AnoE
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.