The original article was published on March 6, 2025.
Hello, I'm Munetoshi Ishikawa, a mobile client developer for the LINE messaging app.
This article is the latest installment of our weekly series "Improving code quality". For more information about the Weekly Report, please see the first article.
This function is for one person only
The following calculateSomeFormula is a function that returns the result of a long-running calculation. This function assumes thread safety to perform multiple calculations concurrently. (In Kotlin, coroutines are usually used for concurrency control, but here we assume that coroutines cannot be used due to language or platform restrictions on the caller side.)
class FormulaCalculator {
fun calculateSomeFormula(x: Int): Int {
... // Takes long time to calculate
}
}
Furthermore, let's say you want to display whether a calculation is in progress on the UI. The following code attempts to achieve this by adding a property called isCalculating.
class FormulaCalculator {
val isCalculating: Boolean get() = calculationState.get()
private val calculationState: AtomicBoolean = AtomicBoolean()
fun calculateSomeFormula(x: Int): Int {
calculationState.set(true)
return try {
... // Takes long time to calculate
} finally {
calculationState.set(false)
}
}
}
Is there any problem with this code?
State lending
isCalculating does not correctly indicate the "calculating" state. Let's assume that calculateSomeFormula(x = 42) and calculateSomeFormula(x = 43) are called simultaneously. In this case, it may be executed in the following order:
x = 42callscalculateSomeFormulax = 42setscalculationStatetotruex = 43callscalculateSomeFormulax = 43updates the value ofcalculationState, buttrueis maintained- The calculation for
x = 42completes x = 42setscalculationStatetofalse
At the point when step 6 is completed, isCalculating becomes false even though the calculation for x = 43 is still in progress.
This issue arises because the function call state is treated as the state of a single receiver. Since multiple function calls can be made to one receiver, it is necessary to distinguish individual calls.
Here, we will explain methods using return values and separating the receiver's state.
Option 1: Include call state in the function's return value
Since whether a calculation is in progress is a state associated with a function call, expressing the state in the return value allows for a 1:1 relationship. A typical method is to return a promise or future object like CompletableFuture or Deferred, from which you can obtain whether a calculation is in progress. In the following SomeFuture, you can check isFinished to individually determine whether a calculation is in progress.
class SomeFuture<T>(action: () -> T) {
...
fun isFinished(): Boolean { ... }
fun onFinished(subscriber: (T) -> Unit)
}
class FormulaCalculator {
...
fun calculateSomeFormula(x: Int): SomeFuture<Int> =
SomeFuture {
... // Takes long time to calculate
}
}
Option 2: Separate the receiver's state for each function call
Depending on the specification, you may want to manage it as the receiver's state rather than the state of individual functions to display "whether there is any ongoing calculation". In that case, instead of a simple Boolean, it is better to use a value that can distinguish individual function calls. For example, you can create an object for each function call and manage it with a set or map, or count the ongoing calls. In this case, simply counting the ongoing calls as shown below is sufficient.
class FormulaCalculator {
val isCalculating: Boolean get() = ongoingCalculationCount.get() > 0
private val ongoingCalculationCount: AtomicInteger = AtomicInteger()
fun calculateSomeFormula(x: Int): Int {
ongoingCalculationCount.incrementAndGet()
return try {
... // Takes long time to calculate
} finally {
ongoingCalculationCount.decrementAndGet()
}
}
}
State lending even without concurrency
Even if a function is executed in a single thread, caution is needed if there is a possibility of recursive calls or interruptions. In the following code, calculateSomeFormula is called recursively, so isCalculating becomes false even though the function has not completed.
class FormulaCalculator {
val isCalculating = false
private set
fun calculateSomeFormula(x: Int): Int {
isCalculating = true
if (x > 0)
val z = calculateAnother(y)
...
}
isCalculating = false
}
private fun calculateAnother(y: Int): Int {
...
calculateSomeFormula(value)
}
}
When defining or reviewing a function, it is necessary to check whether there is a possibility of the function being executed again during its execution, and if there is a possibility, verify that it operates correctly.
In a nutshell
When you need the state of a function call, make sure you can distinguish individual calls.
Keywords: instance state, call state, re-entrancy