Tôi đã được thông báo rằng Ngoại lệ chỉ nên được sử dụng trong các trường hợp ngoại lệ. Làm thế nào để tôi biết nếu trường hợp của tôi là đặc biệt?


99

Trường hợp cụ thể của tôi ở đây là người dùng có thể chuyển một chuỗi vào ứng dụng, ứng dụng phân tích cú pháp và gán nó cho các đối tượng có cấu trúc. Đôi khi người dùng có thể gõ một cái gì đó không hợp lệ. Ví dụ, đầu vào của họ có thể mô tả một người nhưng họ có thể nói tuổi của họ là "quả táo". Hành vi đúng trong trường hợp đó là khôi phục giao dịch và để báo cho người dùng biết đã xảy ra lỗi và họ sẽ phải thử lại. Có thể có một yêu cầu để báo cáo về mọi lỗi chúng ta có thể tìm thấy trong đầu vào, không chỉ đầu tiên.

Trong trường hợp này, tôi lập luận rằng chúng ta nên ném một ngoại lệ. Anh ấy không đồng ý, nói: "Trường hợp ngoại lệ nên là ngoại lệ: Dự kiến ​​người dùng có thể nhập dữ liệu không hợp lệ, vì vậy đây không phải là trường hợp ngoại lệ" Tôi thực sự không biết làm thế nào để tranh luận về điểm đó, bởi vì theo định nghĩa của từ, anh ấy có vẻ đúng

Nhưng, đó là sự hiểu biết của tôi rằng đây là lý do tại sao Ngoại lệ được phát minh ngay từ đầu. Nó đã từng là bạn đã kiểm tra kết quả để xem nếu một lỗi xảy ra. Nếu bạn không kiểm tra, những điều tồi tệ có thể xảy ra mà bạn không nhận ra.

Không có ngoại lệ, mọi cấp độ của ngăn xếp cần kiểm tra kết quả của các phương thức mà chúng gọi và nếu lập trình viên quên kiểm tra một trong các cấp này, mã có thể vô tình tiến hành và lưu dữ liệu không hợp lệ (ví dụ). Có vẻ nhiều lỗi dễ dàng theo cách đó.

Dù sao, hãy thoải mái để sửa bất cứ điều gì tôi đã nói ở đây. Câu hỏi chính của tôi là nếu ai đó nói Ngoại lệ phải là ngoại lệ, làm sao tôi biết trường hợp của tôi là ngoại lệ?


3
có thể trùng lặp? Khi ném một ngoại lệ . Mặc dù nó đã bị đóng cửa ở đó, nhưng tôi nghĩ nó phù hợp ở đây. Vẫn còn một chút triết lý, một số người và cộng đồng có xu hướng xem ngoại lệ là một loại kiểm soát dòng chảy.
thorsten müller

8
Khi người dùng bị câm, họ cung cấp đầu vào không hợp lệ. Khi người dùng thông minh, họ chơi bằng cách cung cấp đầu vào không hợp lệ. Do đó, đầu vào người dùng không hợp lệ không phải là một ngoại lệ.
mouviciel

7
Ngoài ra, đừng nhầm lẫn một ngoại lệ , đó là một cơ chế rất cụ thể trong Java và .NET, với một lỗi là một thuật ngữ chung chung hơn nhiều. Có nhiều cách xử lý lỗi hơn là ném ngoại lệ. Thảo luận này chạm vào các sắc thái giữa các ngoại lệlỗi .
Eric King

4
! "Exceptional" = "Hiếm khi xảy ra"
ConditionRacer

3
Tôi thấy các ngoại lệ Vexing của Eric Lippert là lời khuyên đúng đắn.
Brian

Câu trả lời:


87

Các ngoại lệ được phát minh để giúp xử lý lỗi dễ dàng hơn với ít sự lộn xộn mã hơn. Bạn nên sử dụng chúng trong các trường hợp khi chúng giúp xử lý lỗi dễ dàng hơn với ít sự lộn xộn mã hơn. Hoạt động kinh doanh "ngoại lệ chỉ dành cho trường hợp đặc biệt" này bắt nguồn từ thời điểm xử lý ngoại lệ được coi là thành công không thể chấp nhận được. Đó không còn là trường hợp trong phần lớn mã, nhưng mọi người vẫn nói ra quy tắc mà không nhớ lý do đằng sau nó.

Đặc biệt là trong Java, có lẽ là ngôn ngữ yêu thích ngoại lệ nhất từng được hình thành, bạn không nên cảm thấy tồi tệ khi sử dụng các ngoại lệ khi nó đơn giản hóa mã của bạn. Trong thực tế, Integerlớp riêng của Java không có phương tiện để kiểm tra xem một chuỗi có phải là số nguyên hợp lệ hay không mà không có khả năng ném a NumberFormatException.

