Là ngoại lệ như dòng kiểm soát được coi là một antipotype nghiêm trọng? Nếu vậy, tại sao?


129

Quay trở lại vào cuối những năm 90, tôi đã làm việc khá nhiều với một cơ sở mã sử dụng các ngoại lệ làm kiểm soát luồng. Nó thực hiện một máy trạng thái hữu hạn để lái các ứng dụng điện thoại. Gần đây tôi nhớ lại những ngày đó vì tôi đã làm các ứng dụng web MVC.

Cả hai đều có Controllerquyết định nơi tiếp theo và cung cấp dữ liệu cho logic đích. Hành động của người dùng từ miền của điện thoại trường học cũ, như âm DTMF, đã trở thành tham số cho phương thức hành động, nhưng thay vì trả lại một cái gì đó như a ViewResult, họ đã ném một StateTransitionException.

Tôi nghĩ rằng sự khác biệt chính là phương pháp hành động là voidchức năng. Tôi không nhớ tất cả những điều tôi đã làm với thực tế này nhưng tôi đã do dự thậm chí không đi theo con đường nhớ nhiều vì kể từ khi công việc đó, như 15 năm trước, tôi chưa bao giờ thấy điều này trong mã sản xuất ở bất kỳ công việc nào khác . Tôi cho rằng đây là một dấu hiệu cho thấy nó được gọi là chống mẫu.

Đây có phải là trường hợp, và nếu vậy, tại sao?

Cập nhật: khi tôi đặt câu hỏi, tôi đã có sẵn câu trả lời của @ MasonWheeler vì vậy tôi đã đi với câu trả lời bổ sung kiến ​​thức của mình nhiều nhất. Tôi nghĩ rằng anh ấy là một câu trả lời là tốt.


2
Câu hỏi liên quan: lập trình
viên.stackexchange.com/

25
Không. Trong Python, sử dụng ngoại lệ làm luồng điều khiển được coi là "pythonic".
dùng16764

4
Nếu tôi làm một việc như vậy tôi Java, tôi chắc chắn sẽ không ném ngoại lệ cho nó. Tôi sẽ xuất phát từ một số hệ thống phân cấp không ngoại lệ, không lỗi, có thể ném được.
Thomas Eding

2
Để thêm vào các câu trả lời hiện có, đây là một hướng dẫn ngắn đã phục vụ tốt cho tôi: - Không bao giờ sử dụng ngoại lệ cho "con đường hạnh phúc". Đường dẫn hạnh phúc có thể là cả hai (cho web) toàn bộ yêu cầu hoặc chỉ đơn giản là một đối tượng / phương thức. Tất cả các quy tắc lành mạnh khác vẫn được áp dụng, tất nhiên :)
Houen

8
Không phải ngoại lệ luôn kiểm soát dòng chảy của một ứng dụng?

Câu trả lời:


109

Có một cuộc thảo luận chi tiết về điều này trên Wiki của Ward . Nói chung, việc sử dụng các ngoại lệ cho dòng điều khiển là một mô hình chống, với nhiều tình huống đáng chú ý - và ngôn ngữ cụ thể (xem ví dụ Python ) ho ngoại lệ ho .

Như một tóm tắt nhanh về lý do tại sao, nói chung, đó là một mô hình chống:

  • Các ngoại lệ về bản chất là các tuyên bố GOTO tinh vi
  • Do đó, lập trình với các ngoại lệ, dẫn đến khó đọc và hiểu mã hơn
  • Hầu hết các ngôn ngữ đều có cấu trúc điều khiển hiện có được thiết kế để giải quyết vấn đề của bạn mà không sử dụng ngoại lệ
  • Các lập luận về hiệu quả có xu hướng được đưa ra cho các trình biên dịch hiện đại, có xu hướng tối ưu hóa với giả định rằng các ngoại lệ không được sử dụng cho luồng điều khiển.

Đọc cuộc thảo luận tại wiki của Ward để biết thêm thông tin chuyên sâu.


Xem thêm một bản sao của câu hỏi này, ở đây


18
Câu trả lời của bạn làm cho nó nghe có vẻ như ngoại lệ là xấu cho tất cả các trường hợp, trong khi câu hỏi được tập trung vào các ngoại lệ như kiểm soát dòng chảy.
whatsisname

