LY Corporation Tech Blog

We are promoting the technology and development culture that supports the services of LY Corporation and LY Corporation Group (LINE Plus, LINE Taiwan and LINE Vietnam).

This post is also available in the following languages. Japanese

Improving code quality - Session 61: This function is for one person only

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:

  1. x = 42 calls calculateSomeFormula
  2. x = 42 sets calculationState to true
  3. x = 43 calls calculateSomeFormula
  4. x = 43 updates the value of calculationState, but true is maintained
  5. The calculation for x = 42 completes
  6. x = 42 sets calculationState to false

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

List of articles on techniques for improving code quality