While I’ve rallied against painting all code with
final in Java, and have accepted that it’s handy to have
The rule is that a function should return something. This can be enforced, to a degree by treating all variables as immutable constants, including inputs to a function. With this approach, which underpins thread-safe functional programming, you’re structurally less likely to have bugs.
However, there are a couple of caveats about solving this problem with things like
- Sometimes it’s a reasonable thing to ask a function to accumulate its result into an existing container – like an array or map, because using a cloning approach to make a new one to return would be cumbersome
- Making a reference constant does not guarantee immutability, so you can appear to be taking things seriously, with the constant keyword all over the place, while still violating the general principle
Why Functions Should Return Something
If a function operates on one its inputs alone and returns a value, then:
- It probably has one responsibility
- It’s very testable
- It’s easier to understand
Conversely, if the function does all sorts of things, perhaps forwarding to the next one, in a chain of calls, then it’s harder to understand, debug and test… it’s probably harder to get right. It may also waste memory while it’s doing it.
Attacking the Inputs
Notwithstanding the idea that we may (occasionally) be passing an object down to a function for it to accumulate values into. We should be looking at how to avoid modifying the input parameters.
- Don’t redefine an input parameter – if you need to iterate on something for which the input is the first one, then use an iterate to track current, rather than rewrite the input variable in a loop
- Don’t mutate the inputs as this means you have a backwards relationship with your caller, who may naively assume they can reuse their input, but can’t – this may be unavoidable in some cases, but those cases should be clearly advertised and rare
The general rule is that it’s a special assumption that you control the state of an input object.
In a recent example, a calling function was using an iterator on a list while the function it called was receiving both the current item and the iterator. The subroutine was deleting items from the iterator as well as using the current item.
We could not explain to ourselves why this algorithm worked, though it appeared to.
Short, testable, single responsibility, testable functions use their inputs wisely.