139 lines
5.0 KiB
Plaintext
139 lines
5.0 KiB
Plaintext
![]() |
== 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
|
|||
|
|
|||
|
[cols="3,2"]
|
|||
|
|===
|
|||
|
| **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
|
|||
|
|
|||
|
[source,kotlin,diff-id=1,diff-type=noncompliant]
|
|||
|
----
|
|||
|
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
|
|||
|
|
|||
|
[source,kotlin,diff-id=1,diff-type=compliant]
|
|||
|
----
|
|||
|
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
|
|||
|
|
|||
|
[source,kotlin,diff-id=2,diff-type=noncompliant]
|
|||
|
----
|
|||
|
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
|
|||
|
|
|||
|
[source,kotlin,diff-id=2,diff-type=compliant]
|
|||
|
----
|
|||
|
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
|
|||
|
|
|||
|
* https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/require.html[Kotlin API Docs, require]
|
|||
|
* https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/check.html[Kotlin API Docs, check]
|
|||
|
|
|||
|
=== Articles & blog posts
|
|||
|
|
|||
|
* https://bignerdranch.com/blog/write-better-code-using-kotlins-require-check-and-assert/[Jeremy W. Sherman, Write Better Code Using Kotlin’s Require, Check and Assert]
|