In Croatia, most of roads resemble battlefields. They are so full of holes and patches from all kinds of repairs over time, that they have to re-pave them every five years or so. It is an awful waste of taxpayer’s money, and makes you wish for the world of Jennifer Government to come be. Anyway, as soon as they re-pave the roads, not a week usually passes before they come again, with jackhammers and heavy machinery of all sorts, and start drilling away, blocking the road in process and causing mass-frustration, just because some wacko has suddenly remembered that it would be nice idea to pass the optic cable underneath, or some valve started leaking.
Well, this is a typical example of hardcoding in civil engineering. Better, albeit in short-term probably more expensive solution, is to build underground tunnels when roads are rebuilt, and have all the infrastructure go through these tunnels, enabling easy repairs or upgrades without causing any interference to traffic or damage to roads themselves.
Yesterday, I promised to write about another sort of hardcoding, the hardcoded input. Now, how do you hardcode the input? This is a possibility:
IF "Buy-from Vendor No." = 'V-0930' THEN
"Line Discount %" := 20;
Things like this make me lose brain tissue.
Now, I didn’t just come up with this one. Sadly, but I’ve actually seen something exactly like this out in the wild, with an explanation that 90% of purchase transactions come from a single vendor, and for that vendor the fixed agreed discount on everything is 20% and it’s unlikely to change. So this was the simplest way to implement this business logic, because it saves time of both developer, and end-user.
What is so badly wrong about this example. Quite a few things:
It supposes that this situation is actually a one-off situation. This specific example entwines two one-off situations: that one specific vendor has discount, and how much specifically that discount is. In case either there is another vendor who needs similar functionality, or the discount for this vendor changes, the developer who did this will probably start jackhammering the code away to meet this new requirement.
A setting is directly embedded into code. Settings should come from user input, not from programming them directly. When something like this actually hits the production environment, users (at least experienced ones) will start wondering where the heck this comes from. Then they will start looking at all sorts of settings, only to find that there is no such setting. Settings as called settings, because they are used to set up things. When you come to a dark room, you immediately look for a light switch, when your stereo is too loud, you push buttons to silence it down. These are settings. You use them to configure the environment to your liking, preferences and needs. I wouldn’t like my house to have a room with no lights just because I will always use that room during daytime anyway, and it has windows to provide for light. What about solar eclipses? Settings are discovered through exceptions, and exceptions are everywhere.
It creates logical dependency across system tiers. A well designed system should have no dependency between its architectural tiers (no matter if it is two, three or multi-tiered system). Microsoft Dynamics NAV is (currently) a system with two tiers, one being user interface plus business logic, and the second being data. You shouldn’t have any logical dependency between business logic (or user interface) and data. Data should be separate. A well designed system must enable the tiers to be separated and easily replaced, which is something that usually happens. Data is volatile, and can easily experience changes, while business logic is relatively steady. Having business logic depend on data would make it as volatile as the data itself, which results in a lot of extra maintenance work. In the example above, two pieces of information which belong to the domain of data (data tier) somehow intermingled their way into domain of business logic. In the example above, vendor number can easily change, rendering the business logic unusable.
It links a meaning to information which can’t bear any meaning to the system. Textual information shouldn’t bear any meaning to the system, and no (algorithmic) logic should ever depend on such information. The same is true of numeric information, it shouldn’t have any meaning beyond the numeric value it contains, and logic, such as branching, should almost never depend on a numeric value. Although there was time in history when this was acceptable, newer-generation programming languages replace this approach with concept of enumerators, which is exactly what C/AL knows as options, and these are a valid way to link a meaning to information which is (in itself) numeric.
When you solve problems, you should know that one devil never comes alone. If you shoot only one at a time, you’ll soon drop exhausted. Instead, you should reasonably expect that every single problem that occurs, might as well be the first know occurrence of the series of other similar problems, something that I use to call a class of problems. You can recognize a class of problems from a common pattern. While it definitely is difficult to recognize a pattern from a single occurrence, you should do your best. Patterns come through generalizations.
In the example above, generalization is vendor. If there is a single vendor which brings an exception to the system, you may rightfully anticipate that other vendors may exhibit a similar behavior. Another generalization in this example is line discount. The pattern in the example above is that some vendors can have default line discounts. When you design the solution to this problem, you’ll take into account not a single occurrence, but the whole class represented by the pattern you identified. So you’ll probably add a field called Default Line Discount % to the Vendor table, and then simply pull the value from that field when needed.
Whenever you hardcode something, you also make long-term adverse effect on maintainability of your code. Unless you know exactly where in your code you have hardcoded settings, or you documented your code well, finding functionality of this kind in the source, especially with cascaded validations involving several tables and codeunits, you may waste countless hours looking for these gems.
I hope I proved my point why hardcoding is a bad thing, no matter whether you hardcode your output (messages) or your input (settings). A big thanks to Peter and Dave, who commented on my last post, and made good points about pitfalls in my argumentation, this is great about blogs that they can spark off useful discussions.