Là các biến lỗi là một mô hình chống hoặc thiết kế tốt?


44

Để xử lý một số lỗi có thể không dừng thực thi, tôi có một errorbiến mà khách hàng có thể kiểm tra và sử dụng để đưa ra ngoại lệ. Đây có phải là một Anti-Pattern? Có cách nào tốt hơn để xử lý việc này? Để biết ví dụ về vấn đề này, bạn có thể thấy API mysqli của PHP . Giả sử rằng các vấn đề về khả năng hiển thị (bộ truy cập, phạm vi công cộng và riêng tư, là biến trong một lớp hoặc toàn cầu?) Được xử lý chính xác.


6
Đây là những gì try/ catchtồn tại cho. Ngoài ra, bạn có thể đặt try/ catchnhiều hơn nữa lên ngăn xếp ở một vị trí phù hợp hơn để xử lý nó (cho phép phân tách mối quan tâm lớn hơn).
jpmc26

Một điều cần lưu ý: nếu bạn sẽ sử dụng xử lý dựa trên ngoại lệ và bạn có một ngoại lệ, bạn không muốn hiển thị quá nhiều thông tin cho người dùng. Sử dụng một trình xử lý lỗi như Elmah hoặc Raygun.io để chặn nó và hiển thị thông báo lỗi chung cho người dùng. KHÔNG BAO GIỜ hiển thị dấu vết ngăn xếp hoặc thông báo lỗi cụ thể cho người dùng, vì họ tiết lộ thông tin về hoạt động bên trong của ứng dụng, có thể bị lạm dụng.
Nzall

4
@Nate Lời khuyên của bạn chỉ áp dụng cho các ứng dụng quan trọng về bảo mật nơi người dùng hoàn toàn không tin cậy. Thông báo lỗi mơ hồ là một mô hình chống. Vì vậy, việc gửi báo cáo lỗi qua mạng mà không có sự đồng ý rõ ràng của người dùng.
piedar

3
@piedar Tôi đã tạo một câu hỏi riêng biệt, nơi điều này có thể được thảo luận một cách tự do hơn: lập trình
viên.stackexchange.com/questions/245255/ chủ

26
Một nguyên tắc chung trong thiết kế API mang đến cho bạn khá xa là luôn luôn xem PHP đang làm gì và sau đó thực hiện điều ngược lại chính xác.
Philipp

Câu trả lời:


65

Nếu một ngôn ngữ vốn đã hỗ trợ các ngoại lệ, thì nên ưu tiên ném ngoại lệ và khách hàng có thể bắt ngoại lệ nếu họ không muốn nó dẫn đến thất bại. Trong thực tế, các máy khách của mã của bạn mong đợi các ngoại lệ và sẽ gặp nhiều lỗi vì chúng sẽ không kiểm tra các giá trị trả về.

Có khá nhiều lợi thế để sử dụng ngoại lệ nếu bạn có sự lựa chọn.

Tin nhắn

Các ngoại lệ chứa thông báo lỗi người dùng có thể đọc được các nhà phát triển có thể sử dụng để gỡ lỗi hoặc thậm chí hiển thị cho người dùng nếu muốn. Nếu mã tiêu thụ không thể xử lý ngoại lệ, nó luôn có thể ghi nhật ký để các nhà phát triển có thể duyệt nhật ký mà không phải dừng lại ở mọi dấu vết khác để tìm ra giá trị trả về là gì và ánh xạ nó trong bảng để tìm ra đâu là giá trị ngoại lệ thực tế.

Với các giá trị trả về, không có thông tin bổ sung nào có thể dễ dàng được cung cấp. Một số ngôn ngữ sẽ hỗ trợ thực hiện các cuộc gọi phương thức để nhận thông báo lỗi cuối cùng, vì vậy mối quan tâm này đã được xóa bỏ một chút, nhưng điều đó đòi hỏi người gọi phải thực hiện thêm các cuộc gọi và đôi khi sẽ yêu cầu quyền truy cập vào một 'đối tượng đặc biệt' mang thông tin này.

Trong trường hợp có thông báo ngoại lệ, tôi cung cấp càng nhiều ngữ cảnh càng tốt, chẳng hạn như:

Không thể truy xuất chính sách tên "foo" cho "thanh" người dùng, được tham chiếu trong hồ sơ người dùng.

So sánh điều này với mã trả về -85. Bạn thích cái nào hơn?

Ngăn xếp cuộc gọi

Các ngoại lệ thường có ngăn xếp cuộc gọi chi tiết giúp mã gỡ lỗi nhanh hơn và nhanh hơn và cũng có thể được ghi lại bằng mã cuộc gọi nếu muốn. Điều này cho phép các nhà phát triển xác định chính xác vấn đề thường đến dòng chính xác và do đó rất mạnh mẽ. Một lần nữa, so sánh tệp này với tệp nhật ký với các giá trị trả về (chẳng hạn như -85, 101, 0, v.v.), bạn thích cái nào hơn?

