简体   繁体   中英

Too many if-statements

I am using Spring-Boot and Java 11 and I have a service that is becoming too bloated and hard to read because it has too many if statements. For the sake of simplicity, let us say that we have this object:

public class Holiday{
    private Enum type;
    private LocalDate date;
    private LocalTime begin;
    private LocalTime end;
    private boolean active;     
}

In my Service, I need to perform some logic if two or more holidays fall on the same day. This is where it gets complicated because I have to check different use cases.

For example:

public class MyService{

if (holiday1 == PUBLIC && holiday2 == CUSTOM) {
  if (holiday1.getDayTime() == FULL_DAY && holiday2.getDayTime() != FULL_DAY) {
      DayTimeEnum dayTime = getDayTimeOfHoliday(holiday2.getBegin(), holiday2.getEnd());
      
      if (dayTime == FULL_DAY) {
         //doSomething...             
      }
      
      if (dayTime == SECOND_HALF_OF_DAY) {
         //doSomething...         
      }
      //....and much more if-statements....  
  }
}

The code above is only pseudocode to show the nature of my problem. In my real implementation, I need to do much more checks and validations. So my code get's very messy and not reusable...

I did some research and tried to figure out alternatives that are less hardcoded and uses less if statements.

The idea would be to have some validation-rules in their own class and make them reusable everywhere. I would like to be able to chain/combine these rules, depending on the use-case.

I am trying to refactor the code in a functional approach. I would like to have a list of functions/behaviours, that I can apply and combine if necessary.

I've looked at the functional interfaces and I like the idea of combining predicates, so I would like to achieve a similar approach as this:

Predicate<String> predicate1 =  str -> str.startsWith("J");
Predicate<String> predicate2 =  str -> str.length() < 4;
List<String> result = names.stream().filter(predicate1.or(predicate2)) //I also could combine them 
with predicate1.and(predicate2)
.collect(Collectors.toList());

I thought of defining all my checks as predicates in a class and using them in different places where needed, however, predicates can only be used on collections.

Is there a better way of achieving this?

Since there is no return within the if , this suggests all of the conditions need to be evaluated. In that case, perhaps use a List<AbstractHolidayHandler> , where each concrete implementation checks the condition. For example:

class AbstractHolidayHandler {

   public final execute(...) {
      if (shouldProceed(...)) {
         proceed(...);
      }
   }

   protected abstract boolean shouldProceed(...);

   protected abstract void proceed(...);

   // shared methods for use by any implementation
   protected boolean isFullDay(...) {
      return ...
   }

   protected boolean isSecondHalfOfDay(...) {
      return ...
   }
}

With a couple concrete implementations such as:

class FullDayHolidayHandler extends AbstractHolidayHandler {
 
    @Override
    protected boolean shouldProceed(...) {
      return isFullDay(...);
    }

    @Override
    protected void proceed(...) {
       // do something...
    }
}

and: class SecondHalfOfDayHolidayHandler extends AbstractHolidayHandler {

    @Override
    protected boolean shouldProceed(...) {
      return isSecondHalfOfDay(...);
    }

    @Override
    protected void proceed(...) {
       // do something...
    }
}

then MyService simplifies to:

class MyService {
   private List<AbstractHolidayHandler> holidayHandlers;

      ...
      holidayHandlers.forEach(h -> h.execute(...);
}

Note that at least some of the conditionals should probably be part of the Holiday class itself. For example, instead of:

if (holiday1 == PUBLIC && holiday2 == CUSTOM) {

do

if (holiday1.isPublic() && holiday2.isCustom()) {

And since each implementation is isolated, it becomes much easier to unit test.

You can create a HashMap similar to a look-up table, where your keys will be FULL_DAY , SECOND_HALF_DAY etc. and values will keep your logic as some service classes.

Not necessarily a better way or anything, but don't forget that you can chain predicates together in advance to provide pipelines for later use. It can be easier to reason about things that way:

Predicate<String> predicateLongerThan4 = tested->tested.length() > 4;
Predicate<String> predicateStartsWithUpperCase = tested->tested.substring(0,1).toUpperCase().equals(tested.substring(0,1));
Predicate<String> longerThan4AndStartsWithUpperCase = predicateLongerThan4.and(predicateStartsWithUpperCase);

Oh, and don't feel too constrained by how and where you can use something. When I wrote this just now and wanted to sanity check it I used Optional.of for instance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM