Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jorgeborges/766612d39cf8eef2eeda995348243318 to your computer and use it in GitHub Desktop.
Save jorgeborges/766612d39cf8eef2eeda995348243318 to your computer and use it in GitHub Desktop.
The Pragmatic Programmer Chapter 4: Pragmatic Paranoia

Pragmatic Paranoia

This chapter is about defensive coding practices, both against users of your software and against yourself. Pragmatic Programmers, after all, understand that everyone makes mistakes, even themselves.

Design by Contract

This confused the shit out of me. I wasn't entirely sure what the difference was between a contract and a unit test.

Contracts are introduced in the context of employment contracts. That is, before any work is done, the responsibilities of both parties are defined, as well as the consequences of failing. Contracts in programming are similar. As I mentioned before, I wasn't able to make a strong distinction between contracts and unit tests (given some condition, when some event, then this should happen, though I may be confusing this with BDD). It's all a bit jumbled in my head.

According to Bertrand Meyer's Design by Contract, which he developed and put into practice with the language Eiffel, there are three parts to a contract: preconditions, postconditions, and invariants.

Preconditions: What must be true before a method (routine) is called.

Postconditions: What must be true after a routine is called.

Class invariants: Seriously, what the hell does this mean?

A class ensures that this condition is always true from the perspective of a caller. During internal processing of a routine, the invariant may not hold, but by the time the routine exits and control returns to the caller, the invariant must be true. (Note that a class cannot give unrestricted write-access to any data member that participates in the invariant.)

-The Pragmatic Programmer, p110

They mention the Liskov Substitution Principle here, which is part of SOLID principles, lazy code, assertions, crashing early in order to give stack traces at the point of failure...ah, a more in-depth treatment of invariants comes in on page 116.

The example they use is a method that iterates through an array of numbers and searches for the highest value. There is a variable, let's call it a memo, that stores the highest value encountered yet. The loop invariant condition states that, by the end of the execution of the loop, the memo should contain the highest value in the array. While the memo may not contain the highest value on any given iteration of the loop, by the end of the execution of that object, the condition should be true.

In the Semantic Invariants section, the authors explain a philosophical, rather than programmatic, invariant. They describe the programming of a debit card transaction switch that. In charging a customer, the customer should never be charged unnecessarily. That is, if an error occurs during runtime, the card should not be charged. The semantic invariant was as follows: Err in favor of the customer. This invariant guided the code.

Dead Programs Tell No Lies

Dead programs tell no lies is essentially the same idea that was mentioned in the prior section - crash early. As software users, we're used to crashes being a bad thing, but as a developer, they can be useful for diagnoses. Prefer to crash early and spit out a diagnostic stack trace.

Assertive Programming

This is really interesting, and something I hadn't encountered before. From what I can gather from the context, an assertion is simply a diagnostic check that confirms that your assumptions are true.

"Tip #33: If it can't happen, use assertions to ensure that it won't."

So, for instance, assert(sun.will_rise?) before you do anything that assumes a day/night cycle.

I initially assumed that assertions would automatically throw an error upon evaluating false, but they mention later that you can (optionally?) choose to throw an exception upon the failure of an assertion.

This may be confusing me because I've primarily worked with Ruby, a dynamic language, instead of a compiled language. I think this is a compiler thing. SOMEONE HALP.

When to Use Exceptions

"Will this code still run if I remove all the exception handlers?" If the answer is "no," then maybe exceptions are being used in nonexceptional circumstances.

The Pragmatic Programmer, p126

The explanation here is that exceptions should be reserved for truly exceptional events. The example given is a routine that attempts to open a file. Failure to open a file is not truly an exceptional event. It can happen any time the routine specifies a file that doesn't exist. An exceptional event would be, from the examples in the back, if that routine caused a memory allocation failure. That's not related to the original intent of the routine.

How to Balance Resources

I'm going to start skimping on the descriptions for these a bit because it's late and I'm tired.

A few handy guidelines for resource allocation and deallocation:

  1. The routine that allocates a resource should also deallocate it. This keeps resources from being locked up unnecessarily.
  2. Resources should be deallocated in the opposite order they were allocated in. In the case of nested resources, in which A allocates B allocates C, it is best to deallocate in the reverse order. This prevents orphaned resources if one refers to another.
  3. When allocating the same set of resources in different places, make sure to allocate them in the same order. If process A opens File1 and waits for access to File2, while process B opens File2 and waits for access to File1, they will deadlock and wait forever.

This was cool. They drew a comparison between class constructors/deconstructors and resource allocation/deallocation:

"If you are programming in an object-oriented language, you may find it useful to encapsulate resources in classes. Each time you need a particular resource type, you instantiate an object of that class. When the object goes out of scope, or is reclaimed by the garbage collector, the object's destructor then deallocates the wrapped resource."

Handy.

~

There were some language specific tips for balancing resources and what to do if you couldn't balance resources. The chapter ended with more defensive coding practice: making sure to check that the resources were being balanced. Wow! These guys sure are dedicated to that paranoia.

Good chapter, once I understood it better. I like to hear about resource allocation because it's not something I deal with on a day to day basis as a Rubyist, and I would like to know more about it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment