Skip to main content

Kotlin Code Review

Code reviews are an essential part of the software development process. They help maintain code quality, ensure adherence to best practices, and facilitate knowledge sharing among team members. In this guide, we'll explore how to conduct effective code reviews specifically for Kotlin codebases.

Introduction to Code Reviews in Kotlin

Code review is a systematic examination of code written by one developer by other team members. For Kotlin projects, code reviews need to focus not only on general programming concepts but also on Kotlin-specific idioms and features.

Effective code reviews can:

  • Identify bugs and issues early
  • Ensure code follows team standards and Kotlin best practices
  • Share knowledge between team members
  • Improve overall code quality and maintainability
  • Foster a collaborative development culture

Setting Up a Code Review Process

1. Establish Guidelines

Before starting code reviews, establish clear guidelines that define what to look for during reviews:

kotlin
// ✅ Good - Following Kotlin naming conventions
fun calculateTotalPrice(items: List<Item>): Double {
return items.sumOf { it.price }
}

// ❌ Bad - Not following Kotlin naming conventions
fun Calculate_total_price(Items: List<Item>): Double {
var total = 0.0
for (i in Items) {
total += i.price
}
return total
}

2. Use Tools to Automate Checks

Set up static code analysis tools to catch issues before manual review:

  • Detekt: A static code analysis tool for Kotlin
  • ktlint: A Kotlin linter with built-in formatter
  • Android Studio inspections: Built-in code analysis

3. Define a Review Checklist

Create a Kotlin-specific checklist that reviewers can follow:

  • Code follows Kotlin style guide
  • Proper use of nullability and smart casts
  • Effective use of Kotlin's functional features
  • Appropriate exception handling
  • Correct usage of scope functions

What to Look for in Kotlin Code Reviews

1. Kotlin Idioms and Language Features

Check if code is using Kotlin's features effectively:

kotlin
// ✅ Good - Using Kotlin's features effectively
val user = users.find { it.id == userId } ?: return null

// ❌ Bad - Not leveraging Kotlin's features
var foundUser: User? = null
for (user in users) {
if (user.id == userId) {
foundUser = user
break
}
}
if (foundUser == null) {
return null
}

2. Null Safety

Ensure proper handling of nullable types:

kotlin
// ✅ Good - Proper null handling
fun processUser(user: User?) {
user?.let {
println("Processing user: ${it.name}")
} ?: run {
println("No user to process")
}
}

// ❌ Bad - Potential NPE or unnecessary null checks
fun processUser(user: User?) {
if (user != null) {
println("Processing user: " + user.name)
} else {
println("No user to process")
}
}

3. Immutability

Favor immutable data when possible:

kotlin
// ✅ Good - Using immutable collections
val users = listOf("Alice", "Bob", "Charlie")

// ❌ Bad - Using mutable collections when not needed
val users = mutableListOf("Alice", "Bob", "Charlie")

4. Extension Functions

Look for opportunities to use extension functions:

kotlin
// ✅ Good - Using extension function
fun String.isValidEmail(): Boolean {
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"
return matches(emailRegex.toRegex())
}

val email = "[email protected]"
if (email.isValidEmail()) {
// Process email
}

// ❌ Bad - Using traditional utility function
fun isValidEmail(email: String): Boolean {
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"
return email.matches(emailRegex.toRegex())
}

val email = "[email protected]"
if (isValidEmail(email)) {
// Process email
}

5. Function Length and Complexity

Functions should be concise and have a single responsibility:

kotlin
// ✅ Good - Small functions with single responsibility
fun getFullName(user: User): String = "${user.firstName} ${user.lastName}"

fun getUserDisplayName(user: User): String {
return user.displayName ?: getFullName(user)
}

// ❌ Bad - Doing too much in one function
fun getUserInfo(user: User): String {
var result = ""
if (user.displayName != null) {
result = user.displayName
} else {
result = user.firstName + " " + user.lastName
}
if (user.isVerified) {
result += " ✓"
}
return result
}

Real-World Code Review Example

Let's walk through a complete code review example with a simple data processing function:

Original Code

kotlin
fun processData(data: List<String>?): HashMap<String, Int> {
var result = HashMap<String, Int>()
if (data != null) {
for (item in data) {
if (item.length > 3) {
var count = result.get(item)
if (count == null) {
result.put(item, 1)
} else {
result.put(item, count + 1)
}
}
}
}
return result
}