Thất bại trong cách tiếp cận thiên vị nhanh

Nếu một phương thức được gọi ở đâu đó không thành công, nó sẽ đưa ra một ngoại lệ. Mã gọi phải loại bỏ ngoại lệ một cách rõ ràng nếu không nó sẽ thất bại. Tôi đã thấy điều này thực sự tuyệt vời bởi vì trong quá trình phát triển và thử nghiệm (và ngay cả trong sản xuất), mã bị lỗi nhanh chóng, buộc các nhà phát triển phải sửa nó. Trong trường hợp giá trị trả về, nếu kiểm tra giá trị trả về bị bỏ lỡ, lỗi sẽ âm thầm bị bỏ qua và lỗi xuất hiện ở đâu đó không mong muốn, thường có chi phí cao hơn để gỡ lỗi và sửa lỗi.

Ngoại lệ gói và Unwrapping

Các ngoại lệ có thể được bọc bên trong các ngoại lệ khác và sau đó mở ra nếu cần. Ví dụ: mã của bạn có thể ném mã ArgumentNullExceptionmà cuộc gọi có thể bao bọc bên trong UnableToRetrievePolicyExceptionvì thao tác đó đã thất bại trong mã cuộc gọi. Mặc dù người dùng có thể được hiển thị một thông báo tương tự như ví dụ tôi đã cung cấp ở trên, một số mã chẩn đoán có thể hủy bỏ ngoại lệ và thấy rằng ArgumentNullExceptionđã gây ra sự cố, điều đó có nghĩa đó là lỗi mã hóa trong mã của người tiêu dùng. Điều này sau đó có thể kích hoạt cảnh báo để nhà phát triển có thể sửa mã. Các kịch bản nâng cao như vậy không dễ thực hiện với các giá trị trả về.

Mã đơn giản

Điều này khó hơn một chút để giải thích, nhưng tôi đã học được thông qua mã hóa này với cả giá trị trả về cũng như ngoại lệ. Mã được viết bằng các giá trị trả về thường sẽ thực hiện cuộc gọi và sau đó có một loạt kiểm tra về giá trị trả về là gì. Trong một số trường hợp, nó sẽ thực hiện cuộc gọi đến một phương thức khác và bây giờ sẽ có một loạt kiểm tra khác cho các giá trị trả về từ phương thức đó. Với các trường hợp ngoại lệ, việc xử lý ngoại lệ đơn giản hơn nhiều trong hầu hết các trường hợp. Bạn có một khối thử / bắt / cuối cùng, với thời gian chạy cố gắng hết sức để thực thi mã trong các khối cuối cùng để dọn dẹp. Ngay cả các khối thử / bắt / cuối cùng lồng nhau cũng tương đối dễ theo dõi và duy trì hơn so với lồng nhau nếu / khác và các giá trị trả về được liên kết từ nhiều phương thức.

Phần kết luận

Nếu nền tảng bạn đang sử dụng hỗ trợ các ngoại lệ (đặc biệt là Java hoặc .NET), thì bạn chắc chắn nên cho rằng không có cách nào khác ngoại trừ ném ngoại lệ vì các nền tảng này có hướng dẫn để ném ngoại lệ và khách hàng của bạn sẽ mong đợi vì thế. Nếu tôi đang sử dụng thư viện của bạn, tôi sẽ không bận tâm kiểm tra các giá trị trả về vì tôi hy vọng các ngoại lệ sẽ bị ném, đó là cách thế giới trong các nền tảng này.

Tuy nhiên, nếu là C ++, thì sẽ khó khăn hơn một chút để xác định vì một cơ sở mã lớn đã tồn tại với mã trả về và một số lượng lớn các nhà phát triển được điều chỉnh để trả về các giá trị trái ngược với ngoại lệ (ví dụ: Windows đầy rẫy với HRESULT) . Hơn nữa, trong nhiều ứng dụng, nó cũng có thể là một vấn đề về hiệu năng (hoặc ít nhất là được coi là có).


5
Windows trả về các giá trị HRESULT từ các hàm C ++ để duy trì khả năng tương thích C trong các API công khai của nó (và vì bạn bắt đầu rơi vào một thế giới bị tổn thương khi cố gắng tạo ra các ngoại lệ xuyên qua các ranh giới). Đừng mù quáng làm theo mô hình của hệ điều hành nếu bạn đang viết một ứng dụng.
Cody Grey

2
Điều duy nhất tôi muốn thêm vào điều này là đề cập đến khớp nối lỏng lẻo. Ngoại lệ cho phép bạn xử lý rất nhiều tình huống bất ngờ ở nơi thích hợp nhất. Ví dụ: trong một ứng dụng web, bạn muốn trả lại 500 cho bất kỳ ngoại lệ nào mà mã của bạn chưa được chuẩn bị, thay vì làm hỏng ứng dụng web. Vì vậy, bạn cần một số loại bắt tất cả ở đầu mã của bạn (hoặc trong khung của bạn). Một tình huống tương tự tồn tại trong GUI máy tính để bàn. Nhưng bạn cũng có thể đặt các trình xử lý ít chung hơn ở nhiều vị trí khác nhau trong mã để xử lý nhiều tình huống lỗi khác nhau theo cách phù hợp với quy trình hiện tại đang được thử.
jpmc26

2
@TrentonMaki Nếu bạn đang nói về lỗi từ các nhà xây dựng C ++, câu trả lời tốt nhất là ở đây: parashift.com/c++-faq-lite/ctors-can-throw.html . Nói tóm lại, hãy ném một ngoại lệ, nhưng hãy nhớ làm sạch các rò rỉ tiềm năng trước. Tôi không biết bất kỳ ngôn ngữ nào khác, nơi chỉ cần ném thẳng từ một nhà xây dựng là một điều xấu. Hầu hết người dùng API tôi nghĩ sẽ thích bắt ngoại lệ hơn là kiểm tra mã lỗi.
Ogre Psalm33

3
"Các kịch bản nâng cao như vậy không dễ thực hiện với các giá trị trả về." Chắc chắn bạn có thể! Tất cả những gì bạn phải làm là tạo ra một ErrorStateReturnVariablesiêu hạng và một trong các thuộc tính của nó là InnerErrorState(là một thể hiện của ErrorStateReturnVariable), mà việc thực hiện các lớp con có thể được thiết lập để hiển thị một chuỗi lỗi ... oh, chờ đã. : p
Brian S

5
Chỉ vì một ngôn ngữ hỗ trợ các ngoại lệ không làm cho chúng trở thành thuốc chữa bách bệnh. Các ngoại lệ giới thiệu các đường dẫn thực thi ẩn và do đó các hiệu ứng của chúng cần được kiểm soát chính xác; thử / bắt rất dễ dàng để thêm, nhưng để phục hồi đúng là khó , ...
Matthieu M.

21

Các biến lỗi là một di tích từ các ngôn ngữ như C, nơi không có ngoại lệ. Ngày nay, bạn nên tránh chúng trừ khi bạn đang viết thư viện có khả năng được sử dụng từ chương trình C (hoặc ngôn ngữ tương tự mà không xử lý ngoại lệ).

Tất nhiên, nếu bạn có một loại lỗi có thể được phân loại tốt hơn là "cảnh báo" (= thư viện của bạn có thể cung cấp kết quả hợp lệ và người gọi có thể bỏ qua cảnh báo nếu anh ta cho rằng nó không quan trọng), thì chỉ báo trạng thái ở dạng của một biến có thể có ý nghĩa ngay cả trong các ngôn ngữ có ngoại lệ. Nhưng hãy cẩn thận. Người gọi của thư viện có xu hướng bỏ qua những cảnh báo như vậy ngay cả khi họ không nên. Vì vậy, hãy suy nghĩ hai lần trước khi giới thiệu một cấu trúc như vậy vào lib của bạn.


1
Cảm ơn đã giải thích! Câu trả lời của bạn + Omer Iqbal đã trả lời câu hỏi của tôi.
Mikayla Maki

Một cách khác để xử lý "cảnh báo" là ném ngoại lệ theo mặc định và có một số loại cờ tùy chọn để ngăn ngoại lệ bị ném.
Cá Cyanfish

1
@Cyanfish: có, nhưng người ta phải cẩn thận không để quá nhiều thứ như vậy, đặc biệt là khi làm thư viện. Tốt hơn cung cấp một cơ chế cảnh báo đơn giản và làm việc hơn 2, 3 hoặc nhiều hơn.
Doc Brown

Một ngoại lệ chỉ nên được ném khi một thất bại, kịch bản không xác định hoặc không thể phục hồi đã được trải nghiệm - trường hợp đặc biệt . Bạn nên mong đợi tác động hiệu suất khi xây dựng các ngoại lệ
Gusdor

@Gusdor: hoàn toàn, đó là lý do tại sao một "cảnh báo" điển hình nên IMHO không ném ngoại lệ theo mặc định. Nhưng điều này cũng phụ thuộc một chút vào mức độ trừu tượng. Đôi khi, một dịch vụ hoặc thư viện đơn giản là không thể quyết định liệu một sự kiện bất thường có nên được coi là một ngoại lệ theo quan điểm của người gọi hay không. Cá nhân, trong những tình huống như vậy, tôi thích lib chỉ để đặt chỉ báo cảnh báo (không có ngoại lệ), hãy để người gọi kiểm tra cờ đó và ném ngoại lệ nếu anh ta nghĩ nó phù hợp. Đó là những gì tôi đã nghĩ đến khi tôi viết ở trên "tốt hơn hết là cung cấp một cơ chế cảnh báo một cách tự do".
Doc Brown

20

Có nhiều cách để báo hiệu lỗi:

  • một biến lỗi để kiểm tra: C , Go , ...
  • một ngoại lệ: Java , C # , ...
  • một trình xử lý "điều kiện": Lisp (chỉ?), ...
  • một sự trở lại đa hình: Haskell , ML , Rust , ...

Vấn đề của biến lỗi là rất dễ quên để kiểm tra.

Vấn đề của các trường hợp ngoại lệ là tạo ra các đường dẫn thực thi ẩn, và mặc dù try / Catch rất dễ viết, đảm bảo phục hồi đúng trong mệnh đề bắt thực sự rất khó (không có hỗ trợ từ các hệ thống / trình biên dịch kiểu).

Vấn đề của các trình xử lý điều kiện là chúng không kết hợp tốt: nếu bạn có thực thi mã động (các hàm ảo), thì không thể dự đoán được các điều kiện nào sẽ được xử lý. Hơn nữa, nếu điều kiện tương tự có thể được nêu ra ở một vài điểm, không có gì để nói rằng một giải pháp thống nhất có thể được áp dụng mỗi lần, và nó nhanh chóng trở nên lộn xộn.

Trả về đa hình ( Either a btrong Haskell) là giải pháp yêu thích của tôi cho đến nay:

  • rõ ràng: không có con đường thực thi ẩn
  • rõ ràng: tài liệu đầy đủ trong chữ ký loại chức năng (không có gì ngạc nhiên)
  • khó bỏ qua: bạn phải khớp mẫu để có kết quả mong muốn và xử lý trường hợp lỗi

Vấn đề duy nhất là chúng có khả năng dẫn đến việc kiểm tra quá mức; các ngôn ngữ sử dụng chúng có thành ngữ để xâu chuỗi các cuộc gọi của các hàm sử dụng chúng, nhưng nó vẫn có thể cần thêm một chút gõ / lộn xộn. Trong Haskell này sẽ là monads ; tuy nhiên, điều này đáng sợ hơn nhiều so với âm thanh, xem Lập trình định hướng đường sắt .


1
Câu trả lời tốt! Tôi đã hy vọng tôi có thể nhận được một danh sách các cách để xử lý lỗi.
Mikayla Maki

Arrghhhh! Đã bao nhiêu lần tôi thấy điều này "Vấn đề của biến lỗi là rất dễ quên để kiểm tra". Vấn đề với các ngoại lệ là rất dễ quên để bắt nó. Sau đó, ứng dụng của bạn gặp sự cố. Sếp của bạn không muốn trả tiền để sửa nó nhưng khách hàng của bạn ngừng sử dụng ứng dụng của bạn vì họ cảm thấy thất vọng với các sự cố. Tất cả chỉ vì một cái gì đó sẽ không ảnh hưởng đến việc thực hiện chương trình nếu mã lỗi được trả lại và bỏ qua. Lần duy nhất tôi từng thấy mọi người bỏ qua mã lỗi là khi nó không quan trọng đến thế. Mã cao hơn lên chuỗi biết có gì đó không ổn.
Dunk

1
@ Dunk: Tôi không nghĩ rằng câu trả lời của tôi cũng đưa ra lời xin lỗi về các ngoại lệ; mặc dù, nó cũng có thể phụ thuộc vào loại mã bạn viết. Kinh nghiệm làm việc cá nhân của tôi có xu hướng hệ thống ủng hộ rằng thất bại trong sự hiện diện của sai lầm vì dữ liệu im lặng tham nhũng là tồi tệ hơn (và không bị phát hiện) và các dữ liệu tôi làm việc trên rất có giá trị cho khách hàng (tất nhiên, nó cũng có nghĩa là các bản sửa lỗi khẩn cấp).
Matthieu M.

Đó không phải là một câu hỏi về tham nhũng dữ liệu im lặng. Đó là một câu hỏi để hiểu ứng dụng của bạn và biết khi nào và nơi bạn cần xác minh nếu một hoạt động thành công hay không. Trong nhiều trường hợp, việc quyết định và xử lý nó có thể bị trì hoãn. Không nên nhờ người khác nói với tôi khi tôi phải xử lý một thao tác thất bại, điều mà một ngoại lệ đòi hỏi. Đó là ứng dụng của tôi, tôi biết khi nào và ở đâu tôi muốn xử lý bất kỳ vấn đề nào có liên quan. Nếu mọi người viết các ứng dụng có thể làm hỏng dữ liệu thì đó chỉ là một công việc thực sự tồi tệ. Viết các ứng dụng bị sập (mà tôi thấy rất nhiều) cũng đang làm một công việc kém.
Dunk

12