14
@MasonWheeler Sự khác biệt là các vòng lặp for / while chứa kiểm soát luồng của chúng thay đổi rõ ràng và làm cho mã dễ đọc. Nếu bạn thấy một câu lệnh for trong mã của mình, bạn không cần phải cố gắng tìm ra tệp nào chứa cuối vòng lặp. Goto không tệ vì một số vị thần nói rằng họ xấu, đơn giản là vì họ khó theo dõi hơn các cấu trúc lặp. Trường hợp ngoại lệ là tương tự, không phải là không thể nhưng đủ cứng để họ có thể nhầm lẫn mọi thứ.
Bill K

24
@BillK, sau đó lập luận rằng, và đừng đưa ra những tuyên bố đơn giản về cách ngoại lệ là gotos.
Winston Ewert

6
Được rồi nhưng nghiêm túc, điều gì xảy ra với phía máy chủ và ứng dụng phát triển lỗi với các câu lệnh bắt trống trong JavaScript? Đó là một phenomon khó chịu khiến tôi mất rất nhiều thời gian và tôi không biết làm thế nào để hỏi mà không cần ca ngợi. Lỗi là bạn của bạn.
Erik Reppen

12
@mattnz: ifforeachcũng GOTOs tinh vi . Thành thật mà nói, tôi nghĩ rằng so sánh với goto không hữu ích; nó gần giống như một thuật ngữ xúc phạm. Việc sử dụng GOTO về bản chất không phải là xấu - nó có vấn đề thực sự và các trường hợp ngoại lệ có thể chia sẻ những điều đó, nhưng chúng có thể không. Sẽ hữu ích hơn khi nghe những vấn đề đó.
Eamon Nerbonne

110

Trường hợp sử dụng ngoại lệ được thiết kế cho "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 cho tôi (hoặc một cái gì đó tiếp tục cuộc gọi ngăn xếp) nên biết cách xử lý nó. "

Trường hợp sử dụng thứ cấp là "Tôi vừa gặp phải một lỗi nghiêm trọng và ngay bây giờ thoát khỏi luồng kiểm soát này để ngăn ngừa tham nhũng dữ liệu hoặc thiệt hại khác quan trọng hơn là cố gắng tiếp tục."

Nếu bạn không sử dụng ngoại lệ vì một trong hai lý do này, có lẽ cách tốt hơn để làm điều đó.


18
Điều này không trả lời câu hỏi mặc dù. Những gì họ được thiết kế cho là không liên quan; điều duy nhất có liên quan là tại sao sử dụng chúng cho luồng điều khiển là xấu, đó là một chủ đề bạn không tiếp xúc. Ví dụ, các mẫu C ++ được thiết kế cho một thứ nhưng hoàn toàn tốt để sử dụng cho siêu lập trình, một cách sử dụng mà các nhà thiết kế không bao giờ lường trước được.
Thomas Bonini

6
@Krelp: Các nhà thiết kế không bao giờ lường trước được nhiều điều, chẳng hạn như kết thúc với một hệ thống mẫu Turing- Complete một cách tình cờ! Các mẫu C ++ hầu như không phải là một ví dụ tốt để sử dụng ở đây.
Mason Wheeler

15
@Krelp - Các mẫu C ++ KHÔNG 'hoàn toàn ổn' cho siêu lập trình. Chúng là một cơn ác mộng cho đến khi bạn hiểu đúng, và sau đó chúng có xu hướng hướng tới mã chỉ viết nếu bạn không phải là thiên tài mẫu. Bạn có thể muốn chọn một ví dụ tốt hơn.
Michael Kohne

Các ngoại lệ gây hại cho thành phần chức năng nói riêng và mã brevity nói chung. Ví dụ như trong khi tôi là thiếu kinh nghiệm tôi đã sử dụng một ngoại lệ trong một dự án, và nó gây ra những rắc rối thời gian dài, bởi vì 1) mọi người phải nhớ để bắt nó, 2) bạn không thể viết const myvar = theFunctionvì myvar phải được tạo ra của try-catch, do đó, nó không còn là hằng số. Điều đó không có nghĩa là tôi không sử dụng nó trong C # vì nó là chủ đạo vì bất kỳ lý do gì, nhưng dù sao tôi cũng đang cố gắng giảm chúng.
Hi-Angel

