Tại sao không sử dụng ngoại lệ như luồng kiểm soát thường xuyên?


192

Để tránh tất cả các câu trả lời tiêu chuẩn mà tôi có thể có trên Google, tôi sẽ cung cấp một ví dụ mà tất cả các bạn có thể tấn công theo ý muốn.

C # và Java (và quá nhiều loại khác) có rất nhiều loại hành vi 'tràn' mà tôi không thích chút nào (ví type.MaxValue + type.SmallestValue == type.MinValuedụ: ví dụ int.MaxValue + 1 == int.MinValue:).

Nhưng, nhìn thấy bản chất xấu xa của tôi, tôi sẽ thêm một chút xúc phạm đến thương tích này bằng cách mở rộng hành vi này sang, giả sử là một DateTimekiểu Ghi đè . (Tôi biết DateTimeđược niêm phong bằng .NET, nhưng vì lợi ích của ví dụ này, tôi đang sử dụng ngôn ngữ giả giống hệt như C #, ngoại trừ thực tế là DateTime không được niêm phong).

AddPhương thức ghi đè :

/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan. 
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and 
/// continue from DateTime.MinValue. 
/// </returns>
public DateTime override Add(TimeSpan ts) 
{
    try
    {                
        return base.Add(ts);
    }
    catch (ArgumentOutOfRangeException nb)
    {
        // calculate how much the MaxValue is exceeded
        // regular program flow
        TimeSpan saldo = ts - (base.MaxValue - this);
        return DateTime.MinValue.Add(saldo)                         
    }
    catch(Exception anyOther) 
    {
        // 'real' exception handling.
    }
}

Tất nhiên là nếu có thể giải quyết điều này dễ như vậy, nhưng thực tế là tôi chỉ không hiểu tại sao bạn không thể sử dụng ngoại lệ (về mặt logic, tôi có thể thấy rằng khi hiệu suất là một vấn đề mà trong một số trường hợp ngoại lệ nên tránh ).

Tôi nghĩ rằng trong nhiều trường hợp, chúng rõ ràng hơn cấu trúc if và không phá vỡ bất kỳ hợp đồng nào mà phương thức đang thực hiện.

IMHO các loại Không bao giờ sử dụng chúng cho dòng chảy chương trình thông thường Phản ứng của mọi người dường như không được phát triển tốt vì sức mạnh của phản ứng đó có thể biện minh.

Hay là tôi nhầm?

Tôi đã đọc các bài viết khác, xử lý tất cả các trường hợp đặc biệt, nhưng quan điểm của tôi là không có gì sai với nó nếu cả hai bạn:

  1. Thông thoáng
  2. Tôn trọng hợp đồng theo phương pháp của bạn

Bắn tôi đi.


2
+1 Tôi cũng cảm thấy như vậy. Bên cạnh hiệu suất, lý do chính đáng duy nhất để tránh luồng ngoại lệ cho kiểm soát luồng là khi mã người gọi sẽ dễ đọc hơn nhiều với các giá trị trả về.

4
là: return -1 nếu có gì đó xảy ra, trả về -2 nếu có thứ gì khác, v.v ... thực sự dễ đọc hơn ngoại lệ?
kender

1
Thật đáng buồn khi một người bị mang tiếng xấu khi nói sự thật: rằng ví dụ của bạn không thể được viết bằng câu lệnh if. (Điều này không có nghĩa là nó đúng / hoàn chỉnh.)
Ingo

8
Tôi sẽ lập luận rằng, ném một ngoại lệ đôi khi có thể là lựa chọn duy nhất của bạn. Tôi đã ví dụ một thành phần kinh doanh khởi tạo trạng thái bên trong của nó trong hàm tạo bằng cách truy vấn cơ sở dữ liệu. Đôi khi, không có dữ liệu thích hợp trong cơ sở dữ liệu. Ném một ngoại lệ trong hàm tạo là cách duy nhất để hủy bỏ hiệu quả việc xây dựng đối tượng. Điều này được nêu rõ trong hợp đồng (Javadoc trong trường hợp của tôi) của lớp, vì vậy tôi không gặp vấn đề gì khi mã máy khách có thể (và nên) bắt ngoại lệ đó khi tạo thành phần và tiếp tục từ đó.
Stefan Haberl

1
Kể từ khi bạn đưa ra một giả thuyết, trách nhiệm của bạn cũng là trích dẫn bằng chứng / lý do chứng thực. Đối với người mới bắt đầu, hãy nêu một lý do tại sao mã của bạn vượt trội hơn so với iftuyên bố tự viết tài liệu ngắn hơn nhiều . Bạn sẽ thấy điều này rất khó khăn. Nói cách khác: chính tiền đề của bạn là thiếu sót, và kết luận bạn rút ra từ đó là sai.
Konrad Rudolph

Câu trả lời:


169

Bạn đã bao giờ thử gỡ lỗi một chương trình đưa ra năm ngoại lệ mỗi giây trong quá trình hoạt động bình thường chưa?

Tôi có.

Chương trình này khá phức tạp (nó là một máy chủ tính toán phân tán) và một sửa đổi nhỏ ở một bên của chương trình có thể dễ dàng phá vỡ một cái gì đó ở một nơi hoàn toàn khác.

Tôi ước tôi có thể khởi chạy chương trình và chờ ngoại lệ xảy ra, nhưng có khoảng 200 trường hợp ngoại lệ trong quá trình khởi động trong quá trình hoạt động bình thường

Quan điểm của tôi: nếu bạn sử dụng ngoại lệ cho các tình huống thông thường, làm thế nào để bạn xác định vị trí bất thường (tức là ngoại lệ al)?

Tất nhiên, có những lý do mạnh mẽ khác để không sử dụng ngoại lệ quá nhiều, đặc biệt là hiệu suất khôn ngoan


13
Ví dụ: khi tôi gỡ lỗi một chương trình .net, tôi khởi chạy nó từ studio trực quan và tôi yêu cầu VS phá vỡ mọi ngoại lệ. Nếu bạn dựa vào các ngoại lệ như một hành vi dự kiến, tôi không thể làm điều đó nữa (vì nó sẽ phá vỡ 5 lần / giây) và việc xác định phần có vấn đề của mã sẽ phức tạp hơn nhiều.
Brann

15
+1 để chỉ ra rằng bạn không muốn tạo ra một haystack ngoại lệ để tìm kim thực sự đặc biệt.
Grant Wagner

16
đừng có câu trả lời này chút nào, tôi nghĩ mọi người hiểu nhầm ở đây Nó không liên quan gì đến việc gỡ lỗi cả, nhưng với thiết kế. Đây là lý do tròn trong hình thức pures tôi sợ. Và quan điểm của bạn thực sự bên cạnh câu hỏi như đã nêu trước đây
Peter

11
@Peter: Gỡ lỗi mà không phá vỡ các ngoại lệ là khó khăn và bắt tất cả các ngoại lệ là đau đớn nếu có rất nhiều trong số chúng theo thiết kế. Tôi nghĩ rằng một thiết kế khiến việc gỡ lỗi khó gần như bị hỏng một phần (nói cách khác, thiết kế ĐÃ làm gì đó với gỡ lỗi, IMO)
Brann

7
Ngay cả khi bỏ qua thực tế là hầu hết các tình huống tôi muốn gỡ lỗi không tương ứng với các ngoại lệ bị ném, câu trả lời cho câu hỏi của bạn là: "theo loại", ví dụ: tôi sẽ nói với trình gỡ lỗi của mình chỉ bắt Ass AssError hoặc StandardError hoặc một cái gì đó tương ứng với những điều xấu xảy ra. Nếu bạn gặp rắc rối với điều đó, thì làm thế nào để bạn đăng nhập - bạn không đăng nhập theo cấp độ và lớp, chính xác để bạn có thể lọc chúng? Bạn có nghĩ rằng đó cũng là một ý tưởng tồi?
Ken

157

Các ngoại lệ về cơ bản là các gototuyên bố không cục bộ với tất cả các hậu quả sau này. Sử dụng ngoại lệ cho điều khiển luồng vi phạm nguyên tắc ít gây ngạc nhiên nhất , làm cho các chương trình khó đọc (hãy nhớ rằng các chương trình được viết cho lập trình viên trước).

Hơn nữa, đây không phải là những gì các nhà cung cấp trình biên dịch mong đợi. Họ hy vọng các ngoại lệ sẽ bị ném hiếm khi và họ thường để throwmã khá kém hiệu quả. Ném ngoại lệ là một trong những hoạt động đắt nhất trong .NET.

Tuy nhiên, một số ngôn ngữ (đáng chú ý là Python) sử dụng ngoại lệ làm cấu trúc kiểm soát luồng. Ví dụ, các trình vòng lặp đưa ra một StopIterationngoại lệ nếu không có thêm mục nào. Ngay cả các cấu trúc ngôn ngữ tiêu chuẩn (như for) cũng dựa vào điều này.


11
này, ngoại lệ không đáng kinh ngạc! Và bạn mâu thuẫn với chính mình khi bạn nói "đó là một ý tưởng tồi" và sau đó tiếp tục nói "nhưng đó là một ý tưởng tốt trong con trăn".
hasen

6
Tôi vẫn chưa bị thuyết phục: 1) Bên cạnh câu hỏi, rất nhiều chương trình phi du thuyền không thể quan tâm ít hơn (ví dụ: giao diện người dùng) 2) Đáng kinh ngạc: như tôi đã nói nó chỉ gây ngạc nhiên vì nó không được sử dụng, nhưng câu hỏi vẫn còn: tại sao không sử dụng id ở nơi đầu tiên? Nhưng, vì đây là câu trả lời
Peter

