In code I see people not understand this all the time. Here's an example: let's say you present the user with their previous orders, and give them the option to filter those orders by some criterion.
The shitty way to do this is to have two variables:
Filter* orderFilter = NULL;
bool filterSpecified = false;
void setOrderFilter (filter* newFilter) {
orderFilter = newFilter;
filterSpecified = true;
}
So instead of just checking if a filter has been assigned, you have a separate boolean. What happens if the boolean is true but the filter is null? What would the vice versa case even mean?The global codebase is riddled with dumb errors like this.
Languages with Sum Types represent this much more elegantly with arbitrary numbers of variants and force you to check which one you have before accessing the more specific data (e.g. the filter) inside.
If there's always a filter, then there's no longer a branch at the point it's used -- that needs to be tested and maintained.
I used to work with a programmer who I found difficult because their code was an ever-increasing number of "if" statements as each new case came along. Conversely he would say coding is relatively easy and that I was overcomplicating a problem by thinking about it; all I needed to do is "add an if statement here".
Our code is riddled with heavily overloaded meanings in a single value/domain, and if you have a variety of filters, you end up with this atrocity:
Foo *f1; // hey, just test for NULL
double f2; // hey, just use isnan()
int f3; // aaaaah, crap
bool f3_specified; // god why
std::string f4;
// requires heavy drugs to solve existential catastrophes
Compare that to template <typename T>
struct Filter {
bool is_specified;
T value;
};
Filter<Foo *> f1;
Filter<double> f2;
Filter<int> f3;
Filter<std::string> f4;
And the variety of code that has to work with either of these examples.What happens if the boolean is true but the filter is null?
An assertion fails miserably.
What would the vice versa case even mean?
That an assertion failed miserably.
What happens if the filter is one? Minus one? Equals to PC(IP), BP? If NULL filter has to search for NULL values in a dataset? If we are looking for NAN values in a corrupted one (this one is even more tricky)?
Deeply!
I like wrapping the information in a data structure exactly as you suggested, and if you never mutate the dereferenced pointer, it's equivalent to what they proposed. Big if, though.
The best is to do this generically with https://en.m.wikipedia.org/wiki/Option_type , since this pattern comes up regularly, not just with this one domain concept of "Filter"
if (host == "load-balancer-1.internal.dns") {
req.source = HOSTS::LOAD_BALANCER_1;
req.source += PORT_MAPPING[HOSTS::LOAD_BALANCER_1];
}
else if (host == "load-balancer-2.internal.dns") {
// repeat
}
After a few quarters of services, layers, and people being added and removed, it becomes a 500-line monstrosity and sits in every critical path. And because now it's a 500-line class (half of which is defunct, but good luck figuring out which half), nobody has time to read through it and figure out that it should have been a single assignment statement. class ReadingMaterial:
is_magazine = False
def is_periodical():
return is_magazine
Using a base class (e.g. a Magazine and a Book class, or something) and inheritance, is much clearer than monkeying around with state.However, you don't need to name all variables "answerObject".