12
var a = 1;

function myFunction() {
    ++a;
    return true;
}

// Alert pops up.
if (myFunction() && a === 2) {
    alert("Hello, world!");
}

// Alert does not pop up.
if (a === 3 && myFunction()) {
    alert("Hello, universe!");
}

https://jsfiddle.net/3oda22e4/6/

myFunction increments a variable and returns something. If I use a function like that in an if statement that contains the variable which it increments, the condition would be order-dependent.

Is it good or bad practice to do this, and why?

clickbait
  • 2,818
  • 1
  • 25
  • 61
  • 5
    for the sake of readability I'd try to avoid side effects like that - but it might be helpful if you'd provide a more detailed use case – msrd0 Dec 20 '17 at 05:10
  • 4
    The code has almost no practical use. It is SO bad in so many ways it defies description. It's as though you never heard of (1)Abstraction. ... (2)Inheritance. ... (3)Polymorphism. Call a function to do a job, Call it whenever you need it,The function should return a externally consistent answer. – Jon Goodwin Dec 20 '17 at 05:42
  • 5
    @JonGoodwin I don't see what this has to do with either inheritance or polymorphism. – Bergi Dec 20 '17 at 05:48
  • 1
    Its not a bad practice. Consider the function time(). I also have several real world examples below. – danny117 Dec 27 '17 at 22:56
  • 1
    Just thought of this. consider the function moveNext() on a database. It moves something. Not a bad practice because rows can be added before the process reading the rows finishes it makes the getRow(n) function obsolete. – danny117 Dec 27 '17 at 23:09

6 Answers6

14

Conditions are order-dependent whether you change the variables used in the condition or not. The two if statements that you used as an example are different and will be different whether you use myFunction() or not. They are equivalent to:

if (myFunction()) {
   if (a === 2) {
     alert("Hello, world!")
   }
}

// Alert does not pop up.
if (a === 3) {
   if (myFunction()) {
     alert("Hello, universe!")
   }
}

In my opinion, the bad practice in your code is not the fact that you change the condition's operands value inside the condition, but the fact that your application state is exposed and manipulated inside a function that does not even accept this state changing variable as a parameter. We usually try to isolate the functions from the code outside their scope and use their return value to affect the rest of the code. Global variables are 90% of the time a bad idea and as your code base gets larger and larger they tend to create problems that are difficult to trace, debug and solve.

Roumelis George
  • 6,602
  • 2
  • 16
  • 31
  • 2
    To add to this, the order-dependence of conditions is sometimes useful for optimization. We call it short-circuiting. I suggest reading the answer to [this question](https://stackoverflow.com/questions/9344305/what-is-short-circuiting-and-how-is-it-used-when-programming-in-java) for a quick explanation of short circuit evaluaton. – Brishna Batool Dec 26 '17 at 09:25
5

It's bad practice, for the following reasons:

  • The code is far less readable than well-constructed code. This is very important if the code is later examined by a third party.

  • If myfunction is changed later, the code flow is completely unpredictable, and might require a major documentation update.

  • Small and simple changes can have drastic effects on the execution of the code.

  • It looks amateur.

JMP
  • 4,417
  • 17
  • 30
  • 41
5

If you have to ask, it's hardly a good practice. Yes, it's a bad practice for exactly the reason you mentioned: changing the order of operands of a logical operation should not affect the outcome, and therefore side effects in conditions should generally be avoided. Especially when they are hidden in a function.

Whether the function is pure (only reads state and does some logic) or whether it mutates state should be obvious from its name. You have several options to fix this code:

  • put the function call before the if:

    function tryChangeA() {
        a++;
        return true;
    }
    
    var ok = tryChangeA();
    if (ok && a == 2) … // alternatively: if (a == 2 && ok)
    
  • make the mutation explicit inside the if:

    function testSomething(val) {
        return true;
    }
    
    if (testSomething(++a) && a == 2) …
    
  • put the logic inside the called function:

    function changeAndTest() {
        a++;
        return a == 2;
    }
    
    if (changeAndTest()) …
    
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    I think actually only your first suggestion is a good one. (put the function call before the if) the other two still mix a condition testing with state changing. A predicate should NEVER change state. – Tiago Coelho Dec 23 '17 at 16:59
  • 2
    @TiagoCoelho I agree that the first is the best, but I wouldn't be as absolute as saying "never". Sure `changeAndTest` is not a pure predicate any more, but there are patterns (especially with loops) where you do things like `regex.match(string)` or `i--` in a condition, and I don't see much advantage in writing `const ok = changeAndTest(); if (ok) …` over the more concise variant without the temporary variable. – Bergi Dec 23 '17 at 19:10
5

MyFunction violates a principle called Tell, Don't Ask.

MyFunction changes the state of something, thus making it a command. If MyFunction succeeds or somehow fails to increment a, it shouldn't return true or false. It was given a job and it must either try to succeed or if it finds that job is impossible at the moment, it should throw an exception.

In the predicate of an if statement, MyFunction is used as a query.

Generally speaking, queries should not exhibit side-effects (i.e. not changing things that can be observed). A good query can be treated like a calculation in that for the same inputs, it should produce the same outputs (sometimes described as being "idempotent").

It's also important to know that these are guidelines to help you and others reason about the code. Code that can cause confusion, will. Confusion about code is a hatchery for bugs.

There are good patterns like the Trier-Doer pattern which can be used like your code example, but everyone reading it must understand what's happening though names and structure.

Greg
  • 404
  • 4
  • 15
  • 1
    Your going to have a really hard time with the moveNext() database function. It manipulates internal pointers and returns different results each time its called. – danny117 Dec 27 '17 at 23:14
3

The code presents more then one bad practice actually:

var a = 1;
function myFunction() {
    ++a; // 1
    return true;
}

if (myFunction() && a === 2) { // 2, 3, 4
    alert("Hello, world!")
}

if (a === 3 && myFunction()) { // 2, 3, 4
    alert("Hello, universe!")
}
  1. Mutates a variable in a different scope. This may or may not be a problem, but usually it is.

  2. Calls a function inside an if statement condition. This do not cause problems in itself, but it's not really clean. It's a better practice to assign the result of that function to a variable, possibly with a descriptive name. This will help whoever reads the code to understand what exactly you want to check inside that if statement. By the way, the function always return true.

  3. Uses some magic numbers. Imagine someone else reading that code, and it is part of a large codebase. What those numbers mean? A better solution would be to replace them with well named constants.

  4. If you want to support more messages, you need to add more conditions. A better approach would be to make this configurable.

I would rewrite the code as follows:

const ALERT_CONDITIONS = { // 4
  WORLD_MENACE: 2,
  UNIVERSE_MENACE: 3,
};

const alertsList = [
  {
    message: 'Hello world',
    condition: ALERT_CONDITIONS.WORLD_MENACE,
  },
  {
    message: 'Hello universe',
    condition: ALERT_CONDITIONS.UNIVERSE_MENACE,
  },
];


class AlertManager {
  constructor(config, defaultMessage) {
    this.counter = 0; // 1
    this.config = config; // 2
    this.defaultMessage = defaultMessage;
  }

  incrementCounter() {
    this.counter++;
  }

  showAlert() {
    this.incrementCounter();
    let customMessageBroadcasted = false;
    this.config.forEach(entry => { //2
      if (entry.condition === this.counter) {
        console.log(entry.message);
        customMessageBroadcasted = true; // 3
      }
    });

    if (!customMessageBroadcasted) {
      console.log(this.defaultMessage)
    }
  }
}

const alertManager = new AlertManager(alertsList, 'Nothing to alert');
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
  1. A class with a precise function, that use its own internal state, instead of a bunch of functions that rely on some variable that could be located anywhere. Whether to use a class or not, it's a matter of choice. It could be done in a different way.

  2. Uses a configuration. That means that would you want to add more messages, you don't have to touch the code at all. For example, imagine that configuration coming from a database.

  3. As you may notice, this mutates a variable in the outer scope of the function, but in this case it does not cause any issue.

  4. Uses constants with a clear name. (well, it could be better, but bear with me given the example).

  • 1
    I don't think there's anything wrong with using a function call as a predicate as part of a conditional statement, as long as the function really is a pure function. Introducing a local symbol solely as a place to store a function result seems much worse. – Pointy Dec 26 '17 at 14:39
  • 1
    In my opinion using a function call in a conditional statement is confusing. I prefer to assign the result to a variable with a descriptive name. For example: `const hasFuel = driveCar(1000); if (!hasFuel) { refill(); }` – Giacomo Cosimato Dec 26 '17 at 16:01
  • 1
    This doesn't need OOP to fix the underlying problem. Before you break out OOP, look at the core of the problem. The original problem can be solved with functional programming. Adding architecture is a separate issue. – TinkerTenorSoftwareGuy Dec 26 '17 at 18:27
  • 1
    "Whether to use a class or not, it's a matter of choice. It could be done in a different way." – Giacomo Cosimato Dec 26 '17 at 22:51
1

A function that changes stuff. What is the world coming too? This function must change stuff and return different values each time its called.

Consider the dealCard function for a deck of playing cards. it deals the cards 1-52. Each time it is called it should return a different value.

function dealCard() {
    ++a;
    return cards(a);
}

/* we'll just assume the array cards is shuffled */

/* for the sake of brevity we'll assume the deck is infinite and doesn't loop at 52*/

danny117
  • 5,581
  • 1
  • 26
  • 35
  • 1
    My point was more that I don't want my kitchen rearranged because I got a phone call. However, if I have an arrangement with a cleaning service and they call saying they'll be over in 15 mins that's a different story. If `a` in this case "belongs" to `dealCards` and everybody is ok with a call to that method changing `a` then there's no problem. The Ruby array shuffle just gives you a brand new array that has been shuffled, so it's not even your stuff that gets messed with. – Greg Dec 27 '17 at 15:48
  • 1
    Greg. More functions that are order dependent. getNationalDebt("USA"); time(); IKEA("Kitchens"); – danny117 Dec 27 '17 at 20:41
  • 1
    All of those should be inputs to a predicable, reliable program that I can reason about and reliably test. – Greg Dec 27 '17 at 21:26
  • 1
    Yes. For a function that is dependent on ordering of calls the simple unit test is the function returns a different result on subsequent calls. The more advanced testing of functions that are dependent on the ordering of call is to test the result is contained in the known results for that function (test card actually exists in the deck). Testing usDebtclock genera of functions testing requires two calls where 1st call result is less than 2nd call result... – danny117 Dec 27 '17 at 22:50