2
@AndreasBonini Những gì chúng được thiết kế / dành cho các vấn đề vì điều đó thường dẫn đến các quyết định thực hiện trình biên dịch / khung / thời gian chạy phù hợp với thiết kế. Ví dụ, ném một ngoại lệ có thể đắt hơn rất nhiều so với việc trả lại đơn giản vì nó thu thập thông tin nhằm giúp ai đó gỡ lỗi mã như dấu vết ngăn xếp, v.v.
binki

29

Các ngoại lệ mạnh mẽ như Continuations và GOTO. Họ là một cấu trúc dòng điều khiển phổ quát.

Trong một số ngôn ngữ, chúng là cấu trúc dòng điều khiển phổ quát duy nhất . JavaScript, chẳng hạn, không có Continuations cũng không GOTO, thậm chí nó không có các cuộc gọi Đuôi phù hợp. Vì vậy, nếu bạn muốn triển khai luồng điều khiển tinh vi trong JavaScript, bạn phải sử dụng Ngoại lệ.

Dự án Microsoft Volta là một dự án nghiên cứu (hiện đã ngừng) để biên dịch mã .NET tùy ý thành JavaScript. .NET có Ngoại lệ mà ngữ nghĩa của nó không ánh xạ chính xác tới JavaScript, nhưng quan trọng hơn, nó có Chủ đề và bạn phải ánh xạ chúng bằng cách nào đó sang JavaScript. Volta đã làm điều này bằng cách triển khai Volta Continuations bằng cách sử dụng Ngoại lệ JavaScript và sau đó triển khai tất cả các cấu trúc luồng điều khiển .NET theo thuật ngữ của Volta Continuations. Họ đã phải sử dụng Ngoại lệ làm luồng điều khiển, vì không có luồng điều khiển nào khác đủ mạnh.

Bạn đã đề cập đến Máy nhà nước. SM là tầm thường để thực hiện với các cuộc gọi Đuôi phù hợp: mọi trạng thái là một chương trình con, mọi chuyển đổi trạng thái là một cuộc gọi chương trình con. SM cũng có thể dễ dàng được thực hiện với GOTOhoặc Coroutines hoặc Continuations. Tuy nhiên, Java không có bất kỳ của những bốn, nhưng nó không có ngoại lệ. Vì vậy, hoàn toàn chấp nhận được khi sử dụng chúng làm luồng điều khiển. (Chà, thực ra, lựa chọn chính xác có lẽ sẽ là sử dụng một ngôn ngữ với cấu trúc luồng điều khiển thích hợp, nhưng đôi khi bạn có thể bị mắc kẹt với Java.)


7
Nếu chỉ có chúng tôi có đệ quy cuộc gọi đuôi thích hợp, có lẽ chúng tôi đã có thể chi phối sự phát triển web phía máy khách bằng JavaScript và sau đó tiếp tục bằng cách lan sang phía máy chủ và tới thiết bị di động như một giải pháp nền tảng pan tối ưu như cháy rừng. Than ôi, nhưng nó không phải như vậy. Chết tiệt không đủ chức năng hạng nhất của chúng tôi, đóng cửa và mô hình hướng sự kiện. Những gì chúng tôi thực sự cần là thực tế.
Erik Reppen

20
@ErikReppen: Tôi nhận ra rằng bạn chỉ đang mỉa mai, nhưng. . . Tôi thực sự không nghĩ rằng việc chúng ta "đã thống trị phát triển web phía máy khách bằng JavaScript" có liên quan gì đến các tính năng của ngôn ngữ. Nó có độc quyền trong thị trường đó, vì vậy đã có thể thoát khỏi rất nhiều vấn đề không thể viết ra với sự mỉa mai.
ruakh