Code Review Comments

  1. Using mutable map when immutability would work
  2. Not using Kotlin's null safety features effectively
  3. Manual iteration when functional operations would be clearer
  4. Verbose map operations instead of using built-in functions

Improved Code After Review

kotlin
fun processData(data: List<String>?): Map<String, Int> {
return data?.filter { it.length > 3 }
?.groupingBy { it }
?.eachCount()
?: emptyMap()
}

Benefits of the Improved Code

  • More concise: Reduced from 14 lines to 5 lines
  • Null safety: Properly handles null input with the safe call operator
  • Immutability: Returns an immutable Map instead of HashMap
  • Functional approach: Uses Kotlin's functional operations for better readability
  • No manual counters: Uses built-in groupingBy and eachCount functions

Providing Constructive Feedback

When reviewing code, it's important to provide feedback that is:

  1. Specific: Point to exact code locations and issues
  2. Educational: Explain why something is an issue
  3. Actionable: Suggest improvements
  4. Balanced: Highlight both positive aspects and areas for improvement

Example of constructive feedback:

I noticed you're using a for-loop to iterate through the list and manually count occurrences.
While this works, Kotlin provides more concise ways to accomplish this using functional operations.

Consider using `groupingBy().eachCount()` which automatically handles the counting logic:

```kotlin
val result = items.groupingBy { it }.eachCount()

This approach is more readable and reduces the chance of errors in the counting logic.

I do like how you've documented the function with KDoc comments - that's very helpful!


## Practical Tips for Code Reviewers

1. **Review in small batches**: It's easier to focus on 200-400 lines at a time
2. **Use automated tools first**: Let automated tools catch formatting and basic issues
3. **Focus on logic, not style**: If you have style guides and linters, focus on logic and design
4. **Ask questions instead of making statements**: "What do you think about using X here?" vs "You should use X here"
5. **Reference documentation or articles**: Link to Kotlin docs or articles that explain concepts

## Handling Common Kotlin-Specific Review Issues

### 1. Overuse of Extension Functions

```kotlin
// ❌ Inappropriate extension function - not related to the String type
fun String.createUser(): User {
return User(this, "[email protected]")
}

// ✅ Better as a regular function
fun createUserFromName(name: String): User {
return User(name, "[email protected]")
}

2. Excessive Use of let and Other Scope Functions

kotlin
// ❌ Overuse of scope functions making code hard to follow
user?.let { u ->
u.address?.let { a ->
a.city?.let { c ->
println(c.toUpperCase())
}
}
}

// ✅ Better using safe calls directly
println(user?.address?.city?.toUpperCase())

3. Incorrect Use of val vs var

kotlin
// ❌ Using var when value never changes
fun calculateTotal(items: List<Item>): Double {
var total = 0.0
for (item in items) {
total += item.price
}
return total
}

// ✅ Better using functional approach with immutable variables
fun calculateTotal(items: List<Item>): Double {
return items.sumOf { it.price }
}

Summary

Effective code reviews are crucial for maintaining high-quality Kotlin code. By focusing on Kotlin-specific features and idioms, you can help ensure your codebase follows best practices and leverages the language's strengths.

Key takeaways:

  • Establish clear guidelines for Kotlin code reviews
  • Use automated tools to handle basic issues
  • Focus on proper use of Kotlin's features (nullability, functional programming, etc.)
  • Provide specific, educational, and actionable feedback
  • Create a positive code review culture focused on learning and improving

Additional Resources

Exercises

  1. Code Review Practice: Review the following code snippet and list improvements:
kotlin
fun fetchAndProcessData(url: String) {
var data = null
try {
data = fetchData(url)
} catch (e: Exception) {
println("Error: " + e.message)
return
}

var results = ArrayList<Result>()
for (item in data) {
var result = processItem(item)
if (result != null) {
results.add(result)
}
}

for (result in results) {
saveResult(result)
}
}
  1. Refactor Challenge: Refactor this function to make better use of Kotlin features:
kotlin
fun findLongestString(strings: List<String>?): String {
if (strings == null || strings.isEmpty()) {
return ""
}

var longest = ""
for (str in strings) {
if (str.length > longest.length) {
longest = str
}
}
return longest
}
  1. Create a Code Review Checklist: Develop a personal checklist with at least 10 items to use when reviewing Kotlin code.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)