Skip to content

Java utility-library designed to replace `if-else` statements and ternary operators with a featured fluent interface

License

Notifications You must be signed in to change notification settings

ciechanowiec/conditional

Repository files navigation

Conditional // Fluent if-else Replacement

1. Overview

Conditional is a Java utility-library designed to replace if-else statements and ternary operators with a featured fluent interface. The library directs developers towards expressing boolean conditional logic in a linear format, fostering clear and streamlined execution flows. By markedly reducing the cognitive complexity typically caused by if-else structures, Conditional makes the codebase more readable, simplifying testing and debugging processes.

Significantly, the library doesn’t make use of any if-else statement or ternary operator inside.

2. Quick Start

2.1. Basic Concepts

  1. The core class of the library is eu.ciechanowiec.conditional.Conditional. It can be instantiated via a static conditional(boolean describedValue) method. That method accepts a boolean argument (true/false), which becomes an immutable value described by a constructed Conditional. Described value determines behavior of a given instance of Conditional.

  2. To a given instance of Conditional multiple actions can be submitted in a fluent manner.

  3. Every submitted action is bound either to a true or false boolean value, depending on the used submission method.

  4. Submitted actions are supposed to be executed in a void manner or to be executed and return a value in the result of that execution. To achieve that, usage of Conditional is finalized via an execute(…​) or get(…​) methods respectively, that trigger actions bound to a value described by a given instance of Conditional.

  5. Conditional is lazy, which means that respective submitted actions will be triggered when and only when an execute(…​) or get(…​) methods are called, hence mere submission of an action doesn’t suffice to trigger that action.

2.2. Maven Dependency

Conditional library is extremely lightweight (~20 KB). To use it, the following Maven dependency can be added to a project:

<dependency>
  <groupId>eu.ciechanowiec</groupId>
  <artifactId>conditional</artifactId>
  <version>1.0.5</version>
</dependency>

2.3. Usage Example with Void Action

With Conditional:
import static eu.ciechanowiec.conditional.Conditional.conditional;

public static void main(String[] args) {
    conditional(10 % 2 == 0) (1)
            .onTrue(() -> System.out.println("Checked number is even")) (2)
            .onFalse(() -> System.out.println("Checked number is odd")) (3)
            .execute(); (4)
}
Checked number is even
  1. Constructs an instance of Conditional that in this case describes a true boolean value.

  2. Submits a void action to this Conditional and bounds it to a true value.

  3. Submits a void action to this Conditional and bounds it to a false value.

  4. Executes all submitted actions, bound to the value described by this Conditional, i.e. bound to a true value in this case.

Analogue with if-else:
public static void main(String[] args) {
    if (10 % 2 == 0) {
        System.out.println("Checked number is even");
    } else {
        System.out.println("Checked number is odd");
    }
}

2.4. Usage Example with Action Returning Value

With Conditional:
import static eu.ciechanowiec.conditional.Conditional.conditional;

public static void main(String[] args) {
    String evenOrOdd = evenOrOdd(10);
    System.out.println("Result: " + evenOrOdd);
}

private static String evenOrOdd(int numToCheck) {
    return conditional(numToCheck % 2 == 0) (1)
                      .onTrue(() -> {
                          System.out.println("Returning a value on true...");
                          return "Even!";
                      }) (2)
                      .onFalse(() -> "Odd!") (3)
                      .get(String.class); (4)
}
Returning a value on true...
Result: Even!
  1. Constructs an instance of Conditional that in this case describes a true boolean value.

  2. Submits an action to this Conditional and bounds it to a true value. The action prints Returning a value on true…​ and returns Even! when triggered.

  3. Submits an action to this Conditional and bounds it to a false value. The action returns Odd! when triggered.

  4. Executes a respective action submitted to this Conditional and bound to the value described by this Conditional, i.e. bound to a true value in this case. After that, returns a return value that is produced in the result of execution of the action, but cast into a specified type (into String in this case).

Analogue with if-else:
public static void main(String[] args) {
    String evenOrOdd = evenOrOdd(10);
    System.out.println("Result: " + evenOrOdd);
}

private static String evenOrOdd(int numToCheck) {
    if (numToCheck % 2 == 0) {
        System.out.println("Returning a value on true...");
        return "Even!";
    } else {
        return "Odd!";
    }
}

3. The Idea behind Conditional

3.1. Cognitive Complexity of if-else Statements