Tôi nghĩ nó thật kinh khủng. Tôi hiện đang cấu trúc lại một ứng dụng Java sử dụng các giá trị trả về thay vì các ngoại lệ. Mặc dù bạn hoàn toàn có thể không làm việc với Java, nhưng tôi nghĩ rằng điều này vẫn áp dụng.

Bạn kết thúc với mã như thế này:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

Hoặc này:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

Tôi thực sự muốn có những hành động ném ngoại lệ, vì vậy bạn kết thúc với một cái gì đó như:

x.doActionA();
x.doActionB();

Bạn có thể gói nó trong một lần thử và nhận thông báo từ ngoại lệ hoặc bạn có thể chọn bỏ qua ngoại lệ, ví dụ như khi bạn xóa một cái gì đó có thể đã biến mất. Nó cũng bảo tồn dấu vết ngăn xếp của bạn, nếu bạn có. Các phương pháp cũng trở nên dễ dàng hơn. Thay vì tự xử lý các ngoại lệ, họ chỉ ném những gì sai.

Mã hiện tại (khủng khiếp):

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

Mới và cải tiến:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

Dấu vết Strack được lưu giữ và thông điệp có sẵn trong trường hợp ngoại lệ, thay vì "Có gì đó không ổn!".

Tất nhiên, bạn có thể cung cấp các thông báo lỗi tốt hơn và bạn nên. Nhưng bài đăng này ở đây vì mã hiện tại tôi đang làm việc là một nỗi đau và bạn không nên làm như vậy.


1
Mặc dù vậy, có một số trường hợp, 'mới và cải tiến' của bạn sẽ mất bối cảnh xảy ra ngoại lệ ban đầu. Ví dụ: trong "phiên bản hiện tại (khủng khiếp)" của doActionA (), mệnh đề bắt sẽ có quyền truy cập vào các biến thể hiện và thông tin khác từ đối tượng kèm theo để đưa ra một thông báo hữu ích hơn.
Được thông báo vào

1
Đó là sự thật, nhưng hiện tại không xảy ra ở đây. Và bạn luôn có thể bắt ngoại lệ trong doActionA và bọc nó trong một ngoại lệ khác bằng một thông báo trạng thái. Sau đó, bạn vẫn sẽ có dấu vết ngăn xếp thông báo hữu ích. throw new Exception("Something went wrong with " + instanceVar, ex);
mrjink

Tôi đồng ý, nó có thể không xảy ra trong tình huống của bạn. Nhưng bạn không thể "luôn luôn" đưa thông tin vào doActionA (). Tại sao? Người gọi của doActionA () có thể là người duy nhất nắm giữ thông tin mà bạn cần đưa vào.
Được thông báo vào

2
Vì vậy, có người gọi bao gồm nó khi nó xử lý ngoại lệ. Điều tương tự áp dụng cho câu hỏi ban đầu, mặc dù. Không có gì bạn có thể làm bây giờ mà bạn không thể làm với các ngoại lệ và nó dẫn đến mã sạch hơn. Tôi thích ngoại lệ hơn các booleans trả về hoặc thông báo lỗi.
mrjink

Bạn đưa ra giả định thường sai rằng một ngoại lệ xảy ra trong bất kỳ "someOperation" nào có thể được xử lý và dọn sạch theo cùng một cách. Điều xảy ra trong cuộc sống thực là bạn cần nắm bắt và xử lý các ngoại lệ cho mỗi thao tác. Vì vậy, bạn không chỉ ném một ngoại lệ như trong ví dụ của bạn. Ngoài ra, sử dụng các ngoại lệ sau đó kết thúc việc tạo ra một loạt các khối bắt thử lồng nhau hoặc một loạt các khối thử bắt. Nó thường làm cho mã ít dễ đọc hơn. Tôi không được đặt ra ngoại lệ, nhưng tôi sử dụng công cụ thích hợp cho tình huống cụ thể. Ngoại lệ chỉ là 1 công cụ.
Dunk

5

"Để xử lý một số lỗi có thể xảy ra, điều đó không nên dừng thực thi",

Nếu bạn có nghĩa là các lỗi không nên dừng thực thi chức năng hiện tại, nhưng nên được báo cáo cho người gọi theo một cách nào đó - thì bạn có một vài tùy chọn chưa thực sự được đề cập. Trường hợp này thực sự là một cảnh báo nhiều hơn là một lỗi. Ném / Trả lại không phải là một lựa chọn vì nó kết thúc chức năng hiện tại. Một thông số lỗi hoặc trả về thông báo lỗi chỉ cho phép xảy ra tối đa một trong những lỗi này.

Hai mẫu mà tôi đã sử dụng là:

  • Một bộ sưu tập lỗi / cảnh báo, được truyền vào hoặc giữ như một biến thành viên. Mà bạn nối các công cụ vào và chỉ cần tiếp tục xử lý. Cá nhân tôi không thực sự thích cách tiếp cận này vì tôi cảm thấy nó không cho phép người gọi.

  • Truyền vào một đối tượng xử lý lỗi / cảnh báo (hoặc đặt nó làm biến thành viên). Và mỗi lỗi gọi một chức năng thành viên của trình xử lý. Bằng cách này, người gọi có thể quyết định phải làm gì với các lỗi không kết thúc như vậy.

Những gì bạn chuyển đến các bộ sưu tập / trình xử lý này phải chứa đủ ngữ cảnh để xử lý lỗi một cách "chính xác" - Một chuỗi thường quá ít, việc truyền cho nó một số trường hợp Ngoại lệ thường hợp lý - nhưng đôi khi lại cau mày (vì lạm dụng Ngoại lệ) .

Mã đánh máy bằng cách sử dụng một trình xử lý lỗi có thể trông như thế này

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}

3
+1 để nhận thấy rằng người dùng muốn cảnh báo, thay vì lỗi. Có thể đáng để đề cập đến warningsgói của Python , đưa ra một mô hình khác cho vấn đề này.
James_pic

Cảm ơn câu trả lời tuyệt vời! Đây là nhiều hơn những gì tôi muốn thấy, các mẫu bổ sung để xử lý các lỗi trong đó thử / bắt truyền thống có thể không đủ.
Mikayla Maki

Có thể hữu ích khi đề cập rằng một đối tượng gọi lại lỗi đã qua có thể đưa ra một ngoại lệ khi xảy ra lỗi hoặc cảnh báo nhất định - thực sự, đó có thể là hành vi mặc định - nhưng cũng có thể hữu ích đối với phương tiện mà nó có thể hỏi chức năng gọi để làm một cái gì đó. Ví dụ: phương pháp "xử lý lỗi phân tích cú pháp" có thể cung cấp cho người gọi một giá trị mà phân tích cú pháp sẽ được coi là đã trả về.
supercat

5

Thường không có gì sai khi sử dụng mẫu này hoặc mẫu đó, miễn là bạn sử dụng mẫu mà mọi người khác sử dụng. Trong phát triển Objective-C , mẫu được ưu tiên nhiều là truyền một con trỏ trong đó phương thức được gọi có thể ký gửi một đối tượng NSError. Các ngoại lệ được dành riêng cho các lỗi lập trình và dẫn đến sự cố (trừ khi bạn có các lập trình viên Java hoặc .NET viết ứng dụng iPhone đầu tiên của họ). Và điều này hoạt động khá tốt.


4

Câu hỏi đã được trả lời, nhưng tôi không thể tự giúp mình.

Bạn thực sự không thể mong đợi Exception cung cấp giải pháp cho tất cả các trường hợp sử dụng. Búa ai?

Có những trường hợp Ngoại lệ không phải là kết thúc tất cả và là tất cả, ví dụ, nếu một phương thức nhận được yêu cầu và chịu trách nhiệm xác thực tất cả các trường được thông qua, và không chỉ trường đầu tiên thì bạn phải nghĩ rằng nó có thể cho biết nguyên nhân gây ra lỗi cho nhiều hơn một trường. Cũng có thể chỉ ra liệu bản chất của xác nhận có ngăn người dùng đi xa hơn hay không. Một ví dụ về điều đó sẽ là một mật khẩu không mạnh. Bạn có thể hiển thị một thông báo cho người dùng chỉ ra rằng mật khẩu đã nhập không mạnh lắm, nhưng nó đủ mạnh.

Bạn có thể lập luận rằng tất cả các xác nhận hợp lệ này có thể được đưa ra như một ngoại lệ ở cuối mô-đun xác thực, nhưng chúng sẽ là mã lỗi trong bất cứ điều gì trừ tên.

Vì vậy, bài học ở đây là: Ngoại lệ có vị trí của chúng, cũng như mã lỗi. Lựa chọn sáng suốt.


Tôi nghĩ rằng điều này khá ngụ ý một thiết kế xấu. Một phương thức lấy các đối số sẽ có thể xử lý các đối số đó hay không - không có gì ở giữa. Ví dụ của bạn nên có một Validator(giao diện) được đưa vào phương thức đang đề cập (hoặc đối tượng đằng sau nó). Tùy thuộc vào việc tiêm Validator, phương pháp sẽ tiến hành với mật khẩu xấu - hoặc không. Mã xung quanh sau đó có thể thử WeakValidatornếu người dùng yêu cầu mã đó sau, ví dụ: mã WeakPasswordExceptionbị ném bởi lần thử đầu tiên StrongValidator.
jhr

À, nhưng tôi không nói đây không phải là giao diện hay trình xác nhận. Tôi thậm chí đã không đề cập đến JSR303. Và, nếu bạn đọc kỹ, tôi chắc chắn không nói mật khẩu yếu, thay vào đó, nó không mạnh lắm. Mật khẩu yếu sẽ là một lý do để ngăn chặn dòng chảy và yêu cầu người dùng nhập mật khẩu mạnh hơn.
Alexandre Santos

