Optional Method Parameters
There are some valid arguments against using Optional as a method parameter type, but they are not good enough to say that you should avoid it at all costs.
Join the DZone community and get the full member experience.
Join For FreeI have recently come across an interesting problem: should you use Optional as a method parameter type? The majority of sources says that you shouldn’t, but let’s weigh the arguments before making a verdict.
Why Not Just Pass Nulls Around?
Before we get into the case of using Optional, we should understand why we even have this problem. We could just pass nulls around, right? Well, not quite. As I’ve written in my post about null handling, you should never pass null as an argument to a public method. It’d require you to read all the code down the call stack to make sure that null value is actually supported and won’t blow the application. And once you’d do that, the callee code could not be modified in any way that does not support null, which is a rather unwanted constraint. Therefore, you need another solution in case one of the parameters is missing.
Now, that we’re done with nulls, let’s evaluate using the Optional type.
“It Was Not Meant to Be Used Like This”
The majority of articles that I read, and even my fantastic IDE point out, that Optional as a method argument is bad because the original intent of creating Optional was to:
[..] provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”.
I’m sorry to say this, but this is a really bad argument. If there would be arguments for using Optional as a parameter type that outweigh the arguments against, it would only mean that the class is better than it intended to be and everyone should be happy. It’s like saying that you should not use a bread knife to open a package in your kitchen because the bread knife was not meant to do that.
“It Causes Conditional Logic Inside Methods”
The source of this argument is the top answer in a related question on StackOverflow. To quote the original author:
Using Optional parameters causing conditional logic inside the methods is literally contra-productive.
This argument refers to a situation when you use Optional as a method parameter just to do conditional logic based on its presence:
public void myMethod(Optional<String> optionalArgument) {
// some code
if (optionalArgument.isPresent()) {
doSomething(optionalArgument.get());
} else {
doSomethingElse();
}
// some code
}
It is an argument, but a limited one. The first limitation that I see is that oftentimes you would not do any conditional logic on the argument — you just want to pass it through:
public void myMethod(Optional<String> optionalArgument) {
// some code
doSomething(optionalArgument);
// some code
}
The second limitation is that the only reasonable alternative that you have in most cases is method overloading. Then, if a client has a potential null on his side, he’s forced to do the conditional logic himself:
public void myClient() {
// some code
String optionalArgument = ...
if (optionalArgument == null) {
myMethod();
} else {
myMethod(optionalArgument);
}
// some code
}
This, depending on the case might be a good thing or a bad thing.
“It’s Still Perfectly Possible to Pass in Null to a Method”
That’s a quote from another StackOverflow answer. I’d say that it can be an argument against Optional in general, but not solely against Optional arguments, as you can return a null instead of Optional, too. As a general argument, it still seems weak, as we’re not talking about a dangerous toy for a kid, we’re talking about programmers (smart guys, you know?). If we followed this logic, we’d have to say that using Java is unsafe as you can do almost anything via reflection. Would that stop you from using Java? Obviously not.
“Function Does More Than One Thing”
Ah, us clean coders! Such a good movement, but sometimes we tend to overdo things. But yes, that could be a code smell, especially if that one thing would be the conditional logic we talked about earlier. I’m not so sure if it would be so evil if the logic looked like this:
public void myMethod(Optional<String> optionalArgument) {
// some code
String argument = optionalArgument.orElse("reasonable default");
doSomething(argument);
// some code
}
Does it still do more than one thing? Maybe. Is that bad for your codebase? Hard to judge, you’d have to consider other forces.
“The Client Code Has to Explicitly Wrap”
Now, we’re on the client side of things and it seems like a valid argument. If the client has the value and knows that it’s surely not null, wrapping it into an optional might seem like a waste.
public void myClient() {
// some code
String argument = ...
myMethod(Optional.of(argument));
// some code
}
On the other hand, we’ve shown already that if the client has a potential null in hand, he might get even more complexity if we opt for overloading. Just look at the first client example above and compare it to this one:
public void myClient() {
// some code
String optionalArgument = ...
myMethod(Optional.ofNullable(optionalArgument));
// some code
}
Also, don’t forget that the client can leverage static imports to control the complexity of the invocation.
To conclude this argument, know your clients! A safe approach would be to choose one depending on the client’s needs. If you don’t know all your clients e.g. in a case of library code, you should probably stick to overloading.
“[..] Imagine Having Two or Three”
Daniel Olszewski, in his article titled Java 8 Optional Use Cases, argues that the case of Optional method parameters is much worse in the case of multiple such parameters. My intuition suggests that this could be the case, but I can’t come up with a good example. He does not provide any examples or reasoning for such state of things, just:
Uncle Bob definitely wouldn’t be proud of such code
All due respect to Uncle Bob, that’s not a valid argument. I’d say that you should apply common sense and other known guidelines to figure out what’s the best in such case.
We’re done with the arguments against Optional method parameters that I found online. Let me augment those with some of my recent reasoning.
Pass-Thru Optional
Imagine that one of your API parameters is optional and it’s represented by a null. The data from your API is supposed to go from the controller, through application and domain services, down to the depths of your domain model. And it’s only the domain model that knows the “reasonable default” for that field, e.g. null, which you shouldn’t pass around for some boundary value like MAX_INT. Obviously, you could just make this default value a public constant and pass it in the controller. But do you really want to couple the controller to an implementation detail of your domain class, especially if that would mean passing a null around? I don’t.
This is the case that we’ve already seen in the argument about conditional logic. The Optional is supposed to pass through down the call stack to be handled by the class that actually knows what to do it. In our case, it would look like this:
class Controller {
ApplicationService service;
void method(String potentialNull) {
// stuff
service.method(Optional.ofNullable(potentialNull));
// stuff
}
}
class ApplicationService {
DomainService service;
void method(Optional<String> argument) {
// stuff
service.method(argument);
// stuff
}
}
class DomainService {
void method(Optional<String> argument) {
// stuff
DomainObject object = ...
object.method(argument);
// stuff
}
}
class DomainObject {
void method(Optional<String> potentialArgument) {
String argument = potentialArgument.orElse("reasonable default");
// stuff
}
}
I will skip the overloading version for brevity (you can see it in this gist), but it’s much longer and less expressive. Instead of stating clearly in the code: This parameter is optional, we’re adding the complexity of multiple analogous overloads. And it’s just for one such parameter, “imagine having two or three!”
What’s More Testable?
This question is the reason why I started the research and then decided to write this article. If we consider overloading the right alternative to Optional method parameters, we might end up with a lot of unnecessary tests.
Let’s say that our method (and the private ones that it calls) has two testable side-effects and one error case in which it throws an exception. If I were to write tests for this method, it would look more or less like this:
def "myMethod has side effect 1" {
when:
myMethod(ofNullable(argument))
then:
// assert side effect 1
where:
argument << [null, "some string"]
}
def "myMethod has side effect 2" {
when:
myMethod(ofNullable(argument))
then:
// assert side effect 2
where:
argument << [null, "some string"]
}
def "myMethod throws an exception when something" {
given:
// some bad state
when:
myMethod(ofNullable(argument))
then:
thrown SomeException
where:
argument << [null, "some string"]
}
That’s already a decent amount of code. Now, imagine testing the two overloaded methods… I don’t want to make you scroll down in annoyance but if someone wants to see it, I created a gist. And with all that being said, let’s once again “imagine having two or three.”
Conclusion
There are some valid arguments against using Optional as method parameter type, but to me, they are not good enough to be able to say with confidence that you should avoid it at all costs. And don’t forget, there are also arguments for using Optional this way. I’d say that you should keep each of them in the back of your head and evaluate things on a case by case basis. Like always, it’s an it depends problem, and it requires a bit of programming sense.
If you have any additional arguments for or against using Optional as method parameter type, let me know in comments!
Published at DZone with permission of Grzegorz Ziemoński, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments