Jonathan Boccara's blog

How to Write a Condition With Interdependent Variables

Published December 13, 2019 - 0 Comments

Sometimes, the simplest requirements can be tricky to code in an expressive manner.

For example, I recently had to code up some logic to determine if a transaction consisted in paying money or receiving money.

To determine this, the transaction has two relevant parameters:

  • the price of the transaction, that can be positive or negative. A negative price doesn’t mean much in life but let’s say that it exists as a sort of accounting rule.
  • the fact that we’re buying or selling into that transaction.

Now here are how those two parameters can determine the fact that we’re paying or receiving money:

  • Buying at a positive price means paying money,
  • Buying at a negative price means receiving money,
  • Selling at a positive price means receiving money,
  • Selling at a negative price means paying money.

Once you’ve accepted that negative prices can exist, the above requirement is pretty simple. So it should be straightforward to code, right?

But when you get to the keyboard to write up the code, many options present themselves to you. Not sure of what was the best way to write it, I asked around on Twitter:

Several people answered (thanks to everyone!), and the interesting thing is that they had very different answers.

Reading those answers was instructive, as they show several approaches to the simple but essential topic of how to write a condition with expressive code. I’d like to share the takeaways with you.

But before you read on, why don’t you give it a go yourself? You have the price and an enum describing if the position is buying or selling. How would you determine if we’re paying or receiving money?

The most concise approach

Several people chose to take advantage of the boolean logic that the requirement can be reduced to: a XOR.

Indeed, to be paying money you need to have a positive price, or to be in a selling position, but not both at the same time.

Said differently, paying money is positive price XOR selling position. And receiving money is the opposite of paying money:

Direction computeDirection(double price, Position position)
{
    return (price >= 0) ^ (position == Position::Sell) ? Direction::Pay : Direction::Receive;
}

This is extremely short and efficient. But the trade-off to get that conciseness is that the code doesn’t read like the requirement.

Pattern matching

One elegant solution that was proposed is to use pattern matching:

In C++ we don’t have pattern matching (yet?), and we can’t test several things at the same time like above in a switch statement. So the closest we can get to the above code in C++ is this:

Direction computeDirection(double price, Position position)
{
    if (position == Position::Buy && price >= 0)
    {
        return Direction::Pay;
    }
    else if (position == Position::Sell && price < 0)
    {
        return Direction::Pay;
    }
    else
    {
        return Direction::Receive;
    }
}

Wow, this C++ version is a lot less sexy. Perhaps it would look better without the braces?

Direction computeDirection(double price, Position position)
{
    if (position == Position::Buy && price >= 0) return Direction::Pay;
    else if (position == Position::Sell && price < 0) return Direction::Pay;
    else return Direction::Receive;
}

Meh.

At least we could remove the duplicated return statement:

Direction computeDirection(double price, Position position)
{
    if ((position == Position::Buy && price >= 0)
     || (position == Position::Sell && price < 0))
    {
        return Direction::Pay;
    }
    else
    {
        return Direction::Receive;
    }
}

This looks better. This code also comes down to what several people suggested.

It’s interesting to note even if the snippet in the Tweet is elegant, without pattern matching in the language the most readable solution (at least, to me) is not the one that strives to look like pattern matching.

Introducing levels of abstraction

On Fluent C++ we have the structural vision of programming that it all comes down to respecting levels of abstraction.

In this spirit, one of the solution introduces intermediary levels of abstraction:

  • determining if the transaction is paying money
  • determining if the transaction is receiving money

As opposed to the other solutions that attempt to deal with both cases in the same code, this solution separates out the two aspects, and aggregates them with intermediary levels of abstraction:

Introducing an intermediary value

Here is an astute solution that simplifies the if statement by introducing an intermediary value:

Note that it no longer looks like the requirement. It’s like a new algorithm to work out whether we’re paying or receiving money.

Both requirements will get to the same results, but maybe this way of seeing the requirement makes more sense.

We won’t debate if it does in terms of finance here. But one thing is sure: it is important that your code stays in line with the requirement. If your code reformulates the requirement, then you should talk with the person who gave it to you (PO, domain expert, …).

Perhaps the new vision of the requirement is better, and the domain experts should reformulate it on their side too. Or perhaps the new requirement misses an aspect of the domain that makes this new way of expressing the requirement somehow inconsistent with the rest of the domain.

Either way, it would be a good opportunity to dig further in the domain with the people you’re working with.

This is the sort of ideas that is advocated in Domain Driven Design.

Modifying the position

Here is another approach that introduces an intermediary value, this time by changing the buy/sell position and not the price:

Simple code

After seeing many astute ways of coding up the requirement, here is a pretty simple piece of code that does the job.

This is my personal favourite, as it looks like the requirement but in a simpler expression, and it is implementable in C++.

A simple requirement, many solutions

It is interesting to see that despite the simplicity of the requirement, there are so many ways to write code to execute it. Most of the code snippets choose some sides of various trade-offs, as often when programming.

It’s instructive to explore those possibilities. Thanks a lot to all the people who answered my Tweet!

How about you? How would you have gone about writing code for the initial requirement?

You will also like

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin