Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
  • Documentation
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Product
    • Security Analysis
    • Code Quality
    • Agentic Chat
    • RuleSets
    • Memories
    • Analytics
    • Command List
    • Configurations
    Patterns
    • Languages
      • Supported Languages
      • Python
      • Java
      • JavaScript
      • TypeScript
      • Node.js
      • React
      • Fastify
      • Next.js
      • Terraform
      • C#
      • C++
      • C
      • Go
      • Rust
      • Swift
      • React Native
      • Spring Boot
      • Kotlin
      • Flutter
      • Ruby
      • PHP
      • Scala
      • Perl
      • R
      • Dart
      • Elixir
      • Erlang
      • Haskell
      • Lua
      • Julia
      • Clojure
      • Groovy
      • Fortran
      • COBOL
      • Pascal
      • Assembly
      • Bash
      • PowerShell
      • SQL
      • PL/SQL
      • T-SQL
      • MATLAB
      • Objective-C
      • VBA
      • ABAP
      • Apex
      • Apache Camel
      • Crystal
      • D
      • Delphi
      • Elm
      • F#
      • Hack
      • Lisp
      • OCaml
      • Prolog
      • Racket
      • Scheme
      • Solidity
      • Verilog
      • VHDL
      • Zig
      • MongoDB
      • ClickHouse
      • MySQL
      • GraphQL
      • Redis
      • Cassandra
      • Elasticsearch
    • Security
    • Performance
    Integrations
    • Code Repositories
    • Team Messengers
    • Ticketing
    Enterprise
    • Enterprise Deployment Overview
    • Enterprise Configurations
    • Observability and Fallbacks
    • Create Your Own GitHub App
    • Self-Hosting Options
    • RBAC
    Patterns
    Languages

    Kotlin

    Kotlin is a modern, concise, and safe programming language that is fully interoperable with Java. It is designed to be more expressive and concise than Java, with features that help avoid common programming errors.

    Kotlin, despite being a modern and well-designed language, still has common anti-patterns that can lead to bugs, performance issues, and maintenance problems. Here are the most important anti-patterns to avoid when writing Kotlin code.

    // Anti-pattern: Excessive use of !!
    fun processUser(user: User?) {
        val name = user!!.name
        val age = user!!.age
        val address = user!!.address!!
        // NullPointerException if any is null
    }
    
    // Better approach: Safe calls and Elvis operator
    fun processUser(user: User?) {
        val name = user?.name ?: "Unknown"
        val age = user?.age ?: 0
        val address = user?.address?.toString() ?: "No address"
    }

    The !! operator forces a nullable type to be non-null and throws a NullPointerException if the value is null. Use safe calls (?.) and the Elvis operator (?:) instead.

    // Anti-pattern: Regular class for data
    class User(val name: String, val age: Int) {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other !is User) return false
            return name == other.name && age == other.age
        }
        
        override fun hashCode(): Int {
            var result = name.hashCode()
            result = 31 * result + age
            return result
        }
        
        override fun toString(): String {
            return "User(name='$name', age=$age)"
        }
    }
    
    // Better approach: Use data classes
    data class User(val name: String, val age: Int)
    // equals, hashCode, toString, copy are generated automatically

    Use data classes for simple data containers to automatically get equals(), hashCode(), toString(), and copy() methods.

    // Anti-pattern: Ignoring Java nullability
    fun processJavaString(javaUtil: JavaUtil) {
        val str = javaUtil.getStringThatMightBeNull()
        println(str.length)  // Potential NPE
    }
    
    // Better approach: Handle potential nulls
    fun processJavaString(javaUtil: JavaUtil) {
        val str = javaUtil.getStringThatMightBeNull()
        if (str != null) {
            println(str.length)
        }
        // Or
        println(str?.length ?: 0)
    }

    Platform types (types coming from Java) may be nullable even if not marked as such. Always handle them carefully.

    // Anti-pattern: Extension function abuse
    fun String.toInteger(): Int = this.toInt()
    fun String.toDouble(): Double = this.toDouble()
    fun String.isValidEmail(): Boolean = this.matches(Regex("^[A-Za-z](.*)([@]{1})(.*)$"))
    
    // Better approach: Use utility class for related functions
    object StringUtils {
        fun toInteger(s: String): Int = s.toInt()
        fun toDouble(s: String): Double = s.toDouble()
        fun isValidEmail(s: String): Boolean = s.matches(Regex("^[A-Za-z](.*)([@]{1})(.*)$"))
    }

    While extension functions are powerful, overusing them can lead to namespace pollution. Group related functions in utility classes or objects.

    // Anti-pattern: Callbacks for async operations
    fun fetchUserData(userId: String, callback: (User) -> Unit, errorCallback: (Exception) -> Unit) {
        thread {
            try {
                val user = api.getUser(userId)
                callback(user)
            } catch (e: Exception) {
                errorCallback(e)
            }
        }
    }
    
    // Better approach: Use coroutines
    suspend fun fetchUserData(userId: String): User {
        return withContext(Dispatchers.IO) {
            api.getUser(userId)
        }
    }
    
    // Usage
    lifecycleScope.launch {
        try {
            val user = fetchUserData(userId)
            updateUI(user)
        } catch (e: Exception) {
            showError(e)
        }
    }

    Use coroutines for asynchronous operations instead of callbacks or threads for more readable and maintainable code.

    // Anti-pattern: Manual implementation
    fun <T> findFirst(list: List<T>, predicate: (T) -> Boolean): T? {
        for (item in list) {
            if (predicate(item)) {
                return item
            }
        }
        return null
    }
    
    // Better approach: Use standard library functions
    fun <T> findFirst(list: List<T>, predicate: (T) -> Boolean): T? {
        return list.find(predicate)
    }

    Kotlin’s standard library provides many useful functions. Use them instead of reimplementing common functionality.

    // Anti-pattern: Manual property initialization
    class UserViewModel {
        private var _user: User? = null
        val user: User
            get() = _user ?: throw IllegalStateException("User not initialized")
        
        fun init() {
            _user = fetchUser()
        }
    }
    
    // Better approach: Use lazy delegation
    class UserViewModel {
        val user: User by lazy {
            fetchUser()
        }
    }

    Use property delegation (lazy, Delegates.observable, etc.) for common property patterns.

    // Anti-pattern: Using enums or constants for state
    class UserRepository {
        companion object {
            const val STATE_LOADING = 0
            const val STATE_SUCCESS = 1
            const val STATE_ERROR = 2
        }
        
        var state = STATE_LOADING
        var data: User? = null
        var error: Exception? = null
    }
    
    // Better approach: Use sealed classes
    sealed class Result<out T> {
        object Loading : Result<Nothing>()
        data class Success<T>(val data: T) : Result<T>()
        data class Error(val exception: Exception) : Result<Nothing>()
    }
    
    class UserRepository {
        var result: Result<User> = Result.Loading
    }

    Use sealed classes to represent states with associated data instead of using constants or enums.

    // Anti-pattern: Verbose object initialization
    fun createUser(name: String, age: Int): User {
        val user = User(name, age)
        user.status = "Active"
        user.lastLoginDate = Date()
        return user
    }
    
    // Better approach: Use scope functions
    fun createUser(name: String, age: Int): User {
        return User(name, age).apply {
            status = "Active"
            lastLoginDate = Date()
        }
    }

    Use scope functions (let, run, with, apply, also) to make code more concise and readable.

    // Anti-pattern: Non-inline higher-order functions
    fun <T> measureTime(action: () -> T): Pair<T, Long> {
        val startTime = System.currentTimeMillis()
        val result = action()
        val endTime = System.currentTimeMillis()
        return result to (endTime - startTime)
    }
    
    // Better approach: Use inline for higher-order functions
    inline fun <T> measureTime(action: () -> T): Pair<T, Long> {
        val startTime = System.currentTimeMillis()
        val result = action()
        val endTime = System.currentTimeMillis()
        return result to (endTime - startTime)
    }

    Use inline for higher-order functions to avoid the overhead of lambda object creation and virtual function calls.

    // Anti-pattern: Misusing companion objects
    class User {
        companion object {
            // Instance-related method in companion object
            fun getName(user: User): String {
                return user.name
            }
        }
        
        var name: String = ""
    }
    
    // Better approach: Proper use of companion objects
    class User(val name: String) {
        companion object {
            // Factory method - appropriate for companion object
            fun createWithDefaultName(): User {
                return User("John Doe")
            }
        }
        
        // Instance method belongs to the class, not companion
        fun getName(): String {
            return name
        }
    }

    Use companion objects for factory methods and static utilities, not for instance-related functionality.

    // Anti-pattern: Repeating complex types
    fun processUserData(data: Map<String, List<Pair<String, Any>>>) {
        // Process data
    }
    
    fun storeUserData(data: Map<String, List<Pair<String, Any>>>) {
        // Store data
    }
    
    // Better approach: Use type aliases
    typealias UserData = Map<String, List<Pair<String, Any>>>
    
    fun processUserData(data: UserData) {
        // Process data
    }
    
    fun storeUserData(data: UserData) {
        // Store data
    }

    Use type aliases to give meaningful names to complex types, making code more readable and maintainable.

    // Anti-pattern: Extension functions for properties
    fun String.isEmail(): Boolean {
        return matches(Regex("^[A-Za-z](.*)([@]{1})(.*)$"))
    }
    
    // Usage
    if (email.isEmail()) {
        // Do something
    }
    
    // Better approach: Use extension properties
    val String.isEmail: Boolean
        get() = matches(Regex("^[A-Za-z](.*)([@]{1})(.*)$"))
    
    // Usage
    if (email.isEmail) {
        // Do something
    }

    Use extension properties instead of extension functions when the function doesn’t take parameters and returns a value.

    // Anti-pattern: Anonymous class implementation
    val clickListener = object : OnClickListener {
        override fun onClick(view: View) {
            // Handle click
        }
    }
    
    // Better approach: Use lambda for single-method interfaces
    val clickListener = OnClickListener { view ->
        // Handle click
    }

    For single-method interfaces (SAM interfaces), use lambda expressions instead of object expressions.

    // Anti-pattern: Manual collection operations
    fun getAdultNames(people: List<Person>): List<String> {
        val adults = ArrayList<Person>()
        for (person in people) {
            if (person.age >= 18) {
                adults.add(person)
            }
        }
        
        val names = ArrayList<String>()
        for (adult in adults) {
            names.add(adult.name)
        }
        
        return names
    }
    
    // Better approach: Use collection operations
    fun getAdultNames(people: List<Person>): List<String> {
        return people
            .filter { it.age >= 18 }
            .map { it.name }
    }

    Use Kotlin’s collection operations (map, filter, reduce, etc.) for more concise and readable code.

    // Anti-pattern: Not using destructuring
    fun processCoordinates(point: Pair<Int, Int>) {
        val x = point.first
        val y = point.second
        // Process coordinates
    }
    
    // Better approach: Use destructuring declarations
    fun processCoordinates(point: Pair<Int, Int>) {
        val (x, y) = point
        // Process coordinates
    }

    Use destructuring declarations to extract multiple values from objects like pairs, triples, and data classes.

    // Anti-pattern: String concatenation
    fun greeting(name: String, age: Int): String {
        return "Hello, " + name + "! You are " + age + " years old."
    }
    
    // Better approach: Use string templates
    fun greeting(name: String, age: Int): String {
        return "Hello, $name! You are $age years old."
    }
    
    // For complex expressions
    fun complexGreeting(person: Person): String {
        return "Hello, ${person.fullName}! You are ${person.age} years old."
    }

    Use string templates ($variable or ${expression}) instead of string concatenation for more readable code.

    // Anti-pattern: Positional arguments
    fun createUser("John", "Doe", 30, true, "admin")
    
    // Better approach: Use named arguments
    fun createUser(
        firstName = "John",
        lastName = "Doe",
        age = 30,
        isActive = true,
        role = "admin"
    )

    Use named arguments for better readability, especially when calling functions with many parameters.

    // Anti-pattern: Using callbacks for data streams
    interface DataListener {
        fun onNewData(data: Data)
        fun onError(error: Exception)
    }
    
    class DataRepository {
        private val listeners = mutableListOf<DataListener>()
        
        fun addListener(listener: DataListener) {
            listeners.add(listener)
        }
        
        fun removeListener(listener: DataListener) {
            listeners.remove(listener)
        }
        
        fun fetchData() {
            // Fetch data and notify listeners
        }
    }
    
    // Better approach: Use Flow
    class DataRepository {
        fun fetchData(): Flow<Data> = flow {
            // Fetch data and emit
            try {
                val data = api.fetchData()
                emit(data)
            } catch (e: Exception) {
                throw e
            }
        }
    }
    
    // Usage
    viewModelScope.launch {
        dataRepository.fetchData()
            .catch { e -> handleError(e) }
            .collect { data -> updateUI(data) }
    }

    Use Kotlin’s Flow for reactive programming instead of callbacks or custom observer patterns.

    Spring BootFlutter
    websitexgithublinkedin
    Powered by Mintlify
    Assistant
    Responses are generated using AI and may contain mistakes.