This post contains a few points extracted from the book. If you find these notes useful, I highly recommend you check out his book for more detailed explanations.
My favourite takeaways from the book were his discussions on:
- Working with developers
- Suck Less
Part One – Principles for Programmers
1 Before you Begin…
In order to become an excellent programmer, you must first want to become an excellent programmer. No amount of training will turn somebody who does not want to be excellent into an excellent programmer.
2 The Engineer Attitude
I can solve this problem the right way.
If you don’t know the right way to solve a problem, take a break from it, walk away for a while. Let the mind work on it in the background, then come back to it in a day or two.
Software code that maintained its simplicity while providing the flexibility needed for reasonably possible future enhancements would be designed the “right way”.
3 The Singular Secret of the Rockstar Programmer
The better you understand what you are doing, the better you will do it.
The better that you understand the most fundamental level of your field, the easier it will be to learn the next level. The better you understand that level, the easier the next one after that will be, and so on. Then once you’ve gone from the most fundamental simplicities to the highest complexities, you can start all over again and find amazingly that there is so much more to know the very, very bottom.
4 Software Design, in Two Sentences
- Focus more on reducing the Effort of Maintenance than the Effort of Implementation
- The Effort of Maintenance is proportional to the complexity of the system
Part Two – Software Complexity and Its Causes
5 Clues to Complexity
The code is hard to understand, you have to add “hacks” to keep things working, it’s often misused.
6 Ways to Create Complexity: Break Your API
An API is sort of a promise, You can always interact with our program this way, safely and exactly like we said. When you release a new version of your product that doesn’t support the API from your old version, you’re breaking that promise.
Don’t release bad APIs. If you do, they must be maintained, and that is especially difficult with a small team. And consider versioning the API, so old clients still work, but new clients can take advantage of the newer API with its better design and enhanced functionality.
7 When Is Backwards-Compatibility Not Worth It?
If you find yourself in a situation where there is infinite backwards-compatibility and no forward progress, this means total death for your product. You need to ditch it.
This also gives one good reason why you shouldn’t just add features willy-nilly to your program. One day you might have to support backwards-compatibility for that feature that you added “because it was easy to add even though it’s not very useful.”
The only time you should seriously consider ditching backwards-compatibility is when keeping it is preventing you from adding obviously useful and important new features.
8 Complexity is a Prison
Complexity is a prison; simplicity is freedom.
Part Three – Simplicity and Software Design
9 Design from the Start
My policy on projects that I control is that we never add a feature unless the design can support it simply.
If you don’t think about the future, then all of your code will be poorly designed and much too complex.
If your project lacks a strict design, and it continues to grow, then you will eventually end up over your head in complexity.
10 The Accuracy of Future Predictions
The accuracy of future predictions decreases relative to the complexity of the system and the distance into the future you are trying to predict.
When we look only at the present, the data that we have, and the software system that we have now, we are much more likely to make a correct decision than if we try to predict where our software is going in the future. Most mistakes in software design result from assuming that you will need to do something (or never do something) in the future.
Keep software as simple as necessary, it will be easier to apply incremental changes in future.
11 Simplicity and Strictness
The stricter your application is, the simpler it is to write.
There’s a trade-off between simplicity (strictness) and usability.
Less-strict programs often take more code than strict ones, which is often directly where the complexity comes from.
By the way, if you’re writing frameworks or languages for programmers, one of the best things you can do is make this type of user interface “non-strictness” as simple as possible, to eliminate the trade-off between usability and complexity, and let them have the best of both worlds.
Strictness is mostly about what input you allow.
Computers should never “guess” or “try to do their best” with input.
12 Two Is Too Many
Essentially, I know how generic my code needs to be, by noticing that I’m tempted to cut and paste some code, and then instead of cutting and pasting it, designing a generic solution that meets just those two specific needs.
A developer should ideally never have to modify one part of the code in a similar or identical way to how they just modified a different part of it.
Don’t Repeat Yourself.
13 Sane Software Design
This chapter uses an analogy to highlight some design principles.
- Think about the future, don’t try to predict the future
- Allow for change, develop to standards, for flexibility
- Implement small incremental changes that are well tested
- Be consistent and adhere to standards
Part Four – Debugging
14 What is a Bug?
- The program did not behave according to the programmer’s intentions, or
- The programmer’s intentions did not fulfill common and reasonable user expectations
15 The Source of Bugs
Bugs most commonly come from somebody’s failure to reduce complexity. Less commonly, they come from a misunderstanding of something that was actually simple.
Your responsibility is writing clear code.
- The simpler your code is, the fewer bugs you will have
- Always work to simplify everything about your program
16 Make it Never Come Back
The way that software companies get into unmanageable situations with their codebases is not really handling problems until they are done.
No matter how bad a codebase is, you can resolve its problems.
A problem is resolved to the degree that no human being will ever have to pay attention to it again.
17 The Fundamental Philosophy of Debugging
- Familiarity with what a working system does
- Accepting that you don’t already know the cause of the problem
- Looking at data until you know what causes the problem
- Fixing the cause and not the symptoms
Sometimes people have a very hard time debugging. Mostly these are people who believe that in order to debug a system, you have to think about it instead of looking at it.
When you start debugging, realize that you do not already know the answer.
- Remember what a working system behaves like
- Figure out what you need to look at in order to get more data
Debugging is accomplished by gathering data until you understand the cause of the problem.
The way that you gather data, is almost always, by looking at something.
Clarify the bug, follow up with the user to find the exact problem, steps to reproduce it, frequency, and any other information necessary to understand what the issue is. Don’t try to search for the cause until you understand the effects.
Look at the system. Once you’ve clarified the bug, you have to look at the various parts of the system. Which parts of the system to look at is based on your knowledge of the system. Usually it’s logs, monitoring, error messages, core dumps, or some other output of the system. If you don’t have these, you might have to launch or release a new version of the system that provides the information before you can fully debug the system.
Debugging is accomplished by comparing the data that you have to what you know the data from a working system should look like.
Find the real cause.
You will know that you have found a real cause when you are confident that fixing it will make the problem never come back.
Very often, the best fix for a bug is a fix that actually deletes code or simplifies the system.
Part Five – Engineering in Teams
18 Effective Engineering Productivity
If you want to solve developer problems, you have to find out what those problems are from developers.
Find out what problems developers think they have. Don’t make any judgements. Go around and talk to people. Don’t just ask the managers or senior executives. They usually say something completely different from what the real software engineers say.
If you ask most developers for an emotional reaction to the code that they work on or work with, they will almost always have something. I ask questions like, “Is there some part of the job that you find really annoying?” “Is there some piece of code that’s always been frustrating to work with?” “Is there some past of the codebase that you’re afraid to touch because you think you’ll break it?” And to managers, “Is there some part of the codebase that developers are always complaining about?”
You can adjust these questions to your situation, and remember that you want to be having a real conversation with developers – not just robotically reading off a list of questions.
After a while of doing this, you’ll start to get the idea that there is a common theme. There might be several themes that come up.
Credibility and Solving Problems. The first thing you should do with the data is find some problem that developers know they have, that you know you can do something about in a short period of time and deliver that solution. The point of this change is to make your work credible.
When you work in engineering productivity, you live or die by your personal credibility.
Once you’ve established your basic credibility by handling this initial problem, you can start to look at what problem the developers have and what you think the best solution to that would be.
You can’t change everything about a team’s culture or development process all at once. You have to do it incrementally, deal with the “fallout” of the change and wait for that to calm down before moving on to the next step. If you tried to change everything all at once, you’d essentially have a rebellion on your hands – a rebellion that would result in the end of your credibility and the failure of all your efforts.
The Blocker. Sometimes a very senior person will resist change. How much progress you can make depends partly upon your communication skills, partly on your willingness to persist, but also partly in how you go about resolving this situation. Find your allies and create a core support group for the efforts you are making. Almost always, the majority of developers want sanity to prevail, even if they aren’t saying anything.
Publicly encouraging somebody when they want to improve something goes a long way.
Sometimes there is one loud person overruling the majority. Do what you can with the group of people who support you, and make the progress that you can that way.
Don’t be adversarial or argumentative, but listen to the person and see if you can work with them.
It’s not worth your sanity and happiness to go up against somebody who will never listen to reason and who is dead set on stopping you at all costs.
- Be kind
- Form a group of supporters
- Don’t jeopardize your credibility
- Find the things that you can do to help
At some point, you need to start changing the way people write software in order to solve the problem. You’re going to need to get down to simplifying code. Propose refactoring where you can.
Code reviews are also an excellent mechanism to impart change.
Solve the problems that people know they have, not the problems you think they have.
19 Measuring Developer Productivity
“Computer programmer,” like “carpenter,” is a skill, not a job.
The way to measure the productivity of a developer is to measure the product that they produce.
- Focus on code simplicity first.
- Almost all software problems can be traced back to some failure to apply software engineering principles and practices
Don’t focus on how many lines of code (LoC) a developer writes.
20 How to Handle Code Complexity in a Software Company
Resolving code complexity usually requires detailed work at the level of the individual contributor.
Get data from individual contributors and then work with them to help them resolve the issues.
- Ask each member of your team to write down a list of what frustrates them about the code.
- Call a meeting and have each developer present, dig into specifics: file, class, method, etc.
- Submit bug reports for each identified problem, be specific: file, class, method, etc.
- Prioritize the issues.
- Assign each bug to an individual contributor.
- Schedule time in each sprint for developers to work on the bugs.
The essence of good software design in all situations is taking the right actions in the right sequences.
21 Refactoring is About Features
Refactoring is essentially an organizational process.
Pick a feature that you want to get implemented, and figure out what you could refactor that would make it easier to implement that.
The key principle to cleaning up a complex codebase is to always refactor in the service of a feature.
You first goal is to get the system into a place where it’s getting better over time, instead of worse.
You have to balance the fact that you do need to make forward progress on your feature goals, and that you can’t just refactor your code forever.
There is no perfect design, there is only a better design.
When you’re refactoring, the idea is to change the design from one that doesn’t currently suit the purpose well to a design that fits the current purpose that piece of code has.
22 Kindness and Code
Software engineering is fundamentally a human discipline.
In reality, software systems are written by people, They are read by people, modified by people, understood by people. They represent the mind of the developers that wrote them.
Software is the product of people, and it is usually the product of a group of those people who had to work together, communicate, understand each other, and collaborate.
Be kind and make better software.
23 Open Source Community Simplified
Growing and maintaining an open-source community depends essentially on three things:
- Getting people interested in contributing
- Removing the barriers to entering the project and contributing
- Retaining contributors so that they keep contributing
Part Six – Understanding Software
24 What is a Computer?
A computer is any piece of matter which can carry out a series of symbolic instructions and compare data in assistance of a human goal.
25 The Components of Software
The MVC pattern is a good example.
When I’m writing software, I usually build the Structure first, then I work on the Actions, and then I work on the displaying of the Result.
26 Software Revisited
All software consists of:
This is essentially the traditional Input, Processing, Output model with the importance of structure being noted.
27 Software as Knowledge
Software is, fundamentally, a solid object that is made of knowledge. It follows all the rules and laws of knowledge. It behaves exactly as knowledge behaves in just about any given situation, except that it’s in concrete form.
28 The Purpose of Technology
When technology attempts to solve problems of matter, energy, space or time, it is successful. When it attempts to solve human problems of the mind, communication, ability, etc. it fails or backfires dangerously.
Companies that focus on solving human problems with technology are likely to fail. Companies that focus on resolving problems that can be expressed in terms of material things at least have the possibility of success.
29 Privacy Simplified
There are two types of privacy:
- Privacy of space
- Privacy of information
In short, no matter what you do, in order to live, you must exchange information with other people. The more things you do, the more information you will have to exchange.
30 Simplicity and Security
A big part of writing secure software is simplicity.
- We need to limit the number of “ways in” to our software.
- How many different possible attacks are there against each way in?
The best way to get real security in things is simplicity
31 Test-Driven Development
The cycle of all software development is:
Observation -> Decision -> Action
32 The Philosophy of Testing
In a sense, testing software is the reverse of the traditional scientific method, where you test the universe and then use the results of that experiment to refine your hypothesis.
- Test what is important
- Assert expectations
- Test Boundaries
- Test assumptions
- Test design
“End to End” testing is where you make an assertion that involves one complete “path” through the logic of the system.
If a system is designed in such a way that is can only be tested via end-to-end tests, then that is a symptom of broad architectural problems in the code.
“Integration Testing” is where you take two or more components of a system and specifically test how they behave when put together. Compared to end to end testing, integration testing involves a bit more isolation of components as opposed to just running a test on the whole system as a “black box”.
“Unit Testing” is where you take one component alone and test that it behaves properly. Unit testing is most valuable when you have a component that presents strong guarantees to the world outside of itself and you want to validate those guarantees.
The overall goal of testing is then to gain valid knowledge about the system.
Part Seven – Suck Less
33 The Secret of Success: Suck Less
All you have to do to succeed in software is to consistently suck less with every release.
Even if you didn’t fix everything in this release, if you sucked less, your users will have faith that eventually, the things that bother them will be fixed.
You have to get out releases frequently enough that people believe that you really will suck less.
If you release frequently, but instead of fixing the things in your software that suck, you just add new features that don’t fix the sucking, the patience of the individual user is going to run out. They’re not going to wait forever for your software to stop sucking.
A program that has half the feature set of others, but doesn’t suck majorly, is better than the feature-rich alternatives.
34 How We Figured Out What Sucked
Sometimes the big issues in a software project don’t get handled because they do require that much work to fix. This doesn’t mean that you can ignore them, it just means that you have to plan for a long project, and figure out how you can keep getting releases out in the meantime.
Usually there are one or two HUGE things that really suck, and they’re obvious – those are the things to focus on first, even if they require a tremendous amount of work.
After all that is fixed, you will notice a bunch of other things that sucked, a new batch of “totally obvious” things to fix – things that had been there all the time, but were just overshadowed by the previous set of “totally obvious” things.
At some point, you may need to prioritize these issues. Two good tools to help with that are:
- A Product Survey
- A Usability Study
Analyze the user feedback you receive to prioritize.
35 The Power of No
The most important word in a software designer’s vocabulary is “no”.
Recognizing Bad Ideas:
- The implementation of the feature violates the laws of software design
- The feature doesn’t help the users
- The proposal is obviously stupid
- A change doesn’t fix a proven problem
- Uncertainty in the idea’s validity
If you can only think of a bad solution, do not implement the solution.
36 Why Programmers Suck
The vast majority (90% or more) of programmers have absolutely no idea what they are doing.
Real stupidity is not knowing that you don’t know.
37 The Secret of Fast Programming: Stop Thinking
Any time you find yourself stopping to think, something is wrong
When you understand that there are unshakable laws to software design, that can eliminate a lot of the “stopping to think” moments.
Often, the simplest piece of code to start with is the “core” of the application. If you’re not sure how to write even that core code yet, then just start with the code you are sure about.
Make sure you have eaten enough, and are hydrated.
Overcome self-doubt by learning more about the tasks at hand until you become certain enough to write code.
Truly smart people learn, observe, decide, and act. You must act.
38 Developer Hubris
The true humility required of a developer is the willingness to remove their identity from the user’s world.
39 Consistency Does Not Mean Uniformity
Consistency is really important in both the backend and frontend of an application. But that doesn’t mean that every single thing should look exactly the same.
40 Users Have Problems, Developers Have Solutions
In the world of software, it is the job of software developers to solve the problems of the users.
Problems come from users, not from developers.
41 Instant Gratification = Instant Failure
Software is always a long-term process.
42 Success Comes From Execution, Not Innovation
It doesn’t matter how good or new my idea is. It matters how well I carry it out in the real world.
43 Excellent Software
A truly excellent program carries out the user’s intention exactly as they intended it.
An excellent program:
- Does exactly what the user told it to do
- Behaves exactly like the user expects it to behave
- Does not block the user from communicating their intention