Ngoài ra, mặc dù bạn không thể chỉ dựa vào xác thực giao diện người dùng, hãy lưu ý nếu giao diện người dùng của bạn được thiết kế đúng, chẳng hạn như sử dụng công cụ quay vòng để nhập các giá trị số ngắn, thì giá trị không phải là số thực sự ở phía sau sẽ thực sự là một điều kiện đặc biệt


10
Đẹp quét, có. Trên thực tế, trong ứng dụng trong thế giới thực do tôi thiết kế, hiệu năng đã tạo ra sự khác biệt và tôi phải thay đổi nó để không ném ngoại lệ cho các hoạt động phân tích cú pháp nhất định.
Robert Harvey

17
Tôi không nói rằng vẫn không có trường hợp nào hiệu suất đạt được là một lý do hợp lệ, nhưng những trường hợp đó là ngoại lệ (ý định chơi chữ) chứ không phải là quy tắc.
Karl Bielefeldt

11
@RobertHarvey Thủ thuật trong Java là ném các đối tượng ngoại lệ được chế tạo sẵn, thay vì throw new .... Hoặc, ném ngoại lệ tùy chỉnh, trong đó fillInStackTrace () được ghi đè. Sau đó, bạn không nên nhận thấy bất kỳ sự suy giảm hiệu suất, không nói về lượt truy cập .
Ingo

3
+1: Chính xác những gì tôi sẽ trả lời. Sử dụng nó khi nó đơn giản hóa mã. Các ngoại lệ có thể cung cấp mã rõ ràng hơn nhiều trong đó bạn không phải bận tâm kiểm tra giá trị trả về ở mọi cấp độ trong ngăn xếp cuộc gọi. (Giống như mọi thứ khác, nếu sử dụng sai cách, nó có thể khiến mã của bạn trở thành một mớ hỗn độn khủng khiếp.)
Leo