1
Đệ quy đuôi sẽ là một phần thưởng tuyệt vời (chúng không dùng các tính năng chức năng để có nó trong tương lai) nhưng vâng, tôi sẽ nói rằng nó đã chiến thắng trước VB, Flash, Applet, v.v ... vì các lý do liên quan đến tính năng. Nếu nó không làm giảm sự phức tạp và bình thường hóa một cách khéo léo như nó sẽ có sự cạnh tranh thực sự tại một số điểm. Tôi hiện đang chạy Node.js để xử lý việc viết lại 21 tệp cấu hình cho ngăn xếp C # + Java gớm ghiếc và tôi biết tôi không phải là người duy nhất làm việc đó. Nó rất giỏi ở những gì nó giỏi.
Erik Reppen

1
Nhiều ngôn ngữ không có gotos (Goto được coi là có hại!), Coroutines hoặc continuations và thực hiện kiểm soát luồng hoàn toàn hợp lệ chỉ bằng cách sử dụng if, whiles và gọi hàm (hoặc gosub!). Trên thực tế, hầu hết trong số này có thể được sử dụng để mô phỏng lẫn nhau, giống như việc tiếp tục có thể được sử dụng để đại diện cho tất cả các điều trên. (Ví dụ, bạn có thể sử dụng một thời gian để thực hiện if, ngay cả khi đó là cách mã hóa hôi thối). Vì vậy, không có ngoại lệ KHÔNG cần thiết để thực hiện kiểm soát luồng nâng cao.
Shayne

2
Vâng, bạn có thể đúng nếu. Tuy nhiên, "For" ở dạng kiểu C có thể được sử dụng như một cách lúng túng trong khi đó, và sau đó có thể được sử dụng cùng với một if để thực hiện một máy trạng thái hữu hạn có thể mô phỏng tất cả các dạng điều khiển luồng khác. Một lần nữa, cách hôi thối để mã, nhưng yeah. Và một lần nữa, goto coi có hại. (Và tôi cũng tranh luận, với việc sử dụng các ngoại lệ trong các trường hợp không ngoại lệ). Hãy nhớ rằng có những ngôn ngữ hoàn toàn hợp lệ và rất mạnh mẽ cung cấp cả goto và ngoại lệ và hoạt động tốt.
Shayne

19

Như những người khác đã đề cập rất nhiều, ( ví dụ trong câu hỏi Stack Overflow này ), 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 chỉ kiểm soát luồng. 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 breakcontinuetrong 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 cách bên trong jOOQ (thư viện trừu tượng SQL cho Java) (từ chối trách nhiệm: Tôi làm việc cho nhà cung cấp), các trường hợp 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 chúng ta sử dụng những thứ không phải địa phương gotonày chỉ là những gì Mason Wheeler đã nói trong câu trả lời của mình:

"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 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.


11

Sử dụng các ngoại lệ cho luồng điều khiển thường được coi là một mô hình chống, nhưng có những trường hợp ngoại lệ (không có ý định chơi chữ).

Người ta đã nói hàng ngàn lần, rằng các ngoại lệ được dành cho các điều kiện đặc biệt. Một kết nối cơ sở dữ liệu bị hỏng một điều kiện đặc biệt. Một người dùng nhập các chữ cái trong một trường đầu vào chỉ nên cho phép số là không .

Một lỗi trong phần mềm của bạn khiến một chức năng được gọi với các đối số bất hợp pháp, ví dụ: nullnếu không cho phép, một điều kiện đặc biệt.

Bằng cách sử dụng ngoại lệ cho một cái gì đó không phải là ngoại lệ, bạn đang sử dụng trừu tượng không phù hợp cho vấn đề bạn đang cố gắng giải quyết.

Nhưng cũng có thể có một hình phạt hiệu suất. Một số ngôn ngữ có triển khai xử lý ngoại lệ ít nhiều hiệu quả, do đó, nếu ngôn ngữ bạn chọn không có xử lý ngoại lệ hiệu quả, nó có thể rất tốn kém, hiệu quả-khôn ngoan *.

Nhưng các ngôn ngữ khác, ví dụ như Ruby, có một cú pháp giống như ngoại lệ cho luồng điều khiển. Những tình huống đặc biệt được xử lý bởi các raise/ rescuenhà khai thác. Nhưng bạn có thể sử dụng throw/ catchcho các cấu trúc luồng điều khiển giống như ngoại lệ **.

