A Comprehensive Set of Rules for AI Model Coding in Kotlin
This document outlines a detailed set of rules and best practices for an AI model generating or analyzing Kotlin code. The objective is to produce code that is idiomatic, readable, maintainable, efficient, robust, and leverages Kotlin's modern features effectively.
Current Date: May 9, 2025
Build System: Gradle with Kotlin DSL (build.gradle.kts
, settings.gradle.kts
) must be used as the main build and compilation system.
I. File Structure and Organization
File Naming:
.kt
extension.UserProfile.kt
for class UserProfile
).UpperCamelCase
(e.g., StringExtensions.kt
, NetworkUtils.kt
).@file:JvmName("...")
to control the generated Java class name if interoperability is key.File Encoding:
UTF-8
.File Organization:
.kt
file.@file:
annotations (e.g., @file:JvmName
, @file:Suppress
).package
declaration.import
statements.Package and Imports:
4.1. Package Declaration:
* Every Kotlin source file should start with a package
declaration.
* The default (unnamed) package is strongly discouraged for any non-trivial code.
4.2. Import Statements:
* Import statements must follow the package
declaration and precede any other declarations.
* Imports should be grouped and ordered as follows (though IDEs often manage this automatically):
1. Standard library imports (kotlin.*
, kotlinx.*
).
2. java.*
, javax.*
imports (if used).
3. Other external library imports (e.g., org.*
, com.*
, io.*
).
4. Internal project imports.
* Within each group, imports should typically be alphabetized.
* Use specific imports (e.g., import kotlin.collections.List
).
* Wildcard imports (import foo.bar.*
) are generally discouraged for types. They are acceptable for:
* Importing all members of an object
(e.g., utility functions).
* Importing enum constants.
* Importing all cases of a sealed class for when
expressions.
* Use import ... as ...
for renaming if there are name clashes.
* Remove all unused imports. IDEs usually offer tools for this.
II. Naming Conventions
General Principles:
IO
, UI
, Http
).Packages:
com.example.project.featurename
). Multi-word names can also use camel case if preferred by the project (e.g., com.example.myProject.uiUtils
), though simple concatenation is more common.Classes, Interfaces, Objects, Enums, Type Aliases, Annotations:
UpperCamelCase
(PascalCase) (e.g., ImageProcessor
, UserService
, NetworkRequest
, ResultCallback
).Serializable
, Focusable
) or follow the noun convention. No I
prefix is generally recommended in idiomatic Kotlin.Test
(e.g., UserServiceTest
).Functions (Methods):
lowerCamelCase
(e.g., WorkspaceUserData()
, calculateScore()
, processImage()
).is
, has
, should
(e.g., isEmpty()
, hasConnection()
, shouldRetry()
).`given valid input when processed then returns success`
).Properties and Variables (including parameters):
lowerCamelCase
(e.g., userName
, itemCount
, isLoading
).is
, has
, should
prefixes are common (e.g., val isValid: Boolean
, var hasError: Boolean
).i
, j
, k
) or very short, obvious lambdas (it -> ...
).Constants (compile-time constants const val
, top-level or object val
for runtime constants):
UPPER_SNAKE_CASE
) (e.g., MAX_RETRIES
, DEFAULT_TIMEOUT_MS
).const val
for true compile-time constants (primitive types and String
).val
in an object
or at the top level, also in UPPER_SNAKE_CASE
.Type Parameters (Generics):
T
, E
, K
, V
, R
).Input
, Output
, RequestType
).Backing Properties:
private var _items: MutableList<String>
with val items: List<String> get() = _items
).III. Code Formatting
(Generally, align with the official Kotlin coding conventions, often enforced by IntelliJ IDEA / Android Studio's default formatter).
Indentation:
Line Length:
Wrapping Lines:
.
), place the .
on the new line with a single indent.when
entries, enum classes) and discretionary at the call site. They make reordering items and adding new items easier.Braces ({}
):
=
used instead (expression body).
fun square(x: Int): Int = x * x // Expression body
fun process(items: List<String>) { // Block body
items.forEach {
println(it)
}
}
if
, else
, when
, for
, while
, do-while
), braces should always be used if the body spans multiple lines. For single-line bodies, braces are optional but often recommended for consistency, especially in if/else if/else
chains.Blank Lines (Vertical Whitespace):
Horizontal Whitespace:
+
, -
, *
, /
, %
, =
, ==
, !=
, &&
, ||
, ->
in lambdas, elvis ?:
, range ..
, etc.)...
) if it's clear. 0..i
is common.++a
, -b
, !c
).if
, when
, for
, while
) and the corresponding opening parenthesis.,
) or colon (:
).(
or [
or <
(in generics).)
or ]
or >
(in generics)..
and ?.
): No spaces around them.//
.class Map<K, V>
.::
): No spaces around ::
.?
): No space before ?
(e.g., String?
).Semicolons:
String Templates:
${expression}
for expressions in string templates.$variable
for simple variable references without braces.IV. Documentation and Comments (KDoc)
KDoc (/** ... */
):
**bold**
, *italic*
, `code`
, links [text](URL)
).[ElementName]
or [Link Text][ElementName]
.@
.
@param name description
: Documents a value parameter of a function or a type parameter of a class/function.@return description
: Documents the return value.@constructor description
: Documents the primary constructor.@receiver description
: Documents the receiver of an extension function.@property name description
: Documents a property (useful for primary constructor properties).@throws class description
or @exception class description
: Documents exceptions that can be thrown. Since Kotlin doesn't have checked exceptions, document notable exceptions callers should be aware of.@sample qualified.function.name
: Embeds example code from a specified function.@see identifier
: Adds a link to the "See also" section.@author text
: Specifies the author.@since text
: Specifies the version when the element was introduced.@suppress
: Excludes the element from generated documentation.@Deprecated
annotation instead of a @deprecated
KDoc tag. Provide ReplaceWith
if applicable.Implementation Comments:
//
): For short explanations or end-of-line comments./* ... */
): For multi-line comments explaining complex logic. Not typically used for API documentation.// TODO: Description
for tasks.// FIXME: Description
for known issues needing fixes.V. Declarations
Properties (val
, var
):
val
) over mutable ones (var
) whenever possible.lateinit var
for non-null properties initialized later (e.g., in onCreate
for Android, dependency injection). Use with caution as it can lead to UninitializedPropertyAccessException
.by lazy {}
, by Delegates.observable {}
, etc.) when appropriate.Functions:
fun name(): Type = expression
) for simple functions that fit on one line.Unit
return type should be omitted.Primary Constructors:
init {}
blocks can be used for initialization logic.Secondary Constructors:
constructor(...)
. Each secondary constructor must delegate to the primary constructor or another secondary constructor using this(...)
.Data Classes (data class
):
equals()
, hashCode()
, toString()
, copy()
, and componentN()
functions.val
properties).Sealed Classes and Interfaces (sealed class
, sealed interface
):
Result<Success, Error>
).Objects (object
):
Companion Objects (companion object
):
companion object Factory
).Type Aliases (typealias
):
typealias UserClickHandler = (User) -> Unit
VI. Control Flow
if
Expressions:
if
is an expression in Kotlin, meaning it can return a value. Prefer using its expression form where it simplifies code.
val result = if (condition) processA() else processB()
when
Expressions:
when
as a more powerful and readable alternative to switch
statements in Java.when
is also an expression.else
branch is mandatory if when
is used as an expression (unless the compiler can prove all cases are covered, e.g., with sealed classes or enums).when (x) {
1 -> print("x == 1")
2, 3 -> print("x == 2 or x == 3")
in 4..7 -> print("x is in range 4 to 7")
is String -> print("x is a String of length ${x.length}")
else -> print("x is something else")
}
Loops (for
, while
, do-while
):
for
loops for iterating over ranges, collections, or anything that provides an iterator.list.forEach { ... }
or other collection processing functions where idiomatic and readable.while
and do-while
loops behave as in Java.VII. Null Safety
Non-Nullable Types by Default:
NullPointerExceptions
at compile time.Nullable Types (?
):
?
only when null
is a valid and expected value for that variable/property/return type.Safe Calls (?.
):
?.
to access properties or call functions on nullable types. The expression evaluates to null
if the receiver is null
.
val length = nullableString?.length
Elvis Operator (?:
):
?:
to provide a default value if a nullable expression is null
.
val name = nullableName ?: "Guest"
Not-Null Assertion (!!
):
!!
operator whenever possible. It converts any value to a non-null type and throws a NullPointerException
if the value is null
. It defeats the purpose of compile-time null safety.null
at that point and other null-safe constructs are less readable or not applicable.Safe Casts (as?
):
as?
for safe casting. It returns null
if the cast is not possible, instead of throwing a ClassCastException
.let
, run
, apply
, also
, with
for Nullables:
nullableValue?.let { ... }
or nullableValue?.run { ... }
are common for executing code only if a value is not null.VIII. Collections and Lambdas/Higher-Order Functions
Read-Only vs. Mutable Collections:
List
, Set
, Map
) and mutable (MutableList
, MutableSet
, MutableMap
) collection interfaces.Collection Literals:
listOf()
, setOf()
, mapOf()
for creating read-only collections.mutableListOf()
, mutableSetOf()
, mutableMapOf()
for mutable collections.Higher-Order Functions and Lambdas:
forEach
, map
, filter
, find
, groupBy
, flatMap
, fold
, reduce
).it
can be used as its implicit name.IX. Coroutines (for Asynchronous Programming)
Structured Concurrency:
CoroutineScope
(e.g., viewModelScope
, lifecycleScope
in Android, or a custom scope). Avoid GlobalScope
as it can lead to leaks and unmanageable coroutines.Dispatchers:
Dispatchers.Main
: For UI-related tasks (in UI applications).Dispatchers.IO
: For I/O-bound operations (network requests, disk access).Dispatchers.Default
: For CPU-intensive work.withContext(...)
to switch dispatchers within a coroutine for a specific block of code.Coroutine Builders:
launch
: For "fire-and-forget" coroutines that don't return a result to the caller.async
: For coroutines that compute a result, returning a Deferred<T>
. Call await()
on the Deferred
object to get the result.awaitAll()
for multiple async
operations.Suspending Functions (suspend
):
suspend
.Exception Handling:
try-catch
blocks within coroutines.launch
, exceptions propagate to the parent scope and can be handled by a CoroutineExceptionHandler
in the CoroutineContext
.async
, exceptions are stored in the Deferred
and thrown when await()
is called. Wrap await()
calls in try-catch
or handle exceptions on the parent scope if async
is used with supervisorScope
.SupervisorJob
or supervisorScope
if the failure of one child coroutine should not cancel other children or the parent.Flow:
Flow
over Channel
for reactive data streams.lifecycleScope.launch { flow.collect { ... } }
or flow.launchIn(scope)
).Flow
should generally not be suspend
themselves. Use flowOn(Dispatcher)
to change the context of upstream operations.X. Idiomatic Kotlin
Scope Functions (let
, run
, with
, apply
, also
):
let
: Context object it
, returns lambda result. Useful for null checks and transforming.run
: Context object this
(or no receiver for non-extension run
), returns lambda result. Useful for object configuration and computation.with
: Context object this
(passed as argument), returns lambda result. Similar to run
.apply
: Context object this
, returns context object itself. Useful for object configuration (builder-style).also
: Context object it
, returns context object itself. Useful for side effects or actions on the object without modifying it.Extension Functions and Properties:
Single-Expression Functions:
fun name(...): ReturnType = expression
for functions with a single expression body.Named Arguments and Default Parameter Values:
Destructuring Declarations:
data class
instances or pairs/triples) into multiple variables: val (name, age) = user
.for ((key, value) in map) { ... }
.Operator Overloading:
Infix Functions (infix fun
):
to
for creating Pair
s, custom DSLs).mapOf("key" to "value")
Type Checks and Casts:
is
check or a safe cast within the same scope.is
and !is
for type checks.as?
for safe casts. Avoid unsafe as
unless the cast is guaranteed to succeed.XI. Error Handling
Exceptions:
IllegalArgumentException
, IllegalStateException
, NoSuchElementException
, etc.) where appropriate. Create custom exceptions for domain-specific errors.@throws
in KDoc.Result
Type (kotlin.Result<T>
):
Result
type (or a custom sealed class hierarchy) for operations that can fail but where failure is an expected outcome, not necessarily an exceptional one. This makes success and failure explicit in the function signature.runCatching { ... }
is a convenient way to execute a block and wrap its outcome (success or caught exception) in a Result
.fold
, getOrNull
, exceptionOrNull
, onSuccess
, onFailure
to process Result
objects.Sealed Classes for Rich Error Modeling:
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val code: Int, val message: String) : NetworkResult<Nothing>()
object NetworkUnavailable : NetworkResult<Nothing>()
}
XII. Testing
Frameworks:
Test Naming:
`fun `user should be retrieved when ID is valid`()`
Structure:
@Nested
classes (JUnit 5) or Kotest's nesting capabilities to group related tests.@TestInstance(Lifecycle.PER_CLASS)
with JUnit 5 to allow val
properties for test setup and avoid lateinit
or static
workarounds.Assertions:
assertEquals(expected, actual)
works well due to auto-generated equals()
.Mocking:
mockito-kotlin
helpers). MockK can mock final classes and objects by default.Coroutines Testing:
kotlinx-coroutines-test
library, which provides TestCoroutineDispatcher
(or StandardTestDispatcher
/UnconfinedTestDispatcher
in newer versions), TestCoroutineScope
(or TestScope
), and runTest
builder to control virtual time and test suspending functions deterministically.Testability:
XIII. Performance Considerations
value class
):
value class
for lightweight wrappers around a single value to achieve type safety without runtime overhead (the wrapper is often erased at compile time).inline
):
inline
for functions with lambda parameters to reduce the overhead of function calls and lambda object creation, especially for small, frequently used higher-order functions. Be aware of increased bytecode size.crossinline
and noinline
modifiers for lambda parameters within inline functions as needed.map
, filter
). For large collections, consider using Sequence
for lazy evaluation, which processes elements one by one.IntArray
, DoubleArray
, etc.) instead of Array<Int>
to avoid boxing.XIV. Security Best Practices
NullPointerExceptions
, a common source of crashes and vulnerabilities.kotlinx.serialization
with proper configuration and validation.XV. AI-Specific Considerations
kotlinx.serialization
for robust and efficient serialization to JSON, Protobuf, etc.Closeable.use { ... }
for AutoCloseable
resources.XVI. Code Review and Evolution