139 lines
5.0 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

== 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 Kotlins Require, Check and Assert]