5

According to Is it wrong to use a boolean parameter to determine behavior?, I know it is bad:

public void myFunction(boolean b){
    if(b){
    }else{
    }
}

and it should have separate function:

public void myFunctionTrue(){
}

public void myFunctionFalse(){
}

However, this implementation only moves the if-else from inside to outside:

public static void main(String[] s){
    boolean b=(something from input, eg:from network, or txt config);
    if(b){
        myFunctionTrue();
    }else{
        myFunctionFalse();
    }
}

which still has a if-else to determine behaviour. How can I implement this code so that it doesn't contain any if-else in order to fit the rule inside Is it wrong to use a boolean parameter to determine behavior? that no if-else to serve a boolean value at all?

For example, is there something like that:

System.callFunctionByName("myFunction"+b);

?

(but it seems result in more complex code!)

6
  • 4
    What can we tell you that the Fowler article doesn't already describe in great detail? Nov 28, 2017 at 3:56
  • How do I convert the boolean value into method name so that I can call different functions automatically?
    – ocomfd
    Nov 28, 2017 at 5:23
  • Possible duplicate of Style for control flow with validation checks
    – gnat
    Nov 28, 2017 at 6:00
  • @Ipad1gs Your example with myFunctionTrue and myFunctionFalse does not use any boolean parameters tpo determine behavior. Nov 28, 2017 at 11:28
  • 1
    The answer I gave in this post describes what chances you have to get rid 'if-else'. Polymorphism in OOP is a great way.
    – Ewald B.
    Nov 29, 2017 at 14:03

6 Answers 6

12

You say, "... this implementation only moves the if-else from inside to outside" as if that's no big deal but in fact the second example moves the if check closer to the code that knows what the boolean means and thus closer to the code that actually cares about which procedure to execute. That makes the code more clear.

It also promotes reuse in situations where the programmer can statically determine which procedure (s)he wants to execute. (S)he doesn't have to artificially create a boolean just to get the effects (s)he wants.

8

I believe that question is more about passing arguments down multiple layers. For example something like

void a(int x, boolean b) {
    doSomethingWithX(x);
    doSomethingElse(b);
}

void doSomethingElse(boolean b) {
    doY();
    doAnotherThing(b);
}

void doAnotherThing(boolean b) {
    // finally do something with b
}

bwent through 2 layers having nothing to do with the code, only to finally be handied way down the call stack. If it's only used there, why does the calling code (the code calling a) know about it? The author of the accepted answer in that question gives a couple options on how to avoid that (querying for the value of b closer to where it's used for example).

You can't avoid if-else in your code, so don't try. Do try to make your code clear within the context, and don't haul around stow-aways.

6

How can I implement this code so that it doesn't contain any if-else in order to fit the rule inside

