xcuti/kotlin icon
public
Published on 5/9/2025
kotlin

Rules

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

  1. File Naming:

    • Kotlin source files must have the .kt extension.
    • The filename should typically describe the contents of the file.
      • If a file contains a single class or interface (often the case), the filename should match its name, including case (e.g., UserProfile.kt for class UserProfile).
      • If a file contains multiple related top-level declarations (e.g., a set of extension functions for a specific type, or related utility functions), the name should be descriptive of its purpose in UpperCamelCase (e.g., StringExtensions.kt, NetworkUtils.kt).
      • If a file contains only top-level declarations that are all part of a facade for a specific functionality, consider using @file:JvmName("...") to control the generated Java class name if interoperability is key.
  2. File Encoding:

    • All Kotlin source files must be encoded in UTF-8.
  3. File Organization:

    • Kotlin allows multiple top-level declarations (classes, interfaces, objects, functions, properties, type aliases) in a single .kt file.
    • Group closely related declarations in the same file. For example:
      • A class and its relevant extension functions.
      • A sealed class hierarchy.
      • A set of constants related to a specific domain.
    • Avoid overly large files. If a file becomes too long or contains too many disparate concepts, refactor it into smaller, more focused files.
    • Order of contents within a file (general guideline, can vary):
      1. License or copyright information (if present).
      2. @file: annotations (e.g., @file:JvmName, @file:Suppress).
      3. package declaration.
      4. import statements.
      5. Top-level declarations (properties, functions, classes, interfaces, objects, type aliases), ordered logically.
  4. 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

  1. General Principles:

    • Names should be descriptive, clear, and self-explanatory.
    • Use English for all identifiers.
    • Avoid abbreviations unless they are standard and widely understood (e.g., IO, UI, Http).
  2. Packages:

    • Package names should be all lowercase.
    • Words are typically concatenated (e.g., 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.
    • Use reversed domain name notation for top-level packages.
    • Avoid underscores.
  3. Classes, Interfaces, Objects, Enums, Type Aliases, Annotations:

    • Names should be nouns or noun phrases.
    • Use UpperCamelCase (PascalCase) (e.g., ImageProcessor, UserService, NetworkRequest, ResultCallback).
    • Interface names can sometimes be adjectives (e.g., Serializable, Focusable) or follow the noun convention. No I prefix is generally recommended in idiomatic Kotlin.
    • Test class names should match the class they test, suffixed with Test (e.g., UserServiceTest).
  4. Functions (Methods):

    • Names should be verbs or verb phrases in lowerCamelCase (e.g., WorkspaceUserData(), calculateScore(), processImage()).
    • For functions that return a boolean, consider prefixes like is, has, should (e.g., isEmpty(), hasConnection(), shouldRetry()).
    • Test function names: Use backticks for descriptive names with spaces (e.g., `given valid input when processed then returns success`).
  5. Properties and Variables (including parameters):

    • Use lowerCamelCase (e.g., userName, itemCount, isLoading).
    • Names should be descriptive.
    • For boolean properties, is, has, should prefixes are common (e.g., val isValid: Boolean, var hasError: Boolean).
    • Avoid single-letter names except for trivial loop counters (i, j, k) or very short, obvious lambdas (it -> ...).
  6. Constants (compile-time constants const val, top-level or object val for runtime constants):

    • Names should be all uppercase, with words separated by underscores (UPPER_SNAKE_CASE) (e.g., MAX_RETRIES, DEFAULT_TIMEOUT_MS).
    • Declared using const val for true compile-time constants (primitive types and String).
    • For other immutable reference types that act as constants, use val in an object or at the top level, also in UPPER_SNAKE_CASE.
  7. Type Parameters (Generics):

    • Typically single uppercase letters (e.g., T, E, K, V, R).
    • If a more descriptive name enhances readability for complex generic types, it can be used, following class naming conventions (e.g., Input, Output, RequestType).
  8. Backing Properties:

    • If a backing property is needed (e.g., for custom getter/setter logic with a private mutable property), prefix the private property name with an underscore (e.g., 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).

  1. Indentation:

    • Use 4 spaces for indentation. Do not use tabs.
  2. Line Length:

    • Aim for a maximum line length of 100-120 characters. Lines exceeding this should be wrapped.
  3. Wrapping Lines:

    • Continuation indent: Typically 4 spaces (one indent level) or 8 spaces (two indent levels) depending on context (e.g., parameter lists, chained calls).
    • When wrapping chained calls (using .), place the . on the new line with a single indent.
    • When wrapping binary operators, the operator usually goes on the next line.
    • When wrapping function or constructor parameters, align parameters under the first parameter if possible, or use a standard continuation indent for each.
    • Trailing commas are encouraged at the declaration site (e.g., in multi-line parameter lists, when entries, enum classes) and discretionary at the call site. They make reordering items and adding new items easier.
  4. Braces ({}):

    • Put the opening brace at the end of the line where the construct begins.
    • Put the closing brace on a separate line, aligned horizontally with the opening construct.
    • For single-expression functions that fit on one line, braces can be omitted and = used instead (expression body).
      fun square(x: Int): Int = x * x // Expression body
      
      fun process(items: List<String>) { // Block body
          items.forEach {
              println(it)
          }
      }
      
    • For control structures (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.
  5. Blank Lines (Vertical Whitespace):

    • Use one blank line:
      • Between function declarations.
      • Between property declarations if they are logically separate or have comments/annotations.
      • Between class/object/interface declarations.
      • To separate logical blocks of code within functions.
      • Before a block or single-line comment that applies to a group of statements.
    • Avoid multiple consecutive blank lines.
  6. Horizontal Whitespace:

    • Around Operators:
      • Put spaces around binary operators (+, -, *, /, %, =, ==, !=, &&, ||, -> in lambdas, elvis ?:, range .., etc.).
      • Exception: No spaces around the "range to" operator (..) if it's clear. 0..i is common.
      • No spaces around unary operators (++a, -b, !c).
    • Around Keywords: Put spaces after control flow keywords (if, when, for, while) and the corresponding opening parenthesis.
    • Commas and Colons: Put a space after a comma (,) or colon (:).
    • Parentheses, Brackets, Angle Brackets:
      • No space after ( or [ or < (in generics).
      • No space before ) or ] or > (in generics).
      • No space before an opening parenthesis in a function/constructor declaration or call.
    • Dot Qualifiers (. and ?.): No spaces around them.
    • Comments: Put a space after //.
    • Type Parameters: No spaces around angle brackets used for type parameters: class Map<K, V>.
    • Member References (::): No spaces around ::.
    • Nullable Types (?): No space before ? (e.g., String?).
  7. Semicolons:

    • Omit semicolons whenever possible. They are only required to separate multiple statements on the same line (which is discouraged for readability) or when an expression used as a statement needs to be distinguished from a following lambda.
  8. String Templates:

    • Use ${expression} for expressions in string templates.
    • Use $variable for simple variable references without braces.

IV. Documentation and Comments (KDoc)

  1. KDoc (/** ... */):

    • Use KDoc for documenting all public or protected top-level declarations (classes, interfaces, objects, functions, properties) and any non-trivial internal/private ones.
    • The first paragraph is the summary description. Subsequent paragraphs provide detailed descriptions.
    • Use Markdown for inline markup (e.g., **bold**, *italic*, `code`, links [text](URL)).
    • Link to other elements using square brackets: [ElementName] or [Link Text][ElementName].
    • Block Tags: Start on a new line, beginning with @.
      • @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.
    • Use @Deprecated annotation instead of a @deprecated KDoc tag. Provide ReplaceWith if applicable.
  2. Implementation Comments:

    • Single-Line Comments (//): For short explanations or end-of-line comments.
    • Block Comments (/* ... */): For multi-line comments explaining complex logic. Not typically used for API documentation.
    • Comments should explain why the code is written a certain way, or clarify complex logic, not just restate what the code does (which should be evident from the code itself).
    • Avoid redundant comments. Keep comments concise and up-to-date.
    • // TODO: Description for tasks.
    • // FIXME: Description for known issues needing fixes.

V. Declarations

  1. Properties (val, var):

    • Prefer immutable properties (val) over mutable ones (var) whenever possible.
    • Declare properties at the top of a class, before constructors or functions.
    • Initialize properties where they are declared if possible.
    • Use 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.
    • Use delegated properties (by lazy {}, by Delegates.observable {}, etc.) when appropriate.
  2. Functions:

    • Use expression bodies (fun name(): Type = expression) for simple functions that fit on one line.
    • Specify return types explicitly for public/protected functions. For private functions or local functions, type inference is acceptable if clear. Unit return type should be omitted.
    • Use default arguments instead of overloads when a function has multiple parameters with common default values.
    • Use named arguments when calling functions with multiple parameters, especially if their meaning isn't obvious from the order or if there are boolean flags.
  3. Primary Constructors:

    • Keep primary constructors concise. Declare properties directly in the primary constructor if possible.
    • init {} blocks can be used for initialization logic.
  4. Secondary Constructors:

    • If a class needs multiple constructors, define secondary constructors using constructor(...). Each secondary constructor must delegate to the primary constructor or another secondary constructor using this(...).
    • Minimize the use of secondary constructors; often, default arguments or factory functions are better alternatives.
  5. Data Classes (data class):

    • Use for classes that primarily hold data. The compiler automatically generates equals(), hashCode(), toString(), copy(), and componentN() functions.
    • Properties in the primary constructor are used for these generated functions.
    • Ensure properties are meaningful for equality, hashing, etc.
    • Data classes should ideally be immutable (val properties).
  6. Sealed Classes and Interfaces (sealed class, sealed interface):

    • Use to represent restricted class hierarchies, where a value can only be one of a limited set of types.
    • Particularly useful for representing states or outcomes (e.g., Result<Success, Error>).
    • All direct subclasses of a sealed class must be declared in the same file (before Kotlin 1.5) or the same module and same package (Kotlin 1.5+).
  7. Objects (object):

    • Use for singletons.
    • Use for creating anonymous objects (implementing interfaces or extending classes on the fly).
    • Use for organizing utility functions and constants if they don't belong to a specific class instance.
  8. Companion Objects (companion object):

    • Use for factory methods or members that are conceptually tied to a class but don't require an instance of it (similar to static members in Java).
    • Can implement interfaces.
    • Can have a name (e.g., companion object Factory).
  9. Type Aliases (typealias):

    • Use to provide alternative names for existing types, especially for complex generic types or function types, to improve readability.
    • typealias UserClickHandler = (User) -> Unit

VI. Control Flow

  1. 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()
      
  2. when Expressions:

    • Use when as a more powerful and readable alternative to switch statements in Java.
    • when is also an expression.
    • The 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).
    • Can match against constants, ranges, types, or arbitrary expressions.
      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")
      }
      
  3. Loops (for, while, do-while):

    • Prefer for loops for iterating over ranges, collections, or anything that provides an iterator.
    • Use list.forEach { ... } or other collection processing functions where idiomatic and readable.
    • while and do-while loops behave as in Java.

VII. Null Safety

  1. Non-Nullable Types by Default:

    • Embrace Kotlin's non-nullable types to prevent NullPointerExceptions at compile time.
  2. Nullable Types (?):

    • Declare a type as nullable using ? only when null is a valid and expected value for that variable/property/return type.
  3. Safe Calls (?.):

    • Use the safe call operator ?. to access properties or call functions on nullable types. The expression evaluates to null if the receiver is null. val length = nullableString?.length
  4. Elvis Operator (?:):

    • Use the elvis operator ?: to provide a default value if a nullable expression is null. val name = nullableName ?: "Guest"
  5. Not-Null Assertion (!!):

    • Avoid !! 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.
    • Only use it if you are absolutely certain the value will not be null at that point and other null-safe constructs are less readable or not applicable.
  6. Safe Casts (as?):

    • Use as? for safe casting. It returns null if the cast is not possible, instead of throwing a ClassCastException.
  7. let, run, apply, also, with for Nullables:

    • Scope functions like nullableValue?.let { ... } or nullableValue?.run { ... } are common for executing code only if a value is not null.

VIII. Collections and Lambdas/Higher-Order Functions

  1. Read-Only vs. Mutable Collections:

    • Distinguish between read-only (List, Set, Map) and mutable (MutableList, MutableSet, MutableMap) collection interfaces.
    • Prefer read-only interfaces for function parameters and return types if the collection is not intended to be modified by that function or its caller. Expose mutable collections only when necessary.
  2. Collection Literals:

    • Use listOf(), setOf(), mapOf() for creating read-only collections.
    • Use mutableListOf(), mutableSetOf(), mutableMapOf() for mutable collections.
  3. Higher-Order Functions and Lambdas:

    • Embrace Kotlin's standard library functions for collection processing (e.g., forEach, map, filter, find, groupBy, flatMap, fold, reduce).
    • Write lambdas concisely:
      • If a lambda has a single parameter, it can be used as its implicit name.
      • If the last parameter of a function is a lambda, it can be passed outside the parentheses.
      • If a lambda is the only argument to a function call, the parentheses can be omitted.
    • For complex or multi-line lambdas, consider explicit parameter names for clarity.

IX. Coroutines (for Asynchronous Programming)

  1. Structured Concurrency:

    • Always launch coroutines within a specific CoroutineScope (e.g., viewModelScope, lifecycleScope in Android, or a custom scope). Avoid GlobalScope as it can lead to leaks and unmanageable coroutines.
    • Jobs launched in a scope are automatically cancelled when the scope is cancelled.
  2. Dispatchers:

    • Choose appropriate dispatchers for coroutine execution:
      • 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.
    • Use withContext(...) to switch dispatchers within a coroutine for a specific block of code.
  3. 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.
    • Use awaitAll() for multiple async operations.
  4. Suspending Functions (suspend):

    • Mark functions that perform long-running operations (like I/O or complex calculations) as suspend.
    • Suspending functions can only be called from other suspending functions or within a coroutine.
    • Suspending functions should be main-safe: they should not block the main thread if called from it (by internally using appropriate dispatchers for blocking work).
  5. Exception Handling:

    • Use standard try-catch blocks within coroutines.
    • For launch, exceptions propagate to the parent scope and can be handled by a CoroutineExceptionHandler in the CoroutineContext.
    • For 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.
    • Use SupervisorJob or supervisorScope if the failure of one child coroutine should not cancel other children or the parent.
  6. Flow:

    • Use Kotlin Flow for handling streams of asynchronous data.
    • Prefer Flow over Channel for reactive data streams.
    • Collect flows in a lifecycle-aware manner (e.g., using lifecycleScope.launch { flow.collect { ... } } or flow.launchIn(scope)).
    • Functions returning Flow should generally not be suspend themselves. Use flowOn(Dispatcher) to change the context of upstream operations.

X. Idiomatic Kotlin

  1. Scope Functions (let, run, with, apply, also):

    • Use scope functions to execute a block of code in the context of an object, making code more concise and readable. Understand the differences:
      • 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.
    • Choose the function that makes the code most readable for the specific task.
  2. Extension Functions and Properties:

    • Add new functionality to existing classes without inheriting from them.
    • Define them at the top level or within objects.
    • Keep extensions focused and related to the type they extend.
  3. Single-Expression Functions:

    • Use fun name(...): ReturnType = expression for functions with a single expression body.
  4. Named Arguments and Default Parameter Values:

    • Use named arguments for clarity when calling functions with multiple parameters, especially booleans or if the order isn't obvious.
    • Use default parameter values to reduce the need for function overloads.
  5. Destructuring Declarations:

    • Unpack objects (especially data class instances or pairs/triples) into multiple variables: val (name, age) = user.
    • Useful in loops over maps: for ((key, value) in map) { ... }.
  6. Operator Overloading:

    • Use judiciously to improve readability for operations that have a clear symbolic representation (e.g., arithmetic operators, comparison operators, collection access). Follow conventions.
  7. Infix Functions (infix fun):

    • Use for functions with a single parameter that represent a natural pairing or operation (e.g., to for creating Pairs, custom DSLs).
    • mapOf("key" to "value")
  8. Type Checks and Casts:

    • Use smart casts: The compiler automatically casts a variable to a more specific type after an is check or a safe cast within the same scope.
    • Prefer is and !is for type checks.
    • Use as? for safe casts. Avoid unsafe as unless the cast is guaranteed to succeed.

XI. Error Handling

  1. Exceptions:

    • Kotlin does not have checked exceptions. All exceptions are unchecked.
    • Throw exceptions for exceptional, unrecoverable error conditions.
    • Use standard Kotlin exceptions (IllegalArgumentException, IllegalStateException, NoSuchElementException, etc.) where appropriate. Create custom exceptions for domain-specific errors.
    • Document notable exceptions that a caller might need to handle using @throws in KDoc.
  2. Result Type (kotlin.Result<T>):

    • Consider using the 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.
    • Use fold, getOrNull, exceptionOrNull, onSuccess, onFailure to process Result objects.
  3. Sealed Classes for Rich Error Modeling:

    • For more complex error scenarios where different failure types carry different data, use a sealed class hierarchy to model results.
      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

  1. Frameworks:

    • Use standard testing frameworks like JUnit 5.
    • Consider Kotlin-specific testing libraries like Kotest for more expressive syntax, matchers, and features (e.g., property-based testing, data-driven tests).
  2. Test Naming:

    • Use backticks for descriptive test function names with spaces: `fun `user should be retrieved when ID is valid`()`
  3. Structure:

    • Follow Arrange-Act-Assert (AAA) or Given-When-Then (GWT) patterns.
    • Use @Nested classes (JUnit 5) or Kotest's nesting capabilities to group related tests.
    • Use @TestInstance(Lifecycle.PER_CLASS) with JUnit 5 to allow val properties for test setup and avoid lateinit or static workarounds.
  4. Assertions:

    • Use clear and specific assertions. Kotest and AssertJ provide rich assertion libraries.
    • For data classes, direct equality comparison assertEquals(expected, actual) works well due to auto-generated equals().
  5. Mocking:

    • Use mocking libraries like MockK (idiomatic for Kotlin) or Mockito (with mockito-kotlin helpers). MockK can mock final classes and objects by default.
  6. Coroutines Testing:

    • Use 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.
  7. Testability:

    • Design code for testability (dependency injection, avoiding tight coupling to global state or static methods for business logic).

XIII. Performance Considerations

  1. General:
    • Write clear, idiomatic Kotlin first. Avoid premature optimization.
    • Profile code to identify actual bottlenecks before optimizing.
  2. Inline Classes (value class):
    • Use value class for lightweight wrappers around a single value to achieve type safety without runtime overhead (the wrapper is often erased at compile time).
  3. Inline Functions (inline):
    • Use 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.
    • Use crossinline and noinline modifiers for lambda parameters within inline functions as needed.
  4. Collections:
    • Be mindful of intermediate collection creation when chaining multiple collection operations (e.g., map, filter). For large collections, consider using Sequence for lazy evaluation, which processes elements one by one.
  5. Loops vs. Functional Operations:
    • For performance-critical sections with very large collections, imperative loops might sometimes outperform functional chains, but always profile. Readability often favors functional style.
  6. Primitive Arrays:
    • For performance-critical numerical computations, use specialized primitive arrays (IntArray, DoubleArray, etc.) instead of Array<Int> to avoid boxing.

XIV. Security Best Practices

  1. Input Validation:
    • Validate and sanitize all external inputs (from users, network, files) to prevent injection attacks, invalid states, etc.
  2. Null Safety:
    • Leverage Kotlin's null safety to prevent NullPointerExceptions, a common source of crashes and vulnerabilities.
  3. Dependency Management:
    • Keep dependencies (via Gradle) up-to-date. Use tools to scan for known vulnerabilities in dependencies.
  4. Sensitive Data:
    • Avoid hardcoding sensitive information (API keys, passwords). Use secure configuration methods (environment variables, build configurations, secure vaults).
    • Handle sensitive data with care in memory, logs, and network transmission.
  5. Serialization/Deserialization:
    • Be cautious when deserializing data from untrusted sources. Use libraries like kotlinx.serialization with proper configuration and validation.
  6. Concurrency:
    • Ensure thread safety and proper synchronization when dealing with shared mutable state in concurrent environments (coroutines or traditional threads).
  7. Principle of Least Privilege:
    • Code should run with the minimum permissions necessary.

XV. AI-Specific Considerations

  1. Data Handling for AI Models:
    • Use Kotlin's data classes and collections for efficient and type-safe handling of datasets, features, and configurations.
    • Leverage sequence processing for large datasets to avoid loading everything into memory.
    • For numerical data, consider interoperability with Java libraries like ND4J (if using Deeplearning4j) or explore Kotlin-native numerical libraries if available and suitable.
  2. Model Representation & Configuration:
    • Use data classes or regular classes for model parameters, hyperparameters, and configurations.
    • Consider using type-safe builders (DSL-like) for constructing complex model configurations.
  3. Interoperability with Java AI Libraries:
    • Kotlin has excellent Java interoperability. AI models may leverage existing Java AI/ML libraries (e.g., Deeplearning4j, Tribuo, Weka, Smile).
    • Be mindful of platform types when calling Java code and explicitly specify nullability in Kotlin if needed.
    • Use Kotlin extension functions to provide a more idiomatic API for Java libraries.
  4. Kotlin for Tooling and Pipelines:
    • Kotlin's conciseness and scripting capabilities (e.g., via Gradle Kotlin DSL, KScript) make it suitable for writing data processing pipelines, build scripts, and tooling around AI models.
  5. Coroutines for Asynchronous AI Tasks:
    • Use coroutines for handling asynchronous operations like model loading, background data fetching for training/inference, or managing concurrent inference requests in a server application.
  6. Serialization (AI Models):
    • For custom models or configurations, use kotlinx.serialization for robust and efficient serialization to JSON, Protobuf, etc.
    • If using Java-based ML frameworks, adhere to their recommended model serialization formats (e.g., saving a Deeplearning4j model).
  7. Resource Management:
    • Ensure proper management of resources, especially if dealing with large models, datasets, or native libraries. Use Closeable.use { ... } for AutoCloseable resources.
  8. Kotlin Notebooks / Scripting:
    • For exploratory data analysis, prototyping, or small AI tasks, Kotlin Notebooks or scripting can be effective.

XVI. Code Review and Evolution

  1. Self-Correction/Refinement:
    • The AI model should aim to review its generated Kotlin code against these rules and refine it for idiomatic correctness and clarity.
  2. Adaptability:
    • These rules may evolve with Kotlin language updates and community best practices. The AI should be designed to adapt to updated or new coding standards.