Tại sao là ngoại trừ: vượt qua một thực hành lập trình xấu?


325

Tôi thường thấy các bình luận về các câu hỏi Stack Overflow khác về cách sử dụng except: passkhông được khuyến khích. Tại sao điều này là xấu? Đôi khi tôi không quan tâm đến lỗi là gì và tôi muốn tiếp tục với mã.

try:
    something
except:
    pass

Tại sao sử dụng một except: passkhối xấu? Điều gì làm cho nó xấu? Có phải thực tế là tôi passcó lỗi hay tôi exceptcó lỗi gì không?


1
Ít nhất, tôi sẽ đề nghị bạn đăng nhập nó, vì vậy bạn BIẾT những vấn đề bạn đang bỏ qua. Sử dụng loggingmô-đun ở cấp DEBUG để tránh chúng phát ra trong sản xuất, nhưng giữ cho chúng có sẵn trong quá trình phát triển.
pcurry

Câu trả lời:


346

Như bạn đã đoán chính xác, có hai mặt của nó: Bắt bất kỳ lỗi nào bằng cách chỉ định không có loại ngoại lệ nào sau đó exceptvà chỉ cần chuyển nó mà không thực hiện bất kỳ hành động nào.

Lời giải thích của tôi là một chút, một chút nữa, vì vậy, nó bị phá vỡ:

  1. Đừng bắt bất kỳ lỗi nào . Luôn chỉ định ngoại lệ nào bạn chuẩn bị khôi phục và chỉ bắt những ngoại lệ đó.
  2. Cố gắng tránh đi vào ngoại trừ các khối . Trừ khi mong muốn rõ ràng, đây thường không phải là một dấu hiệu tốt.

Nhưng hãy đi vào chi tiết:

Đừng bắt bất kỳ lỗi

Khi sử dụng một trykhối, bạn thường làm điều này bởi vì bạn biết rằng có khả năng có một ngoại lệ bị ném. Như vậy, bạn cũng đã có một ý tưởng gần đúng về những gì có thể phá vỡ và ngoại lệ nào có thể được ném ra. Trong những trường hợp như vậy, bạn bắt gặp một ngoại lệ vì bạn có thể phục hồi tích cực từ nó. Điều đó có nghĩa là bạn đã chuẩn bị cho ngoại lệ và có một số kế hoạch thay thế mà bạn sẽ tuân theo trong trường hợp ngoại lệ đó.

Ví dụ: khi bạn yêu cầu người dùng nhập số, bạn có thể chuyển đổi đầu vào bằng cách sử dụng int()số có thể tăng a ValueError. Bạn có thể dễ dàng phục hồi điều đó bằng cách đơn giản yêu cầu người dùng thử lại, do đó, bắt ValueErrorvà nhắc lại người dùng sẽ là một kế hoạch phù hợp. Một ví dụ khác là nếu bạn muốn đọc một số cấu hình từ một tệp và tệp đó xảy ra không tồn tại. Vì là tệp cấu hình, bạn có thể có một số cấu hình mặc định là dự phòng, vì vậy tệp không chính xác cần thiết. Vì vậy, bắt một FileNotFoundErrorvà chỉ cần áp dụng cấu hình mặc định sẽ là một kế hoạch tốt ở đây. Bây giờ trong cả hai trường hợp này, chúng tôi có một ngoại lệ rất cụ thể mà chúng tôi mong đợi và có một kế hoạch cụ thể như nhau để phục hồi từ nó. Như vậy, trong mỗi trường hợp, chúng tôi chỉ rõ ràng except rằng ngoại lệ.

Tuy nhiên, nếu chúng ta bắt được tất cả mọi thứ , thì ngoài những trường hợp ngoại lệ đó, chúng ta đã sẵn sàng để phục hồi từ hồi còn có một cơ hội mà chúng ta không có ngoại lệ mà chúng ta không mong đợi và chúng ta thực sự không thể phục hồi; hoặc không nên phục hồi từ.

Hãy lấy ví dụ về tập tin cấu hình từ trên. Trong trường hợp tệp bị thiếu, chúng tôi chỉ áp dụng cấu hình mặc định của mình và có thể quyết định sau đó sẽ tự động lưu cấu hình (vì vậy lần sau, tệp tồn tại). Bây giờ hãy tưởng tượng chúng ta có một IsADirectoryError, hoặc mộtPermissionErrorthay thế. Trong những trường hợp như vậy, có lẽ chúng tôi không muốn tiếp tục; chúng tôi vẫn có thể áp dụng cấu hình mặc định của mình, nhưng sau đó chúng tôi sẽ không thể lưu tệp. Và có khả năng người dùng cũng có cấu hình tùy chỉnh, vì vậy sử dụng các giá trị mặc định có thể không mong muốn. Vì vậy, chúng tôi muốn nói với người dùng về nó ngay lập tức và có thể hủy bỏ việc thực hiện chương trình. Nhưng đó không phải là thứ chúng tôi muốn làm ở đâu đó sâu bên trong một phần mã nhỏ; đây là một cái gì đó có tầm quan trọng ở cấp ứng dụng, vì vậy nó nên được xử lý ở cấp cao nhất vì vậy hãy để ngoại lệ nổi lên.

Một ví dụ đơn giản khác cũng được đề cập trong tài liệu thành ngữ Python 2 . Ở đây, một lỗi đánh máy đơn giản tồn tại trong mã khiến nó bị hỏng. Bởi vì chúng tôi đang bắt mọi ngoại lệ, chúng tôi cũng bắt được NameErrorsSyntaxErrors . Cả hai đều là những sai lầm xảy ra với tất cả chúng ta trong khi lập trình; và cả hai đều là những lỗi chúng tôi hoàn toàn không muốn đưa vào khi vận chuyển mã. Nhưng bởi vì chúng tôi cũng đã bắt được chúng, chúng tôi thậm chí sẽ không biết rằng chúng xảy ra ở đó và mất bất kỳ trợ giúp nào để gỡ lỗi chính xác.

Nhưng cũng có những trường hợp ngoại lệ nguy hiểm hơn mà chúng ta khó có thể chuẩn bị cho. Ví dụ, SystemError thường là thứ hiếm khi xảy ra và chúng ta không thể thực sự lên kế hoạch cho; nó có nghĩa là có một cái gì đó phức tạp hơn đang diễn ra, một cái gì đó có khả năng ngăn chúng ta tiếp tục nhiệm vụ hiện tại.

Trong mọi trường hợp, rất khó có khả năng bạn chuẩn bị cho mọi thứ trong một phần quy mô nhỏ của mã, vì vậy đó thực sự là nơi bạn chỉ nên nắm bắt những ngoại lệ mà bạn đã chuẩn bị. Một số người đề nghị ít nhất bắt Exceptionvì nó sẽ không bao gồm những thứ như SystemExitKeyboardInterruptđó bằng cách thiết kế là để chấm dứt các ứng dụng của bạn, nhưng tôi cho rằng điều này vẫn còn xa quá không đặc hiệu. Chỉ có một nơi mà cá nhân tôi chấp nhận bắt Exceptionhoặc chỉ bất kỳngoại lệ, và đó là trong một trình xử lý ngoại lệ cấp ứng dụng toàn cầu duy nhất có mục đích duy nhất để ghi lại bất kỳ ngoại lệ nào mà chúng tôi chưa chuẩn bị. Bằng cách đó, chúng tôi vẫn có thể giữ lại nhiều thông tin về các trường hợp ngoại lệ không mong muốn, sau đó chúng tôi có thể sử dụng để mở rộng mã của mình để xử lý các mã đó một cách rõ ràng (nếu chúng tôi có thể khôi phục từ chúng) hoặc trong trường hợp có lỗi Lỗi để tạo các trường hợp kiểm tra để đảm bảo nó sẽ không xảy ra lần nữa Nhưng tất nhiên, điều đó chỉ hiệu quả nếu chúng ta chỉ bắt gặp những ngoại lệ mà chúng ta đã mong đợi, vì vậy những điều chúng ta không mong đợi sẽ tự nhiên nổi lên.

Cố gắng tránh đi vào ngoại trừ các khối

Khi rõ ràng nắm bắt một lựa chọn nhỏ của các ngoại lệ cụ thể, có nhiều tình huống chúng ta sẽ ổn bằng cách đơn giản không làm gì cả. Trong những trường hợp như vậy, chỉ cần có except SomeSpecificException: passlà tốt. Hầu hết thời gian, đây không phải là trường hợp vì chúng ta có thể cần một số mã liên quan đến quá trình khôi phục (như đã đề cập ở trên). Đây có thể là ví dụ một cái gì đó thử lại hành động hoặc để thiết lập một giá trị mặc định thay thế.

Nếu đó không phải là trường hợp, ví dụ vì mã của chúng tôi đã được cấu trúc để lặp lại cho đến khi thành công, thì chỉ cần vượt qua là đủ tốt. Lấy ví dụ của chúng tôi từ trên, chúng tôi có thể muốn yêu cầu người dùng nhập số. Bởi vì chúng tôi biết rằng người dùng không muốn làm những gì chúng tôi yêu cầu, nên chúng tôi có thể đặt nó vào một vòng lặp ở vị trí đầu tiên, vì vậy nó có thể trông như thế này:

def askForNumber ():
    while True:
        try:
            return int(input('Please enter a number: '))
        except ValueError:
            pass

Bởi vì chúng tôi tiếp tục cố gắng cho đến khi không có ngoại lệ nào bị ném, chúng tôi không cần phải làm gì đặc biệt trong khối ngoại trừ, vì vậy điều này là tốt. Nhưng tất nhiên, người ta có thể lập luận rằng ít nhất chúng tôi muốn hiển thị cho người dùng một số thông báo lỗi để cho anh ta biết lý do tại sao anh ta phải lặp lại đầu vào.

Tuy nhiên, trong nhiều trường hợp khác, chỉ cần vượt qua exceptlà một dấu hiệu cho thấy chúng ta không thực sự chuẩn bị cho ngoại lệ mà chúng ta đang nắm bắt. Trừ khi những ngoại lệ đó là đơn giản (như ValueErrorhoặc TypeError), và lý do tại sao chúng ta có thể vượt qua là rõ ràng, hãy cố gắng tránh chỉ vượt qua. Nếu thực sự không có gì để làm (và bạn hoàn toàn chắc chắn về điều đó), thì hãy xem xét thêm một nhận xét tại sao lại như vậy; mặt khác, mở rộng khối ngoại trừ để thực sự bao gồm một số mã khôi phục.

except: pass

Người phạm tội tồi tệ nhất là sự kết hợp của cả hai. Điều này có nghĩa là chúng tôi sẵn sàng bắt bất kỳ lỗi nào mặc dù chúng tôi hoàn toàn không chuẩn bị cho nó chúng tôi cũng không làm gì với nó. Bạn ít nhất muốn đăng nhập lỗi và cũng có khả năng reraise nó vẫn chấm dứt việc áp dụng (đó là khó xảy ra, bạn có thể tiếp tục như bình thường sau một MemoryError). Chỉ cần vượt qua mặc dù sẽ không chỉ giữ cho ứng dụng tồn tại phần nào (tất nhiên tùy thuộc vào nơi bạn bắt được), mà còn vứt bỏ tất cả thông tin, khiến bạn không thể phát hiện ra lỗi lỗi, điều này đặc biệt đúng nếu bạn không phải là người phát hiện ra nó.


Vì vậy, điểm mấu chốt là: Chỉ nắm bắt các ngoại lệ bạn thực sự mong đợi và sẵn sàng phục hồi; tất cả những lỗi khác có thể là lỗi bạn nên sửa, hoặc điều gì đó bạn chưa chuẩn bị cho dù sao đi nữa. Vượt qua các ngoại lệ cụ thể là tốt nếu bạn thực sự không cần phải làm gì đó với chúng. Trong tất cả các trường hợp khác, đó chỉ là dấu hiệu của sự suy đoán và lười biếng. Và bạn chắc chắn muốn khắc phục điều đó.


1
"Ít nhất bạn muốn ghi lại lỗi và cũng có khả năng sửa lại nó để vẫn chấm dứt ứng dụng". Bạn có thể chứng minh làm thế nào để "hồi sinh" một ngoại lệ để cho nó tiếp tục sủi bọt ngay cả sau khi bắt được nó không? Điều này có vẻ hữu ích đối với tôi để thêm vào một số thông báo lỗi tùy chỉnh trong khi vẫn để ngoại lệ buộc ứng dụng thoát.
Gabriel Staples

1
Điều này giúp làm rõ: họ sử dụng chăn except, nhưng sau đó gọi raisemà không có đối số để tiếp tục cho phép ngoại lệ nổi lên, chấm dứt ứng dụng. Tôi thích nó: ianbicking.org/blog/2007/09/re-raising-exceptions.html . Trông giống như một ngoại lệ vững chắc cho quy tắc về việc không sử dụng chăn except.
Gabriel Staples

1
@GabrielStaples Vâng, một ngoại lệ bị bắt có thể được truy xuất lại bằng cách sử dụng raise. Bạn thường chỉ làm điều này ở một vài vị trí trong ứng dụng của mình để ghi lại ngoại lệ.
chọc

Điều này là tuyệt vời, tránh đi vào ngoại trừ khối. Tôi sẽ nói rằng làm bất cứ điều gì có vẻ dễ hiểu hơn, đặc biệt là với những người khác. Nhận một bộ mắt trăn thứ hai để xem lại mã của bạn và xem họ có đặt câu hỏi cho khối không. Khả năng đọc là chìa khóa.
radtek

262

Vấn đề chính ở đây là nó bỏ qua tất cả và bất kỳ lỗi nào: Hết bộ nhớ, CPU bị cháy, người dùng muốn dừng, chương trình muốn thoát, Jabberwocky đang giết chết người dùng.

Đây là cách quá nhiều. Trong đầu bạn, bạn đang nghĩ "Tôi muốn bỏ qua lỗi mạng này". Nếu có điều gì đó bất ngờ xảy ra, thì mã của bạn sẽ tiếp tục âm thầm và phá vỡ theo những cách hoàn toàn không thể đoán trước mà không ai có thể gỡ lỗi.

Đó là lý do tại sao bạn nên hạn chế bỏ qua một số lỗi cụ thể và để phần còn lại vượt qua.


75

Thực thi mã giả của bạn theo nghĩa đen thậm chí không đưa ra bất kỳ lỗi nào:

try:
    something
except:
    pass

như thể đó là một đoạn mã hoàn toàn hợp lệ, thay vì ném a NameError. Tôi hy vọng đây không phải là điều bạn muốn.


51

Tại sao là ngoại trừ: vượt qua một thực hành lập trình xấu?

Tại sao điều này là xấu?

try:
    something
except:
    pass

Điều này nắm bắt mọi ngoại lệ có thể, bao gồm GeneratorExit, KeyboardInterruptSystemExit- đó là những ngoại lệ mà bạn có thể không có ý định bắt. Nó giống như bắt BaseException.

try:
    something
except BaseException:
    pass

Các phiên bản cũ hơn của tài liệu nói :

Vì mọi lỗi trong Python đều tạo ra một ngoại lệ, việc sử dụng except:có thể làm cho nhiều lỗi lập trình trông giống như các vấn đề về thời gian chạy, gây cản trở quá trình gỡ lỗi.

Phân cấp ngoại lệ Python

Nếu bạn bắt một lớp ngoại lệ cha mẹ, bạn cũng bắt tất cả các lớp con của họ. Nó thanh lịch hơn nhiều khi chỉ nắm bắt các ngoại lệ bạn chuẩn bị xử lý.

Đây là hệ thống phân cấp ngoại lệ của Python 3 - bạn có thực sự muốn bắt hết chúng không?:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
           +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

Đừng làm điều này

Nếu bạn đang sử dụng hình thức xử lý ngoại lệ này:

try:
    something
except: # don't just do a bare except!
    pass

Sau đó, bạn sẽ không thể làm gián đoạn somethingkhối của mình bằng Ctrl-C. Chương trình của bạn sẽ bỏ qua mọi Ngoại lệ có thể có trong trykhối mã.

Đây là một ví dụ khác sẽ có hành vi không mong muốn tương tự:

except BaseException as e: # don't do this either - same as bare!
    logging.info(e)

Thay vào đó, hãy cố gắng chỉ nắm bắt ngoại lệ cụ thể mà bạn biết bạn đang tìm kiếm. Ví dụ: nếu bạn biết bạn có thể gặp lỗi giá trị khi chuyển đổi:

try:
    foo = operation_that_includes_int(foo)
except ValueError as e:
    if fatal_condition(): # You can raise the exception if it's bad,
        logging.info(e)   # but if it's fatal every time,
        raise             # you probably should just not catch it.
    else:                 # Only catch exceptions you are prepared to handle.
        foo = 0           # Here we simply assign foo to 0 and continue. 

Giải thích thêm với một ví dụ khác

Bạn có thể đang làm điều đó bởi vì bạn đã lướt web và đang nói, UnicodeErrornhưng, vì bạn đã sử dụng tính năng Bắt ngoại lệ rộng nhất, mã của bạn, có thể có các lỗi cơ bản khác, sẽ cố chạy để hoàn thành, lãng phí băng thông , thời gian xử lý, hao mòn thiết bị của bạn, hết bộ nhớ, thu thập dữ liệu rác, v.v.

