Skip to main content

ERR01-G: Do Not Throw Exceptions That Are Superclasses of Other Exceptions That a Method May Throw


Introduction

Throwing the superclass of an exception may seem like a convenient way to handle various errors at once, but it can create challenges. While this approach can enhance code robustness, it also introduces complexities during troubleshooting. Throwing root exception classes such as RuntimeException, Exception, or Throwable can remove the ability to determine why the was thrown, making problem diagnosis difficult or even impossible. The higher up in the exception hierarchy you catch the exception, the more obscured information you encounter, leading to frustration and wasted time in error identification.

Lastly, apart from AssertionError, you should refrain from defining or throwing any subclasses of Error class. Doing so could introduce unpredictable behaviour and complicate error-handling procedures.

Non-Compliant Code Example

The isName function in the following non-compliant code example accepts a string and returns "true" if the string consists of two substrings separated by white space, each starting with an uppercase letter. The method also throws a RuntimeException when passing a null string argument.

// Non-compliant Code Example

function isName(s: String) : boolean {
if (s == null) throw new RuntimeException("Null String")
var names : String[] = s.split(" ")
if (names.length != 2) return false
return
(Character.isUpperCase(names[0].codePointAt(0)) &&
Character.isUpperCase(names[1].codePointAt(0)))
}

Compliant Solution

The following compliant solution throws NullPointerException to denote the specific exceptional condition:

// Compliant Solution

function isName(s : String) : boolean {
if (s == null) throw new NullPointerException()
var names : String[] = s.split(" ")
if (names.length != 2) return false
return
(Character.isUpperCase(names[0].codePointAt(0)) &&
Character.isUpperCase(names[1].codePointAt(0)))
}

The null check is redundant; if removed, the subsequent call to s.equals("") would throw a NullPointerException when s is null. More complex code may require explicit testing of invariants and appropriate throw statements.

Compliant Solution

This compliant solution checks that s is not a null pointer by calling the Objects.requireNonNull method:

// Compliant Solution

function isName(s : String) : boolean {
Objects.requireNonNull(s, "s cannot be null!")
var names : String[] = s.split(" ")
if (names.length != 2) return false
return
(Character.isUpperCase(names[0].codePointAt(0)) &&
Character.isUpperCase(names[1].codePointAt(0)))
}

The Objects.requireNonNull method throws NullPointerException if s is a null pointer.

Compliant Solution

This compliant solution checks simply omits an explicit null pointer check:

// Compliant Solution

function isName(s : String) : boolean {
var names : String[] = s.split(" ")
print("s = " + s)
if (names.length != 2) return false
return
(Character.isUpperCase(names[0].codePointAt(0)) &&
Character.isUpperCase(names[1].codePointAt(0)))
}

This solution is acceptable, provided the program fails and no state data is changed before the exception is thrown.

Non-Compliant Code Example

You can pass a non-exception object to the throw statement. If you pass a non-exception object, Gosu first coerces the object to a String. Next, Gosu wraps the String in a new RuntimeException with a message property containing the String data. Your error-handling code can use the message property for logging or displaying messages to users.

For example, the following code will wrap the string literal "User is not allowed in the bar" in a String object that is used to throw a new RuntimeException:

// Non-compliant Code Example

if (user.Age < 21) {
throw "User is not allowed in the bar"
}

Compliant Solution

This compliant solution throws an IllegalArgumentException, which indicates that a method has been passed using an illegal or inappropriate argument:

// Compliant Solution

if (user.Age < 21) {
throw new IllegalArgumentException("User is not allowed in the bar")
}

Providing direct access to the array objects themselves is safe because String is immutable.

Exceptions

ERR01-G-EX0: Classes that catch and partially handle exceptions may translate specific exceptions into more general exceptions. This translation could potentially result in throwing RuntimeException, Exception, or Throwable exceptions in some cases.

Risk Assessment

Throwing RuntimeException, Exception, or Throwable exceptions prevents classes from catching the intended exceptions without catching unintended exceptions as well.

RuleSeverityLikelihoodRemediation CostPriorityLevel
ERR01-GLowHighly LikelyMediumL6L2

Additional Resources