Skip to main content

Command Palette

Search for a command to run...

Kotlin Code Smell 19 - Setters

Code Missteps: When Objects Play Hard to Set

Updated
2 min read
Kotlin Code Smell 19 - Setters

The first exercise that junior programmers often do involves IDEs, tutorials, and senior developers who teach them an anti-pattern.

Problems

  • Mutability

  • Information Hiding

  • Anemic Models

  • Fail Fast

  • Integrity

  • Duplicated Code

  • Concurrent programming execution

Solutions

  • Avoid Setters

  • Set essential attributes on object construction.

Sample Code

Wrong

// Anemic mutable class
data class PhoneCall(
    var origin: String? = null,
    var destination: String? = null,
    var duration: Long? = null
)

fun main() {
    val janePhoneCall = PhoneCall()
    janePhoneCall.origin = "555-5555"
    janePhoneCall.destination = "444-4444"
    janePhoneCall.duration = 60
}

Mutation brings lots of problems to your code.

fun main() {
    // Since we have a setter, we can create invalid combinations.
    // For example:
    // - We can't exchange the call destination during the call,
    // However, this is not enforced due to the setters' usage.
    val janePhoneCall = PhoneCall()
    janePhoneCall.origin = "555-5555"
    janePhoneCall.destination = "555-5555"
    janePhoneCall.duration = 60
}

// Origin and Destination cannot be the same
// To validate this, we're repeating the same code twice
class PhoneCall(
    origin: String? = null,
    destination: String? = null,
    duration: Long? = null
) {
    var origin: String? = origin
        set(value) {
            if (value == origin)
                throw IllegalArgumentException("Destination cannot be the same as origin")
            field = value
        }

    var destination: String? = destination
        set(value) {
            if (value == destination)
                throw IllegalArgumentException("Destination cannot be the same as origin")
            field = value
        }

    // duration is exposed in seconds as a ripple effect
    // this violates information hiding principle and prevents
    // us from changing its representation
    var duration: Long? = duration
        set(durationInSeconds) {
            field = durationInSeconds
        }
}

// Moreover, multiple threads (or coroutines) can change the same
// object. What is the right state of the object at the end?

Right

class PhoneCall(
    val origin: String,
    val destination: String,
    val duration: Long
) {
    // Single control point.
    // We only create valid phone calls, and they remain valid
    // since they cannot mutate.
    init {
        if (origin == destination)
            throw IllegalArgumentException("Destination cannot be the same as origin")
    }

    // We're explicit about which measure of unit is used
    fun durationInSeconds() = duration
    fun durationInMilliSeconds() = duration * MILLISECONDS_IN_SECOND

    companion object {
        private const val MILLISECONDS_IN_SECOND = 1000
    }
}

Examples

  • DTOs

Exceptions

  • Setting attributes is safe for non-essential attributes. However, it has all the drawbacks and considerations already mentioned.

Conclusion

Creating incomplete and anemic objects is a very bad practice that violates mutability, the fail fast principle, and real-world bijections.

Credits

Kotlin Code Smells

Part 18 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 18 - Arrays Abusers

Beyond Arrays: Designing with First-Class Objects

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!