Nếu người khác đang yêu cầu bạn hoàn thành để họ có thể dựa vào mã của bạn, tôi hiểu cảm giác bắt buộc phải xử lý mọi thứ. Nhưng nếu bạn sẵn sàng thất bại một cách ồn ào khi bạn phát triển, bạn sẽ có cơ hội sửa chữa các vấn đề chỉ có thể bật lên không liên tục, nhưng đó sẽ là những lỗi tốn kém lâu dài.

Với việc xử lý lỗi chính xác hơn, mã của bạn có thể mạnh mẽ hơn.


31
>>> import this

Zen của Python, bởi Tim Peters

Đẹp thì tốt hơn xấu.
Rõ ràng là tốt hơn so với ngầm.
Đơn giản là tốt hơn phức tạp.
Phức tạp tốt hơn phức tạp.
Bằng phẳng là tốt hơn so với lồng nhau.
Thưa thì tốt hơn dày đặc.
Tính dễ đọc.
Trường hợp đặc biệt không đủ đặc biệt để phá vỡ các quy tắc.
Mặc dù thực tế đánh bại sự tinh khiết.
Lỗi không bao giờ nên âm thầm vượt qua.
Trừ khi im lặng rõ ràng.
Trước sự mơ hồ, hãy từ chối sự cám dỗ để đoán.
Nên có một-- và tốt nhất là chỉ có một cách rõ ràng để làm điều đó.
Mặc dù cách đó có thể không rõ ràng lúc đầu trừ khi bạn là người Hà Lan.
Bây giờ tốt hơn bao giờ hết.
Mặc dù không bao giờ thường tốt hơnngay bây giờ
Nếu việc thực hiện khó giải thích, đó là một ý tưởng tồi.
Nếu việc thực hiện dễ giải thích, nó có thể là một ý tưởng tốt.
Không gian tên là một ý tưởng tuyệt vời - hãy làm nhiều hơn nữa!

Vì vậy, đây là ý kiến ​​của tôi. Bất cứ khi nào bạn tìm thấy một lỗi, bạn nên làm một cái gì đó để xử lý nó, tức là viết nó trong logfile hoặc một cái gì đó khác. Ít nhất, nó thông báo cho bạn rằng đã từng có một lỗi.


64
-1 Đối số từ chính quyền không thực sự giải thích bất cứ điều gì. Thẩm quyền có thể sai.
Izkata

23
những gì @Izkata đã viết, VÀ, một dòng bên dưới nó, cùng thẩm quyền viết: "Trừ khi im lặng rõ ràng", đó chính xác là những gì ngoại trừ: vượt qua.
Ofri Raviv

13
@OfriRaviv Không, không phải lỗi đã qua ngầm ? Rõ ràng sẽ yêu cầu đặt tên lỗi phải vượt qua một cách im lặng, nghĩa là, rõ ràng về nó . Đây không phải là những gì ngoại trừ: vượt qua.
Chelonia

24

Bạn nên sử dụng ít nhất except Exception:để tránh bắt ngoại lệ hệ thống như SystemExithoặc KeyboardInterrupt. Đây là liên kết đến tài liệu.

Nói chung, bạn nên xác định rõ ràng các ngoại lệ bạn muốn nắm bắt, để tránh bắt các ngoại lệ không mong muốn. Bạn nên biết những ngoại lệ bạn bỏ qua .


13

Đầu tiên, nó vi phạm hai nguyên tắc Zen của Python :

  • Rõ ràng là tốt hơn so với ngầm
  • Lỗi không bao giờ vượt qua âm thầm

Điều đó có nghĩa là, bạn cố tình làm cho lỗi của bạn âm thầm vượt qua. Hơn nữa, bạn không biết sự kiện, lỗi nào chính xác xảy ra, bởi vì except: passsẽ bắt gặp bất kỳ ngoại lệ nào.

Thứ hai, nếu chúng ta cố gắng trừu tượng ra khỏi Zen of Python và nói về sự tỉnh táo, bạn nên biết rằng việc sử dụng except:passkhiến bạn không có kiến ​​thức và kiểm soát trong hệ thống của mình. Nguyên tắc chung là đưa ra một ngoại lệ, nếu xảy ra lỗi và thực hiện các hành động thích hợp. Nếu bạn không biết trước, những hành động này nên là gì, ít nhất là ghi lại lỗi ở đâu đó (và tốt hơn là nêu lại ngoại lệ):

try:
    something
except:
    logger.exception('Something happened')

Nhưng, thông thường, nếu bạn cố gắng bắt bất kỳ ngoại lệ nào, có lẽ bạn đang làm sai điều gì đó!