Often you can replace scattered boolean flags interpretation with inheritance . (See https://en.wikipedia.org/wiki/Strategy_pattern).

Instead of having

class InvoiceCalculator {
    public void createInvoice(....) {
        ...
        double total = calculateOrderTotal(isB2cCustomer);
        ...
    }
    private double calculateOrderTotal(boolean isB2cCustomer) {
       ...
       if (isB2cCustomer) { .... } else { ... }
       ...
    }
}

you can implement different subclasses (or strategies) like this

class InvoiceCalculatorBase {
    public void createInvoice(....) {
        ...
        double total = calculateOrderTotal();
        ...
    }
    abstract protected double calculateOrderTotal();
}

class InvoiceCalculatorB2c extends InvoiceCalculatorBase {
    protected double calculateOrderTotal() {...}
}

class InvoiceCalculatorB2b extends InvoiceCalculatorBase {
    protected double calculateOrderTotal() {...}
}

This way you have to decide only once, which strategy to use

InvoiceCalculatorBase calulator = (isB2cCustomer) 
             ? new InvoiceCalculatorB2c(...) 
             : new InvoiceCalculatorB2b(...);
1

Passing true or false as parameter makes the code less readable than having two sensibly-named different methods. Imagine a method

public void storeObject(boolean includeDependencies) {
    ...
}

being called like myPerson.storeObject(false) you have to know the boolean's meaning to understand what's going on (I have experienced that often enough...). But having two method names storeBareObject() and storeObjectWithDependencies() makes it immediately clear.

1
  • This fails to address the concern right in the question. Nov 29, 2017 at 3:57
0

With such a mechanical view there is no possibility to determine if an if-statement is "bad" only because the given boolean value is passed in as a parameter of a method. This is only possible if you have a semantical view on the issue.

Let's say the value has a semantic that is used to control more than one behaviour (methods). The behaviours are both located in the same layer so they serve the same "responsibility". Then you may go with the strategy-pattern to enforce decoupling in the point of time of execution but coupling in the semantic evaluation.

0

Using Efficiency to Disambiguate Design Decisions

I actually let computational efficiency resolve my design decisions which might sound bad when designing interfaces for humans to use, but I use it to resolve cases of ambiguity where the pros and cons of two or more design decisions make it difficult to decide which trade-offs we accept when only considering human factors.

Consider this as a basic example:

// Locks or unlocks a mutex.
void Mutex::lock(bool on);

In practice I don't think this is so bad. It would reduce the number of methods the Mutex provides, potentially making its interface a little bit easier to comprehend, the implementation of above function should still be reasonably straightforward, and the client code, even when they don't need to branch, looks reasonably straightforward with a very symmetrical use pattern:

mutex.lock(true);
// do stuff
mutex.lock(false);

We can then compare it to the straightforward alternative:

// Locks the mutex.
void Mutex::lock();

// Unlocks the mutex.
void Mutex::unlock();

At which point our interface gets a little bit bigger but with two functions that are even simpler individually than the one before. At this point our client code would look like this:

mutex.lock();
// do stuff
mutex.unlock();

... which, in my humble opinion, is not a hands-down improvement across the board. It's not necessarily that much more straightforward in terms of the maintainability of Mutex or in terms of the client code using it. Of course it yields two simpler functions in exchange for one modestly complex, but the intellectual overhead of maintaining and using code is often as much proportional to the number of functions there are as the complexity of what the functions do.

From a maintenance standpoint an interface that offers 100 simpler methods averaging 5 LOC each isn't always going to be easier to maintain than one with 30 methods averaging 17 LOC each. The former isn't necessarily going to be easier to use and comprehend either. There's a balancing act between the simplicity of each and every thing and the number of things that have to exist. A system with 100 uber-simple things isn't always easier to maintain and use than one with 30 slightly meatier things that still have clear, comprehensible responsibilities, especially if the former version has more interactions between each and every thing.

But then if I let computational efficiency guide my decisions, it resolves the design for me:

// Locks the mutex.
void Mutex::lock();

// Unlocks the mutex.
void Mutex::unlock();

... because at this point the two functions can be implemented without branching, and in many use cases the client might not benefit at all from the branching involved with accepting a boolean parameter. So voila, there's my final design.

Minimalism

The design I least like is one that tries to provide both solutions:

// Locks the mutex.
void Mutex::lock();

// Unlocks the mutex.
void Mutex::unlock();

// Locks or unlocks a mutex.
void Mutex::lock(bool on);

... because this leads to the biggest interface possible with the most functions for clients to learn how to use and for the developers to maintain. Minimalism is thrown out the window.

People who embrace this type of mindset often end up designing interfaces whose functions and documentation scroll off the map in spite of the interface ultimately not doing that much.

Convenience of Use

Convenience of use is bottom priority for me. If using an interface requires two or three times as much trivial code as ideally possible if the interface provided more functions, I don't go back and change the design and add more functions to the interface. That violates the things I prioritize most which have more to do with minimalism and stability (designing things which find few or no reasons to change which is highly correlated with minimalism).

I'll defer any responsibility of making the interface more convenient to use in all possible scenarios to the people using them to come up with their own convenience helper library or whatever instead of convoluting my designs with their efforts towards convenience. Don't touch my minimalist interfaces and add 20 functions to make them more convenient and blow up the amount of code required to unit test unless there's actually crucial functionality missing which have nothing to do with convenience.

So anyway, that's how I go about thinking about it. It might be handy for some people. For you, I'd say don't worry if the code that uses your design has to write a bunch of if/else statements. Let them come up with a way to make that more convenient on their own. Even if you're both the user and designer of your interface, be a schizo.

Don't let all the little temptations you get from using your interface to crunch code down usage code to a minimum leak into the design of the interface. From the design side, be like Linus Torvalds maintaining the Linux kernel. You don't want to just accept random pull requests from everyone who wants to streamline their code using the kernel. Look more for reasons not to change/expand the design than to constantly change and expand the design. That is the mindset required of a minimalist. Focus on providing genuine needs of the users, not the endless wishlist of wants, since just flawlessly covering the needs is difficult enough.

Polymorphism

Of course you can also use polymorphic solutions. That could include like a table of function pointers or delegates or functors or whatever your language provides in this context. Instead of:

boolean b = ...;
if (b)
    myFunctionTrue();
else
    myFunctionFalse();

You might do:

boolean b = ...;          // some external input, like from a file
functions[b]();           // use the boolean as a key for a table 
                          // of functions

Or something to this effect:

boolean b = ...;          // some external input, like from a file
objects[b].some_method(); // use the boolean to determine what object
                          // with overriden functionality to use
                          // or potentially even create and use through a
                          // factory.

But for the lower object-oriented solution especially, you shouldn't use inheritance and polymorphism as a low-level branching mechanism. Use it when the abstractions you model actually make sense and provide benefits at a scale much wider than avoiding an if/else. Take it easy with that stuff.

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.