detektの概要や導入については以下をご参照ください。
Kotlinの静的解析ツール「detekt」のセットアップ方法
注意
公式ページのルールを簡単に翻訳してまとめたものです。
英語に慣れている方は公式ページを直接見るのがいいです。
https://detekt.github.io/detekt/complexity.html
また、間違っている箇所や不適切な箇所がありましたら教えていただけると嬉しいです。
環境
- Kotlin:1.5.31
- Android Studio:Arctic Fox
- detekt:1.18.1
Default
デフォルトで有効になっているルールの一覧です。
Complexity Rule
ComplexCondition
複雑な条件は、どのような場合にその条件が真か偽かを理解するのが難しくなります。複雑な条件を読みやすく理解しやすくするためには、条件を適切な名前の関数や変数に抽出し、それらを代わりに呼び出すことを検討してください。
// bad
val str = "foo"
val isFoo = if (str.startsWith("foo") && !str.endsWith("foo") && !str.endsWith("bar") && !str.endsWith("_")) {
// ...
}
// good
val str = "foo"
val isFoo = if (str.startsWith("foo") && hasCorrectEnding()) {
// ...
}
fun hasCorrectEnding() = return !str.endsWith("foo") && !str.endsWith("bar") && !str.endsWith("_")
ComplexMethod
複雑なメソッドは、理解するのも読むのも大変です。複雑なメソッドがどのような副作用を持つのか、明らかではないかもしれません。複雑なメソッドは、より理解しやすい小さなメソッドに分割することをお勧めします。
このルールでは、McCabeのCyclomatic Complexity (MCC)という指標を使用して、関数のソースコード内の線形独立パスの数を測定します。独立したパスの数が多いほど、そのメソッドは複雑です。
MCC循環的複雑度について -Qiita
測定対象
・Conditional statements - if, else if, when
・Jump statements - continue, break
・Loops - for, while, do-while, forEach
・Operators &&, ||, ?:
・Exceptions - catch, use
・Scope Functions - let, run, with, apply, and also
LargeClass
このルールでは、大規模なクラスを報告します。クラスは通常、1つの責任を持つべきです。一度に多くのことをするのではなく、大きなクラスを小さなクラスに分割することをお勧めします。
デフォルトはクラス内のソースコードの行数は600以内にするのがベスト
LongMethod
メソッドは1つの責任を持つべきです。行数長いメソッドは、そのメソッドが一度に多くのケースを処理していることを示します。機能を明確に説明して分かりやすい名前付きの行数少なくて小さなメソッドをお勧めします。
行数長いメソッドの機能の一部を別の小さなメソッドに抽出します。
デフォルトはメソッド内のソースコードの行数は60以内にするのがベスト
LongParameterList
コンストラクタや関数の引数の数は少なくすべきです。
// bad
fun sign(userName: String, userId: Int, userSex: String, userMail: String, isCheck: Boolean, token: String) { }
// good
data class User (
var userName: String = "",
var userId: Int = 0,
var userSex: String = "",
var userMail: String = "",
)
fun sign(user: User, isCheck: Boolean, token: String) { }
NestedBlockDepth
最大4レベルの深さでネストすべきです。
// bad
fun f_0(type: ItemType, isCheck: Boolean) {
when (type) {
ItemType.Article -> {
if (isCheck) {
if (article.isNotLoading) {
//do something
}
}
}
}
}
// good
fun f_0(type: ItemType, isCheck: Boolean) {
when (type) {
ItemType.Article -> {
f_1(isCheck)
}
}
}
fun f_1(isCheck: Boolean) {
if (isCheck) {
if (article.isNotLoading) {
//do something
}
}
}
TooManyFunctions
ファイル、クラス、インターフェース、オブジェクト、および列挙内の関数を少なくすべき。
関数が多すぎると、単一責任原則の違反となります。明らかにコードの別の部分にまとめられている機能を抽出することをお勧めします。
デフォルト11個以内
Empty-blocks
EmptyCatchBlock
空のキャッチブロックがあります。
空のキャッチ・ブロックは、例外が無視され、処理されないことを示します。例外を意図的に無視する場合は、 allowedExceptionNameRegexに指定した名前を使用して明示する必要があります。
// bad
fun f() {
try {
//do something
} catch (e: PackageManager.NameNotFoundException) {
}
}
// good
fun f() {
try {
//do something
} catch (e: PackageManager.NameNotFoundException) {
//do something for fixing Exception
}
}
EmptyClassBlock
空のクラスがあります。
EmptyDefaultConstructor
空のデフォルトコンストラクタがあります。
EmptyDoWhileBlock
空のdo/whileループがあります。
EmptyElseBlock
空のelseブロックがあります。
EmptyFinallyBlock
空のfinallyブロックがあります。
EmptyForBlock
空のforブロックがあります。
EmptyFunctionBlock
空の関数ブロックがあります。
EmptyIfBlock
空のifブロックがあります。
EmptyInitBlock
空のinitブロックがあります。
EmptyKtFile
空のKotlinファイルがあります。
EmptySecondaryConstructor
空のセカンダリコンストラクタブロックがあります。
EmptyTryBlock
空のtryブロックがあります。
EmptyWhenBlock
空のwhenブロックがあります。
EmptyWhileBlock
空のwhileブロックがあります。
Exceptions
ExceptionRaisedInUnexpectedLocation
絶対に例外を発生させてはいけない関数内で例外をスローしてはいけない
// bad
class Foo {
override fun toString(): String {
throw IllegalStateException() // exception should not be thrown here
}
}
// good
class Foo {
override fun toString(): String {
//do something
}
}
PrintStackTrace
例外を握りつぶしてはいけない
// bad
fun foo() {
Thread.dumpStack()
}
fun bar() {
try {
// ...
} catch (e: IOException) {
e.printStackTrace()
}
}
// good
val LOGGER = Logger.getLogger()
fun bar() {
try {
// ...
} catch (e: IOException) {
LOGGER.info(e)
}
}
RethrowCaughtException
例外をキャッチされた後、修正なしで再スローしてはいけない
// bad
fun foo() {
try {
// ...
} catch (e: IOException) {
throw e
}
}
// good
fun foo() {
try {
// ...
} catch (e: IOException) {
throw MyException(e)
}
try {
// ...
} catch (e: IOException) {
print(e)
throw e
}
try {
// ...
} catch (e: IOException) {
print(e.message)
throw e
}
}
ReturnFromFinally
finallyブロックでreturn文を使用すると、tryブロックでスローされた例外を破棄されるので
finallyブロックでreturn文を使用してはいけない
// bad
fun foo() {
try {
throw MyException()
} finally {
return // prevents MyException from being propagated
}
}
// good
fun foo() {
try {
throw MyException()
} catch(e: MyException) {
// do something
}
}
SwallowedException
例外を使用しないといけない。または例外をスローすべき
// bad
fun foo() {
try {
// ...
} catch(e: IOException) {
throw MyException(e.message) // e is swallowed
}
try {
// ...
} catch(e: IOException) {
throw MyException() // e is swallowed
}
try {
// ...
} catch(e: IOException) {
bar() // exception is unused
}
}
// good
fun foo() {
try {
// ...
} catch(e: IOException) {
throw MyException(e)
}
try {
// ...
} catch(e: IOException) {
println(e) // logging is ok here
}
}
ThrowingExceptionFromFinally
finallyブロックから例外を投げることは、混乱や例外の破棄につながるため、避けるべきです。
// bad
fun foo() {
try {
// ...
} finally {
throw IOException()
}
}
// good
fun foo() {
try {
// ...
} catch (e: MyException) {
throw IOException()
}
}
ThrowingExceptionsWithoutMessageOrCause
対象例外は引数や詳細な説明なしにスローされてはいけない。
デフォルト対象
['ArrayIndexOutOfBoundsException', 'Exception', 'IllegalArgumentException', 'IllegalMonitorStateException', 'IllegalStateException', 'IndexOutOfBoundsException', 'NullPointerException', 'RuntimeException', 'Throwable']
// bad
fun foo(bar: Int) {
if (bar < 1) {
throw IllegalArgumentException()
}
// ...
}
// good
fun foo(bar: Int) {
if (bar < 1) {
throw IllegalArgumentException("bar must be greater than zero")
}
// ...
}
ThrowingNewInstanceOfSameException
例外を同じ例外タイプの中に包んで、再度スローすることはしてはいけない。
// bad
fun foo() {
try {
// ...
} catch (e: IllegalStateException) {
throw IllegalStateException(e) // rethrows the same exception
}
}
// good
fun foo() {
try {
// ...
} catch (e: IllegalStateException) {
throw MyException(e)
}
}
TooGenericExceptionCaught
現在処理されているケースに対する特定の例外をキャッチすることが望ましい
キャッチする例外の範囲が広すぎると、意図しない例外がキャッチされてしまう可能性があります
デフォルト対象
['ArrayIndexOutOfBoundsException', 'Error', 'Exception', 'IllegalMonitorStateException', 'IndexOutOfBoundsException', 'NullPointerException', 'RuntimeException', 'Throwable']
// bad
fun foo() {
try {
// ... do some I/O
} catch(e: Exception) { } // too generic exception caught here
}
// good
fun foo() {
try {
// ... do some I/O
} catch(e: IOException) { }
}
TooGenericExceptionThrown
現在発生しているケースに対して特定の例外をスローすることが望ましい
スローする例外の範囲が広すぎると、意図しない例外がキャッチされてしまう可能性があります
デフォルト対象
['Error', 'Exception', 'RuntimeException', 'Throwable']
// bad
fun foo(bar: Int) {
if (bar < 1) {
throw Exception() // too generic exception thrown here
}
// ...
}
// good
fun foo(bar: Int) {
if (bar < 1) {
throw IllegalArgumentException("bar must be greater than zero")
}
// ...
}
Naming
ClassNaming
指定された命名規則に従わないクラスやオブジェクトがあります
(default: '[A-Z][a-zA-Z0-9]*')
ConstructorParameterNaming
指定された命名規則に従わないコンストラクタのパラメータがあります
(default: '[a-z][A-Za-z0-9]*')
EnumNaming
指定された命名規則に従わないEnumがあります
(default: '[A-Z][_a-zA-Z0-9]*')
FunctionNaming
指定された命名規則に従わない関数があります
(default: '([a-z][a-zA-Z0-9]*)|(.*
)')
FunctionParameterNaming
指定された命名規則に従わない関数のパラメータがあります
(default: '[a-z][A-Za-z0-9]*')
MatchingDeclarationName
ファイルの名前は、そのファイルの中のコードが何をするかを説明するものでなければなりません。
// bad
class Foo // FooUtils.kt
fun Bar.toFoo(): Foo = ...
fun Foo.toBar(): Bar = ...
// good
class Foo { // Foo.kt
fun stuff() = 42
}
fun Bar.toFoo(): Foo = ...
MemberNameEqualsClassName
クラスまたはオブジェクトと同じ名を持つメンバー関数や変数があります。
クラスのインスタンスを作成するファクトリー関数は、このルールの対象外です。
// bad
class MethodNameEqualsClassName {
fun methodNameEqualsClassName() { }
}
class PropertyNameEqualsClassName {
val propertyEqualsClassName = 0
}
// good
class Manager {
companion object {
// factory functions can have the same name as the class
fun manager(): Manager {
return Manager()
}
}
}
ObjectPropertyNaming
指定された命名規則に従わないオブジェクト内のプロパティがあります。
constantPattern (default: '[A-Za-z][A-Za-z0-9]')
propertyPattern (default: '[A-Za-z][_A-Za-z0-9]')
private PropertyPattern (default: '()?[A-Za-z][_A-Za-z0-9]*')
PackageNaming
指定された命名規則に従わないパッケージがあります。
(default: '[a-z]+(.[a-z][A-Za-z0-9])')
TopLevelPropertyNaming
指定された命名規則に従わないトップレベルの定数があります。
constantPattern (default: '[A-Z][A-Z0-9]')
propertyPattern (default: '[A-Za-z][_A-Za-z0-9]')
private PropertyPattern (default: '?[A-Za-z][_A-Za-z0-9]*')
VariableNaming
指定された命名規則に従わない変数があります。
variablePattern (default: '[a-z][A-Za-z0-9]')
private VariablePattern (default: '(_)?[a-z][A-Za-z0-9]')
Performance
ArrayPrimitive
Arrayを使用すると、暗黙のボックス化が発生し、パフォーマンスが低下します。Kotlin に特化した Array インスタンスの使用を推奨します。
// bad
fun function(array: Array<Int>) { }
fun returningFunction(): Array<Double> { }
// good
fun function(array: IntArray) { }
fun returningFunction(): DoubleArray { }
ForEachOnRange
範囲内でforEachメソッドを使用すると、パフォーマンスが大きく低下します。シンプルなforループの使用をお勧めします。
ベンチマークによると、レンジに対してforEachを使用すると、単純なforループに比べてパフォーマンスが大幅に低下することがわかっています。
// bad
(1..10).forEach {
println(it)
}
(1 until 10).forEach {
println(it)
}
(10 downTo 1).forEach {
println(it)
}
// good
for (i in 1..10) {
println(i)
}
SpreadOperator
ほとんどの場合、拡散演算子を使用すると、メソッドを呼び出す前に、配列の完全なコピーが作成されます。
v1.1.60 以降の Kotlin コンパイラには、配列コンストラクタ関数が vararg パラメータに渡される引数の作成に使用されている場合、配列のコピーをスキップする最適化が施されています。
// bad
val strs = arrayOf("value one", "value two")
val foo = bar(*strs)
fun bar(vararg strs: String) {
strs.forEach { println(it) }
}
// good
// array copy skipped in this case since Kotlin 1.1.60
val foo = bar(*arrayOf("value one", "value two"))
// array not passed so no array copy is required
val foo2 = bar("value one", "value two")
fun bar(vararg strs: String) {
strs.forEach { println(it) }
}
UnnecessaryTemporaryInstantiation
プリミティブ型を String に変換する際、一時的なオブジェクトを使用しないようにしました。これは、プリミティブ型を直接使用する場合と比較して、パフォーマンス上のペナルティがあります。
// bad
val i = Integer(1).toString() // temporary Integer instantiation just for the conversion
// good
val i = Integer.toString(1)
Potential-bugs
DuplicateCaseInWhenExpression
when式の中に重複したcase文フラグがあります。
// bad
when (i) {
1 -> println("one")
1 -> println("one")
else -> println("else")
}
// good
when (i) {
1 -> println("one")
else -> println("else")
}
EqualsAlwaysReturnsTrueOrFalse
常にtrueまたはfalseを返すequals()メソッドがあります。
// bad
override fun equals(other: Any?): Boolean {
return true
}
// good
override fun equals(other: Any?): Boolean {
return this === other
}
EqualsWithHashCodeExist
クラスがequals()メソッドをオーバーライドする際には、hashCode()メソッドもオーバーライドする必要があります。
// bad
class Foo {
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
}
// good
class Foo {
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
}
ExplicitGarbageCollectionCall
ガベージコレクタを明示的に呼び出す必要はありません。
ImplicitDefaultLocale
文字列のフォーマットや大文字小文字の変換を行う際に、暗黙のデフォルト値を使用するよりも、[java.util.Locale]を明示的に渡すことをお勧めします。
デフォルトのロケールは、ほとんどの場合、HTTPヘッダのような機械で読めるテキストには不適切です。
// bad
String.format("Timestamp: %d", System.currentTimeMillis())
val str: String = getString()
str.toUpperCase()
str.toLowerCase()
// good
String.format(Locale.US, "Timestamp: %d", System.currentTimeMillis())
val str: String = getString()
str.toUpperCase(Locale.US)
str.toLowerCase(Locale.US)
InvalidRange
無効な範囲があります。
// bad
for (i in 2..1) {}
for (i in 1 downTo 2) {}
val range1 = 2 until 1
val range2 = 2 until 2
// good
for (i in 2..2) {}
for (i in 2 downTo 2) {}
val range = 2 until 3
IteratorHasNextCallsNextMethod
IteratorインターフェースのhasNext()メソッド内でnextメソッドを呼び出してはいけない。
// bad
class MyIterator : Iterator<String> {
override fun hasNext(): Boolean {
return next() != null
}
}
// good
class MyIterator : Iterator<String> {
override fun hasNext(): Boolean {
return // ...
}
override fun next(): String {
return // ...
}
}
IteratorNotThrowingNoSuchElementException
next()メソッドの実装で返すべき要素がなくなったときに NoSuchElementException を投げるべきです。
// bad
class MyIterator : Iterator<String> {
override fun next(): String {
return ""
}
}
// good
class MyIterator : Iterator<String> {
override fun next(): String {
if (!this.hasNext()) {
throw NoSuchElementException()
}
// ...
}
}
RedundantElseInWhen
冗長なelseケースを含むwhen式があります。
// bad
enum class Color {
RED,
GREEN,
BLUE
}
fun whenOnEnumFail(c: Color) {
when(c) {
Color.BLUE -> {}
Color.GREEN -> {}
Color.RED -> {}
else -> {} // no use
}
}
// good
enum class Color {
RED,
GREEN,
BLUE
}
fun whenOnEnumCompliant(c: Color) {
when(c) {
Color.BLUE -> {}
Color.GREEN -> {}
else -> {}
}
}
fun whenOnEnumCompliant2(c: Color) {
when(c) {
Color.BLUE -> {}
Color.GREEN -> {}
Color.RED -> {}
}
}
UnnecessaryNotNullOperator
不要なnot-null演算子(!!)の使用があります。
// bad
val a = 1
val b = a!!
// good
val a = 1
val b = a
UnnecessarySafeCall
不要なセーフコール演算子(?)使用があります。
// bad
val a: String = ""
val b = a?.length
// good
val a: String? = null
val b = a?.length
UnreachableCode
呼び出されないコードがあります。
// bad
for (i in 1..2) {
break
println() // unreachable
}
throw IllegalArgumentException()
println() // unreachable
fun f() {
return
println() // unreachable
}
// good
for (i in 1..2) {
println()
break
}
println()
throw IllegalArgumentException()
fun f() {
println()
return
}
UnsafeCallOnNullableType
nullable型に対する安全でない呼び出しがあります。
// bad
fun foo(str: String?) {
println(str!!.length)
}
// good
fun foo(str: String?) {
println(str?.length)
}
UnsafeCast
絶対に成功しないキャストがあります。
// bad
fun foo(s: String) {
println(s as Int)
}
fun bar(s: String) {
println(s as? Int)
}
// good
fun foo(s: Any) {
println(s as Int)
}
WrongEqualsTypeParameter
誤って型付けされたパラメータを取り込む equals() メソッドがあります。
// bad
class Foo {
fun equals(other: String): Boolean {
return super.equals(other)
}
}
// good
class Foo {
fun equals(other: Any?): Boolean {
return super.equals(other)
}
}
Style
EqualsNullCall
オブジェクトをnullと比較するには、==を使用することをお勧めします。
// bad
fun isNull(str: String) = str.equals(null)
// good
fun isNull(str: String) = str == null
ForbiddenComment
開発時にのみ使用されるべきコメントがあります。
// bad
val a = "" // TODO: remove please
// FIXME: this is a hack
fun foo() { }
// STOPSHIP:
ForbiddenPublicDataClass
データクラスは、パブリックAPIのバイナリ互換性に悪影響を及ぼします。使用を避けてください。
このルールはライブラリのメンテナに向けたものです。もしあなたが最終的なアプリケーションを開発しているのであれば、この問題は無視して構いません。
// bad
data class C(val a: String) // violation: public data class
// good
internal data class C(val a: String)
FunctionOnlyReturningConstant
1つの定数を返すだけの関数があります。const valを使用すべき。
// bad
fun functionReturningConstantString() = "1"
// good
const val constantString = "1"
LibraryCodeMustSpecifyReturnType
ライブラリのパブリックAPIとして公開される関数やプロパティには、明示的な戻り値の型が必要です。
// bad
// code from a library
val strs = listOf("foo, bar")
fun bar() = 5
class Parser {
fun parse() = ...
}
// good
// code from a library
val strs: List<String> = listOf("foo, bar")
fun bar(): Int = 5
class Parser {
fun parse(): ParsingResult = ...
}
LibraryEntitiesShouldNotBePublic
ライブラリのタイプエイリアスとクラスは、internalまたはprivateを使用すべき
// bad
// code from a library
class A
// good
// code from a library
internal class A
LoopWithTooManyJumpStatements
複数のbreakまたはcontinueステートメントを含むループは、読んで理解しにくい。
// bad
val strs = listOf("foo, bar")
for (str in strs) {
if (str == "bar") {
break
} else {
continue
}
}
MagicNumber
マジックナンバーの使用があります。
// bad
class User {
fun checkName(name: String) {
if (name.length > 42) {
throw IllegalArgumentException("username is too long")
}
// ...
}
}
// good
class User {
fun checkName(name: String) {
if (name.length > MAX_USERNAME_SIZE) {
throw IllegalArgumentException("username is too long")
}
// ...
}
companion object {
private const val MAX_USERNAME_SIZE = 42
}
}
MaxLineLength
定義された最大行長を超えるコードがあります。
(default: 120行)
MayBeConst
const val を使用する可能性のあるプロパティ (val) があります。
const valを使用すると、生成されるバイトコードのパフォーマンスが向上するだけでなく、Javaとの相互運用性も向上します。
// bad
val myConstant = "abc"
// good
const val MY_CONSTANT = "abc"
}
ModifierOrder
正しい順序でない修飾語があります。
// bad
lateinit internal val str: String
// good
internal lateinit val str: String
}
NestedClassesVisibility
ネストクラスが親クラスよりアクセスレベル高い修飾語の使用があります。
// bad
internal class Outer {
// explicit public modifier still results in an internal nested class
public class Nested
}
// good
internal class Outer {
class Nested1
internal class Nested2
}
NewLineAtEndOfFile
ファイル末尾に改行を入れるべき
実際に「C, C++」では、改行なしのソースファイルだと未定義扱いになり誤作動を引き起こす、とのこと。
OptionalAbstractKeyword
不要で削除可能な抽象的な修飾語があります。
// bad
abstract interface Foo { // abstract keyword not needed
abstract fun x() // abstract keyword not needed
abstract var y: Int // abstract keyword not needed
}
// good
interface Foo {
fun x()
var y: Int
}
ProtectedMemberInFinalClass
Kotlin のクラスはデフォルトで final です。したがって、open と protected メンバを含むべきではありません。代わりに private や internal を使うべき
// bad
class ProtectedMemberInFinalClass {
protected var i = 0
}
// good
class ProtectedMemberInFinalClass {
private var i = 0
}
ReturnCount
メソッド内で使用できるreturnの数を制限する
(default: 2)
// bad
fun foo(i: Int): String {
when (i) {
1 -> return "one"
2 -> return "two"
else -> return "other"
}
}
// good
fun foo(i: Int): String {
return when (i) {
1 -> "one"
2 -> "two"
else -> "other"
}
}
SafeCast
このルールはキャストを検査し、代わりに安全なキャストに置き換えることができる
// bad
fun numberMagic(number: Number) {
val i = if (number is Int) number else null
// ...
}
// good
fun numberMagic(number: Number) {
val i = number as? Int
// ...
}
SerialVersionUIDInSerializableClass
Serializableインターフェースを実装しているクラスは、serialVersionUIDも正しく宣言する必要があります
// bad
class IncorrectSerializable : Serializable {
companion object {
val serialVersionUID = 1 // wrong declaration for UID
}
}
// good
class CorrectSerializable : Serializable {
companion object {
const val serialVersionUID = 1L
}
}
ThrowsCount
メソッド内で使用できるthrowの数を制限する
(default: 2)
// bad
fun foo(i: Int) {
when (i) {
1 -> throw IllegalArgumentException()
2 -> throw IllegalArgumentException()
3 -> throw IllegalArgumentException()
}
}
// good
fun foo(i: Int) {
when (i) {
1,2,3 -> throw IllegalArgumentException()
}
}
UnnecessaryAbstractClass
抽象クラスが具象メンバーを持たない場合は、インターフェイスにリファクタリングする必要があります。
抽象的なメンバーを定義していない抽象クラスは、代わりに具象クラスにリファクタリングする必要があります。
// bad
abstract class OnlyAbstractMembersInAbstractClass { // violation: no concrete members
abstract val i: Int
abstract fun f()
}
abstract class OnlyConcreteMembersInAbstractClass { // violation: no abstract members
val i: Int = 0
fun f() { }
}
UnnecessaryApply
apply式は頻繁に使用されますが、見た目の複雑さを軽減するために、通常のメソッドや拡張機能の呼び出しに置き換えるべき。
// bad
config.apply { version = "1.2" } // can be replaced with `config.version = "1.2"`
config?.apply { environment = "test" } // can be replaced with `config?.environment = "test"`
config?.apply { println(version) } // `apply` can be replaced by `let`
// good
config.apply {
version = "1.2"
environment = "test"
}
UnnecessaryInheritance
不要なスーパータイプあります。
AnyやObjectからの継承は不要
// bad
class A : Any()
class B : Object()
// good
class A
class B
UnusedPrivateClass
使用されていないプライベートクラスがあります。
UnusedPrivateMember
使用されていないプライベートプロパティ、関数パラメータがあります。
UselessCallOnNotNull
Kotlin stdlibには、NULLの可能性がある参照を操作するために設計された関数がいくつか用意されています。
// bad
val testList = listOf("string").orEmpty()
val testList2 = listOf("string").orEmpty().map { _ }
val testList3 = listOfNotNull("string")
val testString = ""?.isNullOrBlank()
// good
val testList = listOf("string")
val testList2 = listOf("string").map { }
val testList3 = listOf("string")
val testString = ""?.isBlank()
UtilityClassWithPublicConstructor
具体的な実装を伴わないユーティリティー変数や関数のみを含むクラスは、オブジェクトや非公開のコンストラクタを持つクラスにリファクタリングすることができます。
// bad
class UtilityClassViolation {
// public constructor here
constructor() {
// ...
}
companion object {
val i = 0
}
}
open class UtilityClassViolation private constructor() {
// ...
}
// good
class UtilityClass {
private constructor() {
// ...
}
companion object {
val i = 0
}
}
object UtilityClass {
val i = 0
}
VarCouldBeVal
var宣言は、再割り当てされないため、valにすべき
// bad
fun example() {
var i = 1 // violation: this variable is never re-assigned
val j = i + 1
}
// good
fun example() {
val i = 1
val j = i + 1
}
WildcardImport
ワイルドカードによるインポートは、完全修飾クラス名を用いたインポートに置き換えるべきです。これにより、どのクラスがインポートされているかが明確になり、ネーミングコンフリクトを防ぐことができます。
// bad
import io.gitlab.arturbosch.detekt.*
class DetektElements {
val element1 = DetektElement1()
val element2 = DetektElement2()
}
// good
import io.gitlab.arturbosch.detekt.DetektElement1
import io.gitlab.arturbosch.detekt.DetektElement2
class DetektElements {
val element1 = DetektElement1()
val element2 = DetektElement2()
}