2
... Trừ khi im lặng rõ ràng, đó là trường hợp của OP.
Hyperboreus

Tôi muốn biết giải pháp của bạn. Trong thực tế, khi thực sự không có gì cần làm, tôi chỉ liệt kê các lỗi ngoại trừ và đưa ra nhận xét và viết nhật ký. Rồi cứ qua.
Tăng cường

2
@Hyperboreus, tôi không nghĩ rằng việc bắt tất cả và bất kỳ lỗi nào rõ ràng là bịt miệng chúng, tức là bạn thậm chí không biết, bạn bắt được gì.
Alexander Zhukov

13
"Bởi vì một số người nói như vậy" không thực sự là một câu trả lời cho một "Tại sao?" câu hỏi
Sebastian Negraszus

12

Cấu except:passtrúc về cơ bản làm im lặng bất kỳ và tất cả các điều kiện đặc biệt xuất hiện trong khi mã được bao phủ trong try:khối đang được chạy.

Điều làm cho thực hành tồi tệ này là nó thường không phải là những gì bạn thực sự muốn. Thường xuyên hơn, một số điều kiện cụ thể được đưa ra mà bạn muốn im lặng, và except:passquá nhiều công cụ cùn. Nó sẽ hoàn thành công việc, nhưng nó cũng sẽ che giấu các điều kiện lỗi khác mà bạn có thể không lường trước được, nhưng có thể rất muốn giải quyết theo một cách khác.

Điều làm cho điều này đặc biệt quan trọng trong Python là bởi các thành ngữ của ngôn ngữ này, các ngoại lệ không nhất thiết là lỗi . Tất nhiên, chúng thường được sử dụng theo cách này, giống như trong hầu hết các ngôn ngữ. Nhưng Python đặc biệt đôi khi đã sử dụng chúng để thực hiện một lối thoát thay thế từ một số tác vụ mã không thực sự là một phần của trường hợp chạy bình thường, nhưng đôi khi vẫn được biết là sẽ xuất hiện và thậm chí có thể được dự kiến ​​trong hầu hết các trường hợp. SystemExitđã được đề cập như một ví dụ cũ, nhưng ví dụ phổ biến nhất hiện nay có thể là StopIteration. Sử dụng ngoại lệ theo cách này gây ra rất nhiều tranh cãi, đặc biệt là khi các trình vòng lặp và trình tạo lần đầu tiên được giới thiệu với Python, nhưng cuối cùng ý tưởng đã thắng thế.


12

Lý do số 1 đã được nêu - nó che giấu các lỗi mà bạn không mong đợi.

