- 37. Do not shadow or obscure identifiers in subscopes
- 38. Do not declare more than one variable per declaration
- 39. Use meaningful symbolic constants to represent literal values in program logic
- 40. Properly encode relationships in constant definitions
- 41. Return an empty array or collection instead of a null value for methods that return an array or collection
- 42. Use exceptions only for exceptional conditions
- 43. Use a try-with-resources statement to safely handle closeable resources
- 44. Do not use assertions to verify the absence of runtime errors
- 45. Use the same type for the second and third operands in conditional expressions
- 46. Do not serialize direct handles to system resources
- 47. Prefer using iterators over enumerations
- 48. Do not use direct buffers for short-lived, infrequently used objects
- 49. Remove short-lived objects from long-lived container objects
42. Use exceptions only for exceptional conditions
Exceptions should be used only to denote exceptional conditions; they should not be used for ordinary control flow purposes. Catching a generic object such as Throwable is likely to catch unexpected errors; see The CERT® Oracle® Secure Coding Standard for Java™ [Long 2012] ERR08-J, “Do not catch NullPointerException or any of its ancestors,” for examples. When a program catches a specific type of exception, it does not always know from where that exception was thrown. Using a catch clause to handle an exception that occurs in a distant known location is a poor solution; it is preferable to handle the error as soon as it occurs or to prevent it, if possible.
The nonlocality of throw statements and corresponding catch statements can also impede optimizers from improving code that relies on exception handling. Relying on catching exceptions for control flow also complicates debugging, because exceptions indicate a jump in control flow from the throw statement to the catch clause. Finally, exceptions need not be highly optimized as it is assumed that they are thrown only in exceptional circumstances. Throwing and catching an exception frequently has worse performance than handling the error with some other mechanism.
Noncompliant Code Example
This noncompliant code example attempts to concatenate the processed elements of the strings array:
public String processSingleString(String string) { // ... return string; } public String processStrings(String[] strings) { String result = ""; int i = 0; try { while (true) { result = result.concat(processSingleString(strings[i])); i++; } } catch (ArrayIndexOutOfBoundsException e) { // Ignore, we're done } return result; }
This code uses an ArrayIndexOutOfBoundsException to detect the end of the array. Unfortunately, because ArrayIndexOutOfBoundsException is a RuntimeException, it could be thrown by processSingleString() without being declared in a throws clause. So it is possible for processStrings() to terminate prematurely before processing all of the strings.
Compliant Solution
This compliant solution uses a standard for loop to concatenate the strings.
public String processStrings(String[] strings) { String result = ""; for (int i = 0; i < strings.length; i++) { result = result.concat(processSingleString(strings[i])); } return result; }
This code need not catch ArrayIndexOutOfBoundsException because it is a runtime exception, and such exceptions indicate programmer errors, which are best resolved by fixing the defect.
Applicability
Use of exceptions for any purpose other than detecting and handling exceptional conditions complicates program analysis and debugging, degrades performance, and can increase maintenance costs.
Bibliography
[Bloch 2001] |
Item 39, “Use Exceptions Only for Exceptional Conditions” |
[JLS 2013] |
Chapter 11, “Exceptions” |
[Long 2012] |
ERR08-J. Do not catch NullPointerException or any of its ancestors |