Yonatan Karp-Rudin
Yonatan Karp-Rudin

Follow

Yonatan Karp-Rudin

Follow
Kotlin Smell 013 - Companion Object Functions

Kotlin Smell 013 - Companion Object Functions

Yet another global access coupled with laziness that cannot be mocked.

Yonatan Karp-Rudin's photo
Yonatan Karp-Rudin
·Dec 18, 2022·

1 min read

Play this article

Table of contents

  • Problems
  • Solutions
  • Examples
  • Sample Code
  • Conclusion
  • More info
  • Credits

TL;DR: The companion object functions are globally available, and cannot be replaced for testing.

Problems

  • Coupling

  • Testability

  • Protocol Overloading

  • Cohesion

Solutions

  • A class Single Responsibility Principle is to create an instance. Honor it when possible.

  • Delegate the method to the instance if possible.

  • Create stateless objects. Don't call them helpers.

Examples

  • Static class initializers

  • Static class methods

  • Static attributes

Sample Code

Wrong

class DateStringHelper {
    companion object {
        private val formatter = 
            DateTimeFormatter.ofPattern("yyyy-MM-dd")

        fun format(date: OffsetDateTime): String  =
            formatter.format(date)
    }
}

fun main() {
    print(DateStringHelper.format(OffsetDateTime.now()))
}

Right

class DateToStringFormatter(private val date: OffsetDateTime) {

    fun englishFormat(): String {
        val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
        return formatter.format(date)
    }
}

fun main() {
    print(DateToStringFormatter(OffsetDateTime.now()).englishFormat())
}

Conclusion

Using the companion object method pollutes the class protocol with "library methods", which breaks cohesion and generates coupling. We should extract them with refactorings.

We cannot manipulate the companion classes and use them polymorphically, so we can't mock them or plug them into our tests.

Therefore, we have a global reference too difficult to decouple.

More info

Credits

 
Share this