This article is just highlighting the tip of the iceberg from the original book. Please read the book for better detail and examples given by the author.
Before moving to the section, this is the first tip to be given:
You Can't Write Perfect Software
Knowing that no one writes perfect code, including themselves, Pragmatic Programmers build in defenses against their own mistakes.
What is the correct program? One that does no more and no less than it claims to be.
The function shall have the expectation, as described by Meyer:
- Preconditions: The routine requirements.
- Postconditions: The guaranteed statement when the routine is done.
- Class invariants: The condition is always true from the perspective of a caller.
The contract between a routine and any potential caller can be read as:
If all the routine's preconditions are met by the caller, the routine shall guarantee that all postconditions and invariants will be true when it completes.
Note: I still don't really get the meaning of the class invariants. I assume it was about the variable we passed on to the function, should not be mutated to a different type or interface. CMIIW
In any programming language, whether it's functional, object-oriented, or procedural, DBC forces you to think.
By not implementing DBC in your function, you are back to programming by coincidence which is where many projects start, finish, and fail.
You can implement the contract in your code by writing an Assertion. By having it, you can crash early and report more accurate information about the problem.
You can use semantic invariants to express inviolate requirements, a kind of "philosophical contract*.
The example given is about validating the same payment transaction should not happened twice. The contract would be, no matter what failure it might happen, the error should be on the side of not processing a transaction, rather than processing a duplicate transaction.
It's easy to fall into the "it can't happen" mentality. When that really happens, Pragmatic Programmers tell themselves that something very, very bad has happened.
That's one reason why each and every case/switch statement needs to have a default clause, to detect when the "impossible" has happened.
When it happens, Read the Damn Error Message.
If you try to use a lot of
try...catch to run a function, the Pragmatic Programmer would prefer to just run it directly. Two reasons:
- The application code isn't eclipsed by the error handling
- The code is less coupled
One of the benefits of detecting problems as soon s you can is that you can crash earlier, and crashing is often the best thing you can do.
When your code discovers that something that was supposed to be impossible just happened, your program is no longer viable.
count can't be negative"
"Logging can't fail"
"This can never happen...*
Let's not practice this kind of self-deception, particularly when coding. Instead, add code to check it. The easiest way to do this is with assertions.
The assertion is to check a condition that should never happen. Make sure the assertion method itself doesn't do any side effects that might create new errors.
A common misconception about assertions:
Assertions add some overhead to the code. If the code has been tested and shipped, the assertion shouldn't be needed because they check for things that should never happen.
The assumption above is wrong because.
- Testing might not find all the bugs.
- The production environment could be different from the testing environment
If you do have performance issues from your assertion, turn off only those assertions that really hit you.
Finish What You Start
It simply means that the function or object that allocates a resource should be responsible for deallocating it.
An example of this case could be: When you run a function that uses and rewrite a global variable, make sure that you are aware of it and update or reset the value back to what it supposed to be.
The more surefire way to handle it is to avoid global or shared variable across functions, and "Act Locally" by scoping the variable and pass it around as a function parameter.
Because Pragmatic Programmers trust no one including themselves, it is always a good idea to build code that actually checks that resources are indeed freed appropriately.
We can't see too far ahead into the future, and the further off-axis you look, the darker it gets. So Pragmatic Programmers have a firm rule:
Take Small Steps. Always
What is a task that's too big? Any task that requires "fortune-telling". An example of fortune-telling:
- Estimate completion dates months in the future
- Plan a design for future maintenance or extendability
- Guess the user's future needs
- Guess future tech availability
When we're supposed to design for future maintenance, only to the point as far ahead as you can see.
The more you have to predict the future, the more risk that you'll be wrong. Instead, make it easy to throw out your code and replace it with something better suited.
What are your paranoia during development? Have you been in a situation where your code breaks unexpectedly? Have you been trying to predict the future of your software and how far it can go? Please share your experience and feedback, and I would like to learn from you as well. Thanks for reading!