-1

Hello I am looking for a way to reduce cognitive complexity of method which has multiple if statements. All are basically nullchecks for each item of object and is required.

Code snippet:

private void assignFruit(FruitBasket1<?> fruit1, FruitBasket2 fruit2) {
        
        if (fruit1.name != null) {
            fruit2.setName(fruit1.name.toString());
        }

        if (fruit1.number != null) {
            fruit2.setNumber(fruit1.number.toString());
        }

        if (fruit1.color != null) {
            fruit2.setColor(fruit1.color.toString());
        }

        if (fruit1.region != null) {
            fruit2.setRegion(fruit1.region.toString());
        }
        
}

I have tried to use switch case but since its only null check didnt find feasible, also tried ternary operator. Is there a simpler way to do this as although in the code snippet its only few fields there are almost 20 fields which need be null checked. What are the other possibilities to do this simple manner without modifying the POJO class and managing in this method itself.

Note: There was an error in the code in the way values are set

  • 2
    There is no good answer to this. Sure, you can reduce the complexity by arbitrarily splitting this method in into two or more parts. The metric should improve ... but it doesn't improve the *actual* readability. Hence it is not (IMO) a *good* answer. So-called code quality metrics are often unhelpful ... – Stephen C Aug 15 '23 at 11:02
  • 1
    I would say that your initial design is strange. Why does class `FruitBasket1` have the same attributes as `FruitBasket2`? Maybe one class should extend the other? Why is `FruitBasket1` a generic class? If method `setName` (for example but is applicable to other `set*` methods) simply assigns a value to a class member variable, then the initial value of that member is null, anyway, so what does it matter if you re-assign it a value of null by passing null as the parameter to the `setName` method? – Abra Aug 15 '23 at 11:34
  • 1
    Your requirement doesn't make much sense to me. How does the source `fruitbasket's` attributes become null in the first place. What other alternative is there if someone doesn't set a value? Is there a non-null default value. But then, why not just set that from one `fruitbasket` to the other? – WJS Aug 15 '23 at 13:08
  • On the other hand, if you already had set some attribute and didn't want to wipe it out with null, your requirement in isolation would make sense. But then you would still permit changing attributes of existing fruitbaskets. So you could make an existing `banana` `purple` but not null. In that case, I would still test for null in the `setMethods` and set as appropriate. Barring that, I find your existing solution easy to read and solves your issue. – WJS Aug 15 '23 at 16:29
  • On a different note, I think of a `FruitBasket` as a container to hold different types and quantifies of fruit. So without knowing more about your use case, classes, etc, I would have expected a `Fruit class` where the objects would be assigned to a particular `fruit basket` whatever the latter is defined to be. – WJS Aug 15 '23 at 16:36

5 Answers5

3

If you are willing to add an additional dependency, MapStruct is a very good choice to map Beans/Dtos in a very readable manner.

You could simply add an interface and map these two objects.

In order to ignore null values, you need to set the property, nullValuePropertyMappingStrategy to NullValuePropertyMappingStrategy.IGNORE

Thus, the final code looks like:

@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE )
public interface SimpleSourceDestinationMapper {
    FruitBasket2 sourceToDestination(FruitBasket1 fruitBasket1);
}

Since, FruitBasket1 and FruitBasket2 have same attributes, it will automatically map them all except the null values.

Note: It is just a more readable version of the mapping. In terms of complexity, it is the same.

Guides to Mapstruct: https://www.baeldung.com/mapstruct

hermit
  • 1,048
  • 1
  • 6
  • 16
0

You can extract that logic by using method references for the getters and setters.

For example you can extract Fruit::getColor to a Function<Fruit, String> and squeeze the setter into a BiConsumer<Fruit, String>.

Then you can match each getter to a setter using some generic Pair such as a simple

record Pair<R,L> (R a, L b){}

Then you can store these Pairs into a List and iterate over them:

    List<Pair<Function<Fruit, String>, BiConsumer<Fruit, String>>> getterSetterPair = new ArrayList<>();
    //"connect" getters and setters here
    getterSetterPair.add(new Pair<>(Fruit::getColor, Fruit::setColor));
    getterSetterPair.add(new Pair<>(Fruit::getName, Fruit::setName));

    Fruit fruit1 = null, fruit2 = null;
    for(Pair<Function<Fruit, String>, BiConsumer<Fruit, String>>pair:getterSetterPair) {
        Function<Fruit, String> getter = pair.a();
        BiConsumer<Fruit, String> setter = pair.b();
        //this is doing the logic you did before
        String value = getter.apply(fruit1);
        if(value != null) {
            setter.accept(fruit2, value);
        }
    }

In general, I would also advise you to use a proven library such as MapStruct (see @hermit's) answer for that.

f1sh
  • 11,489
  • 3
  • 25
  • 51
0

You can use Optional<>.

// static imported Optional.ofNullable()
private void assignFruit(FruitBasket1<?> fruit1, FruitBasket2 fruit2) {
   ofNullable(fruit1.name).ifPresent(fruit2::setName);
   ofNullable(fruit1.number).ifPresent(fruit2::setNumber);
   ofNullable(fruit1.color).ifPresent(fruit2::setColor);
   ofNullable(fruit1.region).ifPresent(fruit2::setRegion);
}

You can improve it a little bit more with a static import of ofNullable().

Edit

I'm not sure, but you need to change the type somehow?
Also that is fluent possible with Optional.

// static imported Optional.ofNullable()
private void assignFruit(FruitBasket1<?> fruit1, FruitBasket2 fruit2) {
   ofNullable(fruit1.name).map(Object::toString).ifPresent(fruit2::setName);
   ofNullable(fruit1.number).map(Object::toString).ifPresent(fruit2::setNumber);
   ofNullable(fruit1.color).map(Object::toString).ifPresent(fruit2::setColor);
   ofNullable(fruit1.region).map(Object::toString).ifPresent(fruit2::setRegion);
}
akop
  • 5,981
  • 6
  • 24
  • 51
  • 7
    That this is 'less complex' than the original is in the eye of the beholder. – Arfur Narf Aug 15 '23 at 12:01
  • But this really breaks and throws a NullPointer exception for field not present, so nullable doesnt work? – developer new Aug 15 '23 at 12:06
  • 3
    Using Optional as a verbose replacement for `if` is not really an [intended use of Optional](https://stackoverflow.com/questions/23454952/uses-for-optional). – VGR Aug 15 '23 at 12:06
  • It avoids the warning and it is very smooth to read. It also a little bit safer, because you don't have to get value twice. – akop Aug 15 '23 at 12:09
  • @developernew did you used `Optional.of()`? This is not nullsafe, it has to be `Optional.ofNullable()`. – akop Aug 15 '23 at 12:10
  • @akop Sorry I have edited the question and the code part as it is handled in a different way than direct usage of fields, in that case the above solution doesnot work unfortunately. – developer new Aug 16 '23 at 09:52
  • @developernew edited. But I'm not sure, if I got your point. – akop Aug 16 '23 at 14:25
  • 1
    @developernew The code worked for what you had provided and your changes seem to further complicated the requirements. In cases like this it would be beneficial to provide the classes and a description of what you are trying to do. This has all the traits of an XY Problem. – WJS Aug 16 '23 at 17:14
0

Yes, you can also define the following method:

private static <T> void setIfNotNull(T value, Consumer<T> cons) {
    if (value != null) {
        cons.accept(value);
    }
}

And use it as follows:

    setIfNotNull(fruit1.name, fruit2::setName);
    setIfNotNull(fruit1.number, fruit2::setNumber);
    setIfNotNull(fruit1.color, fruit2::setColor);
    setIfNotNull(fruit1.region, fruit2::setRegion);
Maurice Perry
  • 9,261
  • 2
  • 12
  • 24
0

First, if these attributes are not supposed to be null, how do they get to be null in the first place? And if they are null, why not just copy them? Without fully understanding your use case, it doesn't make much sense. Especially when one wonders what the getter would return.

In any event, presuming that you never want to set the fruit in a basket if it is null, then enforce that invariant in the setMethod itself.

class FruitBasket2 {
   public void setName(FruitBasket1<?> fb) {
         if(fb.name != null) {
             this.name = fb.name;
          }
   }
   ...
   ...
}

Consider that if someone were to use your class FruitBasket, why make the user check for null before setting the value?

WJS
  • 36,363
  • 4
  • 24
  • 39