In a Wonderland, Developers get more intellectual continuously. The code we wrote today ought to be superior to the code we wrote a year or two ago. Now-a-days we have better tools, more mature and modern programming language. Moreover, We possess better code style and practices than we had before.

However, We realize this often in life that we are not in Wonderland. We have probably made our own Dependency Injection or used a tool like Dagger or Koin. Now, If you look at a random class in a codebase, how many fields are you injecting? If you have a class from a complex feature, the code might look like this:

class UserProfilePresenter @Inject constructor(
        private val mainScheduler: Scheduler,
        private val ioScheduler: Scheduler,
        private val profileApi: ProfileApi,
        private val userRepository: UserRepository,
        private val analyticsHandler: AnalyticsHandler,
        private val errorReporter: ErrorReporter,
        private val referrerTracker: ReferrerTracker,
        private val shareTracker: ShareTracker,
)

If you have so many fields in your constructor, calling this constructor would be a pain in the butt. No one would want to provide this many parameters. Yes, it is easy with dependency injection, But you get monolithic class constructors. And, We developers don’t give a damn about the same either. Well, It’s a bad practice if you ask me. It severely affects code readability.

Proof

Let’s assume that you are using manual injection, or a good injection tool like Dagger or Koin, You would notice that you have to write code something like this:

// Koin as dependency injection
UserProfilePresenter(get(), get(), get(), get(), get(), get(), get(), get() )

You won’t have a single clue what you are passing into the constructor without opening the class itself. Furthermore, writing a line like this can make a complex class insanely big. Just imagine having a class like this with more than 15 parameters. It is insanity!!!

Moreover, Writing Tests for a constructor like that is not so pleasant or enjoyable Because you have to mock all those instances and then implement your test case for classes. Large constructors have always been a huge code smell for me. We are violating the very principle of Single Responsibility. If the class needs a high number of parameters, Then it is most likely a risk of the class doing more than one thing. In the book I have been reading, Clean Code by Uncle Bob, He argued that:

The ideal number of parameters for a function should always be zero. Next comes one followed by two. More than three or four arguments requires a special justification.

If you have more than three parameters in your code, Ask yourself a question that is it a clean code? You would now ask me how to reduce the number? Well, one solution is the well-known Builder Pattern. However, What about the code complexity the Builder Pattern introduces for us?

Now, If you ask me, I would suggest that there should not be more than 4 parameters for constructors.

Lift out common parameters

In the example above, I was injecting 2 schedulers:

private val mainScheduler: Scheduler
private val ioScheduler: Scheduler

Let’s lift those schedulers in an interface:

interface TaskSchedulers{
    val ioScheduler: Scheduler
    val defaultScheduler: Scheduler
    val mainScheduler: Scheduler
}

We can do the same for trackers and analytics fields in the constructor. This time, let’s create a class instead of an interface and expose the methods we need.

class ProfileTracker(
        private val analytics: Analytics,
        private val referrerTracker: ReffererTracker,
        private val shareTracker: ShareTracker
){
    fun trackWhenProfileOpened(referrer: String = "Main Menu"){
        referrerTracker.profileWasOpend(referrer)
    }
    fun trackWhenProfileShared(){
        shareTracker.profileWasShared()
    }
    // and you get the idea.
}

The general rule here is Divide and Conquer. Split up common dependencies until you end up at some point where you don’t have much to split and extract. However, At this point make sure to revisit the class and make sure it follows Single Responsibility principle. I know some of you don’t like splitting things and have multiple interfaces or classes lying around in the codebase. Well, for that, It’s purely depending upon your taste and architecture. You can either combine them into one or make something meaningful that is easy to read.

Those large constructors normally grow over time and end up being the monster we saw. To be sure to not let them stay in your codebase. You can at least try these things to verify you do not have left out code to create legacy monsters.

  • Attempt to stay below 4 parameters in your functions or constructors.
  • Divide and Conquer. Split the similar things to be together.
  • Verify all your classes and functions have Single Responsibility.
  • KEEP SOME SANITY AND DON’T CREATE MONSTERS.

Thanks for reading this article, See you in the next one.