Kotlin Code Smell 26 - if-else/when statements

Kotlin Code Smell 26 - if-else/when statements

Code Evolution: Conquering Complexity with Patterns

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

Did you find this article valuable?

Support Yonatan Karp-Rudin by becoming a sponsor. Any amount is appreciated!