Skip to main content

Command Palette

Search for a command to run...

Kotlin Code Smell 26 - if-else/when statements

Code Evolution: Conquering Complexity with Patterns

Updated
2 min read
Kotlin Code Smell 26 - if-else/when statements

First programming lesson: Control structures. Senior developer lesson: avoid them.

Problems

  • Too many decisions together
  • Coupling
  • Duplicated code
  • Violation of Open/Closed Principle.
  • A new condition should not change the main algorithm.
  • Nulls

Solutions

  1. Polymorphism
  2. Create hierarchies/compose objects following the Open closed principle.
  3. Use State pattern to model transitions.
  4. Use Strategy Pattern/Method Object to choose for branches.

Examples

  • Discrete Values
  • State transition
  • Algorithm choice.

Sample Code

Wrong

fun convertToMp3(source: Path, mimeType: String) =
    when(mimeType) {
        "audio/mpeg" -> convertMpegToMp3(source)
        "audio/wav" -> convertWavToMp3(source)
        "audio/ogg" -> convertOggToMp3(source)
        // Lots of new if-else cases
        else -> throw IllegalArgumentException("Unknown mime type")
    }

fun convertMpegToMp3(source: Path) = println("Convert from mpeg")
fun convertWavToMp3(source: Path) = println("Convert from wav")
fun convertOggToMp3(source: Path) = println("Convert from ogg")

Right

val registeredConverters = mapOf(
    "audio/mpeg" to ::convertMpegToMp3,
    "audio/wav" to ::convertWavToMp3,
    "audio/ogg" to ::convertOggToMp3,
    // Lots of other converters
)

fun convertToMp3(source: Path, mimeType: String) =
    registeredConverters[mimeType]
        ?.let { converter -> converter(source) }
        ?: throw IllegalArgumentException("No converter found")

fun convertMpegToMp3(source: Path) = println("Convert from mpeg")
fun convertWavToMp3(source: Path) = println("Convert from wav")
fun convertOggToMp3(source: Path) = println("Convert from ogg")

After Further Refactoring (Using Objects)

interface Mp3Converter {
    fun convertToMp3(source: Path)
}

class ConvertMpegToMp3 : Mp3Converter {
    override fun convertToMp3(source: Path) =
        println("Convert from mpeg")
}

class ConvertWavToMp3 : Mp3Converter {
    override fun convertToMp3(source: Path) =
        println("Convert from wav")
}

class ConvertOggToMp3 : Mp3Converter {
    override fun convertToMp3(source: Path) =
        println("Convert from ogg")
}

// Many more converters

val registeredConverters = mapOf(
    "audio/mpeg" to ConvertMpegToMp3(),
    "audio/wav" to ConvertWavToMp3(),
    "audio/ogg" to ConvertOggToMp3(),
    // Lots of other converters
)

fun convertToMp3(source: Path, mimeType: String) =
    registeredConverters[mimeType]
        ?.convertToMp3(source)
        ?: throw IllegalArgumentException("No converter found")

Conclusion

Excessive use of if-else/when statements in Kotlin can lead to code smells, making the code difficult to maintain and understand. To avoid these issues, it is essential to utilize design patterns such as polymorphism, state patterns, and strategy patterns.

You can almost always replace the if-else/when statement with a map implementation.

Credits

Kotlin Code Smells

Part 11 of 36

In this series, we will see several symptoms and situations that make us doubt the quality of our development. We will present possible solutions. Most are just clues. They are no hard rules.

Up next

Kotlin Code Smell 25 - State as Properties

Avoiding State Pitfalls

More from this blog

Yonatan Karp-Rudin | kotlin for backend developer skills | java for backend developer skills | SpringBoot | Tutorials

57 posts

Experienced Senior Software Engineer passionate about functional programming & Kotlin. Excels in app development, optimization, and team collaboration. Let's create something amazing!