A) The best refactored version does the equivalent of looking up a COMPACT_PRODUCT_WIDTH in a hardcoded global hash table
B) The best refactored version contains some form of the triple (140, 2, 1)
C) The best refactored version replaces the hardcoded strings with constants `COMPACT` and `DETAILED`, defined either as String constants or as an enum
D) The best refactored version uses a switch statement instead of an if-statement
Fundamentally, the function needs to output either the three numbers (140, 2, 1) or the three numbers (500, 5, 7). While there are many superficially-different variants, there really aren’t that many ways to do this.
Here is example code for the best refactoring, which corresponds to Answer B. There are minor variations, but the key idea is to pass around a structure containing all the values for a configuration. You can click on the expandable bullets below to look at the code for the other answers.
public static COMPACT: ProductDisplayType = new ProductDisplayType(140, 2, 1);
public static DETAILED: ProductDisplayType = new ProductDisplayType(500, 5, 6);
public setProductDisplayType(displayType: ProductDisplayType): void {
this.maxDescriptionLength = displayType.maxDescriptionLength;
this.numSimilarProductsToShow = displayType.numSimilarProductsToShow;
this.numReviewsToShow = displayType.numReviewsToShow;
}
Looking at the code for the other solutions, it’s easy to explain why Answer B is better in nearly every way.
First, the disadvantages of the others: Option D is the easiest to eliminate because it is nearly identical to the original, as a switch statement and an if-else statement are both variants of the idea of a conditional. The switch statement will be marginally easier to extend if new options are added other than compact and detailed, but otherwise, it only has the disadvantages of being slightly longer.
The enum option, Answer C, is similar but marginally better. Fundamentally, the function before could take in one of two values, “compact” or “detailed”, and then conditionally branched over them. With this change, it is the exact same, except the values are now called COMPACT and DETAILED instead. The interface is more type-safe, however.
Answer A makes the code straight-line instead of conditional, at least superficially. It also makes it much easier to do the possible future work of making the product display type settings configurable and extensible. However, in a sense, it doesn’t actually eliminate the conditional, because if you were to describe what it does, you would still need to say “It sets the configuration to these values for ‘compact,’ and these other values for ‘detailed.’”
Answer B dominates. It has all the type-safety of Answer B, more brevity than Answer A, and shares the configurability-and-extensibility benefit of Answer A. And it is competitive with the other answers in how easy it is to see the range of possible values for the three variables being set; whether it’s better or worse depends on extra details such as code placement.
However, its greatest advantage is that it is the only solution that truly eliminates the conditional, making the function both shorter and simpler.
There are still a few marginal improvements that can be made on top of this. For instance, if you did this example in a different language that has keyword arguments, it may be beneficial to use them in the ProductDisplayType constructor. But introducing this data structure is the key ingredient in improving this code.
There are tools and techniques from programming language theory that let you look at code like this and come up with the main ways for changing it, so that you can pick the best refactoring without depending on creativity. We teach this focused subset of PL theory in Unit 5 of the Advanced Software Design Course, “Algebraic Refactoring.” There’s a similar example to Answer A in Jimmy’s Strange Loop talk that uses PL theory to explain why it’s still conditional, and this is covered more in one of the first exercises of our Advanced Software Design Course. Also check out this article where we give a detailed discussion of how to truly remove conditionals from code.
Q: But doesn’t answer B best refactoring still contain a conditional, since the calling code must determine whether to pass in COMPACT or DETAILED
A: If the caller previously stored “compact” as a String, it would now store it as a ProductDisplayType. No conditional necessary.
But then you could say that the caller’s caller needs to have the conditional. So you would also change the caller’s caller to store a ProductDisplayType rather than a string.
This process repeats until the original source of the string “compact” is changed to instead provide a ProductDisplayType, and the conditional is truly eliminated.
This is one of the two techniques for truly eliminating a conditional: moving the conditional to a place in the code where the branch is already known.