1

When writing simple or complex java code, it seems quite common to write code like this:

if (obj != null) {
    obj.someMethod();
}

Or more specific:

Foo someValue = null;
if (valueObject != null) {
    someValue = valueObject.getSomeValue();
}

Which would mean someValue stays null when the Value Object is null. Now, as I encounter this type of code quite often (and it increases cognitive/cyclic complexity), I wondered if there is some way to simplify these statements in any way. I looked for a solution which looks like:

Foo someValue = nullOrCompute(valueObject, valueObject::getSomeValue);

After some research it seemed that Java does not provide a similar feature. So, as a test, I wrote the shortcut myself, which looks like this:

public class NullHelper
{
    private static <T> boolean check(T object)
    {
        return object == null;
    }

    public static <T> void nullOrExecute(T object, Consumer<T> func)
    {
        if (!check(object))
            func.accept(object);
    }

    public static <T, R> R nullOrCompute(T object, Function<T, R> func)
    {
        return check(object)
            ? null
            : func.apply(object);
    }

    public static <T, P0, R> R nullOrCompute(T object, BiFunction<T, P0, R> func, P0 param0)
    {
        return check(object)
            ? null
            : func.apply(object, param0);
    }

    // more params
}

Note that you'd have to write your own "NFunction" Interfaces if you wanted to include versions with more params because Java does not provide TriFunctions or higher.

My Question is:

Is this code a good way to simplify a big existing codebase? And if not, is it because there is a more convenient Java feature which I don't know, or is it beacuse I need to improve my custom version (and how)?

Please keep in mind that I am not talking about the most convenient solution in general, but for a solution that can be applied to a big existing codebase.

Thank you in advance.

Raphael Tarita
  • 770
  • 1
  • 6
  • 31
  • 1
    "it seems quite common to write code" with NULL. The root cause is that you use NULL. This is a [terrible practice](https://www.yegor256.com/2014/05/13/why-null-is-bad.html) in an object-oriented paradigm and should be avoided at all costs. – Boris Sep 26 '19 at 16:13
  • 1
    @Boris So you suggest refactoring the entire large codebase to get rid of nulls? Sure, nulls aren't great, but of all terrible object-oriented practices, this is far from the worst one. NullPointerExceptions are some of the easiest problems to find and solve in typical code. – Malt Sep 26 '19 at 16:18
  • @Malt that is right, I am not looking for the perfect paradigm, but for a simple and scalable solution – Raphael Tarita Sep 26 '19 at 16:26
  • @RaphaelTarita I've put my 2 cents in... It's unpopular, just the ideas to think over. Have a look. – Andrew Tobilko Sep 26 '19 at 17:31

3 Answers3

4

Your code:

check(foo)
nullOrExecute(foo, consumer)
nullOrCompute(foo, mapper)
nullOrCompute(foo, func, param)

Equivalent code using optionals:

Optional.ofNullable(foo).isPresent();
Optional.ofNullable(foo).ifPresent(consumer);
Optional.ofNullable(foo).map(mapper)
Malt
  • 28,965
  • 9
  • 65
  • 105
  • Does this also work for methods with multiple parameters? By the way, this seems like the solution I am looking for, thank you – Raphael Tarita Sep 26 '19 at 16:29
  • @RaphaelTarita: Yes, you can use a lambda that closes over other values to pass multiple parameters into the methods you're trying to call. `Optional.ofNullable(foo).map(f -> f.getSomething(oneValue, anotherValue))`. Is that what you mean? – StriplingWarrior Sep 26 '19 at 16:42
  • 1
    Yes, thank you very much. It seems that I have found the solution I have looked for – Raphael Tarita Sep 26 '19 at 16:48
2

Consider using Optional<> instead of null to represent something that can legitimately not be present.

Optional<Bar> valueObject = getValueObject();
Optional<Foo> someValue = valueObject.map(o -> o.getSomeValue());

Optional<> has a handful of methods (map, ifPresent, orElse, and orElseGet) to take different actions depending on whether it has a value.

If you want to keep your changes restricted to smaller scopes (one method, or even one line), you can still accomplish what you're looking for without needing to write custom utility methods.

Foo someValue = Optional.ofNullable(valueObject).map(Bar::getSomeValue).orElse(null);

But I think what you'll find is that if you start this way, the Optional<> paradigm will naturally spread over time and improve the code quality of your project as a whole.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • the OP pointed out that it's a big existing codebase, so rewriting the return type of every method from `T` to `Optional` would be tedious – Andrew Tobilko Sep 26 '19 at 16:20
  • @Tobilko that is right. I knew about Optional<> (and the perks to use a non-null language like Rust), but in my opinion, this is probably way too much refactoring for the massive codebase, mostly because if you want to do it properly, the problems spread so you have to implement the technique everywhere – Raphael Tarita Sep 26 '19 at 16:23
  • You can still leverage Optional without changing method signatures. Using the `ofNullable()` method, you can still turn most of your null-checking logic into one-liners and use a more widely-accepted paradigm than writing your own utilities. – StriplingWarrior Sep 26 '19 at 16:25
  • 1
    I am pro Optional, +1. It is a clear cut, changes will not coincedently forget a null handling, as the API will change over time. Code analysis tools will warn against nullables. It is a clean refactoring you will not be able to achieve with null utils. – Joop Eggen Sep 26 '19 at 16:38
  • @RaphaelTarita: I updated my answer to show that you can still restrict your changes to a single line if that's what you want. You don't have to refactor everything. But what you describe as "the problems spread", I personally see as "the good pattern spreads to make real problems more visible." – StriplingWarrior Sep 26 '19 at 16:39
  • @RaphaelTarita it's hard to call "wrapping a value into another class just to use its methods" a [proper](https://stackoverflow.com/questions/23454952/uses-for-optional) technique... – Andrew Tobilko Sep 26 '19 at 16:43
  • @StriplingWarrior yes, I really could use it in the restricted version. And yes, it would probably be better to refactor the whole codebase to Optionals. But with the size of the codebase I am talking about, this could literally take years to refactor. I am trying to find a solution which I can use in a single location in the codebase, so the team can review the up - and downsides of the shortcut. Then we can talk about applying it in other code locations – Raphael Tarita Sep 26 '19 at 16:45
  • @RaphaelTarita: I totally understand. I'd be the last person to advocate refactoring your whole code base to stop using nulls all at once. That's why I think you should start small, changing the pieces of code that you're already planning to change. The spread of the Optional pattern will happen naturally and organically over time. – StriplingWarrior Sep 26 '19 at 16:47
  • 1
    @StriplingWarrior that's essentially the plan. Thank you for helping me out! – Raphael Tarita Sep 26 '19 at 16:49
1

Optional is faster and allows chaining of non-present fields.

Entirely with legacy nullable objects:

Foo someValue = Optional.ofNullable(valueObject).map(Bar::getSomeValue).orElse(null);

Partly:

Optional<Foo> someValue = Optional.ofNullable(valueObject).map(Bar::getSomeValue);

Ported to Optionals:

Optional<Foo> someValue = valueObject.map(Bar::getSomeValue);

Advantages:

Optional<Baz> someValue = valueObject.map(Bar::getSomeValue).map(Fuz::getSub);

someValue.ifPresent(v -> ... non-null v);

Code style checkers can now finely detect nullables and nonnullables. One may also annotate non-nullables. One may incrementally move to Optionals, just as you intended on extended null handling.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138