If you are new to Kotlin, it's essential to familiarize yourself with some best practices that can benefit any project. This blog post aims to summarize these practices and provide you with valuable insights.

To get started, you can find the basics of Kotlin in the official Kotlin reference documentation https://kotlinlang.org/docs/reference/. It provides a comprehensive guide to understanding the language.

Additionally, it's worth noting that Kotlin has official standard basic coding conventions https://kotlinlang.org/docs/coding-conventions.html. Check on that page to allow your IDEA to automatically verify them, making your coding experience more seamless. They cover topics such as source code organization, naming rules, formatting, documentation comments, and idiomatic use of language features.

Now let's dive deeper into some specific best practices.

8 effective practices for Kotlin

1. Follow official Kotlin’s coding conventions

Kotlin has official standard basic coding conventions check https://kotlinlang.org/docs/coding-conventions.html. Plus you can configure your IDE to automatically verify them, making your coding experience more seamless. 

They cover topics such as source code organisation, naming rules, formatting, documentation comments, and idiomatic use of language features.

2. Ensure Safety

With Immutability:

  • Keep everything read-only unless required to change its actual value:
  • Use val for constants and only use var for variables that actually need to change their value.
  • Prefer using immutable collections ( List , Set, Map) instead of mutable ones ( mutableList, mutableSet, mutableMap).
  • Remember that arguments passed to methods are passed by value, but objects are passed by reference. Be cautious when modifying object properties inside a method, as they’d remain changed afterwards.
  • Protect class variables by returning only immutable ones or copies of the data instead of the original.
  • For any mutable collection within a method that requires to be returned cast it to its equivalent non-mutable version: eg return a mutableSet as Set.

Regularly update your project dependencies to ensure you are using the latest versions and benefiting from bug fixes and new features.

Utilise code quality tools and static analysers, such as linting tools, code formatters, and static analysis tools like SonarQube or Detekt. These tools help identify potential issues, enforce coding standards, and improve code quality and safety.

Apply the visibility modifiers wisely: private, protected, internal, and public (default). Choose appropriate visibility to prevent exposing unnecessary implementation details. https://kotlinlang.org/docs/visibility-modifiers.html

Minimize Nullable Types:

  • Use nullable types ( ? ) only when necessary. Avoid overusing nullable types to ensure code clarity and reduce null pointer exceptions.
  • Utilize safe calls ( ?.) and the Elvis operator ( ?:) with nullable variables, as they can help handle null value effectively: https://kotlinlang.org/docs/reference/null-safety.html#safe-calls https://kotlinlang.org/docs/reference/null-safety.html#elvis-operator
  • Be Cautious with the !! Operator, use it only sparingly and only when you are confident in handling the potential null pointer exception
  • When declaring a variable before assigning a value, consider instead to initialise the value using conditional assignments, like when, if or try:
// with "when"
val skipCode = when (initial) {
            1 -> NO_ALERT_REQUIRED
            2 -> MANUAL_SUPPRESSION
            else -> throw UnsupportedOperationException("")
        }
// or "if"
val skipCode = if (initial == 1) {
   NO_ALERT_REQUIRED
} else if (...)
}
//or with "try:
val skipCode = try {
   getSkipCode()
} catch (e: Exception) {
   throw UnsupportedOperationException("")
}

3. Prioritize Readability and Maintainability

  • Use your common sense as well as feedback from peers' reviews.
  • While following conventions is essential, prioritize readability for a specific task. Strike a balance between convention and readability.
  • Keep your code consistent in terms of naming conventions for variables, methods, classes, packages, etc. Resolve any name clashes if needed with more detailed naming choices.
  • Ensure consistent code formatting.
  • Decide on an error handling strategy (e.g., exceptions, sealed classes, Result types) and use it consistently across the project. Handle errors in a uniform manner to ensure predictability and maintainability.
  • Establish consistent code organisation patterns: Group related functions and properties together, follow the principle of single responsibility and organise your code into logical modules, packages, and files. Maintain a consistent directory structure and naming conventions for files.

4. Classes and functions

For any class member (variable or function) that is not expected to interact with an object set them within the companion object (static from Java or Python) https://kotlinlang.org/docs/object-declarations.html#companion-objects for:

  • Equivalent to static variables or helper functions that are only needed when dealing with this class.
  • Factories, serialisation

Consider defining strongly-linked classes within the same file:

  • Data classes that are only used as return values and the class using them.
  • Collections of data classes and enums used throughout a package. And representative name: such as PackageNameDomainObjects.kt
  • Unless the file gets too long, in which case simply a dedicated package with each of these classes in their own files.

For each class try to only define a primary constructor and, if necessary, an init block. https://kotlinlang.org/docs/classes.html#constructors

And consider default arguments for overloading with these you can prevent using unnecessary secondary constructors https://kotlinlang.org/docs/reference/functions.html#default-arguments

Use sealed classes for methods that either retrieve a value or exception handling https://phauer.com/2019/sealed-classes-exceptions-kotlin/

Define utilities outside any class, as top-level functions, a standard convention is to declare them under [Package/Domain/Etc]Utils.kt file in the package. If it only belongs to a specific class, then set it within the companion object from that given class.
Agree that makes more sense to use constructors instead of builders, The Builder Pattern solves a very common problem in object-oriented programming of how to flexibly create an immutable object without writing many constructors. But when considering a builder, we should focus on whether or not the construction is complex. If we have too simple construction patterns, the effort to create our flexible builder object may far exceed the benefit. https://www.baeldung.com/kotlin/builder-pattern

5. Methods

When calling methods try always using named-arguments, especially when you are not passing all the possible arguments: https://kotlinlang.org/docs/reference/functions.html#named-arguments

For any method’s parameter validation is better to be explicit with a require block that would throw an IllegalParameterException if not met https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/require.html

require(number >= 0.0) { "Number must be non-negative" }

The check function in Kotlin is used to verify certain conditions during runtime. If the condition specified in the check block is not satisfied, it throws an IllegalStateException https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/require.html

check(b != 0) { "Division by zero is not allowed" }

When needing to return multiple values:

  • For internal functions, you can use Pairs and Triples since the creation and handling will always be in the same class
  • But public functions should instead return data classes

6. Strings

7. Operations

== for structural equality and === for referential equality https://kotlinlang.org/docs/equality.html

Use when for:

  • When you’d have used a `switch` from Java or a `case` from python.
  • Code block with more than one `if` statement
  • Structure with only boolean checks, including assignment (x = { when (y) { … } }).
val text = when (y) {
        in 1..9 -> "less than 10"
        in 10..99 -> "more than 10 but less than 100"
        >99 -> "100 or more"
        else -> "negative"
    }

8. Comments and documentation

As with everything, follow Kotlin’s coding conventions https://kotlinlang.org/docs/coding-conventions.html#documentation-comments, like avoid using `@param` and `@return` tags. Instead, incorporate the description of parameters and return values directly into the documentation comment, and add links to parameters wherever they are mentioned.

// Avoid doing this:
/**
 * Returns the absolute value of the given number.
 * @param number The number to return the absolute value for.
 * @return The absolute value.
 */
fun abs(number: Int): Int { /*...*/ }
// Do this instead:
/**
 * Returns the absolute value of the given [number].
 */
fun abs(number: Int): Int { /*...*/ }

Conclusion

In conclusion, adopting best practices in Kotlin can significantly enhance the quality, and maintainability of your codebase.

By following guidelines such as ensuring safety and immutability, keeping dependencies updated, and utilising code quality tools, you can write robust and efficient Kotlin code. Understanding language-specific features like null safety operators, visibility modifiers, and string templates further empowers you to leverage Kotlin's capabilities effectively.

By adhering to these practices, you can streamline development, improve code consistency, and create reliable and maintainable Kotlin projects for your entire team.

Author: Anna Karina Nava Soriano