Vì vậy, mặc dù các trường hợp ngoại lệ thường không được sử dụng cho luồng điều khiển, ngôn ngữ bạn chọn có thể có các thành ngữ khác.

* Ví dụ về việc sử dụng các ngoại lệ tốn kém: Tôi đã từng được thiết lập để tối ưu hóa một ứng dụng ASP.NET Web Form hoạt động kém. Hóa ra, kết xuất của một bảng lớn đang gọi int.Parse()khoảng. một nghìn chuỗi trống trên một trang trung bình, dẫn đến khoảng. một ngàn trường hợp ngoại lệ được xử lý. Bằng cách thay thế mã bằng int.TryParse()tôi cạo đi một giây! Đối với mỗi yêu cầu trang duy nhất!

** Điều này có thể rất khó hiểu đối với một lập trình viên đến với Ruby từ các ngôn ngữ khác, vì cả hai throwcatchlà từ khóa liên quan đến ngoại lệ trong nhiều ngôn ngữ khác.


1
+1 cho "Bằng cách sử dụng ngoại lệ cho một cái gì đó không phải là ngoại lệ, bạn đang sử dụng trừu tượng không phù hợp cho vấn đề bạn đang cố gắng giải quyết."
Giorgio

8

Hoàn toàn có thể xử lý các điều kiện lỗi mà không cần sử dụng ngoại lệ. Một số ngôn ngữ, đặc biệt là C, thậm chí không có ngoại lệ và mọi người vẫn quản lý để tạo các ứng dụng khá phức tạp với nó. Lý do ngoại lệ rất hữu ích là chúng cho phép bạn xác định ngắn gọn hai luồng điều khiển độc lập cơ bản trong cùng một mã: một nếu xảy ra lỗi và một nếu không xảy ra. Không có chúng, bạn sẽ có mã ở khắp nơi giống như thế này:

status = getValue(&inout);
if (status < 0)
{
    logError("message");
    return status;
}

doSomething(*inout);

Hoặc tương đương trong ngôn ngữ của bạn, giống như trở về một tuple với một giá trị như một trạng thái lỗi, vv Thông thường những người chỉ ra như thế nào "đắt" xử lý ngoại lệ là, bỏ bê tất cả các phụ iftuyên bố như trên mà bạn được yêu cầu phải bổ sung nếu bạn don' t sử dụng ngoại lệ.

Mặc dù mô hình này xảy ra thường xuyên nhất khi xử lý lỗi hoặc "điều kiện ngoại lệ" khác, theo ý kiến ​​của tôi nếu bạn bắt đầu thấy mã soạn sẵn như thế này trong các trường hợp khác, bạn có một lý lẽ khá tốt để sử dụng ngoại lệ. Tùy thuộc vào tình huống và cách triển khai, tôi có thể thấy các ngoại lệ được sử dụng hợp lệ trong máy trạng thái, bởi vì bạn có hai luồng điều khiển trực giao: một luồng thay đổi trạng thái và một cho các sự kiện xảy ra trong các trạng thái.

Tuy nhiên, những tình huống đó rất hiếm, và nếu bạn định tạo ra một ngoại lệ (ý định chơi chữ) theo quy tắc, tốt hơn hết là bạn nên chuẩn bị để thể hiện sự vượt trội của nó so với các giải pháp khác. Một sai lệch mà không có sự biện minh như vậy được gọi là một mô hình chống.


2
Miễn là những câu lệnh đó chỉ thất bại trong các trường hợp 'ngoại lệ', logic dự đoán nhánh của CPU hiện đại làm cho chi phí của chúng không đáng kể. Đây là một nơi mà các macro thực sự có thể giúp đỡ, miễn là bạn cẩn thận và đừng cố làm quá nhiều trong chúng.
James

5

Trong Python, các ngoại lệ được sử dụng để kết thúc trình tạo và lặp. Python có các thử / ngoại trừ khối rất hiệu quả, nhưng thực sự việc tạo một ngoại lệ có một số chi phí.

Do thiếu các ngắt đa cấp hoặc một câu lệnh goto trong Python, đôi khi tôi đã sử dụng các ngoại lệ:

class GOTO(Exception):
  pass

