I often hear people praise languages, frameworks, constructs, etc. for being "explicit". I'm trying to understand this logic. The purpose of a language, framework, etc. is to hide complexity. If it makes you specify all kinds of details explicitly, then it's not hiding much complexity, only moving it around. What's so great about explicitness and how do you make a language/framework/API "explicit" while still making it serve its purpose of hiding complexity?
Relying on default behaviour hides important details from people who aren't intimately familiar with the language/framework/whatever.
Consider how Perl code which relies extensively on shorthands is difficult to understand for people who don't know Perl.
I believe that explicit refers to knowing exactly what it is doing when you use it. That is different from knowing exactly how it's done, which is the complex part.
In some cases the opposite is "magic" - as in "then a miracle occurs".
When a developer's reading code trying to understand or debug what's going on, explicitness can be a virtue.
Code is harder to read than to write. In nontrivial applications, a given piece of code will also be read more often than it is written. Therefore, we should write our code to make it as easy on the reader as possible. Code that does a lot of stuff that isn't obvious is not easy to read (or rather, it's hard to understand when you read it). Ergo, explicitness is considered a good thing.
Whether you should be explicit or implicit depends on the situation. You are correct in that often you are trying to hide complexity, and certain things being done behind the scenes for you automatically is good. encapsulation, etc.
Sometimes though frameworks or constructs hide things from us that they should not, and this makes things less clear. Sometimes certain information or settings are hidden from us and hence we don't know what's happening. Assumptions are made that we don't understand and can't determine. Behaviors happen that we can't predict.
Encapsulation: good. Hiding: bad. Making the right call takes experience. Where logic belongs, it should be explicit.
Example: I once removed about 90 lines of code from a series of a dozen code behind pages; data access code, business logic, etc., that did not belong there. I moved them to base pages and the key business object. This was good (encapsulation, separation of concerns, code organization, decoupling, etc.).
I then excitedly realized that I could remove the last line of code from many of these pages, moving it to the base page. It was a line that took a parameter from url and passed it to the business object. Good, right? Well, no, this was bad (I was hiding). This logic belonged here, even though it was almost the same line on every page. It linked the UI intention with the business object. It need to be explicit. Otherwise I was hiding, not encapsulating. With that line, someone looking at that page would know what that page did and why; without it, it would be a pain to determine what was going on.
The purpose of frameworks moving things around is to remove duplication in code and allow easier editing of chunks without breaking the whole thing. When you have only one way of doing something, like say SUM(x,y); We know exactly what this is going to do, no reason to ever need to rewrite it, and if you must you can, but its highly unlikely. The opposite of that is programming languages like .NET that provide very complex functions that you often will need to rewrite if your doing anything but the obvious simple example.
It is about expressing intentions. The reader can't tell if the default was left by mistake or by design. Being explicit removes that doubt.
Frameworks, etc., can be both explicit and hide complexity by offering the right abstractions for the job to be done.
Being explicit allows others to inspect and understand what is meant by the original developer.
Hiding complexity is not equivalent with being implicit. Implicitness would result in code that is only understandable by the person who wrote it as trying to understand what goes on under the hood is akin to reverse engineering in this case.
Explicit code has a theoretical chance of being proved correct. Implicit code never stands a chance in this respect.
Explicit code is maintainable, implicit code is not - this links to providing correct comments and choosing your identifiers with care.
An "explicit" language allows the computer to find bugs in software that a less-explicit language does not.
For example, C++ has the const
keyword for variables whose values should never change. If a program tries to change these variables, the compiler can state that the code is likely wrong.
Explicitness is desirable in the context of making it clear to the reader of your code what you intended to do.
There are many examples, but it's all about leaving no doubt about your intent.
e.g. These are not very explicit:
while (condition);
int MyFunction()
bool isActive; // In C# we know this is initialised to 0 (false)
a = b??c;
double a = 5;
double angle = 1.57;
but these are:
while (condition)
/* this loop does nothing but wait */ ;
private int MyFunction()
int isActive = false; // Now you know I really meant this to default to false
if (b != null) a = b; else a = c;
double a = 5.0;
double angleDegrees = 1.57;
The latter cases leave no room for misinterpretation. The former might lead to bugs when someone fails to read them carefully, or doesn't clearly understand a less readable syntax for doing something, or mixes up integer and float types.
Good abstraction doesn't hide complexities, it takes decisions that are best left to the compiler off of your plate.
Consider garbage collection: The complexities of releasing resources are delegated to a garbage collector which is (presumably) better qualified to make a decision than you, the programmer. Not only does it take the decision off your hands, but it makes a better decision than you would have yourself.
Explicitness is (sometimes) good because it makes it so that certain decisions that in some cases are better left to the programmer are not automatically made by a less qualified agent. A good example is when you're declaring a floating point data type in a c-type language and initializing it to an integer:
double i = 5.0;
if instead you were to declare it as
var i = 5;
the compiler would rightfully assume you want an int and operations later on would be truncated.
It's not so much that explicit is good (certainly the closely-related verbose is bad) as that when implicit goes wrong, it's so hard to tell WTF is going on.
Hack C++ for a decade or two and you'll understand exactly what I mean.
Being explicit vs. implicit is all about what you hide, and what you show.
Ideally, you expose concepts that either the user cares about, or has to care about (whether they want to or not).
The advantage of being explicit is that it's easier to track down and find out what's going on, especially in case of failure. For instance, if I want to do logging, I can have an API that requires explicit initialization with a directory for the log. Or, I can use a default.
If I give an explicit directory, and it fails, I'll know why. If I use an implicit path, and it fails, I will have no idea of what has gone wrong, why, or where to look to fix it.
Implicit behavior is almost always a result of hiding information from the consumer. Sometimes that's the right thing to do, such as when you know in your environment there's only one "answer". However, it's best to know when you're hiding information and why, and ensure that you're letting your consumers work closer to their level of intent, and without trying to hide items of essential complexity.
Frequently implicit behavior is a result of "self-configuring" objects that look at their environment and try to guess the correct behavior. I'd avoid this pattern in general.
One rule I'd probably follow overall is that, for a given API, any operation should either be explicit, or implicit, but never a combination. Either make the operation something the user has to do, or make it something they don't have to think about. It's when you mix those two that you will run into the biggest problems.