|
| 1 | +<p>This rule raises an issue when a generic exception (such as <code>Exception</code> or <code>BaseException</code>) is raised.</p> |
1 | 2 | <h2>Why is this an issue?</h2>
|
2 | 3 | <p>Raising instances of <a href="https://docs.python.org/3/library/exceptions.html#Exception"><code>Exception</code></a> and <a
|
3 | 4 | href="https://docs.python.org/3/library/exceptions.html#BaseException"><code>BaseException</code></a> will have a negative impact on any code trying
|
4 | 5 | to catch these exceptions.</p>
|
5 |
| -<p>First, the only way to handle differently multiple Exceptions is to check their message, which is error-prone and difficult to maintain.</p> |
6 |
| -<p>What’s more, it becomes difficult to catch only your exception. The best practice is to catch only exceptions which require a specific handling. |
7 |
| -When you raise <code>Exception</code> or <code>BaseException</code> in a function the caller will have to add an <code>except Exception</code> or |
8 |
| -<code>except BaseException</code> and re-raise all exceptions which were unintentionally caught. This can create tricky bugs when the caller forgets |
9 |
| -to re-raise exceptions such as <code>SystemExit</code> and the software cannot be stopped.</p> |
10 |
| -<p>It is recommended to either:</p> |
| 6 | +<p>From a consumer perspective, it is generally a best practice to only catch exceptions you intend to handle. Other exceptions should ideally not be |
| 7 | +caught and let to propagate up the stack trace so that they can be dealt with appropriately. When a generic exception is thrown, it forces consumers |
| 8 | +to catch exceptions they do not intend to handle, which they then have to re-raise.</p> |
| 9 | +<p>Besides, when working with a generic type of exception, the only way to distinguish between multiple exceptions is to check their message, which is |
| 10 | +error-prone and difficult to maintain. Legitimate exceptions may be unintentionally silenced and errors may be hidden.</p> |
| 11 | +<p>For instance, if an exception such as <code>SystemExit</code> is caught and not re-raised, it will prevent the program from stopping.</p> |
| 12 | +<p>When raising an exception, it is therefore recommended to raising the most specific exception possible so that it can be handled intentionally by |
| 13 | +consumers.</p> |
| 14 | +<h2>How to fix it</h2> |
| 15 | +<p>To fix this issue, make sure to throw specific exceptions that are relevant to the context in which they arise. It is recommended to either:</p> |
11 | 16 | <ul>
|
12 |
| - <li> raise a more specific <a href="https://docs.python.org/3/library/exceptions.html">Built-in exception</a> when one matches. For example |
| 17 | + <li> Raise a specific <a href="https://docs.python.org/3/library/exceptions.html">Built-in exception</a> when one matches. For example |
13 | 18 | <code>TypeError</code> should be raised when the type of a parameter is not the one expected. </li>
|
14 |
| - <li> create a custom exception class deriving from <code>Exception</code> or one of its subclasses. A common practice for libraries is to have one |
15 |
| - custom root exception class from which every other custom exception class inherits. It enables other projects using this library to catch all errors |
16 |
| - coming from the library with a single "except" statement </li> |
| 19 | + <li> Create a custom exception class deriving from <code>Exception</code> or one of its subclasses. </li> |
17 | 20 | </ul>
|
18 |
| -<p>This rule raises an issue when <code>Exception</code> or <code>BaseException</code> are raised.</p> |
19 |
| -<h3>Noncompliant code example</h3> |
20 |
| -<pre> |
21 |
| -def process1(): |
22 |
| - raise BaseException("Wrong user input for field X") # Noncompliant |
23 |
| - |
24 |
| -def process2(): |
25 |
| - raise BaseException("Wrong configuration") # Noncompliant |
26 |
| - |
27 |
| -def process3(param): |
28 |
| - if not isinstance(param, int): |
29 |
| - raise Exception("param should be an integer") # Noncompliant |
30 |
| - |
31 |
| -def caller(): |
32 |
| - try: |
33 |
| - process1() |
34 |
| - process2() |
35 |
| - process3() |
36 |
| - except BaseException as e: |
37 |
| - if e.args[0] == "Wrong user input for field X": |
38 |
| - # process error |
39 |
| - pass |
40 |
| - elif e.args[0] == "Wrong configuration": |
41 |
| - # process error |
42 |
| - pass |
43 |
| - else: |
44 |
| - # re-raise other exceptions |
45 |
| - raise |
| 21 | +<h3>Code examples</h3> |
| 22 | +<h4>Noncompliant code example</h4> |
| 23 | +<pre data-diff-id="1" data-diff-type="noncompliant"> |
| 24 | +def check_value(value): |
| 25 | + if value < 0: |
| 26 | + raise BaseException("Value cannot be negative") # Noncompliant: this will be difficult for consumers to handle |
46 | 27 | </pre>
|
47 |
| -<h3>Compliant solution</h3> |
48 |
| -<pre> |
49 |
| -class MyProjectError(Exception): |
50 |
| - """Exception class from which every exception in this library will derive. |
51 |
| - It enables other projects using this library to catch all errors coming |
52 |
| - from the library with a single "except" statement |
53 |
| - """ |
54 |
| - pass |
55 |
| - |
56 |
| -class BadUserInputError(MyProjectError): |
57 |
| - """A specific error""" |
58 |
| - pass |
59 |
| - |
60 |
| -class ConfigurationError(MyProjectError): |
61 |
| - """A specific error""" |
62 |
| - pass |
63 |
| - |
64 |
| -def process1(): |
65 |
| - raise BadUserInputError("Wrong user input for field X") |
66 |
| - |
67 |
| -def process2(): |
68 |
| - raise ConfigurationError("Wrong configuration") |
69 |
| - |
70 |
| -def process3(param): |
71 |
| - if not isinstance(param, int): |
72 |
| - raise TypeError("param should be an integer") |
73 |
| - |
74 |
| -def caller(): |
75 |
| - try: |
76 |
| - process1() |
77 |
| - process2() |
78 |
| - process3() |
79 |
| - except BadUserInputError as e: |
80 |
| - # process error |
81 |
| - pass |
82 |
| - except ConfigurationError as e: |
83 |
| - # process error |
84 |
| - pass |
| 28 | +<h4>Compliant solution</h4> |
| 29 | +<pre data-diff-id="1" data-diff-type="compliant"> |
| 30 | +def check_value(value): |
| 31 | + if value < 0: |
| 32 | + raise ValueError("Value cannot be negative") # Compliant |
85 | 33 | </pre>
|
86 | 34 | <h2>Resources</h2>
|
| 35 | +<h3>Documentation</h3> |
87 | 36 | <ul>
|
88 |
| - <li> PEP 352 - <a href="https://www.python.org/dev/peps/pep-0352/#exception-hierarchy-changes">Required Superclass for Exceptions</a> </li> |
89 | 37 | <li> Python Documentation - <a href="https://docs.python.org/3/library/exceptions.html#BaseException">Built-in exceptions</a> </li>
|
90 |
| - <li> <a href="https://cwe.mitre.org/data/definitions/397">MITRE, CWE-397</a> - Declaration of Throws for Generic Exception </li> |
| 38 | + <li> PEP 352 - <a href="https://www.python.org/dev/peps/pep-0352/#exception-hierarchy-changes">Required Superclass for Exceptions</a> </li> |
91 | 39 | </ul>
|
92 | 40 |
|
0 commit comments