사용하지 않는 반환값 검사기(Unused return value checker)
사용하지 않는 반환값 검사기(Unused return value checker)
이 기능은 향후 Kotlin 릴리스에서 안정화되고 개선될 예정이에요. 이슈 트래커 YouTrack에서 피드백을 주시면 감사하겠어요. 관련 KEEP 제안에서 더 자세한 내용을 볼 수 있어요.
본문
사용하지 않는 반환값 검사기(unused return value checker)는 무시된 결과(ignored results)를 감지할 수 있게 해줘요. 이는 Unit, Nothing, Nothing?이 아닌 것을 생성하는 표현식에서 반환되면서도 다음이 아닌 값들을 말해요:
- 변수나 프로퍼티에 저장된 값.
- 반환되거나 throw된 값.
- 다른 함수의 인자로 전달된 값.
- 호출이나 안전 호출에서 리시버로 사용된 값.
if,when,while같은 조건에서 검사된 값.- 람다의 마지막 문장으로 사용된 값.
이 검사기는 ++, -- 같은 증가 연산의 무시된 결과나, condition || return처럼 오른쪽이 현재 함수를 종료하는 불리언 숏컷에 대해서는 보고하지 않아요.
사용하지 않는 반환값 검사기를 활용하면 함수 호출이 의미 있는 결과를 만들어 내지만 그 결과가 조용히 버려지는 버그를 잡을 수 있어요. 이는 예상치 못한 동작을 방지하고 그런 문제를 추적하기 쉽게 만들어 줘요.
다음은 문자열이 생성됐지만 결코 사용되지 않아 검사기가 무시된 결과로 보고하는 예시예요:
fun formatGreeting(name: String): String {
if (name.isBlank()) return "Hello, anonymous user!"
if (!name.contains(' ')) {
// The checker reports a warning that this result is ignored:
// "Unused return value of 'plus'."
"Hello, " + name.replaceFirstChar(Char::titlecase) + "!"
}
val (first, last) = name.split(' ')
return "Hello, $first! Or should I call you Dr. $last?"
}
사용하지 않는 반환값 검사기 구성하기(Configure the unused return value checker)
컴파일러가 무시된 결과를 보고하는 방식을 -Xreturn-value-checker 컴파일러 옵션으로 제어할 수 있어요. 다음 모드를 가지고 있어요:
disable: 사용하지 않는 반환값 검사기를 비활성화해요(기본값).check: 검사기를 활성화하고, 표시된 함수의 무시된 결과에 대해 경고를 보고해요.full: 검사기를 활성화하고, 프로젝트의 모든 함수를 표시된 것으로 취급한 뒤 무시된 결과에 대해 경고를 보고해요.
모든 표시된 함수는 그렇게 전파되며, 여러분의 코드를 의존성으로 사용하는 프로젝트에서 검사기가 활성화되어 있으면 무시된 결과가 보고돼요.
프로젝트에서 사용하지 않는 반환값 검사기를 사용하려면 빌드 구성 파일에 컴파일러 옵션을 추가해요:
// build.gradle(.kts)
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xreturn-value-checker=check")
}
}
<!-- pom.xml -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
..
<configuration>
<args>
<arg>-Xreturn-value-checker=check</arg>
</args>
</configuration>
</plugin>
무시된 결과를 검사할 함수 표시하기(Mark functions to check ignored results)
-Xreturn-value-checker 컴파일러 옵션을 check로 설정하면, 검사기는 Kotlin 표준 라이브러리의 대부분의 함수처럼 표시된(marked) 표현식에서만 무시된 결과를 보고해요.
자신의 코드를 표시하려면 @MustUseReturnValues 애노테이션을 사용해요. 검사기가 다루길 원하는 범위에 따라 파일, 클래스, 함수에 적용할 수 있어요.
예를 들어 파일 전체를 표시할 수 있어요:
// Marks all functions and classes in this file so the checker reports unused return values
@file:MustUseReturnValues
package my.project
fun someFunction(): String
또는 특정 클래스를 표시할 수 있어요:
// Marks all functions in this class so the checker reports unused return values
@MustUseReturnValues
class Greeter {
fun greet(name: String): String = "Hello, $name"
}
fun someFunction(): Int = ...
-Xreturn-value-checker 컴파일러 옵션을 full로 설정하면 검사기를 프로젝트 전체에 적용할 수 있어요. 이 옵션을 쓰면 코드에 @MustUseReturnValues를 주석으로 달 필요가 없어요.
무시된 결과에 대한 보고 억제하기(Suppress reports for ignored results)
특정 함수에 @IgnorableReturnValue를 주석으로 달면 해당 함수에 대한 보고를 억제할 수 있어요. 결과를 무시하는 것이 일반적이고 예상되는 함수, 예를 들어 MutableList.add 같은 함수에 주석을 달아요:
@IgnorableReturnValue
fun <T> MutableList<T>.addAndIgnoreResult(element: T): Boolean {
return add(element)
}
함수 자체에 주석을 달지 않고도 경고를 억제할 수 있어요. 이를 위해 결과를 밑줄 구문(_)이 있는 특수한 이름 없는 변수에 할당하면 돼요:
// Non-ignorable function
fun computeValue(): Int = 42
fun main() {
// Reports a warning: result is ignored
computeValue()
// Suppresses the warning only at this call site with a special unused variable
val _ = computeValue()
}
함수 오버라이드에서의 무시된 결과(Ignored results in function overrides)
함수를 오버라이드하면 오버라이드는 기본 선언의 애노테이션이 정의한 보고 규칙을 상속해요. 이는 기본 선언이 Kotlin 표준 라이브러리나 다른 라이브러리 의존성의 일부일 때도 적용되므로, 검사기는 Any.hashCode() 같은 함수의 오버라이드에 대해서도 무시된 결과를 보고해요.
또한 @IgnorableReturnValue로 표시된 함수를 반환값 사용을 요구하는 다른 함수로 오버라이드할 수 없어요. 하지만 @MustUseReturnValues로 주석 처리된 클래스나 인터페이스에서는, 그 결과를 안전하게 무시할 수 있을 때 오버라이드를 @IgnorableReturnValue로 표시할 수 있어요:
@MustUseReturnValues
interface Greeter {
fun greet(name: String): String
}
object SilentGreeter : Greeter {
@IgnorableReturnValue
override fun greet(name: String): String = ""
}
fun check(g: Greeter) {
// Reports a warning: unused return value
g.greet("John")
// No warning
SilentGreeter.greet("John")
}
고차 함수에서 사용되지 않는 결과 확인하기(Check for unused results in higher-order functions)
let 스코프 함수 같은 일부 고차 함수는 람다의 결과를 반환해요. 고차 함수의 사용되지 않는 람다 결과를 확인하려면 함수의 contract에 Experimental 상태인 returnsResultOf() contract를 추가해요.
Kotlin contracts는 Experimental이에요. 옵트인하려면 contract가 있는 함수를 선언할 때 @OptIn(ExperimentalContracts::class) 애노테이션을 추가해요.
예시를 볼게요:
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@OptIn(ExperimentalContracts::class)
inline fun <T, R> T.customLet(block: (T) -> R): R {
contract {
returnsResultOf(block)
}
return block(this)
}
그런 다음 .customLet() 같은 이 contract가 있는 함수를 사용해 람다 결과가 사용되는지 확인할 수 있어요:
fun handleNullablePackageName(packageName: String?, builder: StringBuilder) {
// The checker doesn't report a warning because the return value of append() can be ignored
packageName?.customLet { builder.append(it) }
// The checker reports a warning because the returned string is unused
packageName?.customLet { "kotlin.$it" }
}
returnsResultOf() contract는 옵트인을 위해 별도의 컴파일러 옵션이 필요해요. 이를 사용하면 2.4.0보다 이전 버전의 Kotlin 컴파일러가 읽을 수 없는 프리릴리스 바이너리가 생성된다는 점을 알아두세요.
프로젝트에서 옵트인하려면 빌드 파일에 다음 컴파일러 옵션을 추가해요:
// build.gradle(.kts)
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xallow-returns-result-of")
}
}
<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xallow-returns-result-of</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
Java 애노테이션과의 상호운용성(Interoperability with Java annotations)
일부 Java 라이브러리는 다른 애노테이션으로 비슷한 메커니즘을 사용해요. 사용하지 않는 반환값 검사기는 다음 애노테이션들을 @MustUseReturnValues를 사용하는 것과 동등하게 취급해요:
com.google.errorprone.annotations.CheckReturnValueedu.umd.cs.findbugs.annotations.CheckReturnValueorg.jetbrains.annotations.CheckReturnValueorg.springframework.lang.CheckReturnValueorg.jooq.CheckReturnValue
또한 com.google.errorprone.annotations.CanIgnoreReturnValue를 @IgnorableReturnValue를 사용하는 것과 동등하게 취급해요.