try:
  # Do lots of stuff
  # in here with multiple exit points
  # each exit point does a "raise GOTO()"
except GOTO:
  pass
except Exception as e:
  #display error

6
Nếu bạn phải chấm dứt tính toán ở đâu đó trong một câu lệnh được lồng sâu và đi đến một số mã tiếp tục phổ biến, thì cách thực hiện cụ thể này rất có thể được xem là một hàm, returnthay cho goto.
9000

3
@ 9000 Tôi chuẩn bị bình luận chính xác điều tương tự ... try:Xin vui lòng giữ các khối thành 1 hoặc 2 dòng, không bao giờ try: # Do lots of stuff.
wim

1
@ 9000, trong một số trường hợp, chắc chắn. Nhưng sau đó, bạn mất quyền truy cập vào các biến cục bộ và bạn di chuyển mã của mình sang một nơi khác, khi quy trình là một quy trình tuyến tính, mạch lạc.
gahooa

4
@gahooa: Tôi đã từng nghĩ như bạn. Đó là một dấu hiệu cấu trúc kém của mã của tôi. Khi tôi suy nghĩ nhiều hơn, tôi nhận thấy rằng bối cảnh cục bộ có thể được gỡ rối và toàn bộ mớ hỗn độn được tạo thành các hàm ngắn với một vài tham số, vài dòng mã và ý nghĩa rất chính xác. Tôi không bao giờ nhìn lại.
9000

4

Hãy phác họa một cách sử dụng Ngoại lệ như vậy:

Các thuật toán tìm kiếm đệ quy cho đến khi một cái gì đó được tìm thấy. Vì vậy, đi lại từ đệ quy người ta phải kiểm tra kết quả để được tìm thấy, và sau đó quay lại, nếu không tiếp tục. Và điều đó lặp đi lặp lại từ một số độ sâu đệ quy.

Bên cạnh đó cần thêm một boolean found(được đóng gói trong một lớp, trong trường hợp có thể chỉ có một int sẽ được trả về), và đối với độ sâu đệ quy, điều tương tự xảy ra.

Việc tháo gỡ một ngăn xếp cuộc gọi như vậy chỉ là một ngoại lệ dành cho. Vì vậy, nó có vẻ như là một phương tiện mã hóa không giống như goto, ngay lập tức và phù hợp hơn. Không cần thiết, sử dụng hiếm, có thể phong cách xấu, nhưng to-the-point. So sánh với hoạt động cắt Prolog.


Hmm, là sự hạ thấp cho vị trí thực sự trái ngược, hoặc cho lập luận không đủ hoặc ngắn? Tôi thực sự tò mò.
Eggen

Tôi đang đối mặt chính xác với tình trạng khó khăn này, tôi đã hy vọng bạn có thể thấy những gì bạn nghĩ về câu hỏi sau đây của tôi? Sử dụng đệ quy các trường hợp ngoại lệ để tích lũy lý do (tin nhắn) cho một codereview
CL22

@Jodes Tôi mới đọc kỹ thuật thú vị của bạn, nhưng hiện tại tôi khá bận rộn. Hy vọng người khác sẽ làm sáng tỏ vấn đề của cô ấy / anh ấy.
Eggen

4

Lập trình là về công việc

Tôi nghĩ cách dễ nhất để trả lời điều này là hiểu được tiến bộ mà OOP đã đạt được trong những năm qua. Tất cả mọi thứ được thực hiện trong OOP (và hầu hết các mô hình lập trình, cho vấn đề đó) được mô hình hóa xung quanh cần thực hiện công việc .

Mỗi khi một phương thức được gọi, người gọi sẽ nói "Tôi không biết làm công việc này, nhưng bạn biết làm thế nào, vì vậy bạn làm điều đó cho tôi."

Điều này đưa ra một khó khăn: điều gì xảy ra khi phương thức được gọi thường biết cách thực hiện công việc, nhưng không phải lúc nào cũng vậy? Chúng tôi cần một cách để giao tiếp "Tôi muốn giúp bạn, tôi thực sự đã làm, nhưng tôi không thể làm điều đó."

