loading...
Cover image for Object Oriented Tricks: #3 Death By Arguments

Object Oriented Tricks: #3 Death By Arguments

voidmaindev profile image Arun Sasidharan ・4 min read

OOT is a mini series on writing maintainable Object Oriented code without pulling your hair out.

Arguments Arguments Arguments

  • Lengthy list of function arguments are tedious for the calling code to use. It also makes it all too easy to pass arguments in the wrong order with little type safety and can reduce readability of the code.
  • They are hard to read and hard to understand till you double take on the function signature every time you call it.

“Each one can confuse and confound, breaking your flow as you’re reading down the code, causing you to double-take.” – Uncle Bob Martin

  • Arguments make it harder to test a function. It’s difficult to write all the test cases ensuring all the various combinations of arguments work properly.

So rather than using them recklessly as conveniences, we should apply a significant amount of restraint and treat each argument as a liability.

Structure

The best functions have as few arguments as possible. Ideally zilch.
niladic > monadic > dyadic > triadic > polyadic
The same guidelines apply for constructors.

Always Be Refactoring

  • Argument Objects: If you pass three or more arguments in your functions, then stop and think. If three or more variables are so cohesive that they can be passed together into a function, why aren’t they an object? https://www.refactoring.com/catalog/introduceParameterObject.html
  • Overloading: One reason to overload functions is so that a client can call the appropriate version of the function for supplying just the necessary parameters.
  • Builder Pattern: Sometimes function overloading goes out of hand and leads to a situation called a Telescoping Constructor anti-pattern. To avoid this, in the Second Edition of Effective Java, Josh Bloch introduces use of the builder pattern for dealing with constructors that require too many parameters.
  • Mutable State: Not Recommended Perhaps the best known and most widely scorned approach in all of software development for using state to reduce parameter methods is the use of global, instance variables. Although not the best solution, but may be appropriate in some cases. Use with caution especially in highly concurrent programs.

“Any global data is always guilty until proven innocent.” – Martin Fowler

Death By Booleans

Most of the time when you pass a boolean into a function, you are loudly declaring to the world that you’ve written a function that does two things. One thing for the true case and another for the false case. Instead, you should have written two functions, one for each case.

A boolean hanging out in the argument list can be a huge source of error and confusion. What does it mean if it’s true? What does it mean if it’s false? If the name of the function and the argument fail to make this perfectly clear, then every time you use this function, you need to dive into the implementation details to understand the right value to be passed. Passing in two booleans is even worse. A function that takes two booleans does four things!
Try guessing the booleans, their order and the behaviour:
IMAGE ALT TEXT HERE
Makes you squint, doesn’t it? Avoid guesses and double-takes by using the Builder Pattern:
IMAGE ALT TEXT HERE

The Null Defense

Passing null to a function or writing a function that expects null to be passed in is almost as bad as passing a boolean into it. In fact, it’s even worse as it is not at all obvious that there are just 2 possible states.

Ofcourse, there is behaviour for the non-null case and one for the null case. A better approach is to create two functions, one that takes a non-null argument and the other that doesn’t take that argument at all. Don’t use null as a pseudo-boolean.

gif

Listen to Gandalf. Null args shall not pass.

Let’s be honest, defensive programming sucks. It’s horrible to litter code with null checks and error checks. We don’t want to think that our teammates carelessly allowed to slip null into our functions. It also means that we don’t trust our unit tests that prevent us from passing that null.

This doesn’t apply for public APIs, as we don’t know who is gonna pass what. If your language supports the @NotNull annotation or a similar concept, use it to mark your public functions and you can fail fast by throwing something like an IllegalArgumentException.

I hope this helps you avoid writing error-prone code as it has helped me. What are your views? This post was inspired by Uncle Bob and his book Clean Code.

Originally posted on Hackernoon

Check out the next trick here.

Posted on by:

voidmaindev profile

Arun Sasidharan

@voidmaindev

Passionate about technology, philosophy and music

Discussion

markdown guide
 

I think it's acceptable to use many optional arguments and booleans in languages that support named arguments (like Python). It's essentially natural support for the builder approach:

progressDialog.show( title="My title", message="Hello", indeterminate=false, cancelable=true, cancelListener=listener )

In lieu of named arguments I also like using an enum instead of a boolean.

//yucky boolean
show( "Message", true )

//with an enum
show( "Message", dlg_cancellable )

It's also forward compatible with adding more options to the function.

I don't necesarily agree that a boolean always implies two different functions. Quite often a boolean triggers only a minor change in the algorithm. It's often better for documentation and discoverability to just have the one fucntion with an option isntead of two differently named functions.

 

Most of the time when you pass a boolean into a function, you are loudly declaring to the world that you’ve written a function that does two things.

Might be my biggest takeaway from this post. While I don't think it applies in all cases (nor do I think you are suggesting it does), I think it may apply often enough that I should start seeing it as a "code smell".

 

This was my take away as well. Not always an issue, but enough to make you give it some thought when you put it in there.

 

Actually, in the front-end, we'll rather take an options object that immediately documents the parameters for you:

myFunction({
  title: 'title',
  message: 'message',
  indeterminate: false,
  cancelable: true,
  listener: callback
});
 

The most common mistake I see is methods that only take one object as a parameter. This is normally a sign the method should be on the parameter's class not the one it is actually on.

 

Interesting point.

There are lots of situations where this is not a great idea. It's too difficult to maintain as a rule when other dependencies required within the function come into play.

However, your point that it should be considered as a place for the method is a great one. It should lead to the consideration of Value Objects.

 

I see null parameters everywhere, in js and in Java, and it's horrible. I always wonder why they did not make an overload with that argument omitted.

 

This is all really great advice!
I've been taking named parameters and default parameters for granted, but those two language features take care of nearly all of these problems.