Và bạn sẽ làm gì với một mật khẩu trung bình-mạnh-nhưng-không-thực sự yếu? Bạn sẽ làm gián đoạn dòng chảy và hiển thị cho người dùng một thông báo cho biết mật khẩu đã nhập không mạnh lắm. Vì vậy, có một MiddlyStrongValidatorhoặc một cái gì đó. Và nếu điều đó không thực sự làm gián đoạn luồng của bạn, thì Validatorphải được gọi trước, đó là trước khi tiến hành luồng trong khi người dùng vẫn đang nhập mật khẩu của họ (hoặc tương tự). Nhưng sau đó, việc xác nhận không phải là một phần của phương pháp được đề cập ở nơi đầu tiên. :) Có lẽ là vấn đề của hương vị sau tất cả ...
jhr

@jhr Trong các trình xác nhận mà tôi đã viết, thông thường tôi sẽ tạo một AggregateException(hoặc tương tự ValidationException) và đặt các ngoại lệ cụ thể cho từng vấn đề xác thực trong InnerExceptions. Ví dụ: có thể là BadPasswordException: "Mật khẩu của người dùng nhỏ hơn độ dài tối thiểu là 6" hoặc MandatoryFieldMissingException: "Tên đầu tiên phải được cung cấp cho người dùng", v.v. Điều này không tương đương với mã lỗi. Tất cả các thông báo này có thể được hiển thị cho người dùng theo cách mà họ sẽ hiểu và nếu một tin nhắn NullReferenceExceptionbị ném thay thế, thì chúng tôi đã gặp lỗi.
Omer Iqbal

4

Có trường hợp sử dụng là mã lỗi được ưu tiên ngoại lệ.

Nếu mã của bạn có thể tiếp tục mặc dù có lỗi, nhưng nó cần báo cáo, thì một ngoại lệ là một lựa chọn kém vì các ngoại lệ chấm dứt luồng. Ví dụ: nếu bạn đang đọc trong một tệp dữ liệu và phát hiện ra nó chứa một số dữ liệu xấu không phải là thiết bị đầu cuối thì tốt hơn là nên đọc trong phần còn lại của tệp và báo cáo lỗi thay vì thất bại hoàn toàn.

Các câu trả lời khác đã đề cập tại sao các ngoại lệ nên được ưu tiên cho các mã lỗi nói chung.


Nếu một cảnh báo cần phải được ghi lại hoặc một cái gì đó, vì vậy nó được. Nhưng nếu một lỗi "cần báo cáo", theo đó tôi giả sử bạn có nghĩa là báo cáo cho người dùng, không có cách nào để đảm bảo rằng mã xung quanh sẽ đọc mã trả về của bạn và thực sự báo cáo nó.
jhr

Không, tôi có nghĩa là báo cáo nó cho người gọi. Trách nhiệm của người gọi là quyết định xem người dùng có cần biết về lỗi hay không, giống như với một ngoại lệ.
Jack Aidley

1
@jhr: Khi nào có bất kỳ sự đảm bảo nào? Hợp đồng cho một lớp học có thể chỉ định rằng khách hàng có trách nhiệm nhất định; nếu khách hàng tuân thủ hợp đồng, họ sẽ làm những việc mà hợp đồng yêu cầu. Nếu họ không, bất kỳ hậu quả sẽ là lỗi của mã máy khách. Nếu khách hàng muốn bảo vệ chống lại các lỗi vô ý của khách hàng và có quyền kiểm soát loại được trả về bằng phương thức tuần tự hóa, người ta có thể sử dụng cờ "tham nhũng có thể không được chấp nhận" và không cho phép khách hàng đọc dữ liệu từ đó mà không gọi AcknowledgePossibleCorruptionphương thức .. .
supercat

1
... nhưng có một lớp đối tượng để lưu giữ thông tin về các vấn đề có thể hữu ích hơn là ném ngoại lệ hoặc trả lại mã lỗi không thành công. Ứng dụng sẽ sử dụng thông tin đó theo cách phù hợp (ví dụ: khi tải tệp "Foo", thông báo cho người dùng rằng dữ liệu có thể không đáng tin cậy và nhắc người dùng chọn tên mới khi lưu).
supercat

1
Có một đảm bảo nếu sử dụng các ngoại lệ: Nếu bạn không bắt được chúng, chúng sẽ ném lên cao hơn - trường hợp xấu nhất đối với UI. Không đảm bảo như vậy nếu bạn sử dụng mã trả về mà không ai đọc. Chắc chắn, làm theo API nếu bạn muốn sử dụng nó. Tôi đồng ý! Nhưng có chỗ cho sai sót, không may ...
jhr

2