4
+1 Thật ra tôi rất vui vì bạn đã chỉ ra sự khác biệt giữa Python và C #. Tôi không nghĩ đó là một mâu thuẫn. Python năng động hơn nhiều và kỳ vọng sử dụng các ngoại lệ theo cách này được đưa vào ngôn ngữ. Nó cũng là một phần của văn hóa EAFP của Python. Tôi không biết cách tiếp cận nào tinh khiết hơn về mặt khái niệm hoặc tự nhất quán hơn, nhưng tôi thích ý tưởng viết mã thực hiện những gì người khác mong đợi , nghĩa là các phong cách khác nhau trong các ngôn ngữ khác nhau.
Đánh dấu E. Haase

13
gotoTất nhiên, không giống với các trường hợp ngoại lệ tương tác chính xác với ngăn xếp cuộc gọi của bạn và với phạm vi từ vựng và không để lại ngăn xếp hoặc phạm vi trong một mớ hỗn độn.
Lukas Eder

4
Trên thực tế, hầu hết các nhà cung cấp VM đều mong đợi các ngoại lệ và xử lý chúng một cách hiệu quả. Như @LukasEder lưu ý các ngoại lệ hoàn toàn không giống với goto ở chỗ chúng có cấu trúc.
Marcin

29

Nguyên tắc của tôi là:

  • Nếu bạn có thể làm bất cứ điều gì để khắc phục lỗi, hãy bắt ngoại lệ
  • Nếu lỗi là một lỗi rất phổ biến (ví dụ: người dùng đã cố đăng nhập bằng mật khẩu sai), hãy sử dụng giá trị trả về
  • Nếu bạn không thể làm bất cứ điều gì để khắc phục lỗi, hãy bỏ qua nó (Hoặc bắt nó trong trình bắt chính của bạn để thực hiện một số tắt ứng dụng bán duyên dáng)

Vấn đề tôi thấy với các trường hợp ngoại lệ là từ quan điểm cú pháp thuần túy (tôi khá chắc chắn rằng chi phí đầu tư là tối thiểu). Tôi không thích các khối thử ở khắp mọi nơi.

Lấy ví dụ này:

try
{
   DoSomeMethod();  //Can throw Exception1
   DoSomeOtherMethod();  //Can throw Exception1 and Exception2
}
catch(Exception1)
{
   //Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}

.. Một ví dụ khác có thể là khi bạn cần gán một cái gì đó cho một tay cầm bằng cách sử dụng một nhà máy và nhà máy đó có thể đưa ra một ngoại lệ:

Class1 myInstance;
try
{
   myInstance = Class1Factory.Build();
}
catch(SomeException)
{
   // Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver();   // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(

Soo, cá nhân, tôi nghĩ bạn nên giữ ngoại lệ cho các điều kiện lỗi hiếm gặp (hết bộ nhớ, v.v.) và sử dụng các giá trị trả về (giá trị, cấu trúc hoặc enum) để kiểm tra lỗi thay thế.

Hy vọng tôi hiểu câu hỏi của bạn chính xác :)


4
re: Ví dụ thứ hai của bạn - tại sao không đặt cuộc gọi đến BestMethodEver bên trong khối thử, sau Build? Nếu Build () ném một ngoại lệ, nó sẽ không được thực thi và trình biên dịch hạnh phúc.
Blorgbeard ra mắt

2
Vâng, đó có thể là những gì bạn sẽ kết thúc, nhưng hãy xem xét một ví dụ phức tạp hơn trong đó chính kiểu myInstance có thể đưa ra các ngoại lệ .. Và các hiệu ứng khác trong phạm vi phương thức cũng có thể. Bạn sẽ kết thúc với rất nhiều khối thử / bắt lồng nhau :(
cwap

Bạn nên thực hiện dịch Exception (sang loại Exception phù hợp với mức độ trừu tượng) trong khối bắt của bạn. FYI: "Multi-Catch" được cho là sẽ đi vào Java 7.
jasonnerothin

FYI: Trong C ++, bạn có thể đặt nhiều sản phẩm khai thác sau khi thử bắt các ngoại lệ khác nhau.
RobH

2
Đối với phần mềm thu nhỏ, bạn cần nắm bắt mọi ngoại lệ. Ít nhất là đưa ra một hộp thoại giải thích rằng chương trình cần phải tắt và đây là một cái gì đó không thể hiểu được mà bạn có thể gửi trong một báo cáo lỗi.
David Thornley

25

Một phản ứng đầu tiên cho rất nhiều câu trả lời:

bạn đang viết cho các lập trình viên và nguyên tắc ít ngạc nhiên nhất

Tất nhiên! Nhưng nếu chỉ là không rõ ràng hơn tất cả các thời gian.

Nó không phải là đáng kinh ngạc, ví dụ: chia (1 / x) bắt (DivisionByZero) rõ ràng hơn bất kỳ nếu với tôi (tại Conrad và những người khác). Thực tế loại chương trình này không được mong đợi là hoàn toàn thông thường, và thực sự, vẫn còn có liên quan. Có lẽ trong ví dụ của tôi một nếu sẽ rõ ràng hơn.

Nhưng DivisionByZero và FileNotFound cho vấn đề đó rõ ràng hơn ifs.

Tất nhiên, nếu nó ít hiệu suất hơn và cần một trăm triệu thời gian mỗi giây, tất nhiên bạn nên tránh nó, nhưng tôi vẫn chưa đọc bất kỳ lý do chính đáng nào để tránh thiết kế quá khổ.

Theo như nguyên tắc ít gây ngạc nhiên nhất: có một mối nguy hiểm về lý luận vòng tròn ở đây: giả sử cả một cộng đồng sử dụng một thiết kế tồi, thiết kế này sẽ trở nên được mong đợi! Do đó, nguyên tắc không thể là một chén và nên được xem xét cẩn thận.

trường hợp ngoại lệ cho các tình huống thông thường, làm thế nào để bạn xác định vị trí bất thường (tức là ngoại lệ)?

Trong nhiều phản ứng sth. như thế này tỏa sáng máng. Chỉ cần bắt chúng, không? Phương pháp của bạn phải rõ ràng, được ghi chép đầy đủ và hợp đồng với nó. Tôi không nhận được câu hỏi đó tôi phải thừa nhận.

Gỡ lỗi trên tất cả các ngoại lệ: giống nhau, điều đó đôi khi chỉ được thực hiện vì thiết kế không sử dụng ngoại lệ là phổ biến. Câu hỏi của tôi là: tại sao nó lại phổ biến ở nơi đầu tiên?


1
1) Bạn có luôn kiểm tra xtrước khi gọi 1/xkhông? 2) Bạn có bao bọc mọi hoạt động phân chia thành một khối thử bắt để bắt DivideByZeroExceptionkhông? 3) Logic nào bạn đưa vào khối bắt để phục hồi DivideByZeroException?
Lightman

1
Ngoại trừ DivisionByZero và FileNotFound là những ví dụ tồi vì chúng là những trường hợp ngoại lệ nên được coi là ngoại lệ.
0x6C38

Không có gì "quá đặc biệt" về một tập tin không được tìm thấy theo cách hơn những người "chống ngoại lệ" ở đây đang chào hàng. openConfigFile();có thể được theo sau bởi một FileNotFound bị bắt với { createDefaultConfigFile(); setFirstAppRun(); }ngoại lệ FileNotFound được xử lý một cách duyên dáng; không có sự cố, hãy làm cho trải nghiệm của người dùng cuối tốt hơn, không tệ hơn. Bạn có thể nói "Nhưng nếu đây không thực sự là lần chạy đầu tiên và họ sẽ nhận được điều đó mỗi lần thì sao?" Ít nhất là ứng dụng chạy mọi lúc và không bị sập mỗi lần khởi động! Trên 1 đến 10 "điều này thật tồi tệ": "lần chạy đầu tiên" mỗi lần khởi động = 3 hoặc 4, sụp đổ mỗi lần khởi động = 10.
Loduwijk

Ví dụ của bạn là trường hợp ngoại lệ. Không, bạn không luôn luôn kiểm tra xtrước khi gọi 1/x, vì nó thường ổn. Trường hợp đặc biệt là trường hợp không ổn. Chúng ta không nói về sự rung chuyển trái đất ở đây, nhưng ví dụ, đối với một số nguyên cơ bản được cung cấp ngẫu nhiên x, chỉ 1 trong 4294967296 sẽ không thực hiện phép chia. Đó là ngoại lệ và ngoại lệ là một cách tốt để đối phó với điều đó. Tuy nhiên, bạn có thể sử dụng các ngoại lệ để triển khai tương đương với một switchtuyên bố, nhưng nó sẽ khá ngớ ngẩn.
Thor84no

16

Trước các trường hợp ngoại lệ, trong C, đã có setjmplongjmpcó thể được sử dụng để thực hiện việc hủy kiểm soát tương tự khung stack.

Sau đó, cấu trúc tương tự đã được đặt tên: "Ngoại lệ". Và hầu hết các câu trả lời dựa vào ý nghĩa của tên này để tranh luận về cách sử dụng của nó, cho rằng các ngoại lệ được dự định sẽ được sử dụng trong các điều kiện đặc biệt. Đó không bao giờ là ý định trong bản gốc longjmp. Chỉ có những tình huống mà bạn cần để phá vỡ dòng điều khiển trên nhiều khung ngăn xếp.

Các ngoại lệ hơi chung chung hơn ở chỗ bạn cũng có thể sử dụng chúng trong cùng một khung ngăn xếp. Điều này làm tăng sự tương tự với gotođiều mà tôi tin là sai. Gotos là một cặp kết hợp chặt chẽ (và cũng vậy setjmplongjmp). Các ngoại lệ tuân theo một xuất bản / đăng ký lỏng lẻo được kết hợp lỏng lẻo hơn nhiều! Do đó, sử dụng chúng trong cùng một khung stack hầu như không giống với sử dụng gotos.

Nguồn gây nhầm lẫn thứ ba liên quan đến việc chúng được kiểm tra hay không được kiểm tra ngoại lệ. Tất nhiên, các ngoại lệ không được kiểm soát có vẻ đặc biệt khủng khiếp khi sử dụng cho luồng điều khiển và có lẽ rất nhiều thứ khác.

Tuy nhiên, các trường hợp ngoại lệ được kiểm tra là rất tốt cho luồng kiểm soát, một khi bạn vượt qua tất cả các hangout Victoria và sống một chút.

Cách sử dụng yêu thích của tôi là một chuỗi throw new Success()trong một đoạn mã dài thử hết thứ này đến thứ khác cho đến khi nó tìm thấy thứ mà nó đang tìm kiếm. Mỗi thứ - mỗi phần logic - có thể có lồng nhau, do đó break, cũng không có bất kỳ loại thử nghiệm điều kiện nào. Các if-elsemô hình là giòn. Nếu tôi chỉnh sửa elsehoặc làm rối cú pháp theo một cách khác, thì đó là một lỗi lông.

Sử dụng throw new Success() tuyến tính hóa dòng mã. Tôi sử dụng Successcác lớp được xác định cục bộ - tất nhiên là đã kiểm tra - để nếu tôi quên bắt nó, mã sẽ không được biên dịch. Và tôi không bắt được phương pháp khác Success.

Đôi khi mã của tôi kiểm tra hết thứ này đến thứ khác và chỉ thành công nếu mọi thứ đều ổn. Trong trường hợp này tôi có một tuyến tính tương tự bằng cách sử dụng throw new Failure().

Sử dụng một chức năng riêng biệt gây rối với mức độ tự nhiên của khoang. Vì vậy, returngiải pháp là không tối ưu. Tôi thích có một hoặc hai trang mã ở một nơi vì lý do nhận thức. Tôi không tin vào mã được chia cực kỳ chính xác.

Những gì JVM hoặc trình biên dịch làm ít liên quan đến tôi trừ khi có một điểm nóng. Tôi không thể tin rằng có bất kỳ lý do cơ bản nào để trình biên dịch không phát hiện ngoại lệ bị ném và bắt cục bộ và chỉ đơn giản coi chúng là gotos rất hiệu quả ở cấp mã máy.

Theo như việc sử dụng chúng trên các chức năng cho luồng điều khiển - tức là đối với các trường hợp phổ biến thay vì các trường hợp ngoại lệ - tôi không thể thấy chúng sẽ kém hiệu quả hơn nhiều lần phá vỡ, kiểm tra điều kiện, quay trở lại lội qua ba khung ngăn xếp thay vì chỉ khôi phục con trỏ ngăn xếp.

Cá nhân tôi không sử dụng mô hình trên các khung ngăn xếp và tôi có thể thấy nó đòi hỏi sự tinh tế trong thiết kế để làm điều đó một cách thanh lịch như thế nào. Nhưng sử dụng một cách tiết kiệm thì sẽ ổn thôi.

Cuối cùng, liên quan đến các lập trình viên trinh nữ đáng ngạc nhiên, nó không phải là một lý do thuyết phục. Nếu bạn nhẹ nhàng giới thiệu cho họ thực hành, họ sẽ học cách yêu nó. Tôi nhớ C ++ đã từng gây bất ngờ và làm kinh hoàng các lập trình viên C.


3
Sử dụng mẫu này, hầu hết các hàm thô của tôi đều có hai điểm bắt đầu nhỏ - một cho Thành công và một cho Thất bại và đó là nơi hàm kết thúc những thứ như chuẩn bị phản hồi servlet chính xác hoặc chuẩn bị các giá trị trả về. Có một nơi duy nhất để thực hiện kết thúc tốt đẹp. Sự returnthay thế -potype sẽ yêu cầu hai chức năng cho mọi chức năng như vậy. Một cái bên ngoài để chuẩn bị phản hồi của servlet hoặc các hành động khác như vậy, và một cái bên trong để thực hiện tính toán. Tái bút: Một giáo sư người Anh có lẽ sẽ đề nghị tôi sử dụng "đáng kinh ngạc" thay vì "đáng ngạc nhiên" trong đoạn cuối :-)
necromancer

11

Anwser tiêu chuẩn là các ngoại lệ không thường xuyên và nên được sử dụng trong các trường hợp ngoại lệ.

Một lý do, điều quan trọng đối với tôi, là khi tôi đọc try-catchcấu trúc điều khiển trong phần mềm tôi duy trì hoặc gỡ lỗi, tôi cố gắng tìm hiểu lý do tại sao bộ mã gốc sử dụng xử lý ngoại lệ thay vì if-elsecấu trúc. Và tôi mong đợi để tìm một câu trả lời tốt.

Hãy nhớ rằng bạn viết mã không chỉ cho máy tính mà còn cho các lập trình viên khác. Có một ngữ nghĩa liên quan đến một trình xử lý ngoại lệ mà bạn không thể vứt bỏ chỉ vì máy không bận tâm.


Một câu trả lời không được đánh giá cao tôi nghĩ. Máy tính có thể không hoạt động chậm khi phát hiện thấy một ngoại lệ bị nuốt nhưng khi tôi đang làm việc với mã của người khác và tôi tình cờ thấy nó dừng lại trong dấu vết của tôi trong khi tôi làm việc nếu tôi bỏ lỡ điều gì đó quan trọng mà tôi đã làm Tôi không biết, hoặc nếu thực sự không có lời biện minh nào cho việc sử dụng mô hình chống này.
Tim Abell

9

Làm thế nào về hiệu suất? Trong khi tải thử nghiệm một ứng dụng web .NET, chúng tôi đã đứng đầu với 100 người dùng được mô phỏng trên mỗi máy chủ web cho đến khi chúng tôi sửa một ngoại lệ thường xảy ra và con số đó tăng lên 500 người dùng.


8

Josh Bloch đề cập đến chủ đề này rộng rãi trong Java hiệu quả. Đề xuất của anh ấy đang chiếu sáng và cũng nên áp dụng cho .Net (ngoại trừ các chi tiết).

Đặc biệt, ngoại lệ nên được sử dụng cho các trường hợp đặc biệt. Những lý do cho điều này là liên quan đến khả năng sử dụng, chủ yếu. Để một phương thức nhất định có thể sử dụng tối đa, các điều kiện đầu vào và đầu ra của nó phải được hạn chế tối đa.

Ví dụ, phương thức thứ hai dễ sử dụng hơn phương thức thứ nhất:

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 * @throws AdditionException if addend1 or addend2 is less than or equal to zero
 */
int addPositiveNumbers(int addend1, int addend2) throws AdditionException{
  if( addend1 <= 0 ){
     throw new AdditionException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new AdditionException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 */
public int addPositiveNumbers(int addend1, int addend2) {
  if( addend1 <= 0 ){
     throw new IllegalArgumentException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new IllegalArgumentException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

Trong cả hai trường hợp, bạn cần kiểm tra để đảm bảo rằng người gọi đang sử dụng API của bạn một cách thích hợp. Nhưng trong trường hợp thứ hai, bạn yêu cầu nó (ngầm). Các ngoại lệ mềm vẫn sẽ bị ném nếu người dùng không đọc javadoc, nhưng:

  1. Bạn không cần phải ghi chép lại.
  2. Bạn không cần phải kiểm tra nó (tùy thuộc vào mức độ tích cực của chiến lược kiểm tra đơn vị của bạn).
  3. Bạn không yêu cầu người gọi xử lý ba trường hợp sử dụng.

Điểm mặt đất là ngoại lệ nên không được sử dụng như mã trở lại, chủ yếu là do bạn đã phức tạp không chỉ API CỦA BẠN, nhưng API của người gọi là tốt.

Làm điều đúng đắn đi kèm với chi phí, tất nhiên. Chi phí là mọi người cần hiểu rằng họ cần đọc và làm theo tài liệu. Hy vọng rằng đó là trường hợp nào.


7

Tôi nghĩ rằng bạn có thể sử dụng Ngoại lệ để kiểm soát luồng. Tuy nhiên, có một sự thất bại của kỹ thuật này. Tạo ngoại lệ là một điều tốn kém, bởi vì họ phải tạo ra một dấu vết ngăn xếp. Vì vậy, nếu bạn muốn sử dụng Ngoại lệ thường xuyên hơn là chỉ báo hiệu một tình huống đặc biệt, bạn phải đảm bảo rằng việc xây dựng dấu vết ngăn xếp không ảnh hưởng tiêu cực đến hiệu suất của bạn.

Cách tốt nhất để cắt giảm chi phí tạo ngoại lệ là ghi đè phương thức fillInStackTrace () như thế này:

public Throwable fillInStackTrace() { return this; }

Một ngoại lệ như vậy sẽ không có stacktraces điền vào.


Ngăn xếp cũng yêu cầu người gọi "biết về" (nghĩa là có sự phụ thuộc vào) tất cả các vật phẩm ném trong ngăn xếp. Đây là một điều xấu. Ném ngoại lệ phù hợp với mức độ trừu tượng (ServiceExceptions trong Dịch vụ, DaoExceptions từ các phương thức Dao, v.v.). Chỉ cần dịch nếu cần thiết.
jasonnerothin

5

Tôi thực sự không thấy cách bạn kiểm soát luồng chương trình trong mã bạn đã trích dẫn. Bạn sẽ không bao giờ thấy ngoại lệ nào khác ngoài ngoại lệ ArgumentOutOfRange. (Vì vậy, mệnh đề bắt thứ hai của bạn sẽ không bao giờ bị đánh). Tất cả những gì bạn đang làm là sử dụng một cú ném cực kỳ tốn kém để bắt chước một câu lệnh if.

Ngoài ra, bạn không thực hiện các hoạt động độc ác hơn khi bạn chỉ cần ném một ngoại lệ hoàn toàn để nó bị bắt ở một nơi khác để thực hiện kiểm soát luồng. Bạn đang thực sự xử lý một trường hợp đặc biệt.


5

Dưới đây là những thực hành tốt nhất tôi đã mô tả trong bài viết trên blog của mình :

  • Ném một ngoại lệ để nêu một tình huống bất ngờ trong phần mềm của bạn.
  • Sử dụng các giá trị trả về để xác nhận đầu vào .
  • Nếu bạn biết cách xử lý các trường hợp ngoại lệ mà thư viện ném, hãy bắt chúng ở mức thấp nhất có thể .
  • Nếu bạn có một ngoại lệ không mong muốn, hãy loại bỏ hoàn toàn hoạt động hiện tại. Đừng giả vờ rằng bạn biết cách đối phó với chúng .

4

Bởi vì mã khó đọc, bạn có thể gặp khó khăn khi gỡ lỗi, bạn sẽ giới thiệu các lỗi mới khi sửa lỗi sau một thời gian dài, nó tốn kém hơn về tài nguyên và thời gian, và nó làm phiền bạn nếu bạn gỡ lỗi mã của mình và trình gỡ lỗi tạm dừng về sự xuất hiện của mọi ngoại lệ;)


4

Ngoài các lý do đã nêu, một lý do không sử dụng ngoại lệ cho kiểm soát luồng là nó có thể làm phức tạp quá trình gỡ lỗi.

Ví dụ: khi tôi đang cố gắng theo dõi một lỗi trong VS, tôi thường sẽ bật "phá vỡ mọi ngoại lệ". Nếu bạn đang sử dụng các ngoại lệ để kiểm soát luồng thì tôi sẽ thường xuyên sử dụng trình gỡ lỗi và sẽ phải bỏ qua các ngoại lệ không đặc biệt này cho đến khi tôi gặp vấn đề thực sự. Điều này có khả năng khiến ai đó phát điên !!


1
Tôi đã xử lý một higer: Gỡ lỗi trên tất cả các ngoại lệ: giống nhau, điều đó chỉ được thực hiện vì thiết kế không sử dụng ngoại lệ là phổ biến. Câu hỏi của tôi là: tại sao nó lại phổ biến ở nơi đầu tiên?
Peter

Vì vậy, câu trả lời của bạn về cơ bản là "Thật tệ vì Visual Studio có một tính năng này ..."? Tôi đã lập trình được khoảng 20 năm nay và thậm chí tôi không nhận thấy rằng có một tùy chọn "phá vỡ mọi ngoại lệ". Tuy nhiên, "vì tính năng này!" Nghe có vẻ là một lý do yếu. Chỉ cần theo dõi ngoại lệ cho nguồn của nó; hy vọng bạn đang sử dụng một ngôn ngữ giúp việc này trở nên dễ dàng - nếu không thì vấn đề của bạn là do các tính năng ngôn ngữ, chứ không phải do việc sử dụng chung các trường hợp ngoại lệ.
Loduwijk

3

Giả sử bạn có một phương pháp thực hiện một số tính toán. Có nhiều tham số đầu vào nó phải xác nhận, sau đó trả về một số lớn hơn 0.

Sử dụng các giá trị trả về để xác nhận lỗi xác thực, thật đơn giản: nếu phương thức trả về một số nhỏ hơn 0, thì đã xảy ra lỗi. Làm thế nào để biết sau đó tham số nào không xác nhận?

Tôi nhớ từ ngày C của tôi, rất nhiều hàm trả về mã lỗi như thế này:

-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY

Vân vân.

Có thực sự ít đọc hơn sau đó ném và bắt một ngoại lệ?


đó là lý do tại sao họ phát minh ra enum :) Nhưng những con số ma thuật là một chủ đề hoàn toàn khác .. en.wikipedia.org/wiki/iêu
Isak Savo

Ví dụ tuyệt vời. Tôi đã định viết điều tương tự. @IsakSavo: Enums không hữu ích trong tình huống này nếu phương thức được dự kiến ​​sẽ trả về một số giá trị hoặc đối tượng có nghĩa. Ví dụ: getAccountBalance () sẽ trả về một đối tượng Money chứ không phải đối tượng AccountBalanceResultEnum. Rất nhiều chương trình C có một mẫu tương tự trong đó một giá trị sentinel (0 hoặc null) biểu thị một lỗi và sau đó bạn phải gọi một hàm khác để lấy mã lỗi riêng để xác định lý do lỗi xảy ra. (API C của MySQL giống như thế này.)
Mark E. Haase

3

Bạn có thể sử dụng móng vuốt của búa để vặn ốc, giống như bạn có thể sử dụng ngoại lệ cho luồng điều khiển. Điều đó không có nghĩa đó là mục đích sử dụng của tính năng này. Câu iflệnh thể hiện các điều kiện, mà mục đích sử dụng của nó là kiểm soát dòng chảy.

Nếu bạn đang sử dụng một tính năng theo cách không có chủ ý trong khi chọn không sử dụng tính năng được thiết kế cho mục đích đó, sẽ có một chi phí liên quan. Trong trường hợp này, sự rõ ràng và hiệu suất chịu đựng không có giá trị gia tăng thực sự. Điều gì sử dụng ngoại lệ mua cho bạn qua iftuyên bố được chấp nhận rộng rãi ?

Nói một cách khác: chỉ vì bạn có thể không có nghĩa là bạn nên .


1
Bạn có nói rằng không cần ngoại lệ, sau khi chúng tôi có ifsử dụng bình thường hoặc sử dụng thực thi không nhằm mục đích vì nó không có ý định (đối số vòng tròn)?
Val

1
@Val: Trường hợp ngoại lệ dành cho các tình huống đặc biệt - nếu chúng tôi phát hiện đủ để ném ngoại lệ và xử lý nó, chúng tôi có đủ thông tin để không ném nó và vẫn xử lý nó. Chúng ta có thể đi thẳng vào logic xử lý và bỏ qua việc thử / bắt đắt tiền, không cần thiết.
Bryan Watts

Theo logic đó, bạn có thể không có ngoại lệ và luôn thực hiện thoát hệ thống thay vì ném ngoại lệ. Nếu bạn muốn làm bất cứ điều gì trước khi thoát, sau đó tạo một trình bao bọc và gọi đó. Ví dụ Java: public class ExitHelper{ public static void cleanExit() { cleanup(); System.exit(1); } }Sau đó, chỉ cần gọi nó thay vì ném: ExitHelper.cleanExit();Nếu đối số của bạn là âm thanh, thì đây sẽ là cách tiếp cận ưa thích và sẽ không có ngoại lệ. Về cơ bản, bạn đang nói "Lý do duy nhất cho các trường hợp ngoại lệ là sụp đổ theo một cách khác."
Loduwijk

@Aaron: Nếu tôi vừa ném vừa bắt ngoại lệ, tôi có đủ thông tin để tránh làm như vậy. Điều đó không có nghĩa là tất cả các ngoại lệ đột nhiên gây tử vong. Mã khác tôi không kiểm soát có thể bắt được nó, và điều đó tốt. Lập luận của tôi, vẫn còn âm thanh, là ném và bắt một ngoại lệ trong cùng một bối cảnh là không cần thiết. Tôi cũng không, tôi cũng không nói rằng tất cả các ngoại lệ sẽ thoát khỏi quy trình.
Bryan Watts

@BryanWatts Công nhận. Nhiều người khác đã nói rằng bạn chỉ nên sử dụng các ngoại lệ cho bất cứ điều gì bạn không thể phục hồi và bởi phần mở rộng sẽ luôn gặp sự cố với các ngoại lệ. Đây là lý do tại sao rất khó để thảo luận về những điều này; không chỉ có 2 ý kiến, mà nhiều ý kiến. Tôi vẫn không đồng ý với bạn, nhưng không mạnh mẽ. Có những lúc ném / bắt cùng nhau là mã dễ đọc nhất, dễ bảo trì nhất, đẹp nhất; thông thường điều này xảy ra nếu bạn đã bắt được các ngoại lệ khác vì vậy bạn đã thử / bắt và thêm 1 hoặc 2 lần bắt khác sẽ sạch hơn kiểm tra lỗi riêng biệt bằng cách if.
Loduwijk

3

Như những người khác đã đề cập rất nhiều, nguyên tắc ít ngạc nhiên nhất sẽ cấm bạn sử dụng ngoại lệ quá mức cho mục đích kiểm soát dòng chảy. Mặt khác, không có quy tắc nào đúng 100% và luôn có những trường hợp ngoại lệ là "công cụ phù hợp" - nhân tiện, giống như gotochính nó, vận chuyển dưới dạng breakcontinue trong các ngôn ngữ như Java, mà thường là cách hoàn hảo để nhảy ra khỏi các vòng lặp lồng nhau, điều mà không phải lúc nào cũng có thể tránh được.

Bài đăng blog sau đây giải thích một trường hợp sử dụng khá phức tạp nhưng cũng khá thú vị cho một người không phải là người địa phương ControlFlowException :

Nó giải thích bên trong jOOQ (một thư viện trừu tượng SQL cho Java) , các ngoại lệ như vậy đôi khi được sử dụng để hủy bỏ quá trình kết xuất SQL sớm khi gặp một số điều kiện "hiếm".

Ví dụ về các điều kiện như vậy là:

  • Quá nhiều giá trị ràng buộc được gặp phải. Một số cơ sở dữ liệu không hỗ trợ số lượng giá trị liên kết tùy ý trong các câu lệnh SQL của chúng (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). Trong các trường hợp đó, jOOQ hủy bỏ giai đoạn kết xuất SQL và kết xuất lại câu lệnh SQL với các giá trị liên kết được nội tuyến. Thí dụ:

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }

    Nếu chúng tôi trích xuất rõ ràng các giá trị liên kết từ truy vấn AST để đếm chúng mỗi lần, chúng tôi sẽ lãng phí chu kỳ CPU có giá trị cho 99,9% truy vấn không gặp phải vấn đề này.

  • Một số logic chỉ có sẵn gián tiếp thông qua API mà chúng tôi muốn chỉ thực hiện "một phần". Các UpdatableRecord.store()phương pháp tạo ra một INSERThoặc UPDATEtuyên bố, tùy thuộc vào Record's cờ nội bộ. Từ "bên ngoài", chúng tôi không biết loại logic nào được chứa trong store()(ví dụ: khóa lạc quan, xử lý người nghe sự kiện, v.v.) vì vậy chúng tôi không muốn lặp lại logic đó khi chúng tôi lưu trữ một số bản ghi trong một câu lệnh bó, nơi chúng tôi muốn store()chỉ tạo câu lệnh SQL, không thực sự thực thi nó. Thí dụ:

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }

    Nếu chúng tôi đã đưa store()logic vào API "có thể sử dụng lại" có thể được tùy chỉnh để không thực thi SQL, chúng tôi sẽ xem xét việc tạo một API khá khó để duy trì, khó sử dụng lại.

Phần kết luận

Về bản chất, việc sử dụng những người không phải là người địa phương gotonày chỉ là những gì [Mason Wheeler] [5] đã nói trong câu trả lời của anh ấy:

"Tôi vừa gặp phải một tình huống mà tôi không thể giải quyết đúng vào thời điểm này, vì tôi không có đủ ngữ cảnh để xử lý nó, nhưng thói quen gọi tôi (hoặc một cái gì đó khác trong ngăn xếp cuộc gọi) nên biết cách xử lý nó . "

Cả hai cách sử dụng ControlFlowExceptionsđều khá dễ thực hiện so với các lựa chọn thay thế của chúng, cho phép chúng tôi sử dụng lại một loạt logic mà không cần cấu trúc lại nó ra khỏi các phần bên trong có liên quan.

Nhưng cảm giác về điều này là một chút ngạc nhiên cho các nhà bảo trì trong tương lai vẫn còn. Mã cảm thấy khá tế nhị và mặc dù đó là lựa chọn đúng đắn trong trường hợp này, chúng tôi luôn muốn không sử dụng ngoại lệ cho luồng điều khiển cục bộ , nơi dễ dàng tránh sử dụng phân nhánh thông thường if - else.


2

Thông thường, không có gì sai, mỗi lần xử lý một ngoại lệ ở mức thấp. Một ngoại lệ là một thông điệp hợp lệ cung cấp nhiều chi tiết về lý do tại sao một hoạt động không thể được thực hiện. Và nếu bạn có thể xử lý nó, bạn nên làm.

Nói chung nếu bạn biết có khả năng thất bại cao mà bạn có thể kiểm tra ... bạn nên thực hiện kiểm tra ... tức là nếu (obj! = Null) obj.method ()

Trong trường hợp của bạn, tôi không đủ quen thuộc với thư viện C # để biết liệu thời gian ngày có dễ dàng để kiểm tra xem dấu thời gian có nằm ngoài giới hạn hay không. Nếu vậy, chỉ cần gọi if (.isvalid (ts)) nếu không mã của bạn về cơ bản là tốt.

Vì vậy, về cơ bản, bất kỳ cách nào tạo ra mã sạch hơn ... nếu hoạt động để bảo vệ chống lại một ngoại lệ dự kiến ​​sẽ phức tạp hơn là chỉ xử lý ngoại lệ; hơn bạn có sự cho phép của tôi để xử lý ngoại lệ thay vì tạo các vệ sĩ phức tạp ở mọi nơi.


Thêm điểm: Nếu Ngoại lệ của bạn cung cấp thông tin nắm bắt thất bại (một getter như "Param getWhatParamMishedMeUp ()"), nó có thể giúp người dùng API của bạn đưa ra quyết định tốt về những việc cần làm tiếp theo. Mặt khác, bạn chỉ đặt tên cho trạng thái lỗi.
jasonnerothin

2

Nếu bạn đang sử dụng các trình xử lý ngoại lệ cho luồng điều khiển, thì bạn quá chung chung và lười biếng. Như một người khác đã đề cập, bạn biết điều gì đó đã xảy ra nếu bạn đang xử lý xử lý trong trình xử lý, nhưng chính xác thì sao? Về cơ bản, bạn đang sử dụng ngoại lệ cho một câu lệnh khác, nếu bạn đang sử dụng nó cho luồng điều khiển.

Nếu bạn không biết trạng thái có thể xảy ra, thì bạn có thể sử dụng trình xử lý ngoại lệ cho các trạng thái không mong muốn, ví dụ: khi bạn phải sử dụng thư viện của bên thứ ba hoặc bạn phải nắm bắt mọi thứ trong UI để hiển thị một lỗi tốt nhắn tin và đăng nhập ngoại lệ.

Tuy nhiên, nếu bạn biết điều gì có thể sai và bạn không đặt câu lệnh if hoặc thứ gì đó để kiểm tra, thì bạn chỉ đang lười biếng. Cho phép trình xử lý ngoại lệ trở thành tất cả cho những thứ bạn biết có thể xảy ra là lười biếng và nó sẽ quay trở lại ám ảnh bạn sau này, bởi vì bạn sẽ cố gắng khắc phục tình huống trong trình xử lý ngoại lệ của mình dựa trên giả định có thể sai.

Nếu bạn đặt logic trong trình xử lý ngoại lệ của mình để xác định chính xác điều gì đã xảy ra, thì bạn sẽ khá ngu ngốc khi không đưa logic đó vào khối thử.

Xử lý ngoại lệ là giải pháp cuối cùng, vì khi bạn hết ý tưởng / cách để ngăn chặn điều gì đó không ổn, hoặc mọi thứ nằm ngoài khả năng kiểm soát của bạn. Giống như, máy chủ ngừng hoạt động và bạn không thể ngăn chặn ngoại lệ đó bị ném.

Cuối cùng, có tất cả các kiểm tra được thực hiện ở phía trước cho thấy những gì bạn biết hoặc mong đợi sẽ xảy ra và làm cho nó rõ ràng. Mã phải rõ ràng trong ý định. Bạn thích đọc gì hơn?


1
Hoàn toàn không đúng: "Về cơ bản, bạn đang sử dụng ngoại lệ cho một câu lệnh khác, nếu bạn đang sử dụng nó cho luồng điều khiển." Nếu bạn sử dụng nó cho luồng điều khiển, bạn biết chính xác những gì bạn nắm bắt và không bao giờ sử dụng một lệnh bắt chung, nhưng cụ thể một khóa học!
Peter

2

Bạn có thể quan tâm đến việc xem xét hệ thống điều kiện của Common Lisp, một dạng khái quát hóa các trường hợp ngoại lệ được thực hiện đúng. Bởi vì bạn có thể thư giãn ngăn xếp hoặc không theo cách được kiểm soát, bạn cũng có thể "khởi động lại", cực kỳ tiện dụng.

Điều này không có gì nhiều để làm với các thực tiễn tốt nhất trong các ngôn ngữ khác, nhưng nó cho bạn thấy những gì có thể được thực hiện với một số suy nghĩ thiết kế theo (đại khái) hướng bạn đang nghĩ đến.

Tất nhiên, vẫn có những cân nhắc về hiệu suất nếu bạn nảy lên và xuống ngăn xếp như một yo-yo, nhưng đó là một ý tưởng tổng quát hơn nhiều so với cách tiếp cận "ôi thôi, cho phép" mà hầu hết các hệ thống ngoại lệ bắt / ném.


2

Tôi không nghĩ có gì sai khi sử dụng Ngoại lệ để kiểm soát luồng. Các ngoại lệ có phần giống với các phần tiếp theo và trong các ngôn ngữ được nhập tĩnh, Các ngoại lệ mạnh hơn các phần tiếp theo, vì vậy, nếu bạn cần các phần tiếp theo nhưng ngôn ngữ của bạn không có chúng, bạn có thể sử dụng Ngoại lệ để triển khai chúng.

Chà, thực ra, nếu bạn cần tiếp tục và ngôn ngữ của bạn không có chúng, bạn đã chọn ngôn ngữ sai và bạn nên sử dụng một ngôn ngữ khác. Nhưng đôi khi bạn không có một sự lựa chọn: lập trình web phía máy khách là các ví dụ điển hình - có chỉ không có cách nào để có được xung quanh JavaScript.

Một ví dụ: Microsoft Volta là một dự án cho phép viết các ứng dụng web bằng .NET thẳng và để cho khung công tác tìm ra bit nào cần chạy ở đâu. Một hậu quả của việc này là Volta cần có khả năng biên dịch CIL thành JavaScript, để bạn có thể chạy mã trên máy khách. Tuy nhiên, có một vấn đề: .NET có đa luồng, JavaScript thì không. Vì vậy, Volta thực hiện các phần tiếp theo trong JavaScript bằng cách sử dụng Ngoại lệ JavaScript, sau đó triển khai Chủ đề .NET bằng cách sử dụng các phần tiếp theo đó. Bằng cách đó, các ứng dụng Volta sử dụng các luồng có thể được biên dịch để chạy trong trình duyệt chưa sửa đổi - không cần Silverlight.


2

Một lý do thẩm mỹ:

Một lần thử luôn đi kèm với một cái bẫy, trong khi nếu không phải đi kèm với một cái khác.

if (PerformCheckSucceeded())
   DoSomething();

Với thử / bắt, nó trở nên dài dòng hơn nhiều.

try
{
   PerformCheckSucceeded();
   DoSomething();
}
catch
{
}

Đó là 6 dòng mã quá nhiều.


1

Nhưng bạn sẽ không biết điều gì xảy ra trong Phương thức mà bạn gọi. Bạn sẽ không biết chính xác nơi ngoại lệ được ném. Không kiểm tra đối tượng ngoại lệ một cách chi tiết hơn ....


1

Tôi cảm thấy không có gì sai với ví dụ của bạn. Ngược lại, sẽ là một tội lỗi nếu bỏ qua ngoại lệ được ném bởi hàm được gọi.

Trong JVM, việc ném một ngoại lệ không quá tốn kém, chỉ tạo ra ngoại lệ với xyzException mới (...), bởi vì cái sau liên quan đến việc đi bộ ngăn xếp. Vì vậy, nếu bạn có một số ngoại lệ được tạo trước, bạn có thể ném chúng nhiều lần mà không mất chi phí. Tất nhiên, theo cách này bạn không thể truyền dữ liệu cùng với ngoại lệ, nhưng tôi nghĩ đó là một điều tồi tệ phải làm.


Xin lỗi, điều này hoàn toàn sai, Brann. Nó phụ thuộc vào điều kiện. Không phải lúc nào điều kiện cũng tầm thường. Do đó, một câu lệnh if có thể mất hàng giờ, hoặc vài ngày hoặc thậm chí lâu hơn.
Ingo

Trong JVM, đó là. Không đắt hơn một sự trở lại. Đi hình. Nhưng câu hỏi là, bạn sẽ viết gì trong câu lệnh if, nếu không phải là chính mã đã có trong hàm được gọi để nói một trường hợp đặc biệt từ một trường hợp bình thường --- do đó sao chép mã.
Ingo

1
Ingo: một tình huống đặc biệt là một tình huống bạn không mong đợi. tức là một người bạn chưa từng nghĩ đến như một lập trình viên. Vì vậy, quy tắc của tôi là "viết mã không ném ngoại lệ" :)
Brann

1
Tôi không bao giờ viết trình xử lý ngoại lệ, tôi luôn khắc phục sự cố (trừ khi tôi không thể làm điều đó vì tôi không kiểm soát được mã lỗi). Và tôi không bao giờ đưa ra ngoại lệ, ngoại trừ khi mã tôi viết được dành cho người khác sử dụng (ví dụ: thư viện). Không cho tôi thấy mâu thuẫn?
Brann

1
Tôi đồng ý với bạn để không ném ngoại lệ một cách điên cuồng. Nhưng chắc chắn, "đặc biệt" là một câu hỏi về định nghĩa. String.parseDouble đưa ra một ngoại lệ nếu nó không thể cung cấp một kết quả hữu ích, ví dụ, theo thứ tự. Nó nên làm gì khác? Trả lại NaN? Còn phần cứng không phải của IEEE thì sao?
Ingo

1

Có một vài cơ chế chung thông qua đó một ngôn ngữ có thể cho phép một phương thức thoát ra mà không trả lại giá trị và thư giãn cho khối "bắt" tiếp theo:

  • Phương thức kiểm tra khung ngăn xếp để xác định trang web cuộc gọi và sử dụng siêu dữ liệu cho trang web cuộc gọi để tìm thông tin về một trykhối trong phương thức gọi hoặc vị trí nơi phương thức gọi lưu trữ địa chỉ của người gọi; trong trường hợp sau, kiểm tra siêu dữ liệu cho người gọi của người gọi để xác định theo cùng kiểu với người gọi ngay lập tức, lặp lại cho đến khi tìm thấy một trykhối hoặc ngăn xếp trống. Cách tiếp cận này thêm rất ít chi phí cho trường hợp không có ngoại lệ (nó không loại trừ một số tối ưu hóa) nhưng tốn kém khi xảy ra ngoại lệ.

  • Có phương thức trả về một cờ "ẩn" để phân biệt trả về bình thường với một ngoại lệ và yêu cầu người gọi kiểm tra cờ đó và nhánh đó thành một thói quen "ngoại lệ" nếu nó được đặt. Thủ tục này thêm 1-2 hướng dẫn cho trường hợp không có ngoại lệ, nhưng tương đối ít chi phí khi xảy ra ngoại lệ.

  • Yêu cầu người gọi đặt thông tin xử lý ngoại lệ hoặc mã tại một địa chỉ cố định liên quan đến địa chỉ trả lại xếp chồng. Ví dụ, với ARM, thay vì sử dụng lệnh "Chương trình con BL", người ta có thể sử dụng chuỗi:

        adr lr,next_instr
        b subroutine
        b handle_exception
    next_instr:
    

Để thoát bình thường, chương trình con chỉ cần làm bx lrhoặc pop {pc}; trong trường hợp có lối ra bất thường, chương trình con sẽ trừ 4 khỏi LR trước khi thực hiện trả lại hoặc sử dụng sub lr,#4,pc(tùy thuộc vào biến thể ARM, chế độ thực thi, v.v.) Cách tiếp cận này sẽ gặp trục trặc rất nặng nếu người gọi không được thiết kế để phù hợp với nó.

Một ngôn ngữ hoặc khung sử dụng các ngoại lệ được kiểm tra có thể có lợi từ việc xử lý các ngoại lệ với cơ chế như # 2 hoặc # 3 ở trên, trong khi các ngoại lệ không được kiểm tra được xử lý bằng cách sử dụng # 1. Mặc dù việc triển khai các ngoại lệ được kiểm tra trong Java khá phiền toái, nhưng chúng sẽ không phải là một khái niệm tồi nếu có một phương tiện mà một trang web cuộc gọi có thể nói, về cơ bản, "Phương pháp này được tuyên bố là ném XX, nhưng tôi không mong đợi nó Nếu làm như vậy, nếu có, hãy nghĩ lại như một ngoại lệ "không được kiểm tra". Trong một khung mà các ngoại lệ được kiểm tra được xử lý theo cách đó, chúng có thể là một phương tiện kiểm soát dòng chảy hiệu quả cho những thứ như phương pháp phân tích cú pháp trong một số ngữ cảnh khả năng thất bại cao, nhưng nơi thất bại sẽ trả lại thông tin khác về cơ bản so với thành công. Tôi Tuy nhiên, tôi không biết về bất kỳ khuôn khổ nào sử dụng một mô hình như vậy. Thay vào đó, mô hình phổ biến hơn là sử dụng cách tiếp cận đầu tiên ở trên (chi phí tối thiểu cho trường hợp không có ngoại lệ, nhưng chi phí cao khi ném ngoại lệ) cho tất cả các ngoại lệ.

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.