4
@Brendan Giả sử có một số ngoại lệ xảy ra và mã xử lý lỗi là 4 cấp độ bên dưới trong ngăn xếp cuộc gọi. Nếu bạn sử dụng mã lỗi, tất cả 4 hàm trên trình xử lý cần phải có loại mã lỗi làm giá trị trả về của chúng và bạn phải thực hiện một chuỗi if (foo() == ERROR) { return ERROR; } else { // continue }ở mọi cấp độ. Nếu bạn ném một ngoại lệ không được kiểm tra, sẽ không có "lỗi trả lại lỗi" ồn ào và dư thừa. Ngoài ra, nếu bạn chuyển các hàm làm đối số, sử dụng mã lỗi có thể thay đổi chữ ký hàm thành loại không tương thích, mặc dù lỗi có thể không xảy ra.
Doval

72

Khi nào nên ném một ngoại lệ? Khi nói đến mã, tôi nghĩ rằng giải thích sau đây rất hữu ích:

Một ngoại lệ là khi một thành viên không hoàn thành nhiệm vụ, nó được cho là thực hiện như được chỉ định bởi tên của nó . (Jeffry Richter, CLR qua C #)

Tại sao nó hữu ích? Nó gợi ý rằng nó phụ thuộc vào bối cảnh khi một cái gì đó nên được xử lý như một ngoại lệ hay không. Ở cấp độ của các cuộc gọi phương thức, bối cảnh được đưa ra bởi (a) tên, (b) chữ ký của phương thức và (b) mã máy khách, sử dụng hoặc dự kiến ​​sẽ sử dụng phương thức.

Để trả lời câu hỏi của bạn, bạn nên xem mã, nơi đầu vào của người dùng được xử lý. Nó có thể trông giống như thế này:

public void Save(PersonData personData) {  }

Có phải tên phương thức gợi ý rằng một số xác nhận được thực hiện? Không. Trong trường hợp này, PersonData không hợp lệ sẽ ném ngoại lệ.

Giả sử rằng lớp có một phương thức khác trông như thế này:

public ValidationResult Validate(PersonData personData) {  }

Có phải tên phương thức gợi ý rằng một số xác nhận được thực hiện? Đúng. Trong trường hợp này, PersonData không hợp lệ không nên ném ngoại lệ.

Để kết hợp mọi thứ lại với nhau, cả hai phương thức đều gợi ý rằng mã máy khách sẽ trông như thế này:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

Khi không rõ liệu một phương thức có nên đưa ra một ngoại lệ hay không, thì có thể đó là do tên phương thức hoặc chữ ký được chọn kém. Có lẽ thiết kế của lớp không rõ ràng. Đôi khi bạn cần sửa đổi thiết kế mã để có câu trả lời rõ ràng cho câu hỏi liệu có nên ném ngoại lệ hay không.


Mới hôm qua tôi đã thực hiện một cuộc structgọi là "ValidationResult" và cấu trúc mã của tôi theo cách bạn mô tả.
paul

4
Nó không giúp trả lời câu hỏi của bạn, nhưng tôi chỉ muốn chỉ ra rằng bạn hoàn toàn hoặc cố ý tuân theo nguyên tắc phân tách truy vấn lệnh ( en.wikipedia.org/wiki/Command-query_separation ). ;-)
Theo Lenndorff

Ý kiến ​​hay! Một nhược điểm: Trong ví dụ của bạn, việc xác thực thực sự được thực hiện hai lần: Một lần trong khi Validate(trả về Sai nếu không hợp lệ) và một lần trong khi Save(ném một ngoại lệ cụ thể, được ghi chép rõ ràng nếu không hợp lệ). Tất nhiên, kết quả xác nhận có thể được lưu trữ bên trong đối tượng, nhưng điều đó sẽ tăng thêm độ phức tạp, vì kết quả xác thực sẽ cần phải bị vô hiệu hóa khi thay đổi.
Heinzi

@Heinzi Tôi đồng ý. Nó có thể được cấu trúc lại để Validate()được gọi bên trong Save()phương thức và các chi tiết cụ thể từ ValidationResultcó thể được sử dụng để xây dựng một thông điệp phù hợp cho ngoại lệ.
Phil

3
Điều này tốt hơn câu trả lời tôi chấp nhận. Ném khi cuộc gọi không thể làm điều mà nó đáng lẽ phải làm.
Andy

31

Trường hợp ngoại lệ phải là ngoại lệ: Người dùng có thể nhập dữ liệu không hợp lệ, vì vậy đây không phải là trường hợp ngoại lệ

Về lập luận đó:

  • Dự kiến ​​một tệp có thể không tồn tại, vì vậy đó không phải là trường hợp ngoại lệ.
  • Dự kiến ​​kết nối đến máy chủ có thể bị mất, vì vậy đó không phải là trường hợp ngoại lệ
  • Dự kiến ​​tệp cấu hình có thể bị cắt xén để không phải là trường hợp ngoại lệ
  • Dự kiến ​​rằng yêu cầu của bạn đôi khi có thể rơi ra, vì vậy đó không phải là trường hợp ngoại lệ

Bất kỳ ngoại lệ nào bạn bắt được, bạn phải mong đợi bởi vì, tốt, bạn đã quyết định bắt nó. Và theo logic này, bạn không bao giờ nên đưa ra bất kỳ ngoại lệ nào mà bạn thực sự có kế hoạch bắt.

Do đó tôi nghĩ rằng "ngoại lệ nên là ngoại lệ" là một quy tắc tồi tệ.

Những gì bạn nên làm phụ thuộc vào ngôn ngữ. Các ngôn ngữ khác nhau có các quy ước khác nhau về thời điểm nên ném ngoại lệ. Python, ví dụ, ném ngoại lệ cho mọi thứ và khi ở Python, tôi làm theo. C ++, mặt khác, ném tương đối ít ngoại lệ, và ở đó tôi làm theo. Bạn có thể đối xử với C ++ hoặc Java như Python và ném ngoại lệ cho mọi thứ, nhưng công việc của bạn không phù hợp với cách ngôn ngữ mong muốn được sử dụng.

Tôi thích cách tiếp cận của Python, nhưng tôi nghĩ rằng nên đánh bóng các ngôn ngữ khác vào nó.


1
@gnat, tôi biết. Quan điểm của tôi là bạn nên tuân theo các quy ước của ngôn ngữ (trong trường hợp này là Java) ngay cả khi chúng không phải là sở thích của bạn.
Winston Ewert

6
+1 "exceptions should be exceptional" is a terrible rule of thumb.Nói tốt! Đó là một trong những điều mà mọi người chỉ lặp lại mà không nghĩ về chúng.
Andres F.

2
"Dự kiến" được xác định bởi không phải bởi đối số hoặc quy ước chủ quan mà bởi hợp đồng của API / chức năng (điều này có thể rõ ràng, nhưng thường chỉ được ngụ ý). Các chức năng / API / hệ thống con khác nhau có thể có những kỳ vọng khác nhau, ví dụ: đối với một số chức năng cấp cao hơn, tệp không tồn tại là trường hợp được mong đợi để xử lý (nó có thể báo cáo điều này cho người dùng thông qua GUI), đối với các chức năng cấp thấp khác, nó là có lẽ không (và do đó nên ném một ngoại lệ). Câu trả lời này dường như bỏ lỡ điểm quan trọng đó ....
mikera

1
@mikera, có một chức năng nên (chỉ) đưa ra các ngoại lệ được xác định trong hợp đồng của nó. Nhưng đó không phải là câu hỏi. Câu hỏi là làm thế nào bạn quyết định hợp đồng đó nên là gì. Tôi khẳng định rằng quy tắc ngón tay cái, "ngoại lệ nên là ngoại lệ" không hữu ích trong việc đưa ra quyết định đó.
Winston Ewert

1
@supercat, tôi không nghĩ nó thực sự quan trọng mà cuối cùng lại phổ biến hơn. Tôi nghĩ rằng câu hỏi quan trọng là có một mặc định lành mạnh. Nếu tôi không xử lý rõ ràng tình trạng lỗi, mã của tôi có giả vờ không có gì xảy ra không, hoặc tôi có nhận được thông báo lỗi hữu ích không?
Winston Ewert

30

Tôi luôn nghĩ về những thứ như truy cập máy chủ cơ sở dữ liệu hoặc API web khi nghĩ đến các ngoại lệ. Bạn mong đợi API máy chủ / web hoạt động, nhưng trong trường hợp đặc biệt thì nó có thể không (máy chủ ngừng hoạt động). Một yêu cầu web có thể thường nhanh chóng, nhưng trong trường hợp đặc biệt (tải cao) thì có thể hết thời gian. Đây là một cái gì đó ngoài tầm kiểm soát của bạn.

Dữ liệu đầu vào của người dùng nằm trong tầm kiểm soát của bạn, vì bạn có thể kiểm tra những gì họ gửi và làm với dữ liệu bạn thích. Trong trường hợp của bạn, tôi sẽ xác thực đầu vào của người dùng trước khi cố lưu nó. Và tôi có xu hướng đồng ý rằng người dùng cung cấp dữ liệu không hợp lệ nên được mong đợi và ứng dụng của bạn sẽ giải thích bằng cách xác thực đầu vào và cung cấp thông báo lỗi thân thiện với người dùng.

Điều đó nói rằng, tôi sử dụng các ngoại lệ trong hầu hết các setters mô hình miền của mình, nơi sẽ hoàn toàn không có cơ hội dữ liệu không hợp lệ. Tuy nhiên, đây là dòng bảo vệ cuối cùng và tôi có xu hướng xây dựng các biểu mẫu đầu vào của mình với các quy tắc xác thực phong phú , do đó trên thực tế không có cơ hội kích hoạt ngoại lệ mô hình miền đó. Vì vậy, khi một setter đang mong đợi một điều, và nhận được một điều khác, đó là một tình huống đặc biệt, điều không nên xảy ra trong các trường hợp thông thường.

EDIT (một cái gì đó khác để xem xét):

Khi gửi dữ liệu do người dùng cung cấp tới db, bạn sẽ biết trước những gì bạn nên và không nên nhập vào các bảng của mình. Điều này có nghĩa là dữ liệu có thể được xác nhận theo một số định dạng dự kiến. Đây là điều bạn có thể kiểm soát. Những gì bạn không thể kiểm soát là máy chủ của bạn bị lỗi ở giữa truy vấn của bạn. Vì vậy, bạn biết truy vấn là ok và dữ liệu được lọc / xác thực, bạn thử truy vấn và nó vẫn thất bại, đây là một tình huống đặc biệt.

Tương tự như vậy với các yêu cầu web, bạn không thể biết liệu yêu cầu sẽ hết thời gian hay không kết nối trước khi bạn thử gửi nó. Vì vậy, điều này cũng đảm bảo một cách tiếp cận thử / bắt, vì bạn không thể hỏi máy chủ nếu nó sẽ hoạt động vài mili giây sau khi bạn gửi yêu cầu.


8
Nhưng tại sao? Tại sao các ngoại lệ ít hữu ích hơn trong việc xử lý các vấn đề được mong đợi hơn?
Winston Ewert

6
@Pinetree, kiểm tra sự tồn tại của tập tin trước khi mở một tập tin là ý tưởng tồi. Tệp có thể ngừng tồn tại giữa kiểm tra và mở, tệp không thể có quyền cho phép bạn mở và kiểm tra sự tồn tại và sau đó mở tệp sẽ yêu cầu hai cuộc gọi hệ thống đắt tiền. Bạn tốt hơn hết là cố gắng mở tệp và sau đó xử lý lỗi không làm như vậy.
Winston Ewert

10
Theo như tôi có thể thấy, gần như tất cả các thất bại có thể được xử lý tốt hơn là phục hồi từ thất bại thay vì cố gắng kiểm tra thành công trước khi ra tay. Cho dù bạn có sử dụng ngoại lệ hay điều gì khác cho thấy thất bại là một vấn đề riêng biệt. Tôi thích ngoại lệ vì tôi không thể vô tình bỏ qua chúng.
Winston Ewert

11
Tôi không đồng ý với tiền đề của bạn rằng vì dữ liệu người dùng không hợp lệ được mong đợi, nên nó không thể được coi là ngoại lệ. Nếu tôi viết một trình phân tích cú pháp và ai đó cung cấp dữ liệu không thể tìm thấy đó, đó là một ngoại lệ. Tôi không thể tiếp tục phân tích cú pháp. Làm thế nào ngoại lệ được xử lý là một câu hỏi hoàn toàn khác.
Điều

4
File.ReadAllBytes sẽ FileNotFoundExceptionđưa ra một thông tin đầu vào sai (ví dụ: tên tệp không tồn tại). Đó là cách hợp lệ duy nhất để đe dọa lỗi này, bạn có thể làm gì khác mà không phải trả lại mã lỗi?
oɔɯǝɹ

16

Tài liệu tham khảo

Từ lập trình viên thực dụng:

Chúng tôi tin rằng các trường hợp ngoại lệ hiếm khi được sử dụng như một phần của dòng chảy thông thường của chương trình; ngoại lệ nên được dành riêng cho các sự kiện bất ngờ. Giả sử rằng một ngoại lệ chưa được lưu sẽ chấm dứt chương trình của bạn và tự hỏi: "Mã này có còn chạy nếu tôi xóa tất cả các trình xử lý ngoại lệ không?" Nếu câu trả lời là "không", thì có lẽ các trường hợp ngoại lệ đang được sử dụng trong các trường hợp không có ngoại lệ.

Họ tiếp tục kiểm tra ví dụ về việc mở một tệp để đọc và tệp không tồn tại - điều đó có nên đưa ra một ngoại lệ không?

Nếu tập tin nên có ở đó, thì một ngoại lệ được bảo hành. [...] Mặt khác, nếu bạn không biết liệu tập tin đó có tồn tại hay không, thì nó có vẻ không đặc biệt nếu bạn không thể tìm thấy nó và trả về lỗi là phù hợp.

Sau đó, họ thảo luận về lý do tại sao họ chọn phương pháp này:

[A] n ngoại lệ thể hiện sự chuyển giao quyền kiểm soát ngay lập tức, không nhắm mục tiêu - đó là một loại xếp tầng goto. Các chương trình sử dụng các ngoại lệ như là một phần của quá trình xử lý thông thường của chúng phải chịu tất cả các vấn đề về khả năng đọc và bảo trì của mã spaghetti cổ điển. Các chương trình này phá vỡ đóng gói: các thói quen và người gọi của họ được kết nối chặt chẽ hơn thông qua xử lý ngoại lệ.

Về tình hình của bạn

Câu hỏi của bạn rút gọn thành "Các lỗi xác nhận có nên đưa ra ngoại lệ không?" Câu trả lời là nó phụ thuộc vào nơi xác nhận đang diễn ra.

Nếu phương thức được đề cập nằm trong một phần của mã nơi giả định rằng dữ liệu đầu vào đã được xác thực, dữ liệu đầu vào không hợp lệ sẽ đưa ra một ngoại lệ; nếu mã được thiết kế sao cho phương thức này sẽ nhận được đầu vào chính xác được nhập bởi người dùng, dữ liệu không hợp lệ sẽ được dự kiến ​​và không nên đưa ra ngoại lệ.


11

Có rất nhiều sự kết hợp triết học ở đây, nhưng nói chung, các điều kiện đặc biệt chỉ đơn giản là những điều kiện mà bạn không thể hoặc không muốn xử lý (ngoài việc dọn dẹp, báo cáo lỗi và tương tự) mà không cần sự can thiệp của người dùng. Nói cách khác, chúng là những điều kiện không thể phục hồi.

Nếu bạn trao cho chương trình một đường dẫn tệp, với ý định xử lý tệp đó theo một cách nào đó và tệp được chỉ định bởi đường dẫn đó không tồn tại, đó là một điều kiện đặc biệt. Bạn không thể làm bất cứ điều gì về điều đó trong mã của mình, ngoài việc báo cáo nó cho người dùng và cho phép họ chỉ định một đường dẫn tệp khác.


1
+1, rất gần với những gì tôi sẽ nói. Tôi muốn nói rằng nó liên quan nhiều hơn đến phạm vi và thực sự không liên quan gì đến người dùng. Một ví dụ điển hình cho điều này là sự khác biệt giữa hai hàm .Net int.Pude và int.TryPude, trước đây không có lựa chọn nào khác ngoài việc ném ngoại lệ vào đầu vào xấu, sau này không bao giờ nên ném ngoại lệ
jmoreno

1
@jmoreno: Ergo, bạn sẽ sử dụng TryPude khi mã có thể làm điều gì đó về điều kiện không thể xem được và Parse khi không thể.
Robert Harvey

7

Có hai mối quan tâm bạn nên xem xét:

  1. bạn thảo luận về một mối quan tâm duy nhất - hãy gọi nó Assignervì mối quan tâm này là gán đầu vào cho các đối tượng có cấu trúc - và bạn thể hiện ràng buộc rằng đầu vào của nó là hợp lệ

  2. một thực hiện tốt giao diện người dùng có một mối quan tâm thêm: xác nhận của người sử dụng đầu vào & phản hồi mang tính xây dựng về lỗi (chúng ta hãy gọi phần này Validator)

Từ quan điểm của Assignerthành phần, việc ném một ngoại lệ là hoàn toàn hợp lý, vì bạn đã thể hiện một ràng buộc đã bị vi phạm.

Từ quan điểm của trải nghiệm người dùng , người dùng không nên nói chuyện trực tiếp với vấn đề này Assignerngay từ đầu. Họ cần được nói chuyện với nó qua các Validator.

Bây giờ, trong trường Validatorhợp, đầu vào của người dùng không hợp lệ không phải là trường hợp ngoại lệ, đó thực sự là trường hợp bạn quan tâm hơn. Vì vậy, đây là một ngoại lệ sẽ không phù hợp và đây cũng là nơi bạn muốn xác định tất cả các lỗi thay vì bảo lãnh đầu tiên.

Bạn sẽ nhận thấy tôi đã không đề cập đến cách những mối quan tâm này được thực hiện. Có vẻ như bạn đang nói về Assignervà đồng nghiệp của bạn đang nói về sự kết hợp Validator+Assigner. Một khi bạn nhận ra có hai riêng biệt (hoặc tách) lo ngại, ít nhất bạn có thể thảo luận về nó một cách hợp lý.


Để giải quyết bình luận của Renan, tôi chỉ giả sử rằng một khi bạn đã xác định được hai mối quan tâm riêng biệt của mình, thì rõ ràng trường hợp nào nên được coi là ngoại lệ trong mỗi bối cảnh.

Trên thực tế, nếu không rõ liệu có thứ gì đó được coi là đặc biệt hay không, tôi cho rằng có lẽ bạn chưa hoàn thành việc xác định các mối quan tâm độc lập trong giải pháp của mình.

Tôi đoán điều đó làm cho câu trả lời trực tiếp đến

... Làm thế nào để tôi biết nếu trường hợp của tôi là đặc biệt?

giữ đơn giản hóa cho đến khi nó là hiển nhiên . Khi bạn có một đống các khái niệm đơn giản mà bạn hiểu rõ, bạn có thể suy luận rõ ràng về việc kết hợp chúng trở lại thành mã, lớp, thư viện hoặc bất cứ điều gì.


-1 Yep, có hai mối quan tâm, nhưng điều này không trả lời câu hỏi "Làm thế nào để tôi biết nếu trường hợp của tôi là đặc biệt?"
RMalke

Vấn đề là trường hợp tương tự có thể là ngoại lệ trong một bối cảnh, và không phải trong một bối cảnh khác. Xác định bối cảnh mà bạn thực sự đang nói về (thay vì kết hợp cả hai) trả lời câu hỏi ở đây.
Vô dụng

... thực ra, có lẽ là không - thay vào đó tôi đã giải quyết vấn đề của bạn trong câu trả lời của tôi.
Vô dụng

4

Những người khác đã trả lời tốt, nhưng đây vẫn là câu trả lời ngắn gọn của tôi. Ngoại lệ là tình huống trong đó một cái gì đó trong môi trường có lỗi, mà bạn không thể kiểm soát và mã của bạn không thể đi tiếp. Trong trường hợp này, bạn cũng sẽ phải thông báo cho người dùng những gì đã sai, tại sao bạn không thể đi xa hơn và giải pháp là gì.


3

Tôi chưa bao giờ là một người hâm mộ tuyệt vời về lời khuyên rằng bạn chỉ nên đưa ra ngoại lệ trong những trường hợp đặc biệt, một phần vì nó không nói gì cả (nó giống như nói rằng bạn chỉ nên ăn thực phẩm có thể ăn được), mà còn bởi vì nó là rất chủ quan, và nó thường không rõ ràng những gì tạo thành một trường hợp đặc biệt và những gì không.

Tuy nhiên, có những lý do chính đáng cho lời khuyên này: ném và bắt ngoại lệ chậm và nếu bạn đang chạy mã của mình trong trình gỡ lỗi trong Visual Studio với nó được đặt để thông báo cho bạn mỗi khi có ngoại lệ bị ném, bạn có thể bị spam bởi hàng tá nếu không hàng trăm tin nhắn rất lâu trước khi bạn gặp vấn đề.

Vì vậy, như một quy tắc chung, nếu:

  • mã của bạn không có lỗi và
  • tất cả các dịch vụ mà nó phụ thuộc đều có sẵn và
  • Người dùng của bạn đang sử dụng chương trình của bạn theo cách nó được sử dụng (ngay cả khi một số đầu vào họ cung cấp không hợp lệ)

sau đó mã của bạn sẽ không bao giờ ném ngoại lệ, ngay cả mã bị bắt sau đó. Để bẫy dữ liệu không hợp lệ, bạn có thể sử dụng trình xác nhận ở cấp độ UI hoặc mã, chẳng hạn như Int32.TryParse()trong lớp trình bày.

Đối với bất cứ điều gì khác, bạn nên tuân thủ nguyên tắc rằng một ngoại lệ có nghĩa là phương thức của bạn không thể thực hiện được những gì tên của nó nói. Nói chung nó không phải là một ý tưởng tốt để sử dụng mã trở lại để chỉ ra sự thất bại (trừ khi tên phương pháp của bạn cho thấy rõ ràng rằng nó làm như vậy, ví dụ TryParse()) vì hai lý do. Đầu tiên, phản hồi mặc định cho mã lỗi là bỏ qua điều kiện lỗi và tiếp tục bất kể; thứ hai, bạn hoàn toàn có thể dễ dàng kết thúc với một số phương thức bằng cách sử dụng mã trả về và các phương thức khác bằng cách sử dụng ngoại lệ và quên đi phương thức nào. Tôi thậm chí đã thấy các cơ sở mã hóa trong đó hai triển khai có thể hoán đổi cho nhau của cùng một giao diện có các cách tiếp cận khác nhau ở đây.


2

Các ngoại lệ sẽ thể hiện các điều kiện mà có khả năng mã gọi ngay lập tức sẽ không được chuẩn bị để xử lý, ngay cả khi phương thức gọi có thể. Ví dụ, xem xét mã đang đọc một số dữ liệu từ một tệp, có thể cho rằng một cách hợp pháp rằng mọi tệp hợp lệ sẽ kết thúc bằng một bản ghi hợp lệ và không bắt buộc phải trích xuất bất kỳ thông tin nào từ một bản ghi một phần.

Nếu thói quen đọc dữ liệu không sử dụng ngoại lệ mà chỉ báo cáo đơn giản là liệu việc đọc có thành công hay không, mã cuộc gọi sẽ phải như sau:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

vv dành ba dòng mã cho mỗi phần công việc hữu ích. Ngược lại, nếu readIntegersẽ ném một ngoại lệ khi gặp phải phần cuối của tệp và nếu người gọi có thể đơn giản chuyển ngoại lệ đó, thì mã sẽ trở thành:

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

Nhìn đơn giản và gọn gàng hơn nhiều, với sự nhấn mạnh hơn nhiều vào trường hợp mọi thứ hoạt động bình thường. Lưu ý rằng trong trường hợp người gọi ngay lập tức sẽ được mong đợi để xử lý một điều kiện, một phương pháp mà trả về một mã lỗi thường sẽ có nhiều hữu ích hơn một mà ném một ngoại lệ. Ví dụ: để tổng tất cả các số nguyên trong một tệp:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

đấu với

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

Mã yêu cầu số nguyên dự kiến ​​rằng một trong những cuộc gọi đó sẽ thất bại. Có mã sử dụng một vòng lặp vô tận sẽ chạy cho đến khi điều đó xảy ra ít thanh lịch hơn nhiều so với việc sử dụng một phương thức chỉ ra các lỗi thông qua giá trị trả về của nó.

Bởi vì các lớp thường không biết những điều kiện nào khách hàng của họ sẽ hoặc sẽ không mong đợi, nên việc cung cấp hai phiên bản phương thức có thể thất bại theo cách mà một số người gọi sẽ mong đợi và những người gọi khác sẽ không có ích. Làm như vậy sẽ cho phép các phương thức như vậy được sử dụng sạch với cả hai loại người gọi. Cũng lưu ý rằng ngay cả các phương thức "thử" cũng nên đưa ra ngoại lệ nếu tình huống phát sinh người gọi có thể không mong đợi. Ví dụ: tryReadIntegerkhông nên ném ngoại lệ nếu nó gặp phải tình trạng cuối tập tin sạch (nếu người gọi không mong đợi điều đó, người gọi sẽ sử dụngreadInteger). Mặt khác, nó có lẽ nên đưa ra một ngoại lệ nếu không thể đọc được dữ liệu vì ví dụ như thẻ nhớ chứa nó đã được rút ra. Mặc dù các sự kiện như vậy phải luôn được công nhận là một khả năng, nhưng không chắc là mã gọi ngay lập tức sẽ được chuẩn bị để làm bất cứ điều gì hữu ích để đáp ứng; nó chắc chắn không nên được báo cáo theo cùng một cách như là một điều kiện cuối tập tin.


2

Điều quan trọng nhất trong việc viết phần mềm là làm cho nó dễ đọc. Tất cả các cân nhắc khác chỉ là thứ yếu, bao gồm làm cho nó hiệu quả và làm cho nó chính xác. Nếu nó có thể đọc được, phần còn lại có thể được chăm sóc trong bảo trì và nếu nó không thể đọc được thì tốt hơn hết là bạn nên vứt nó đi. Do đó, bạn nên ném ngoại lệ khi nó tăng cường khả năng đọc.

Khi bạn đang viết một số thuật toán, chỉ cần nghĩ về người trong tương lai sẽ đọc nó. Khi bạn đến một nơi có thể có một vấn đề tiềm ẩn, hãy tự hỏi liệu người đọc có muốn xem cách bạn xử lý vấn đề đó bây giờ không , hoặc người đọc có thích tiếp tục với thuật toán không?

Tôi thích nghĩ về một công thức cho bánh sô cô la. Khi nó bảo bạn thêm trứng, nó có một lựa chọn: nó có thể giả sử bạn có trứng và tiếp tục với công thức, hoặc nó có thể bắt đầu một lời giải thích về cách bạn có thể lấy trứng nếu bạn không có trứng. Nó có thể lấp đầy cả một cuốn sách với các kỹ thuật săn gà hoang, tất cả để giúp bạn nướng bánh. Điều đó tốt, nhưng hầu hết mọi người sẽ không muốn đọc công thức đó. Hầu hết mọi người muốn chỉ cho rằng trứng có sẵn, và tiếp tục với công thức. Đó là một lời kêu gọi phán xét mà các tác giả cần thực hiện khi viết công thức nấu ăn.

Không thể có bất kỳ quy tắc được đảm bảo nào về những gì tạo ra một ngoại lệ tốt và những vấn đề nào cần được xử lý ngay lập tức, bởi vì nó đòi hỏi bạn phải đọc được suy nghĩ của người đọc. Điều tốt nhất bạn sẽ làm là quy tắc ngón tay cái, và "ngoại lệ chỉ dành cho trường hợp đặc biệt" là một điều khá tốt. Thông thường khi một người đọc đang đọc phương pháp của bạn, họ đang tìm kiếm phương thức nào sẽ thực hiện 99% thời gian và họ không muốn bị lộn xộn với các trường hợp góc kỳ quái như xử lý người dùng nhập dữ liệu bất hợp pháp và những thứ khác gần như không bao giờ xảy ra. Họ muốn xem luồng thông thường của phần mềm của bạn được trình bày trực tiếp, hết lần này đến lần khác như thể không có vấn đề gì xảy ra.


2

Có thể có một yêu cầu để báo cáo về mọi lỗi chúng ta có thể tìm thấy trong đầu vào, không chỉ đầu tiên.

Đây là lý do tại sao bạn không thể ném một ngoại lệ ở đây. Một ngoại lệ ngay lập tức làm gián đoạn quá trình xác nhận. Vì vậy, sẽ có nhiều công việc xung quanh để thực hiện điều này.

Một ví dụ tồi:

Phương thức xác nhận cho Doglớp bằng các ngoại lệ:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Cách gọi:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Vấn đề ở đây là quá trình xác nhận, để có được tất cả các lỗi, sẽ yêu cầu bỏ qua các ngoại lệ đã được tìm thấy. Những điều trên có thể làm việc, nhưng đây là một sự lạm dụng rõ ràng của các ngoại lệ . Loại xác nhận bạn đã yêu cầu nên diễn ra trước khi cơ sở dữ liệu được chạm vào. Vì vậy, không cần phải quay trở lại bất cứ điều gì. Và, kết quả xác nhận có thể là lỗi xác nhận (mặc dù hy vọng là không).

Cách tiếp cận tốt hơn là:

Phương thức gọi:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Phương thức xác nhận:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

Tại sao? Có rất nhiều lý do, và hầu hết các lý do đã được chỉ ra trong các phản ứng khác. Nói một cách đơn giản: Người khác đọc và hiểu nó đơn giản hơn nhiều . Thứ hai, bạn có muốn hiển thị dấu vết ngăn xếp của người dùng để giải thích cho anh ta rằng anh ta đã thiết lập dogsai?

Nếu trong quá trình cam kết trong ví dụ thứ hai, vẫn xảy ra lỗi , mặc dù trình xác nhận của bạn đã xác thực các dogvấn đề bằng 0, thì việc ném ngoại lệ là điều đúng đắn . Giống như: Không có kết nối cơ sở dữ liệu, mục nhập cơ sở dữ liệu đã được sửa đổi bởi người khác trong khi đó, hoặc như thế.

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.