(# 2) - Nó làm cho mã của bạn khó đọc và hiểu. Nếu bạn bắt gặp FileNotFoundException khi bạn đang cố đọc một tệp, thì điều đó khá rõ ràng đối với một nhà phát triển khác về chức năng mà khối 'bắt' nên có. Nếu bạn không chỉ định một ngoại lệ, thì bạn cần bình luận thêm để giải thích những gì khối nên làm.

(# 3) - Nó thể hiện sự lười biếng trong lập trình. Nếu bạn sử dụng thử / bắt chung chung, điều đó cho thấy rằng bạn không hiểu các lỗi thời gian chạy có thể có trong chương trình của mình hoặc bạn không biết ngoại lệ nào có thể xảy ra trong Python. Nắm bắt một lỗi cụ thể cho thấy rằng bạn hiểu cả chương trình của mình và phạm vi lỗi mà Python gây ra. Điều này có nhiều khả năng làm cho các nhà phát triển và người đánh giá mã khác tin tưởng vào công việc của bạn.


12

Vì vậy, mã sản xuất này sản xuất những gì?

fruits = [ 'apple', 'pear', 'carrot', 'banana' ]

found = False
try:
     for i in range(len(fruit)):
         if fruits[i] == 'apple':
             found = true
except:
     pass

if found:
    print "Found an apple"
else:
    print "No apples in list"

Bây giờ hãy tưởng tượng khối try- exceptlà hàng trăm dòng lệnh đến một hệ thống phân cấp đối tượng phức tạp và chính nó được gọi ở giữa cây gọi của chương trình lớn. Khi chương trình gặp sự cố, bạn bắt đầu tìm kiếm ở đâu?


5
Er, cảm ơn những người đã 'sửa chữa' điều này, nhưng xin đừng - nó cố ý sai, theo nghĩa 'câu hỏi phỏng vấn'. Nó có thể tinh tế hơn khi nó xuất hiện lần đầu tiên - hãy thử nó. Quan điểm của tôi là việc loại bỏ 'tất cả các ngoại lệ', đặc biệt là trong Python, khiến việc gỡ lỗi trở nên khó khăn, ngay cả trong hàng tá dòng mã tầm thường.
Ian Harvey

11

Nói chung, bạn có thể phân loại bất kỳ lỗi / ngoại lệ nào trong một trong ba loại :

  • Gây tử vong : Không phải lỗi của bạn, bạn không thể ngăn chặn chúng, bạn không thể phục hồi từ chúng. Bạn chắc chắn không nên bỏ qua chúng và tiếp tục, và để chương trình của bạn ở trạng thái không xác định. Chỉ cần để lỗi chấm dứt chương trình của bạn, bạn không thể làm gì được.

  • Boneheaded : Lỗi của riêng bạn, rất có thể là do lỗi giám sát, lỗi hoặc lập trình. Bạn nên sửa lỗi. Một lần nữa, bạn chắc chắn không nên bỏ qua và tiếp tục.

  • Ngoại sinh : Bạn có thể mong đợi các lỗi này trong các tình huống đặc biệt, chẳng hạn như không tìm thấy tệp hoặc kết nối bị chấm dứt . Bạn nên xử lý rõ ràng những lỗi này và chỉ những lỗi này.

Trong mọi trường hợp except: passsẽ chỉ để chương trình của bạn ở trạng thái không xác định, nơi nó có thể gây ra nhiều thiệt hại hơn.


6

Nói một cách đơn giản, nếu một ngoại lệ hoặc lỗi bị ném, có gì đó không đúng. Nó có thể không phải là một cái gì đó rất sai, nhưng tạo, ném, và bắt lỗi và ngoại lệ chỉ vì mục đích sử dụng câu lệnh goto không phải là một ý tưởng tốt, và nó hiếm khi được thực hiện. 99% thời gian, có một vấn đề ở đâu đó.

Vấn đề cần được giải quyết. Giống như trong cuộc sống, trong lập trình, nếu bạn chỉ để lại vấn đề một mình và cố gắng phớt lờ chúng, chúng sẽ không tự biến mất rất nhiều lần; thay vào đó chúng lớn hơn và nhân lên. Để ngăn chặn vấn đề phát triển trên bạn và tấn công lần nữa xuống đường, bạn 1) loại bỏ nó và dọn dẹp mớ hỗn độn sau đó, hoặc 2) chứa nó và dọn dẹp mớ hỗn độn sau đó.

Chỉ cần bỏ qua các ngoại lệ và lỗi và để chúng như vậy là một cách tốt để trải nghiệm rò rỉ bộ nhớ, kết nối cơ sở dữ liệu nổi bật, khóa không cần thiết về quyền truy cập tệp, v.v.

Trong các trường hợp hiếm hoi, vấn đề rất nhỏ nhặt, tầm thường và - ngoài việc cần một thử ... bắt khối - khép kín , rằng thực sự không có mớ hỗn độn nào để được dọn dẹp sau đó. Đây là những dịp duy nhất khi thực hành tốt nhất này không nhất thiết phải áp dụng. Theo kinh nghiệm của tôi, điều này thường có nghĩa là bất cứ điều gì mã đang làm về cơ bản là nhỏ nhặt và có thể bỏ qua được, và những thứ như thử lại hoặc tin nhắn đặc biệt đều không có giá trị phức tạp cũng như không giữ được chuỗi.

Tại công ty của tôi, quy tắc là hầu như luôn luôn làm một cái gì đó trong một khối bắt và nếu bạn không làm gì cả, thì bạn phải luôn luôn đặt một nhận xét với một lý do rất tốt tại sao không. Bạn không bao giờ được vượt qua hoặc để lại một khối bắt trống khi có bất cứ điều gì phải làm.


6

