Compiler menangkap kesalahan sintaks dan tipe, tetapi banyak masalah kualitas kode tidak terdeteksi pada level compiler. Variabel var yang sebenarnya tidak perlu mutable, baris terlalu panjang, fungsi terlalu kompleks, atau blok catch kosong adalah contoh masalah yang tidak membuat kompilasi gagal namun mengurangi kualitas kode. Static analysis memeriksa kode sumber tanpa menjalankannya untuk menemukan pola-pola tersebut. Pada chapter ini kita akan memahami mengapa static analysis penting, alat utama di ekosistem Kotlin, dan cara mengintegrasikannya ke dalam CI/CD.
Mengapa Static Analysis Penting #
Static analysis bekerja pada level kode sumber, bukan runtime. Hal ini memberi beberapa keuntungan:
- Cepat: pemeriksaan berlangsung dalam hitungan detik, tidak perlu setup runtime.
- Deterministik: tidak ada masalah timing atau environment.
- Dapat diotomatisasi: dijalankan pada setiap commit, sebelum code review manual.
- Tidak bergantung test coverage: menemukan masalah bahkan pada kode yang belum tertest.
Tiga Alat Utama Ekosistem Kotlin #
| Alat | Fokus | Contoh aturan |
|---|---|---|
| detekt | Code smell, kompleksitas, potensi bug | LongMethod, ComplexCondition, TooGenericExceptionCaught |
| ktlint | Style dan konvensi | Indent, no-semicolons, max-line-length |
| IntelliJ inspections | Varietas luas, integrasi IDE | Unused variable, redundant cast, parameter shadowing |
Demo Static Analysis Sederhana #
Berikut implementasi sederhana yang mensimulasikan cara static analyzer memindai kode sumber. Mesin ini menerima kode sebagai string dan melaporkan pelanggaran aturan.
// Demo konsep static analysis: memeriksa kode tanpa menjalankannya.
// Simulasi sederhana dari beberapa aturan detekt/ktlint/IntelliJ inspections.
data class Pelanggaran(
val aturan: String,
val severity: String,
val file: String,
val baris: Int,
val pesan: String
)
class LaporanStatik {
private val temuan = mutableListOf<Pelanggaran>()
fun tambah(p: Pelanggaran) = temuan.add(p)
fun tambah(aturan: String, severity: String, file: String, baris: Int, pesan: String) =
temuan.add(Pelanggaran(aturan, severity, file, baris, pesan))
fun cetak() {
println("Total temuan: ${temuan.size}")
println()
println("%-30s %-10s %-20s %-5s %s".format("Aturan", "Severity", "File", "Baris", "Pesan"))
println("-".repeat(95))
temuan.sortedBy { it.baris }.forEach {
println("%-30s %-10s %-20s %-5d %s".format(it.aturan, it.severity, it.file, it.baris, it.pesan))
}
}
fun totalBySeverity(): Map<String, Int> = temuan.groupingBy { it.severity }.eachCount()
}
// Mock "AST walker" yang memeriksa kode sumber sebagai string.
class AnalisisStatik {
fun periksa(sumber: String, file: String): LaporanStatik {
val laporan = LaporanStatik()
sumber.lines().forEachIndexed { i, baris ->
val no = i + 1
// Aturan: variabel `var` dengan kemungkinan mutable -> peringatan
if (Regex("""\bvar\s+\w+""").containsMatchIn(baris)) {
laporan.tambah("MutableVariable", "CODE_SMELL", file, no,
"Pertimbangkan `val` kecuali memang perlu mutable")
}
// Aturan: panjang baris > 100
if (baris.length > 100) {
laporan.tambah("MaxLineLength", "STYLE", file, no,
"Baris lebih dari 100 karakter")
}
// Aturan: println di production code
if ("println" in baris) {
laporan.tambah("PrintStackTrace", "WARNING", file, no,
"Hindari println di production code; gunakan logger")
}
// Aturan: TODO
if ("TODO" in baris) {
laporan.tambah("TODO", "INFO", file, no,
"TODO belum diselesaikan")
}
}
return laporan
}
}
fun main() {
println("=== Demo Static Analysis ===")
val kodeSumber = """
data class Pengguna(val id: Int, var nama: String) // var: mutable
fun main() {
var total = 0
println("Mulai") // println di production code
// TODO: hapus kode berikut
val daftarPanjangSekaliYangMelebihiBatasStandarKonvensiYaituSeratusKarakter = listOf(1, 2, 3)
for (x in daftarPanjangSekaliYangMelebihiBatasStandarKonvensiYaituSeratusKarakter) {
total += x
}
}
""".trimIndent()
val analisis = AnalisisStatik()
val laporan = analisis.periksa(kodeSumber, "Pengguna.kt")
laporan.cetak()
println()
println("=== Ringkasan per severity ===")
laporan.totalBySeverity().forEach { (sev, n) ->
println(" $sev: $n")
}
}Output yang Dihasilkan:
=== Demo Static Analysis ===
Total temuan: 4
Aturan Severity File Baris Pesan
-----------------------------------------------------------------------------------------------
MutableVariable CODE_SMELL Pengguna.kt 1 Pertimbangkan `val` kecuali memang perlu mutable
MutableVariable CODE_SMELL Pengguna.kt 4 Pertimbangkan `val` kecuali memang perlu mutable
PrintStackTrace WARNING Pengguna.kt 5 Hindari println di production code; gunakan logger
TODO INFO Pengguna.kt 6 TODO belum diselesaikan
=== Ringkasan per severity ===
CODE_SMELL: 2
WARNING: 1
INFO: 1Klasifikasi Severity #
Static analyzer umumnya mengklasifikasikan temuan berdasarkan severity:
- ERROR: masalah yang harus diperbaiki sebelum merge (mis. kemungkinan NPE).
- WARNING: masalah yang harus ditinjau (mis.
catch Exceptionterlalu umum). - INFO: saran gaya (mis. nama variabel terlalu pendek).
- CODE_SMELL: bukan bug, tetapi mengurangi readability.
Integrasi CI/CD #
Pada pipeline CI, static analysis dijalankan sebagai step terpisah setelah kompilasi. Output berupa laporan (HTML, JSON, atau SARIF) yang dapat ditampilkan sebagai comment di pull request. GitHub Actions, GitLab CI, dan Jenkins memiliki plugin untuk parsing laporan detekt dan ktlint.
Kapan Tidak Memakai Static Analysis #
Static analysis tidak menggantikan test. Beberapa masalah hanya dapat ditemukan dengan menjalankan kode (mis. bug logic, masalah concurrency, integrasi dengan sistem eksternal). Static analysis menemukan pola kode bermasalah; test menemukan perilaku bermasalah. Keduanya saling melengkapi.
Referensi Lanjutan #
- detekt — dokumentasi resmi detekt untuk static analysis kode Kotlin
- ktlint — repository resmi ktlint sebagai pelengkap detekt
Penutup #
Static analysis adalah lapisan pertama pertahanan terhadap masalah kualitas kode. Pada chapter berikutnya kita akan mendalami detekt sebagai static analyzer utama di ekosistem Kotlin: konfigurasi, ruleset, custom rule, dan integrasi Gradle.