if-else statements are fundamental in controlling the flow of logic in programming. They dictate the execution path based on conditional evaluations. However, the extensive use of if-else statements, especially in complex methods or processes, significantly increases cognitive complexity. This complexity arises because each if-else block represents a decision point, and when numerous such blocks are present, programmers must keep track of multiple potential execution paths and outcomes. This requirement to constantly map and predict the flow of logic can become mentally taxing, reducing the overall readability and understandability of the code.

3.2. Amplified Complexity in Nested Statements

The cognitive complexity escalates dramatically when if-else statements are nested. Nested structures require developers to understand not just the individual conditions, but also how these conditions interact with and depend on each other. Each level of nesting adds another layer of complexity, making it more challenging to trace the logic and predict the behavior of the code. This complexity is not merely linear; it compounds with each additional layer, leading to a situation where understanding the full scope of the logic requires deep and often exhaustive analysis.

3.3. Maintenance Challenges Leading to Further Complexity

Over time, as software evolves, if-else blocks often undergo modifications and extensions. During maintenance, new conditions are frequently added to existing structures, sometimes in a haphazard or unplanned manner. This can lead to even more convoluted and chaotic logic, further increasing the cognitive complexity. Developers working on such code must not only grapple with the existing complex conditions but also with the intricacies introduced by ongoing changes. This often results in a codebase that is difficult to debug, test, and extend, as each addition or alteration has the potential to affect the logic flow in unpredictable ways.

3.4. if-else Overuse Example

A prime example of the impact of excessive if-else usage can be seen in the updateField(…​) method of the org.apache.felix.scr.impl.inject.field.FieldHandler class within the Apache Felix framework (link). This method is composed of 24 if-else statements, which are spread across 125 lines and include up to four levels of nested if-else blocks. The intricate nesting results in 20 different behavioral permutations based solely on the if-else logic. This means there are 20 distinct paths the execution flow might take within just this single method. The sheer volume and complexity of these conditional statements significantly elevate the cognitive load, rendering the method challenging to comprehend, debug, and test effectively. This case illustrates how deep nesting and overuse of if-else statements can lead to a substantial increase in cognitive complexity, adversely affecting the clarity and maintainability of the code.

3.5. Solution: Conditional

As it was explained above, the extensive and nested use of if-else statements, particularly when chaotically extended, leads to increased cognitive complexity, making the code harder to understand, maintain, and evolve. Therefore, the overuse of if-else structures, despite their necessity, can be counterproductive in complex programming scenarios.

Conditional offers a structured approach to managing conditional logic, addressing the inherent cognitive complexity of if-else statements. It provides a fluent interface that guides developers towards expressing conditional logic in a linear, more intuitive format. This approach effectively streamlines the if-else logic, making it more transparent and reducing the cognitive load associated with understanding and maintaining traditional conditional structures.

By simplifying the representation of conditional logic, Conditional helps in maintaining a cleaner and more organized codebase. It encourages writing concise and clear conditions, minimizing the chances of unwieldy and complex code patterns that often arise with standard if-else statements. This approach not only enhances the readability of the code but also eases the process of debugging and testing, leading to more maintainable software solutions.

4. Further Usage Examples

  1. Actions are executed in the order they were submitted:

    public static void main(String[] args) {
        conditional(true)
                .onTrue(() -> System.out.println("First action executed"))
                .onTrue(() -> System.out.println("Second action executed"))
                .execute();
    }
    First action executed
    Second action executed
  2. Bounding actions to true and false values can be intertwined:

    public static void main(String[] args) {
        conditional(true)
                .onTrue(() -> System.out.println("First action from TRUE"))
                .onFalse(() -> System.out.println("First action from FALSE"))
                .onTrue(() -> System.out.println("Second action from TRUE"))
                .execute();
    }
    First action from TRUE
    Second action from TRUE
  3. Instances of Conditional can be reused:

    public static void main(String[] args) {
        Conditional reusableConditional = conditional(true)
                                            .onTrue(() -> System.out.println("Hello, Universe!"));
        reusableConditional.execute();
        reusableConditional.onTrue(() -> System.out.println("How are you?"));
        reusableConditional.execute();
    }
    Hello, Universe!
    Hello, Universe!
    How are you?
  4. One can command to throw an exception if a given instance of Conditional describes a certain value:

    public static void main(String[] args) {
        conditional(9 % 2 == 0)
                .onTrue(() -> System.out.println("The number is even"))
                .onFalseThrow(new RuntimeException("The number must be even!"))
                .execute();
    }
    // throws a `RuntimeException`

    Similar effect can be achieved via a slightly different API:

    public static void main(String[] args) {
        conditional(9 % 2 == 0)
                .onTrue(() -> System.out.println("The number is even"))
                .onFalse(() -> {
                    System.out.println("I'm going to throw an exception now...");
                    throw new RuntimeException("The number must be even!");
                })
                .execute();
    }
    I'm going to throw an exception now...
    // throws a `RuntimeException`
  5. Actions can be described in multi-line manner:

    public static void main(String[] args) {
        conditional(true)
                .onTrue(() -> {
                    System.out.println("First line from true");
                    System.out.println("Second line from true");
                })
                .execute();
    }
    First line from true
    Second line from true
  6. Execution can be performed a specified amount of cycles:

    public static void main(String[] args) {
        conditional(true)
                .onTrue(() -> System.out.println("Hello, Universe!"))
                .onTrue(() -> System.out.println("How are you?"))
                .execute(2); // pass the number of cycles to execute
    }
    Hello, Universe!
    How are you?
    Hello, Universe!
    How are you?
  7. There are 4 static one-liners (see isTrueOrThrow(…​) and isFalseOrThrow(…​)) that can be used to assure that a given condition has been met and throw an exception otherwise. For instance, one can ensure that a given condition is of true value and command to throw a RuntimeException if it’s not the case:

    public static void main(String[] args) {
        Conditional.isTrueOrThrow(10 % 2 == 0, (1)
                                  new RuntimeException("The number must be even!")); (2)
    }
    1. Condition that is expected to be true. If that’s the case, nothing happens.

    2. The exception that will be thrown if the specified condition isn’t met.

      // nothing happens
  8. Basic execution and get methods of Conditional, i.e. execute(), execute(int cyclesToExecute) and get(Class<T> typeToGet), don’t specify any Exceptions in a method declaration in a throws…​ clause, although those methods are capable of throwing an Exception (the clause is omitted via SneakyThrows on the underlying action). This allows to avoid enforcing of exception handling when basic execution and get methods of Conditional are called.

    In cases, where enforcing of exception handling is, however, required, the overloaded execute(…​) and get(…​) methods can be used. Those overloaded methods will behave the same way as their basic counterparts, the only difference being that their declarations have exception lists (throws…​ clause). Those lists are dynamically defined by the passed exception classes and can be used as a mean to enforce the discussed exception handling:

    public static void main(String[] args) {
        Conditional conditional = conditional(true) (1)
                .onTrue(() -> {
                    throw new IOException();
                });
    
        conditional.execute(); (2)
    
        try {
            conditional.execute(IOException.class); (3)
        } catch (IOException exception) {
            log.error("Unable to read a file", exception);
        }
    
        String resultOne = conditional.get(String.class); (4)
    
        try {
            String resultTwo = conditional.get(String.class, IOException.class); (5)
        } catch (IOException exception) {
            log.error("Unable to read a file", exception);
        }
    }
    1. Upon execution, this instance of Conditional always throws an IOException, which is a subclass of an Exception.

    2. This basic execution method will not enforce exception handling, although it throws an IOException, which is a subclass of an Exception.

    3. This overloaded execution method will enforce exception handling. Handling of IOException will be enforced in this case, because it was passed as a parameter to the execution method.

    4. This basic get method will not enforce exception handling, although it throws an IOException, which is a subclass of an Exception.

    5. This overloaded get method will enforce exception handling. Handling of IOException will be enforced in this case, because it was passed as a parameter to the execution method.

5. Miscellaneous

5.1. API Documentation

Full API documentation of Conditional library can be found at this link: https://www.ciechanowiec.eu/conditional. The most important part is the API description of Conditional class itself: https://www.ciechanowiec.eu/conditional/eu/ciechanowiec/conditional/Conditional.html.

5.2. OSGi

Conditional library is built as an OSGi bundle, therefore it can be used in OSGi environment. Among others, it can be used within Adobe Experience Manager (AEM).

5.3. License

The program is subject to MIT No Attribution License

Copyright © 2022-2024 Herman Ciechanowiec

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.

The Software is provided 'as is', without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.

About

Java utility-library designed to replace `if-else` statements and ternary operators with a featured fluent interface

Resources

License

Stars

Watchers

Forks