Chắc chắn không có gì sai khi không sử dụng ngoại lệ khi ngoại lệ không phù hợp.

Khi việc thực thi mã không bị gián đoạn (ví dụ: tác động đến đầu vào của người dùng có thể có nhiều lỗi, như chương trình để biên dịch hoặc biểu mẫu để xử lý), tôi thấy rằng việc thu thập lỗi trong các biến lỗi như has_errorserror_messagesthực sự là thiết kế thanh lịch hơn nhiều so với ném một ngoại lệ ở lỗi đầu tiên. Nó cho phép tìm tất cả các lỗi trong đầu vào của người dùng mà không buộc người dùng phải gửi lại một cách không cần thiết.


Thú vị đưa vào câu hỏi. Tôi nghĩ rằng vấn đề với câu hỏi của tôi, và sự hiểu biết của tôi, là thuật ngữ không rõ ràng. Những gì bạn mô tả không phải là ngoại lệ, nhưng đó là một lỗi. Chúng ta nên gọi nó là gì?
Mikayla Maki

1

Trong một số ngôn ngữ lập trình động, bạn có thể sử dụng cả giá trị lỗixử lý ngoại lệ . Điều này được thực hiện bằng cách trả về đối tượng ngoại lệ không thay thế thay cho giá trị trả về thông thường, có thể được kiểm tra như giá trị lỗi, nhưng nó sẽ ném ngoại lệ nếu không được kiểm tra.

Trong Perl 6, nó được thực hiện thông qua fail, nếu no fatal;phạm vi rút lại trả về một Failuređối tượng ngoại lệ đặc biệt chưa được xử lý .

Trong Perl 5, bạn có thể sử dụng Contextual :: Return, bạn có thể làm điều này với return FAIL.


-1

Trừ khi có một cái gì đó rất cụ thể, tôi nghĩ rằng có một biến lỗi để xác nhận là một ý tưởng tồi. Mục đích dường như là để tiết kiệm thời gian dành cho xác nhận (bạn chỉ có thể trả về giá trị biến)

Nhưng sau đó nếu bạn thay đổi bất cứ điều gì, bạn phải tính toán lại giá trị đó. Tôi không thể nói nhiều hơn về việc tạm dừng và ném ngoại lệ mặc dù.

EDIT: Tôi đã không nhận ra đây là một câu hỏi về mô hình phần mềm thay vì một trường hợp cụ thể.

Hãy để tôi làm rõ hơn quan điểm của tôi trong một trường hợp cụ thể của tôi trong đó câu trả lời của tôi sẽ có ý nghĩa

  1. Tôi có một bộ sưu tập các đối tượng thực thể
  2. Tôi có các dịch vụ web kiểu thủ tục hoạt động với các đối tượng thực thể này

Có hai loại lỗi:

  1. Các lỗi khi xử lý xảy ra trong lớp dịch vụ
  2. Các lỗi vì có sự không nhất quán trong các đối tượng thực thể

Trong lớp dịch vụ, không có lựa chọn nào khác ngoài việc sử dụng đối tượng Kết quả làm trình bao bọc, tương đương với biến lỗi. Mô phỏng một ngoại lệ thông qua cuộc gọi dịch vụ trên giao thức như http là có thể, nhưng chắc chắn không phải là một điều tốt để làm. Tôi không nói về loại lỗi này và không nghĩ rằng đây là loại lỗi được hỏi trong câu hỏi này.

Tôi đã suy nghĩ về loại lỗi thứ hai. Và câu trả lời của tôi là về loại lỗi thứ hai này. Trong các đối tượng thực thể, có những lựa chọn cho chúng ta, một số trong số chúng là

  • sử dụng biến xác nhận
  • ném một ngoại lệ ngay lập tức khi một trường được đặt không chính xác từ setter

Sử dụng biến xác nhận cũng giống như có một phương thức xác thực duy nhất cho mỗi đối tượng thực thể. Cụ thể, người dùng có thể đặt giá trị theo cách giữ cho setter hoàn toàn là setter, không có tác dụng phụ (đây thường là một cách thực hành tốt) hoặc người ta có thể kết hợp xác nhận vào từng setter và sau đó lưu kết quả vào biến xác thực. Ưu điểm của việc này là tiết kiệm thời gian, kết quả xác thực được lưu vào bộ biến xác thực để khi người dùng gọi xác thực () nhiều lần, không cần thực hiện nhiều xác thực.

Điều tốt nhất để làm trong trường hợp này là sử dụng một phương thức xác nhận duy nhất mà không sử dụng bất kỳ xác nhận nào để xác thực lỗi bộ đệm. Điều này giúp giữ cho setter như chỉ setter.


Tôi thấy những gì bạn đang nói về. Cách tiếp cận thú vị. Tôi rất vui vì đó là một phần của bộ câu trả lời.
Mikayla Maki
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.