Theo tôi, lỗi có một lý do để xuất hiện, đó là âm thanh của tôi ngu ngốc, nhưng đó là như vậy. Lập trình tốt chỉ làm tăng lỗi khi bạn phải xử lý chúng. Ngoài ra, như tôi đã đọc cách đây một thời gian, "Tuyên bố vượt qua là một Tuyên bố hiển thị mã sẽ được chèn sau", vì vậy nếu bạn muốn có một câu lệnh ngoại trừ trống, hãy thoải mái làm điều đó, nhưng đối với một chương trình tốt sẽ có là một phần còn thiếu bởi vì bạn không xử lý những thứ bạn nên có. Xuất hiện ngoại lệ cho bạn cơ hội sửa dữ liệu đầu vào hoặc thay đổi cấu trúc dữ liệu để các ngoại lệ này không xảy ra lần nữa (nhưng trong hầu hết các trường hợp (ngoại lệ mạng, ngoại lệ đầu vào chung) cho thấy các phần tiếp theo của chương trình sẽ không thực thi tốt. Ví dụ, NetworkException có thể chỉ ra kết nối mạng bị hỏng và chương trình không thể gửi / nhận dữ liệu trong các bước chương trình tiếp theo.

Nhưng việc sử dụng một khối vượt qua chỉ cho một khối thực thi là hợp lệ, bởi vì bạn vẫn khác biệt giữa các loại ngoại lệ, vì vậy nếu bạn đặt tất cả các khối ngoại lệ trong một, thì nó không trống:

try:
    #code here
except Error1:
    #exception handle1

except Error2:
    #exception handle2
#and so on

có thể được viết lại theo cách đó:

try:
    #code here
except BaseException as e:
    if isinstance(e, Error1):
        #exception handle1

    elif isinstance(e, Error2):
        #exception handle2

    ...

    else:
        raise

Vì vậy, ngay cả nhiều khối ngoại trừ với câu lệnh vượt qua có thể dẫn đến mã, cấu trúc của nó xử lý các loại ngoại lệ đặc biệt.


4

Tất cả các ý kiến ​​đưa lên cho đến nay là hợp lệ. Nếu có thể bạn cần chỉ định chính xác ngoại lệ bạn muốn bỏ qua. Trường hợp có thể bạn cần phân tích những gì gây ra ngoại lệ, và chỉ bỏ qua những gì bạn có nghĩa là bỏ qua, và không phải phần còn lại. Nếu ngoại lệ khiến ứng dụng "sụp đổ một cách ngoạn mục" thì cũng vậy, bởi vì điều quan trọng hơn là phải biết điều bất ngờ xảy ra khi nó xảy ra, hơn là che giấu rằng vấn đề đã từng xảy ra.

Với tất cả những gì đã nói, đừng coi bất kỳ thực hành lập trình nào là tối quan trọng. Điều này thật ngu ngốc. Luôn luôn có thời gian và địa điểm để thực hiện khối bỏ qua tất cả các ngoại lệ.

Một ví dụ khác về thông số ngu ngốc là việc sử dụng gototoán tử. Khi tôi còn đi học, giáo sư của chúng tôi đã dạy chúng tôi gotođiều hành chỉ đề cập rằng bạn sẽ không sử dụng nó, EVER. Đừng tin rằng mọi người nói với bạn rằng xyz không bao giờ nên được sử dụng và không thể có kịch bản khi nó hữu ích. Luôn luôn có.


1
Trường hợp "goto" là phong cách và là một vấn đề quan điểm, trong khi "ngoại trừ: vượt qua" thường thực sự sai. Nó giả định rằng nếu ai đó đã, ví dụ, "giết -TERM" quá trình của bạn tại thời điểm đó thì nó sẽ bỏ qua nó. Ít nhất đó là hành vi xấu.
Điểm_Under

1
@Score_Under chưa có trường hợp sử dụng cái này phù hợp. Ví dụ: khi một chức năng bạn đang gọi là bổ sung, không rõ nguồn gốc / tác giả, không ảnh hưởng đến chức năng cốt lõi, nhưng nếu sự cố có thể gây ra sự cố. Tôi nhận ra rằng bạn sẽ lập luận rằng những cuộc gọi như vậy nên được nghiên cứu và phân tích đúng đắn, nhưng trong cuộc sống thực không phải lúc nào cũng có thể.
galets

Tuy nhiên, nếu tôi muốn chấm dứt quá trình của bạn, kill -9 không nên là lựa chọn đáng tin cậy duy nhất.
Điểm_Under

2

Xử lý lỗi là rất quan trọng trong lập trình. Bạn cần phải cho người dùng thấy những gì đã sai. Trong rất ít trường hợp bạn có thể bỏ qua các lỗi. Đây là nó là thực hành lập trình rất xấu.


2

Vì nó chưa được đề cập, nên sử dụng phong cách tốt hơn contextlib.suppress:

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')

Lưu ý rằng trong ví dụ được cung cấp, trạng thái chương trình vẫn giữ nguyên, cho dù ngoại lệ có xảy ra hay không. Đó là để nói, somefile.tmpluôn luôn trở thành không tồn tại.

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.