Kotlin Code Review Checklist: Basics to Know

Do you and your team want to develop better Kotlin apps? Getting through an independent code review will give you a serious advantage. Code reviews will help you catch bugs early and boost performance while creating applications that users will love and rate positively. This checklist covers the basics of building Kotlin apps and will simplify Kotlin code reviews.

Scope and Objectives

Pre-review setup is very important. It means checking that everything is in place before the code analysis begins. First, make sure there’s a well-defined scope and objectives to focus on. This way, you’ll ensure your review is targeted and precise.

Key Best Practices:

  • State the review goals clearly—focus on security, performance, readability, and more
  • Define the scope precisely—one module, a specific feature, or the whole app
  • Set measurable success criteria—number of fixed bugs, performance benchmarks, code coverage percentage, etc.
  • Communicate the scope to your team beforehand

Testing Environment Setup

The next crucial step at the pre-review stage is setting up your testing environment. A properly configured environment makes the future review process smooth and efficient. It also will save you from redoing everything in case of a mistake.

Key Best Practices:

  • Check if an IDE is appropriately configured with Kotlin and all dependencies
  • Verify necessary plugins like Detekt or Gradle are installed and functioning
  • Set up a comprehensive test suite, including unit integration and end-to-end tests
  • Confirm that the test suite runs smoothly and that the codebase is ready for testing

Code Documentation

Now it’s time for the next critical stage, checking if your Kotlin code speaks clearly for itself through documentation. Well-documented code isn’t just a courtesy—it’s a superpower for efficient reviews and long-term project health.

Key Best Practices:

  • Write meaningful KDoc comments for classes, functions, and properties
  • Check for clear and concise explanations of complex logic
  • Make sure examples work for public APIs and functions
  • Verify that documentation is up-to-date with the code
  • Encourage documentation generation tools like Dokka
// Good practice of Code Documentation (KDoc)

/**
 * A class representing a user profile.
 *
 * It holds user's personal information and provides methods to manage their profile.
 *
 * @property userId Unique identifier for the user.
 * @property username The user's chosen username.
 */
class UserProfile(val userId: Int, val username: String) {

   /**
* Validates personal information.
*
* @param firstName The user's first name. Can be null.
* @param lastName The user's last name. Can be null.
* @return `true` if both the first name and last name are non-null and non-blank, `false` otherwise.
*/
fun validatePersonalInfo(firstName: String?, lastName: String?) =
!firstName.isNullOrBlank() && !lastName.isNullOrBlank()
}


// Bad practice of Code Documentation (KDoc)

/**
 * UserProfile class.
 */
class UserProfile(val userId: Int, val username: String) {

    /**
     * Function to get full name.
     */
    fun getFullName(firstName: String, lastName: String): String {
        return "$firstName $lastName"
    }
}

Up-to-Date Dependencies

Checking for up-to-date dependencies determines whether your project’s foundation is solid. Utilizing the most current libraries and tools isn’t just an accolade of having the latest features—it’s a vital step for security and stability in the long run.

Key Best Practices:

  • Review dependency updates using Gradle or Maven commands
  • Verify that all dependencies are from trusted and reputable sources
  • Look for dependencies with known security vulnerabilities
  • Check if dependency versions are consistent across the project
  • Use dependency management tools to automate updates and vulnerability checks

Static Analysis Tools

Don’t hesitate to bring in the automated eyes of Static Analysis Tools to your Kotlin code reviews. These tools act as your tireless assistants, catching potential issues early and consistently, freeing up human reviewers for more nuanced aspects of the code.

Key Best Practices:

  • Add static analysis tools to your project’s CI/CD pipeline
  • Use linters like Detekt and Ktlint for Kotlin code-style enforcement
  • Configure static analysis rules to match your project’s specific needs and coding standards
  • Review and address discoveries from static analysis reports
  • Introduce new static analysis rules to other developers

Test Data and Scenarios

Well-conducted tests are your safety net, and reviewing them is just as vital as reviewing the code itself. Solid test data and scenarios ensure your Kotlin code’s reliability, both now and in the future.

  • Review test data for realism and edge cases
  • Check for comprehensive scenario coverage, including positive and negative cases
  • Confirm that tests are independent and don’t rely on external states or dependencies
  • Look for clear and descriptive test names that explain the scenario being tested
  • Promote data-driven testing for scenarios with multiple input variations

Consistent Naming and Comments

Naming, formatting, and commenting can sometimes seem minor, but they’re the bedrock of readability and maintainability in any Kotlin project. They allow for smoother code reviews and more effective alignment for everyone.

Key Best Practices:

  • Confirm that the code sticks to Kotlin coding conventions and style guides
  • Check for consistent naming conventions across the codebase
  • Ensure comments explain non-obvious logic or complex sections
  • Look for opportunities to improve code clarity through better naming or formatting instead of just adding comments
  • Verify that comments are accurate and up-to-date with the code
// Good practice of Naming, Formatting, and Comments

class OrderProcessor {

    fun calculateTotalPrice(orderItems: List, discountPercentage: Double): Double {
        val subtotal = orderItems.sumOf { it.price * it.quantity }
        return subtotal * (1 - (discountPercentage / 100))
    }
}

data class OrderItem(
    val productId: Int,
    val productName: String,
    val quantity: Int,
    val price: Double
)


// Bad practice of Naming, Formatting, and Comments

class order_processor {

    fun calc_price(items: List, disc: Double): Double {
        // calculate sum
        val s = items.sumOf { it.price * it.quantity }

        // discount
        val discount = s * (disc / 100)
        val finalPrice = s - discount

        return finalPrice
    }
}

data class OrderItem(
    val productID: Int,
    val item_name: String,
    val qty: Int,
    val itemPrice: Double
)

Organization of Imports

Keeping the code simple and any imports organized will ultimately speed up reviews, reduce the chances of errors creeping in, and make the codebase understandable for everyone. Code that’s easy to grasp and navigate is a joy to review and maintain. It sets the stage for a healthier and more robust project.

Key Best Practices:

  • Break down complex functions
  • Reduce nesting to improve code navigability
  • Organize imports to clarify dependencies
  • Remove unused imports to reduce clutter
  • Avoid wildcard imports
// Good practice of Import Organization

import com.example.model.User
import com.example.repository.UserRepository
import com.example.service.UserService
import com.example.view.UserView

class UserProcessor {

    private val userRepository = UserRepository()
    private val userService = UserService(userRepository)

    fun displayUserDetails(userId: Int) {
        val user = userService.getUserById(userId) ?: return // Ранній вихід, якщо користувача не знайдено

        val formattedName = user.username.capitalize() // Використання вбудованої функції Kotlin
        val userView = createUserView(user, formattedName)
        userView.display()
    }

    private fun createUserView(user: User, formattedName: String): UserView {
        return UserView(
            name = formattedName,
            email = user.email.orEmpty() // Безпечна обробка null
        )
    }
}


// Bad practice of Import Organization 

import com.example.utils.*
import com.example.view.UserView
import com.example.service.UserService
import com.example.model.User
import com.example.repository.UserRepository

class UserProcessor {

    private val userRepository = UserRepository()
    private val userService = UserService(userRepository)

    fun displayUserDetails(userId: Int) {
        val user = userService.getUserById(userId)
        if (user != null) {
            val name = user.username
            val formattedName = StringUtils.capitalize(name) // Utility function somewhere
            val email = if (user.email != null) user.email else "" // Null check inline

            val view = UserView(formattedName, email)
            view.display()

        }
    }
}

Logical Grouping and Function Length

Well-organized code is fundamentally more straightforward to understand, test, and evolve. By logically grouping functions and keeping them concise, we create a codebase that reviewers can quickly grasp. Changes become less error-prone, and the overall project becomes more maintainable.

Key Best Practices:

  • Group related code into logical units like classes or packages
  • Stick to the Single Responsibility Principle (SRP)
  • Limit function length
  • Refractor overly long functions or poorly grouped code

Proper Feature Utilization

Writing good Kotlin code is more than just having functional code. It’s about utilizing Kotlin’s tools to make apps stronger, readable, and faster. When Kotlin features are correctly used, code reviews become much smoother. Reviewers can quickly understand the important stuff—how the app works and is built—instead of getting lost in complicated or unusual code.

Key Best Practices:

  • Leverage Kotlin’s null safety features (e.g., ?, !!, let, run, also, apply) to minimize NullPointerExceptions and simplify code reviews
  • Use Kotlin data classes for data-centric entities
  • Utilize Kotlin extension functions, higher-order functions, and lambdas
  • Check for idiomatic Kotlin usage, steering away from wordy Java-style patterns
// Good practice of Feature Utilization (Idiomatic Kotlin)

import java.util.Locale

data class Address(val street: String?, val city: String, val zipCode: String?)

fun formatAddress(address: Address?) = address?.run {
    """${street?.capitalizeFirst() ?: "Unknown Street"}, ${city.uppercase()}, ${zipCode ?: "N/A"}"""
} ?: "No Address Provided"

fun String.capitalizeFirst(): String =
    replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }

fun main() {
    val validAddress = Address("Main Street", "Kyiv", "01001")
    val partialAddress = Address(null, "Lviv", null)
    val noAddress: Address? = null

    println(formatAddress(validAddress))
    println(formatAddress(partialAddress))
    println(formatAddress(noAddress))
}

// Bad practice of Feature Utilization (More Java-style/Verbose)

class Address {
    var street: String? = null
    var city: String = ""
    var zipCode: String? = null

    constructor(street: String?, city: String, zipCode: String?) {
        this.street = street
        this.city = city
        this.zipCode = zipCode
    }
}

fun getFormattedAddress(address: Address?): String {
    if (address != null) {
        var streetName = address.street
        if (streetName == null) {
            streetName = "Unknown Street"
        } else {
            streetName = streetName.capitalize()
        }

        var zip = address.zipCode
        if (zip == null) {
            zip = "N/A"
        }

        return streetName + ", " + address.city.toUpperCase() + ", " + zip
    } else {
        return "No Address Provided"
    }
}

fun main() {
    val validAddress = Address("Main Street", "Kyiv", "01001")
    val partialAddress = Address(null, "Lviv", null)
    val noAddress: Address? = null

    println(getFormattedAddress(validAddress))
    println(getFormattedAddress(partialAddress))
    println(getFormattedAddress(noAddress))
}

Handling Nulls Safely

Null Pointer Exceptions are the bane of many developers’ existence, and Kotlin provides powerful tools to avoid them. By precisely reviewing how nulls are handled in Kotlin code, we reduce the risk of crashes, improve app stability, and make the codebase more straightforward to maintain.

Key Best Practices:

  • Use non-null types (?) by default
  • Utilize nullable types (?) when null is valid and expected
  • Promote safe call (?.) and elvis operator (?:) for concise and safe null handling
  • Don’t employ the not-null assertion operator (!!) unless it’s unavoidable
  • Test null handling scenarios, especially for nullable types and external data interactions

Managing Exceptions

No code is perfect, and things will inevitably go wrong. What’s important is that your code knows how to react when these accidents happen and can clearly state what went wrong. Checking for good error handling in code reviews ensures that apps are strong and won’t break easily. Plus, clear error messages provide a helpful guide for developers to find and fix problems quickly.

Key Best Practices:

  • Use Kotlin try-catch blocks to handle potential exceptions and prevent crashes
  • Generate specific and custom exception types to provide richer error context
  • Include informative error messages in exceptions
  • Don’t catch generic Exception unless necessary
  • Review error logging practices
// Good practice of Exception Management

import java.io.File
import java.io.IOException

class FileProcessor {

    fun readFileContent(filePath: String): String = try {
        File(filePath).readText()
    } catch (e: IOException) {
        throw FileProcessingException("Error reading file at path: $filePath", e)
    } catch (e: SecurityException) {
        throw FileProcessingException("Insufficient permissions to read file: $filePath", e)
    }
}

class FileProcessingException(message: String, cause: Throwable? = null) : Exception(message, cause)

fun main() {
    val processor = FileProcessor()
    val filePath = "data.txt"
    try {
        val content = processor.readFileContent(filePath)
        println("File content:\n$content")
    } catch (e: FileProcessingException) {
        println("Error processing file: ${e.message}")
        e.cause?.message?.let { println("Caused by: $it") } // Simplified cause message printing
    }
}

// Bad practice of Exception Management

import java.io.File
import java.io.IOException

class FileProcessor {

    fun readFileContent(filePath: String): String {
        try {
            return File(filePath).readText()
        } catch (e: Exception) { // Catching generic Exception
            throw Exception("File error") // Generic error message
        }
    }
}


fun main() {
    val processor = FileProcessor()
    val filePath = "data.txt"
    try {
        val content = processor.readFileContent(filePath)
        println("File content:\n$content")
    } catch (e: Exception) {
        println("Error: Something went wrong") // Uninformative error message
    }
}

Efficient Collections

Reviewing code is vital to making apps faster and lighter. You can ensure there aren’t too many things being created and you’re using the best ways to store data. Such apps are quicker and more responsive, and they consume less charge. Paying attention to these details in code reviews makes a significant difference for app users.

Key Best Practices:

  • Minimize object creation, especially in loops or frequently called functions
  • Use efficient Kotlin collections like List, Set, and Map appropriately
  • Utilize immutable collections where possible
  • Use collection operations like map, filter, reduce, and fold
  • Review code for potential boxing/unboxing overhead when using primitive types and collections, especially in performance-critical sections
// Good practice of Object Creation and Collection Usage

object StringCache { // Using object declaration for singleton
    private val cache = hashSetOf() // Efficient HashSet for lookups

    fun isCached(text: String) = cache.contains(text)

    fun addToCache(text: String) {
        cache.add(text) // HashSet add() already handles duplicates efficiently
    }
}

fun List.processUniqueToUpperCase() = this
    .filterNot { StringCache.isCached(it) } // Using filterNot for better readability
    .map { it.uppercase() }

fun main() {
    StringCache.addToCache("apple")
    StringCache.addToCache("banana")

    val inputList = listOf("apple", "orange", "banana", "grape")
    val uniqueUpperCaseStrings = inputList.processUniqueToUpperCase() // Using the extension function
    println("Unique Uppercase Strings: $uniqueUpperCaseStrings") // Output: [ORANGE, GRAPE] - Corrected output
}

// Bad practice of Object Creation and Collection Usage

class StringCache { // Class, not object - new instance created every time
    private val cache = ArrayList() // Inefficient ArrayList for lookups

    fun isCached(text: String): Boolean {
        for (item in cache) { // Manual loop for checking existence - slow in ArrayList
            if (item == text) {
                return true
            }
        }
        return false
    }

    fun addToCache(text: String) {
        if (!isCached(text)) {
            cache.add(text)
        }
    }
}

fun processStrings(stringList: List): List {
    val resultList = ArrayList() // Creating a mutable list
    for (str in stringList) { // Manual loop for filtering and transforming
        if (!StringCache().isCached(str)) { // New StringCache instance created unnecessarily!
            resultList.add(str.toUpperCase())
        }
    }
    return resultList
}

fun main() {
    val cache = StringCache() // Instance creation
    cache.addToCache("apple")
    cache.addToCache("banana")

    val inputList = listOf("apple", "orange", "banana", "grape")
    val uniqueUpperCaseStrings = processStrings(inputList)
    println("Unique Uppercase Strings: $uniqueUpperCaseStrings") // Output: [ORANGE, GRAPE]
}

Your Kotlin Development & Audit Services

The opportunity of our modern mobile age is that a global audience of 5 billion smartphone users is waiting for your app to solve their tasks! Make the most of this blessing by utilizing Redwerk’s 20 years of experience in mobile app development. We’ll implement your vision to create a top-rated native Android app, a flawless native iOS app, or even a fully functional hybrid cross-platform app.

Forget project stress and burnt deadlines. Redwerk’s expert developers, designers, QA engineers, and project managers thrive on challenges, transforming them into wins. We’ll help you achieve every business goal on your path to success—take a glance:

  • Idea to Solution. We build custom software solutions from scratch. See how we developed a performant and intuitive Android (Kotlin) testing toolkit that can be used by QA engineers, developers, product specialists, and creatives with equal effectiveness.
  • Legacy Code Modernization. Refactoring isn’t just about tidying up—it’s about breathing new life into your existing system. Our code refactoring specialists inject modern best practices into your project’s DNA. With our experience developing over 20 Android apps across 7 countries, we know what it takes to improve your system’s performance and management.
  • Supercharging Your Product’s Appeal. Our developers don’t just add features—they build within the context of your goals, allowing your solution to grow and adapt effortlessly. Read about how we developed a bike parking system connected to a cloud app, native Android app, and iOS apps for My Bike Valet. We designed their MVP architecture, which provided a strong foundation and led to a straightforward expansion.
  • Native or Cross-Platform? Unlock the Ideal Mobile Strategy. At Redwerk, we are masters of both native and cross-platform. Native is the ultimate choice for peak performance and cutting-edge features, while cross-platform development offers lower prices and a broader reach. Don’t navigate this decision alone—Redwerk’s experts will help you choose the right path.

Whether you’re facing a critical code review, need expert Kotlin guidance, want full-cycle development, or anything in between, Redwerk’s Kotlin experts are here for you. We’re eager to hear about your vision and how we can help. Get in touch with our team whenever you’re ready.

Discover how we built a Kotlin-based resource planner for Mass Movement, increasing supply chain efficiency by 30%

Please enter your business email isn′t a business email