Do not put elephant into function


Is it possible to put an elephant in a refrigerator? Hmmm, with the right approach, anything is possible. But why would I want to put an elephant in a refrigerator?

Source: mindimensions.medium.com/how-do-you-put-an-elephant-into-a-refrigerator-44be6da262b1

Many programmers do not ask themselves this question and pass the elephant – a large class/object to a function and then use only one or two properties of it:

function () {
   
   print .name; //only 'name' property is used!
}

Here we have a coding version:

class Elephant { //large class
   String name;
   int earsSize;
   int trunkLength;
   Jungle jungle;
   ... //more props and dependencies
}

function (Elephant elephant) {
   print elephant.name; //only 'name' property is used of large class
}

You may think: what is the problem? Let’s see.

Problems related with passing (unnecessarily) a large class to a function

  • Lack of clarity and confusion

Somewhere in code you encounter a function with Elephant (large class) argument:

validateElephant (elephant);

What do you expect?

First you have no idea what this function do. You expect some validation as the name suggest, but what exactly? You do not know.

Second, you expect that this function must do a ‘big thing’ because big object is passed into.

Let’s look what function do. It can be something like this:

void validateElephant (Elephant elephant) {
   if(isEmpty(elephant.name)) throw Exception; } 

Waaaaat? Are you joking? One single line of code? And only one property of large class is used! It confuses your brain.

  • Test complexity reaches the sky

It is a big problem. Suppose our Elephant class is complex and has many dependencies:

class Elephant {
   ...
   Jungle jungle;
   Set<Monkey> friends;
   Set<Elephant> relatives;
}

and now you have to test the original method:

validateElephant (elephant)

To test it, you have to create an elephant object, yes … with all the dependencies and their dependencies and so on! Good luck. A lot of work … and guess what? Probably no one will write a test, because the test will take huge amount of time and effort. How the test can look like:

@SuccessIfNoException
void testValidateElephant() {
   Jungle jungle = new Jungle(dependencies);
   Set<Monkey> friends = Set.of(new Monkey(dependencies))
   Set<Elephant> relatives = Set.of(new Elephant("mom", dependencies);
   Elephant elephant = new Elephant(jungle, friends, relatives);
   elephant.name = "Big Trunk";
   validateElephant (elephant);
}

Yes total mess, to only test a simple line of code.
You may say: Let’s use mocks. Ok, this is one of the solutions and in our case (simple method) it will be good. But in real projects the code is more complex and testing with mocks is also complicated because you have to track which objects and properties are used and how in order to mock correctly.

How to make it better?

Instead of passing the whole class

//original version
void validateElephant (Elephant elephant) {
   if(isEmpty(elephant.name)) throw Exception; } 

pass to function only properties that are really used:

//optimized version
void validateElephant (String elephantName) {
   if(isEmpty(elephantName)) throw Exception;
}

Why is passing property(ies) instead of the entire class a good idea?

  • Clarity and simplicity

Now, when you see invocation of optimized function:

validateElephant (elephant.name)

you are sure that the elephant name is validated!

Previously, when you saw

validateElephant (elephant)

you have no idea what function does.

  • Simple(r) testing!

Now let’s see what is needed to test optimized function:

@SuccessIfNoException 
void testValidateElephantName() {  
  validateElephant("Big Trunk"); 
}

You need only one simple line of code and the biggest benefit: test is self-explanatory!

  • Hinder refactoring

Yes, when I try to refactor some of the complex logic/functionality, big object was passed deeper and deeper in the invocation flow. I need to analyze many methods how this big object is used! Lot of effort. To refactor that code it was needed to apply this pattern in many places.

Summary

Put the elephant in the function, if it is really needed, otherwise put properties – the code will be cleaner and testing much simpler.


Leave a Reply

Your email address will not be published. Required fields are marked *