3

I wanted to follow up on this previous question I asked related to @Laive comment, but I couldn't think of an excellent way to do so without asking another question, so here we go.

With the previous question in context, many users suggested the approach of creating a parameter. The object for a method that has many parameters, others pointed out that plans probably should not have many settings.

I am assuming that the technique has been engineered correctly, and it's been decided that the method should have a reasonable number of parameters, but not only 1 or 2).

Creating a parameter object, if you have optional parameters, needs to have some way in your object parameters. Declaring that a certain as the optional parameter is "not there", and then in your code, you would have to handle that case.

I researched blogs and stuff on the topic, and it seems like it is not preferred to have a method signature that accepts nulls as a flag for "optional value not present".

What is the difference between a param object with an optional field which has a "field not set" value, and a method with explicit parameters for which null is a valid value to represent "optional parameter not set", and why is one acceptable and the other not?

2
  • 2
    As you're dealing with a number of issues in this area, it seems to me like maybe your parameters are really an entity/concept that should be formally made manifest, rather than say, making your clients deal with some loosely/informally associated variables. You might then also consider a builder pattern, to construct such a parameter object/entity.
    – Erik Eidt
    Nov 8, 2019 at 17:03
  • I don't think one is worse than the other, they are basically equivalent. But with method arguments you have an alternative: overloads. This is better. You can use a private method for the generic implementation which is called by the public methods. This way you have more control, you can just throw if the client passes null for any argument. Jul 6, 2021 at 17:25

5 Answers 5

7

In general it is a bad idea to use null to indicate an optional value. It is a bad idea whether it is for a return value, an object property, a local variable or any other context. So parameters is just one case of a general rule.

So why is it a bad idea? Two reasons:

  • It is not possible in Java to indicate in the type system if null is allowed or not.
  • The default value for an uninitialized reference is null.

This means a reference can be null for two reasons:

  1. There is a bug and the value was not properly initialized
  2. It is intentionally null to indicate the lack of an optional value

Since the code can't distinguish between the two cases, a bug may go unnoticed because the code cannot distinguish a bug from the legitimate value. Therefore other approaches to indicate optional values are preferred.

Using a parameter-object with nullable properties is not really better though since you have the same issue: You don't know if the property is null due to a bug or intentionally. So I disagree with your premise that this is better.

1
  • That is a good point, I never thought of it that way; I've used null to represent "optional value not present" in my code a lot without thinking that, in a black box, "null" could be indication of a bug in code flow.
    – Ertai87
    Nov 8, 2019 at 22:11
0

The problem with using nulls is that you need to document that null is a valid value, then remember that an object could be null. Also, the person using your method will constantly have to refer to the documentation to see if that parameter could be null.

Instead, if you had a Parameters object, those optional fields could be stored as Optional<Foo> fooParam which forces the user of that parameters class to explicitly check for and deal with missing values:

// this is just sketchy code to get the idea across
class Parameters {
  // if this is specific to one method/class, you could initialize this to the default anyway.
  private Optional<Foo> fooParam = Optional.ofNullable(null);
  private final String required1;
  private final String required2;
  public Parameters(String required1, String required2) {
     // you know what to do
  }

  public void setFooParam(Foo param) {
    fooParam = Optional.ofNullable(param):
  }

  public Optional<Foo> getFooParam() {
    return fooParam;
  }
}



// somewhere else
public void myMethod(Parameters params) {
  Foo foo = params.getFooParam().orElse(DEFAULT_FOO);
  // ...
}

So no explicit checks for null and users of the code don't have to wonder what the params should be for myMethod

4
  • 2
    But according to the creator of the Optional class, Optional is intended to be used only in the return value for methods, and is explicitly not intended to be used as a parameter. How does this approach not violate that constraint (or at the very least, not obfuscate the code to make it look like the constraint isn't being violated when it actually is)?
    – Ertai87
    Nov 8, 2019 at 15:35
  • Furthermore, in your business logic (myMethod in your case), it is true you don't have an explicit null check, but you do have an implicit one (inside Optional::orElse); you're not saving anything except obfuscating away your null check to make it look like you're not doing it when you actually are; I don't understand how this solves the problem.
    – Ertai87
    Nov 8, 2019 at 15:37
  • It's not a parameter, it's a field in a class. I guess if you really wanted, you could have the fooParam be just a Foo that may or may not be null and in getFooParam() return Optional.ofNullable(fooParam) . in my opinion, the code isn't obfuscating anything, it's making things more clear by yelling to the world, THIS COULD BE NULL AND HERE'S WHAT TO DO IF IT IS.
    – Matthew
    Nov 8, 2019 at 15:38
  • 1
    Also, it solves the problem because the implementer of myMethod doesn't have to remember to check for null, the implementer MUST do something about the Optional and it's clearer to the caller of myMethod that the foo param is optional.
    – Matthew
    Nov 8, 2019 at 15:45
0

I think the main problem with nulls as optional parameters is that it makes code harder to read. For example, let's say I have a method with 6 parameters where the last 5 are optional:

foo("example", null, null, 1, null null);

Looking at that, it's not terribly easy to tell which parameter I am providing and which ones I am not. I have to count and look at the definition of the method to understand what's going on. Really there are two problems, though. Too many parameters is probably the bigger one. Anytime you have a function that takes multiple distinct parameters of the same type (i.e. not varargs), it's more difficult to mentally parse:

foo(1, 2, 3, 56, 77, 99);
foo("left", "right", "up", "down", "charm", "strange");

Since nulls are a valid value for any Object reference, they turn distinguishable parameters into indistinguishable ones. A simple example can demonstrate this last point:

Assume a function with lots of parameters like so:

void drawRectangle(Point center, Float width, Float height, Color fill, Color stroke, Scale xScale, Scale yScale);

An example of all the parameters filled in might look like this:

drawRectangle(point(3, 20), 30.0f, 20.0f, RED, BLACK, scale(0, 100, 0, 1000), scale(0, 100, 500, 0));

This is ugly, for sure. Too much going on but if I can remember that x parameters come before y and that fill comes before stroke, you can pretty quickly scan to see what the width parameter is. I know that something of type Point is the center, the dimensions are Floats, etc.

Now let's assume all of the parameters are optional where null represents a default. This is a little far-fetched for something like this but bear with me for demonstrative purposes:

drawRectangle(null, null, 20.0f, null, BLACK, null, null);

Now, when I look at this, it takes more time to make sense of what is being specified. The type information is gone because null is a valid value for any object type. I see a float in there, but which dimension is it? I need to slow down and probably pull up the definition.

2
  • 1
    I'm not sure I see how null in this case is any worse than having a method that takes a whole bunch of parameters of the same data type in the first place; you still have to cross-reference the method signature with the argument list to make sense of what's going on. Not to say the latter isn't also a problem, but given that you are prepared to do the latter, I don't see how null really exacerbates that issue.
    – Ertai87
    Nov 8, 2019 at 22:13
  • I've added a more detailed example to help illustrate the problem.
    – JimmyJames
    Nov 11, 2019 at 15:19
0

Let me start by describing two problems with Java:

  • No distinction between nullable and non-nullable types.
  • No support for parameters defaults

Non-Nullable Types

Many languages (including Java) make no distinction between types/variables that can accept a null value and ones that can't (except for primitive types like int - which can't have a value of null).

A common technique in other languages is to assume a bare type such as: String cannot accept a null value, however the type String? can be either a String or null

This system allows the compiler to provider two protections:

  • Firstly it's not possible to leave a non-nullable variable uninitialized (and/or null).
  • Secondly it's not possible to assign a nullable variable (String?) to a non-nullable variable (String)

You can achieve the same protection that other languages provide in Java by:

  • Using the Optional type, i.e: Optional<String>
  • and Not intentionally using nulls in your code base.

If you follow this pattern strictly you can avoid NPE's introduced by your code. However since you will likely have to use other libraries and the JDK itself, it is still possible for nulls to creep in from other locations.

Parameter Defaults

Other languages also allow defaults values to be set for parameters. These can typically be used in one of two ways:

  • Avoid the need to specify all parameters (assuming the remaining parameters are defaulted)
  • Use "named" parameters to only specify the values you want to pass.

Back to Your question

The primary advantage to a parameter object is that it can be passed through many levels of methods.

Perhaps you have a class that has a few public methods and a bunch of private ones, most parameters are not needed by the top level object, so you can just pass the parameter object through to the lower level methods. When you need to refactor the parameter list you can simply add a new parameter to the object and use it where it needs to be used, without having to go through a long call chain updating all the signatures.

The downside of this solution is parameter bloat - it is too easy to just keep adding additional parameters to the object and passing it around more and more places. When this happens it is no longer clear which of the parameters is actually used by a given method. Note: Some will point out that in this case your main class is two complex and should be broken into smaller classes each with their own dedicated parameter objects.

Parameter default (in other languages) help greatly with your public API - so that your callers don't have to send long lists of parameters, but they don't help you inside your class as you still have to pass a bunch of parameters around.

Another (Java specific) problem is the "double null check" in that you have to check that you have a valid pointer to the parameter object (not null) and that the actual parameter itself is not null, this is where other languages can help by not allowing particular parameters to have a null value.

Java is not inherently "less safe" than other languages - by using good standards for example leveraging the Optional class you can achieve similar levels of compiler type checking, but Java lacks the syntactic sugar of other languages, as a result you may end up with more complex method signatures to achieve the same result.

1
  • "Another (Java specific) problem is the "double null check" in that you have to check that you have a valid pointer to the parameter object (not null) and that the actual parameter itself is not null, this is where other languages can help by not allowing particular parameters to have a null value." I have no idea what you are referring to here. All parameters in Java are pass-by-value. There's no way to have a 'non-null pointer' that points to null in Java. A reference in Java either points to an object, or it's null.
    – JimmyJames
    Jul 6, 2021 at 14:25
