Why is this an issue?
It is a common pattern to validate required preconditions at the beginning of a function or block. There are two different kinds of
preconditions:
- Preconditions about argument values. An example is the assertion that a function argument lies within a specific range. An
IllegalArgumentException
should be thrown if these preconditions are violated.
- Preconditions about the state of the owner or the execution context of the function. An example is when a specific method, such as
open
, init
or prepare
, must be called before the current method can be executed. An
IllegalStateException
should be thrown if these preconditions are violated.
The Kotlin standard library provides the functions check()
, require()
, checkNotNull()
and
requireNotNull()
for this purpose. They should be used instead of directly throwing an IllegalArgumentException
or an
IllegalStateException
.
What is the potential impact?
Readability and Understanding
This change makes it easier to understand the code because the semantics of check()
, require()
,
checkNotNull()
and requireNotNull()
, as well as the fact that this is a preconditions check, are evident to the reader. When
developers share common standards and idioms, they need to spend less effort understanding each other’s code.
Code Redundancy
Using a built-in language feature or a standard API is always better than a custom implementation, because the reimplementation of something that
already exists is unnecessary.
Consistency
When check()
, require()
, checkNotNull()
and requireNotNull()
are used in an idiomatic way,
there is more consistency in what kind of exception is thrown in which situation.
How to fix it
Replace |
With |
if (!condition) throw IllegalArgumentException()
|
require(condition)
|
if (reference == null) throw IllegalArgumentException()
|
requireNotNull(reference)
|
require(reference != null)
|
requireNotNull(reference)
|
if (!condition) throw IllegalStateExceptionException()
|
check(condition)
|
if (reference == null) throw IllegalStateException()
|
checkNotNull(reference)
|
check(reference != null)
|
checkNotNull(reference)
|
throw IllegalStateException()
|
error()
|
A constructor function for the exception message can be provided as an optional argument for check()
, require()
,
checkNotNull()
and requireNotNull()
. This means the message is constructed only if the exception is thrown. For the
error()
function, an optional error message parameter can be provided directly. That is, without a parameter, because an exception is
unconditionally thrown by error()
.
Code examples
Noncompliant code example
fun argumentPreconditions(argument: Int?, limit: Int?) {
if (argument == null) throw IllegalArgumentException() // Noncompliant, replace with requireNotNull
require(limit != null) // Noncompliant, replace with requireNotNull
if (argument < 0) throw IllegalArgumentException() // Noncompliant, replace with require
if (argument >= 0) throw IllegalArgumentException("Argument < $limit") // Noncompliant, replace with require
}
Compliant solution
fun argumentPreconditions(argument: Int?, limit: Int?) {
requireNotNull(argument) // Compliant
requireNotNull(limit) // Compliant
require(argument >= 0) // Compliant
require(argument < limit) {"Argument < $limit"} // Compliant
}
Noncompliant code example
fun statePreconditions() {
if (state == null) throw IllegalStateException() // Noncompliant, replace with checkNotNull
check(ioBuffer != null) // Noncompliant, replace with checkNotNull
if (state < 0) throw IllegalStateException() // Noncompliant, replace with check
if (state == 42) throw IllegalStateException("Unknown question") // Noncompliant, replace with check
when(state) {
0..10 -> processState1()
11..1000 -> processState2()
else -> throw IllegalStateException("Unexpected state $state") // Noncompliant, replace with error
}
}
Compliant solution
fun statePreconditions() {
checkNotNull(state) // Compliant
checkNotNull(ioBuffer) // Compliant
check(state >= 0) // Compliant
check(state != 42) {"Unknown question"} // Compliant
when(state) {
0..10 -> processState1()
11..1000 -> processState2()
else -> error("Unexpected state $state") // Compliant
}
}
Resources
Documentation
Articles & blog posts