Một phương pháp ban đầu để truyền đạt điều này là chỉ cần trả về giá trị "rác". Có thể bạn mong đợi một số nguyên dương, vì vậy phương thức được gọi trả về một số âm. Một cách khác để thực hiện điều này là đặt giá trị lỗi ở đâu đó. Thật không may, cả hai cách đều dẫn đến mã soạn sẵn cho phép tôi kiểm tra mã ở đây để đảm bảo mọi thứ chắc chắn hơn . Khi mọi thứ phát triển phức tạp hơn, hệ thống này sụp đổ.

Một sự tương tự đặc biệt

Giả sử bạn có một thợ mộc, thợ sửa ống nước và thợ điện. Bạn muốn thợ sửa ống nước để sửa bồn rửa của bạn, vì vậy anh ấy hãy xem nó. Sẽ không hữu ích nếu anh ta chỉ nói với bạn, "Xin lỗi, tôi không thể sửa nó. Nó bị hỏng rồi." Chết tiệt, thậm chí còn tệ hơn nếu anh ta nhìn, bỏ đi và gửi cho bạn một lá thư nói rằng anh ta không thể sửa nó. Bây giờ bạn phải kiểm tra thư của mình trước khi bạn biết anh ấy không làm những gì bạn muốn.

Những gì bạn muốn là để anh ấy nói với bạn, "Hãy nhìn xem, tôi không thể sửa nó bởi vì có vẻ như máy bơm của bạn không hoạt động."

Với thông tin này, bạn có thể kết luận bạn muốn thợ điện xem xét vấn đề. Có lẽ thợ điện sẽ tìm thấy một cái gì đó liên quan đến thợ mộc, và bạn sẽ cần thợ mộc sửa nó.

Heck, bạn có thể thậm chí không biết bạn cần một thợ điện, bạn có thể không biết người mà bạn cần. Bạn chỉ là quản lý cấp trung trong một doanh nghiệp sửa chữa nhà, và trọng tâm của bạn là hệ thống ống nước. Vì vậy, bạn nói với ông chủ của bạn về vấn đề, và sau đó ông nói với thợ điện để khắc phục nó.

Đây là những gì ngoại lệ đang mô hình hóa: các chế độ thất bại phức tạp theo kiểu tách rời. Thợ sửa ống nước không cần biết về thợ điện - anh ta thậm chí không cần biết rằng ai đó lên dây chuyền có thể khắc phục vấn đề. Anh ấy chỉ báo cáo về vấn đề anh ấy gặp phải.

Vậy ... một mô hình chống?

Ok, vì vậy hiểu được điểm của ngoại lệ là bước đầu tiên. Tiếp theo là để hiểu thế nào là một mô hình chống.

Để đủ điều kiện là một mô hình chống, nó cần phải

  • giải quyết vấn đề
  • có hậu quả chắc chắn tiêu cực

Điểm đầu tiên dễ dàng được đáp ứng - hệ thống hoạt động, phải không?

Điểm thứ hai là stickier. Lý do chính cho việc sử dụng các ngoại lệ như luồng điều khiển thông thường là xấu là vì đó không phải là mục đích của chúng. Bất kỳ phần chức năng nhất định nào trong một chương trình nên có mục đích tương đối rõ ràng và việc đồng ý chọn mục đích đó dẫn đến sự nhầm lẫn không cần thiết.

Nhưng đó không phải là tác hại dứt khoát . Đó là một cách kém để làm mọi thứ, và kỳ lạ, nhưng một mô hình chống? Không. Chỉ là ... lẻ.


Có một hậu quả xấu, ít nhất là trong các ngôn ngữ cung cấp dấu vết ngăn xếp đầy đủ. Vì mã được tối ưu hóa rất nhiều với nhiều nội tuyến, nên dấu vết ngăn xếp thực và nhà phát triển muốn thấy khác nhau rất nhiều và do đó việc tạo dấu vết ngăn xếp rất tốn kém. Việc lạm dụng các ngoại lệ là rất tệ đối với hiệu suất trong các ngôn ngữ như vậy (Java, C #).
maaartinus

"Đó là một cách kém để làm mọi thứ" - Không nên đủ để phân loại nó thành một mô hình chống?
Có lẽ là

@Maybe_Factor Theo định nghĩa của mẫu kiến, không.
MirroredFate
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.