0

The problem

What is the difference between a param object with an optional field which has a "field not set" value, and a method with explicit parameters for which null is a valid value to represent "optional parameter not set", and why is one acceptable and the other not?

Let's say you have an UpdatePerson use case. Person has many properties, some of which are Address and Employment (for the example code below, I'm going to only list these two properties for brevity's sake). You want to be able to call UpdatePerson, but only provide partial information about the person (i.e. only the fields that need actual updating). Any missing parameters should be ignored and thus remain unchanged.

Therefore, you create your method:

public void UpdatePerson(int personId, Address address, Employment employment)
{
    // ...
}

This example touches on the spirit of your approach with many optional parameters.

So how do you call this method when you want to update the person's address, but not their employment? Well, as you said:

a method with explicit parameters for which null is a valid value to represent "optional parameter not set"

Therefore, you want to do something like:

myPersonService.UpdatePerson(123, newAddress, null);

Okay, that works.

Second question, what if this person gets a new job, but does not move house? Well, similarly:

myPersonService.UpdatePerson(123, null, newEmployment);

So far, so good.

Third question. How do you call this method when the person loses their job? Therefore, their employment should be nullified (hint hint). Which leads to:

myPersonService.UpdatePerson(123, null, null);

But by applying your approach from above, how do we distinguish between not updating the address because it is null, and nullifying the employment because it is null?

When null is a meaningful value in and of itself, null cannot at the same time be used to indicate the absence of a value.

JacquesB's answer is also correct that you may want to reserve null specifically to indicate a bug, therefore also giving you a reason to not give null an intended meaning. It's the same as what I described in principle, just a different concrete example.

The problem here stems from you using the employment method parameter in two ways: as the value holder for the person's Employment property, and as an indicator for whether the UpdatePerson method should update the person's employment or not. Your meanings for null clash, and it becomes impossible to figure out which of the two meanings you're conveying.


Tangentially:

When IndexOf cannot find an occurrence, it returns -1. The reason it is able to do so is because -1 is a meaningless index value, and therefore it can be given a special meaning unrelated to the "normal" meaning of the returned integer value. If -1 were a meaningful index value, which in some languages it is (negative index = counting from the back), then IndexOf wouldn't be able to use it as a special "nothing found" value.


The solution?

You might think to yourself that you can therefore use null as a way to indicate optional parameters if null is not a valid value to be used (for Person.Employment). Technically, you could implement that. But it's a bad idea, for several reasons:

  • This requires a reader to have perfect knowledge of all accepted values for all properties at all times, before the reader can understand exactly what is happening when you call UpdatePerson(123, null, null). Which of these is being updated? Which isn't?
  • You may change the implementation in the future, suddenly (dis)allowing null to be used as a valid value in the entity. This would then force you to have to re-evaluate all usages of this method, in order to now start/stop using null to indicate the absence of an optional parameter
  • You have to use two different systems: one for properties that can be null and one for properties that can't be null.

It is much easier to steer away from using null, and instead using a different marker for presence/absence of a parameter. For example, you could wrap your values in a wrapper class that explicitly indicates absence/presence.

Forgive the C# syntax, I'm no Java dev. As far as I'm aware, I'm only using language features that also exist in Java.

public class Updatable<T>
{
    public Updatable(T value)
    {
        this.Value = value;
        this.ShouldBeUpdated = true;
    }

    public Updatable()
    {
        this.ShouldBeUpdated = false;
    }

    public T Value { get; }
    public bool ShouldBeUpdated{ get; }
}

Now you have a way to see the difference:

// Sets a new value
var setToNewValue = new Updatable<Employment>(new Employment());

// Sets to null
var setToNull = new Updatable<Employment>(null);

// Does not update, retains old value
var ignore = new Updatable<Employment>();

Which means your method can meaningfully understand what needs to be done:

public void UpdatePerson(int personId, Updatable<Address> address, Updatable<Employment> employment)
{
    var person = Get(personId);

    if(address.ShouldBeUpdated)
        person.Address = address.Value;

    if(employment.ShouldBeUpdated)
        person.Employment= employment.Value;

    Update(person);
}

This is just a basic solution, but it covers the biggest issue. Note that while I didn't particularly wrap your updatable properties in a single DTO, it's definitely worth considering doing so. I just kept it simpler for my example's purpose.


Clean coding advice

You've been given this feedback before, both in this question and the previous one, but the problem here is bigger than just what your question focuses on.

Using a silly example, if you ask "should I hold my breath when jumping in a pool of lava?", while technically not an answer to the literal question you're asking, the most sensical answer is "don't jump in a pool of lava to begin with".

Similarly, having too many method parameters is sign of a bad method design. Rather than finding ways to accommodate this design, it should be re-evaluated in its entirety so you never get to this problem area to begin with.

However, you asked your question with a very narrow scope, and it would require a broader view on the situation to find out how you should restructure your classes/methods in order to not end up with such a vast collection of optional parameters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.