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.
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:
ProgressDialog.show("title", "message", false, true, listener)
Makes you squint, doesn't it? Avoid guesses and double-takes by using the Builder Pattern:
java ProgressDialog dialog = ProgressDialog.Builder() .setTitle("title") .setMessage("message") .setIndeterminate(false) .setCancellable(true) .setCancelListener(listener) .build();
The Null Defense
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.
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
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.