1
\$\begingroup\$

I have a Java enum which represents the side of an order (enum Side {ASK, BID}) and I'm constantly writing code with which I'd like to use the ternary operator, e.g.:

BigDecimal orderPrice = order.getSide() == Side.ASK ? calcAskPrice(order) : calcBidPrice(order)

The issue is that I want to future proof the code so that it fails if the provided value is neither ASK nor BID. The obvious solution seems unnecessarily verbose to me, because the pattern needs to be repeates tens (maybe even hundreds?) of times throughout the code. I'd rather not have this everywhere:

BigDecimal orderPrice;
if (order.getSide() == Side.ASK) {
   orderPrice = calcAskPrice(order);
} else if (side == Side.BID) {
   orderPrice = calcBidPrice(order);
} else {
   throw new IllegalArgumentException("The provided order must have a side that is either ASK or BID");
}

Instead I'd prefer a more functional approach, like the ternary operator.

A safer approach works, but is (i) also verbose and (ii) confusing:

BigDecimal orderPrice = order.getSide() == Side.ASK ? calcAskPrice(order) : order.getSide() == Side.BID ? calcBidPrice(order) : null;
assertNotNull(orderPrice);

Thinking about my desired syntax, I decided to add some methods to the enum such that I could write:

BigDecimal orderPrice = order.getSide().ifAsk(calcAskPrice(order)).ifBid(calcBidPrice(order)).elseFail();

This was implemented via an object that would store the value of the enum along with the two result values, and whose elseFail() method would return the appropriate result or fail. I was extremely satisfied with this, until (in pursuit of a separate issue) I noticed that my little evaluator object had a rather large footprint, but in terms of object creation & CPU time.

In retrospect this makes sense, because the code processes large numbers of updates, and is scattered with checks to the order side.

I'm now left with trying to think up a performant/low overhead way to provide similar syntax. The easiest solution that I can think of is to have a method on the enum:

public <T> T ifAskBid(T askResult, T bidResult) {
   // . . .
}

but I'm curious if anybody can think of a nicer solution.

\$\endgroup\$
  • \$\begingroup\$ Does it even makes sense to add values to that enum? \$\endgroup\$ – cHao Apr 20 '17 at 22:56
  • \$\begingroup\$ @cHao Haha no not really, but I've seen it in some of the APIs we work with. For instance, the side of an order could be typed as a MARGIN_ASK rather than a LIMIT_ASK, but that's a conflation of type and side which should be abstracted away on my end. That's the reason I'm worried someone will add it anyhow. What scares me is that if for some reason a third value is added, it would be an absolutely catastrophic error. \$\endgroup\$ – John Dorian Apr 20 '17 at 23:16
4
\$\begingroup\$
BigDecimal orderPrice = order.getSide().ifAsk(calcAskPrice(order)).ifBid(calcBidPrice(order)).elseFail();

This seems rather long and complicated. Consider

BigDecimal orderPrice = order.getSide().calculatePrice(order);

Or you might hide it as

BigDecimal orderPrice = order.calculatePrice();

With

BigDecimal calculatePrice() {
    return getSide().calculatePrice(this);
}

As you can read on Stack Overflow, you can then define different implementations of the method for each value of the enum.

public enum Side {

    ASK {
        @Override
        public BigDecimal calculatePrice(Order order) {
            return Something.calcAskPrice(order);
        }
    },
    BID {
        @Override
        public BigDecimal calculatePrice(Order order) {
            return Something.calcBidPrice(order);
        }
    };


    public BigDecimal calculatePrice(Order order) {
        throw new IllegalArgumentException("The provided order must have a side that is either ASK or BID");
    }

}

I wrote Something, as you don't provide enough context to write whatever it actually is. It's possible that the calcAskPrice and calcBidPrice methods should have their contents pulled into the override methods. Or that you need to pass some kind of additional object to the calculatePrice methods.

There's an argument that overriding a method like this is bad form. I'll leave it up to you if that matters. I did it here to duplicate your original functionality. But maybe you don't need the original functionality. Again, without more context, it's difficult to tell.

Perhaps it should go without saying, but I didn't test this, as there isn't enough context. I did not go the extra mile and verify syntactical correctness by dummying up a driver for this code.

\$\endgroup\$
  • \$\begingroup\$ Awesome! I hadn't thought of that. \$\endgroup\$ – John Dorian Apr 21 '17 at 1:12
1
\$\begingroup\$

The obvious solution seems unnecessarily verbose to me, because the pattern needs to be repeates tens (maybe even hundreds?) of times throughout the code.

I tend to use enums as a thing to represent states (open/done) or constants (monday/tuesday/...). In my opinion, the calculation of a price should be in a separate type and represented by an interface, and not implemented or even delegated in the enum itself. If there's just one thing you have to consider for the calculation of the price, e.g. - maybe a stupid example - an exchange rate, you would have to call the method in the enum with an additional parameter and change all your code. If the calculation is represented in a seperate type, you just inject something like an ExchangeRateService into it, and the callers do not care.

And: It's easier to test your code, since you can mock it and you won't test the calculation of the price implicitly (for those maybe hundreds of occurrences: that means, if something changes within the enum, potentiall all your test cases which call the enum, can fail).

And: In mdfst13 example: The Side does know about Order, while the Order holds a state of Side, that's a bi directional dependency and is very tightly coupled... that's okayish, but imo should be avoided.

The issue is that I want to future proof the code so that it fails if the provided value is neither ASK nor BID

What's wrong with writing a test case?

Thinking about my desired syntax, I decided to add some methods to the enum such that I could write:

BigDecimal orderPrice = order.getSide().ifAsk(calcAskPrice(order)).ifBid(calcBidPrice(order)).elseFail()

Nah, don't do that, it's much harder to understand than if else statements. Also, I only use the ternary operator if it's a plain if-then-else-that statement. Everything more complicated will be like sentence with too many subclauses; plain stupid boolean conditions can still be way too complicated to understand a few lines of code and are a major source of errors.

Hope that helps,...

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.