When and how to use extension functions

Technology has evolved like never before in the past two decades. It grows so much and so fast that we can hardly keep up with it. Look at how some older people struggle to understand new technology. For such people, technology brought a mess just by removing the buttons or the rotary dialer from phones and replacing them with touchscreens. If technology evolution maintains this growing pace, in probably one or two decades, we, the ones that we understand today better than others, will have difficulties in grasping whatever happens around us. Our brains cannot cope with so much information.

By having more and more CPU, RAM and disk space, programming languages have also evolved. Many of today’s engineers do not write code for memory management, have difficulties when thinking about bits, talk about functional programming like their mothers had it in the placenta, do not understand what a call stack is (they know it as the “exception thing” – promising), do not write and don’t know how to write efficient code. Some of those reasons are forgivable, while others are a shame. We need to understand and accept that most cognitive work we needed to do in the past is now delegated to computers. And that’s fine since this is another step in the evolution of humankind.

Much of the cool stuff we talk about today has been approached/discovered/invented/created during the mid of the last century. Some even before that. People in the 1950s were already writing books about functional programming and even made functional programming languages for that time (see LISP). The same goes for design patterns, the ideas behind “the gang of four” being already trialed to the point where they could write a book to avoid common pitfalls.

Nowadays, programming languages have embedded paradigms and good practices, so you would never have to face the problems of the past. But here’s the tricky part: it is easy to go wrong when using a language feature without understanding it.

One of the coolest features of Kotlin is extension functions, while other programming languages have had them for years.

In my opinion, extensions functions are useful when:

  1. There’s a need to add functionality to an API and the API is restricted to extension. In Java, if there’s a need to add an additional function to a final class from a third-party library, then final forbids the use of inheritance and there are no other ways around it. With Kotlin, we can attach this function to the same Java class using the extension function feature.
  2. Syntactic sugar. Sometimes it is easier to grasp a code, cognitively speaking, when things are arranged logically.

Kotlin extension functions can be added to any type without any restrictions. And this is a problem.

During the years I’ve worked with Kotlin and Scala, I saw a real abuse of extension functions. Raise all hands if you ever added multiple extension functions in the same file, frequently named Converters or Translators, like this:

fun Employee.toResourceDto() = ...
fun Notification.toResourceDto() = ...
fun Order.toKafkaDto() = ...

Since Kotlin allows us to write such code, by doing this, we piss off some of the greatest minds in programming. Even though it works fine, there are a few things wrong with this approach:

  • It breaks the Single Responsibility Principle (and sometimes object oriented programming rules). The file contains code related to Employee, Notification and Order. Instead, it should have had only one of them. Some might say this principle applies only to classes, but it can be easily extended to such cases.
  • It breaks the Domain Design. We have three different business domain models in the file instead of a single one.
  • The methods might be too far from their related classes and domains. I’ve seen multiple projects with such a file in the application’s root package. Instead, these methods should live inside the domain packages.

I will not talk explicitly about the downsides of having such a file. I hope they can be figured out from the “wrongs” section above.

There are (at least) two ways to fix this:

  1. Moving Employee.toResourceDto() and Notification.toResourceDto() to EmployeeResource and NotificationResource (or controllers) as private methods: private fun toDto(employee: Employee) and private fun toDto(notification: Notification). The same goes for Order.toKafkaDto(), which can be moved to OrderService.
  2. Adding private access modifier to all methods and moving them inside EmployeeResource, NotificationResource and OrderService classes.

My recommendation would be to go with the second approach since it ticks all of my personal opinions about extension functions and resolves all issues mentioned above.

Sometimes the programming languages offer us too many options to do a simple thing, and, when it comes to costs, this can quickly transform into numbers ending with many zeros. If you don’t know what you are doing, try to stick to the basics. The old stuff and the lessons learned still apply today and will stick we us for a long time. If you want to enjoy the cool stuff, read (and understand!) the docs first.


Posted