Unidentifiable
For patterns, there’s another twist to the problem with their findability. It’s
not a real word, but I didn’t have to explain it to you for you to know it meant
the same as discoverability. Well, that’s one of the problems with software
design patterns. Even when you read or hear a pattern name, you cannot be sure
you know what it pertains to. How can someone immediately deduce the meaning of
even some of the best-named patterns, such as
Furthermore, references collide. All three variants of the
The ambiguity we bring with overlapping terms is a significant problem with
patterns, even when you know they exist. When you know a pattern could help you
make decisions, it’s almost impossible to know what it might be called without
knowing the name. The accepted categories of software patterns don’t improve
things. I applaud the authors of
So, we end up with an extensive collection of patterns that could solve our problems if only we knew about them before we needed them. We need to know their names and roughly what they help with before we can properly look for them. It’s like we need to be vaccinated against the problems by reading the pattern books before we begin writing any code. I needed to know that route-planning algorithms existed before I could use one in my childhood game project. But there’s another problem when seeking solutions: problem visibility.
When it comes to sorting algorithms (again), you know whether or not you need
to sort something. You can quickly tell you have a problem when the order is
incorrect but you would prefer it sorted another way. When it comes to the
problems design patterns usually help solve, sometimes the problems are invisible when
they first appear. The vaccination comparison is even more pertinent in this
case. A pattern often solves a problem lacking visibility until very late in
development. In this case, we would never seek out the pattern. Avid pattern
readers would only stumble upon it by accident, but that would be serendipity,
not procedure. And even then, it would only be fully ingested and recalled when
its specific situational applicability turns up if it piqued their interest.
Consider how often people think to use the
Names inhibit the reach of a good pattern. When we label things, we constrain
them by what we can conceive. We assume their title describes all they can
solve and limits our imagination.
In C++, the vector
, they indicate a piece of memory, so you can use them for much more.
They can result from find operations, reinforced by their usage with map
.
Generally, however, they are overlooked for other purposes because they have
this name, and the common attribute is that of iteration. If they were not
named thus and instead given unique representations based on what they were, it
could open the mind to different opportunities.
An example of the failure of patterns can be seen at the microscopic scale with
the nodiscard
keyword in C++. The nodiscard
keyword provides a mechanism to
raise a warning when you ignore a return value. It’s a solution for
two different problems.
One is the problem of ambiguity in function names as to whether they
are actions or queries. When the keyword was introduced, the standard was
updated, and calls to empty
on containers gained the tag. It protected users
from ambiguity of intent. This is a problem we have with naming things. Should
a function called reverse
reverse the container in place or return a reversed
copy of the container? Without looking at the documentation, it’s hard to know.
The nodiscard
attribute came to the rescue.
The other main reason for nodiscard
was the criticality of responding to the
result. Again, they added the attribute to standard library functions returning
resources requiring management, such as memory allocations and thread creation.
In other situations, discarding is dangerous, such as when you must act on the
result. If you request a lock on a resource, such as a mutex, you should act
differently depending on the outcome of attempting to take that lock.
There’s nothing wrong with having both variants use nodiscard
, but the
problem that is solved is different. In one case, you called the wrong function. In the
other case, you called the correct function but did not handle it sufficiently.
The const
keyword nearly fulfils the demands of the first usage (it doesn’t
for free-functions, but for methods, it almost works), but few compilers even
emit a warning when you fail to use the return value of a const method. Without
phenomena, the coder will continue to use the method ineffectively.
class A {
public:
int f() const {
return 5;
}
};
int main(int argc, char* []) {
A a;
a.f(); // this is almost certainly wrong.
return argc * argc;
}
// Even with -Wall -Wextra -Wpedantic
// No warnings emitted by clang 16 or gcc 13.1
// snippet available at https://godbolt.org/z/TbnbvYPb5
Patterns can have similar overlaps. When they are solution-oriented, they can resolve more than one problem. Named this way, they’re hard to find, but even when you do find them, you may only have one problem variant in mind. So, you disregard them as a solution to your problem, even though they’re compatible.
Let’s return to the
When the