The Pragmatic Programmer is a book by Andrew Hunt and David Thomas written in 1999. I have been reading it over the last few months and my final impression is that I am sure that any programmer out there can learn something by reading it. Novice or experienced, this book is packed with good advice. I have tagged the very best tips with a chili pepper (🌶), for reference.
If you enjoy this article, get the book
So, what do you need to do to become a Pragmatic Programmer?
Early adopter/fast adapter
Inquisitive
Critical thinker
Realistic
Jack of all trades
Cares about the craft
Never runs on auto pilot
Pragmatic Programmers think beyond the immediate problem, always trying to place it in its larger context 🌶
They take responsibility for everything they do
Isn't afraid to admit ignorance or error 🌶
You need to have a broad base of knowledge and experience to pull all of this off. Learning is a continuous and ongoing process
When you accept responsibliity for an outcome, you should expect to be held accountable for it
Before approaching anyone to tell them why something can't be done, is late or broken, stop and listen to yourself. Will they ask "have you tried this..." or "didn't you consider that?..." How will you respond? 🌶
We need to decide what to test at the unit level. Typically programmers throw a few random bits of data at the code and call it tested. We can do much better using the ideas behind design by contract. This style of testing requires you to test subcomponents of a module first. Once the subcomponents have been verified, then the module itself can be tested.
By designing code to pass a test and fulfull its contract, you may well consider boundary conditions and other issues that wouldn't occur to you otherwise.
Constantly review what's happening around you, not just what you personally are doing 🌶
Many users would rather use software with some rough edges today than wait a year for the multimedia version
You must invest in your knowledge portfolio regularly
Having the best ideas, the finest code or the most pragmatic thinking is ultimately sterile unless you can communicate with other people 🌶
When you are faced with an important meeting, jot down the ideas you want to communicate and plan a couple of strategies for getting them across
You are communicating only if you are conveying information. To do that, you need to understand the needs, interests and capabilities of your audience 🌶
Make what you are saying relevant in time, as well as in content. Sometimes all it takes is the simple question "Is this a good time to talk about...?"
Programmers are constantly in maintenance mode
Imposed duplication: Developers feel they have no choice - the environment seems to require duplication.
Inadvertent duplication: Developers don't realize that they are duplicating information
Impatient duplication: Developers get lazy and duplicate because it seems easier. 🌶
Interdeveloper duplication: Multiple people on a team duplicate a piece of information
Bad code requires lots of comments
We all know that in the heat of the moment with deadlines looming, we tend to defer the updating of documentation
Appoint a team member as the project librarian, whose job is to facilitate the exchange of knowledge
Two or more things are orthogonal if changes in one do not affect any of the others
We want to design components that are self contained: independent and with a single well defined purpose. When components are isolated from one another, you know that you can change one without having to worry about the rest.
You get two major benefits if you write orthogonal systems: Increased productivity and reduced risk 🌶
Single components can be designed, coded, unit tested and then forgotten. There is no need to keep changing existing code as you add new code.
An orthogonal approach promotes reuse and will probably be better tested, because it will be easier to design and run tests on its components.
When teams are organized with lots of overlap, members are confused about responsibilities. Every change needs a meeting of the entire team, because any one of them might be affected.
If I dramatically change the requirements behind a particular functions, how many modules are affected? In an orthogonal system the answer should be "one"
Avoid global data 🌶
Keep your code decoupled 🌶
Avoid similar functions
Building unit tests is itself an interesting test of orthogonality. Do you have to drag in a large percentage of the rest of the system just to get a test to compile? If so, you've found a module that is not well decoupled from the rest of the system.
The first part of any time estimation is building an understanding of what is being asked. Estimates given at the coffee machine will come back to haunt you. 🌶
Be strict in what you will accept before you begin, and promise as little as possible in return.
When debugging, don't gloss over a routine or piece of code involved because you "know" it works. Don't assume it - Prove it. 🌶
Is the problem being reported a direct result of the underlying bug, or merely a symptom? Is this bug really in the compiler? Or is it in the OS? Or is it in your code? If you explained this problem in detail to a coworker, what would you say? If the suspect code passes its unit tests, are the tests complete enough? What happens if you run the unit test with this data? Do the ocnditions that caused this bug exist anywhere else in the system?
You could convince yourself that the error can't happen and ignore it. Instead, Pragmatic Programmers tell themselves that if there is an error, something very bad has happened.
A dead program normally does a lot less damage than a crippled one. 🌶
Will this code still run if I remove all the exception handlers? If the answer is "no", maybe exceptions are being used in non-exceptional circumstances.
The routine or object that allocates a resource should be responsible for deallocating.
Deallocate resources in the opposite order to that in which you allocate them.
A good way to stay flexible is to write less code. 🌶
Do you depend on the "tick" coming before the "tock"? Not if you want to stay flexible.
Always design for concurrency
Separate views from models
Fred doesn't know why the code is failing because he didnt know why it worked in the first place. Always be aware of what you are doing. Fred let things get slowly out of hand, until he ended up boiled like the frog. 🌶
Attempting to build an application you don't fully understand or to use a technology you aren't familiar with, is an invitation to be misled by coincidences. 🌶
If you don't have fundamentals or infrastructure correct, brilliant bells and whistles will be irrelevant.
Don't be a slave to history. Don't let existing code dictate future code 🌶
All code can be replaced if it is no longer appropriate
Next time something seems to work, but you don't know why, make sure it isn't just a coincidence.
Rather than construction, software is more like gardening 🌶
Don't try to refactor and add functionality at the same time.
Make sure you have good tests before you begin refactoring. Run the tests as often as possible. That way you will know quickly if your changes have broken anything.
Test your software or your users will
No matter which "best practices" it includes, no method can replace thinking. 🌶
Good requirements documents remain abstract
The team speaks with one voice -- externally -- Internally we encourage lively and robust debate.
Ask yourself if the software meets the performance requirements under real world conditions -- with expected number of users or connections or transactions per second -- Is it scalable?
On documentation, we like to see a simple module level header comment, comments for significant data and type declarations and a brief per class and per method header