Code Simplicity Book Review
This is one of my favorite book. You can finish it during a plane ride. Some of the notes here make not make sense to you unless you read the discussion in the book that gives you context. It also has diagrams with makes it easier to understand the concepts.
Talent involved in programming - reducing complexity to simplicity. Think about reducing the complexity for other programmers. The more people who don't act to reduce the complexity, the more incomprehensible the program becomes. As the program approaches infinite complexity, it becomes impossible to find all the problems with it.
So, a good programmer writes as simple as possible to other programmers. A good programmer creates things that are easy to understand, so that it's really easy to fix all the bugs.
Simplicity is misunderstood to mean that programs should not have a lot of code, or shouldn't use advanced technologies. But that's not true. Sometimes a lot of code actually leads to simplicity; it just means more writing and more reading, which is fine.
Usually more advanced technologies lead to more simplicity, even though you have to learn about them first, which can be troublesome.
Spending a little more time writing simple code turns out to be faster than writing lots of code quickly at the beginning and then spending a lot of time trying to understand it later. Many great programs have stagnated in their development because it took so long to add features to the complex beasts they had become.
The quality of the end result is dependent entirely upon the quality of the machine, the quality of our ideas and the quality of our code. The quality of the code is the largest problem faced by software projects today. Improving the code is the most important problem we must solve in order to improve the result.
Every programmer is a designer. Any given decision must be made by an individual, not by a group of people. Experienced programmers have to figure out how to design a system and split up the tasks between different people.
When we are making decisions about software, our guiding principle can be how we can help. You can prioritize feature requests by asking : Which feature will help people the most? This is a good question to ask about any proposed change to your software system.
The goals of software design.
- To allow us to write software that is as helpful as possible.
- To allow our software to continue to be as helpful as possible.
- To design systems that can be created and maintained as easily as possible by their programmers, so that they can be - and continue to be - as helpful as possible.
The equation of software design.
The desirability of any change is directly proportional to the value of the change and inversely proportional to the effort involved in making the change. There is a very good discussion on this topic as well as on the topic of value. It seems to me that this discussion is basically centered around the 80/20 principle.
He recommends that when considering value, you also have to consider:
- How many users (what percentage) will this change be valuable to?
- How often will this feature be valuable?
- When it is valuable, how valuable will it be?
When considering the effort involved in change, it's important to take into account all the effort that might be involved, not just the time you're going to spend programming.
- How much research will it take?
- How much communication will all of the developers have to do with each other?
- How much time will you spend thinking about the change?
In short, every single piece of time connected with a change is part of the effort cost. The desirability of a change is directly proportional to the value now plus the future value and inversely proportional to the effort of implementation plus the effort of maintenance. In fact, nearly all decisions in software design reduce entirely to measuring the future value of a change versus its effort of maintenance.
The ideal solution and the only way to guarantee success is to design your systems such that the effort of maintenance decreases over time and eventually becomes zero. Changes with higher future value are still more desirable, but as long as every decision has a maintenance cost that approaches zero over time, you can't get yourself into a dangerous future situation.
Often, designing a system that will have decreasing maintenance effort requires a significantly larger effort of implementation - quite a bit more design work and planning are required. The effort of implementation is an insignificant factor in making design decisions and should be ignored.
In short, it is more important to reduce the effort of maintenance than it is to reduce the effort of implementation.
The quality level of your design should be proportional to the length of future time in which your system will continue to help people. Don't lock yourself into any one method of doing things, keep it flexible, don't make any decisions you can't ever change and put a lot of attention on design.
The future is the most important thing to consider in making design decisions. But there is a difference between designing in a way that allows for future change and attempting to predict the future.
In software design we can make certain decisions based on information that we have now, for the purpose of making a better future (decreasing maintenance effort and increasing value), without having to predict the specifics of what's going to happen in that future.
The longer your program exists, the more probable it is that any piece of it will have to change. Software must be as flexible as reasonably possible so that you can adapt it to future changes.
There are three mistakes that software designers make when attempting to cope with the Law of Change:
- Writing code that isn't needed.
- Not making the code easy to change.
- Being too generic.
Writing Code That Isn't Needed
Don't write code until you actually need it. Remove any code that isn't being used. That is, you should also get rid of any code that is no longer needed. You can always add it back later if it becomes needed again.
Not Making the Code Easy to Change.
Rigid design makes changes to code difficult. There are two ways to get a rigid design:
- Make too many assumptions about the future.
- Write code without enough design.
Code should be designed based on what you know now, not on what you think will happen in the future. A certain amount of planning is very valuable in software design. Even if you don't write detailed plans, you'll be fine as long as the changes are small and code stays easily adaptable for the unknown future.
Being too Generic
This involves writing a lot of code that isn't needed. Over engineering occurs when your design makes things more complex instead of simplifying things. Be only as generic as you know you need to be right now. Use the right level of abstraction. Don't speculate customer needs. I have run into problems when I make the generic right from the beginning, instead of waiting for duplication to emerge.
Incremental Development and Design
Pick whatever is simplest to work on at each step, when you get there. Sometimes you may even need to take a single feature and break it down into many small, simple, logical steps so that it can be implemented easily.
Incremental development is a method of building up a whole system by doing work in small pieces. Incremental design is similarly a method of creating and improving the system's design in small increments.
In the context of TDD, it looks like this:
Incremental Development ----> Make it Green
Incremental Design ----> Refactor
The chance of introducing a defect into your program is proportional to the size of the changes you make to it.
The best design is the one that allows for the most change in the environment with the least change in the software.
Never fix anything unless it's a problem and you have evidence showing that the problem really exists. Premature optimization is an example. Get real evidence that a problem is valid before you address it.
Law of Defect Probability
If we can reuse old code, we don't have to write or change as much code when we add new features, so we introduce fewer defects.
The ease of maintenance of any piece of software is proportional to the simplicity of its individual pieces.
Continually work to make the code simpler to make the effort of maintenance decrease over time. Simplicity is relative. It's good to have sections in code documentation like 'New to This Code?' that contain some simple explanations that will help people understand code. These should be written as if the reader knows nothing about the program, because if people are new to something, they probably don't know anything about it.
Context is important, for example in the context of program code, advanced technologies often lead to simplicity, if used right. Sometimes what seems complex in one context is simple in another. There are lots of ways to make your code easy to learn: simple documentation, simple design, step-by-step tutorials, etc.
Code that isn't consistent is harder for a programmer to understand and read.
Readability of code depends primarily on how space is occupied by letters and symbols.
Names should be long enough to fully communicate what something is or does without being so long that they become hard to read.
The purpose of comments is to explain why you did something.
Some projects start out with such a complex set of requirements that they never get a first version out. If you're in this situation, you should trim features. Don't shoot for the moon in first release - get out something that works and make it work better over time.
Common ways to add complexity are:
Expanding the purpose of the software. Stick to the existing purpose of your software - it just has to do what it does well, and you will succeed.
Adding programmers. You are more likely to be successful with a small group of expert programmers than a large group of inexpert programmers.
Changing things that don't need to be changed.
Being locked into bad technologies.
Misunderstanding. Understand the systems and tools you work with.
Poor design or no design.
Reinventing the wheel.
Complexity and Purpose
Think about the user's purpose. Users are happiest with a focused, simple product that never violates its basic purpose.
There are three factors you can look at to determine if a technology is bad before you start using it: survival potential, interoperability and attention to quality.
Survival potential is the likelihood that it will continue to be maintained. Interoperability is a measure of how easy it is to switch from a technology. Ask yourself, 'Can we interact with this technology in some standard way, so it would be easy to switch to another system that follows the same standard?
Attention to Quality
Is it becoming easier to use or more complex? Do the people who maintain the technology care about the quality of their product? Have there recently been a lot of serious security vulnerabilities in the software that seem like they were the result of poor programming.
Complexity and the Wrong Solution
Often, if something is getting very complex, that means there is an error in the design somewhere far below the level where the complexity appears.
What Problem Are You Trying to Solve?
When things get complex, backup and take a look at the problem you are trying to solve. Take a really big step back. You are allowed to question everything. Any method of solving that problem is acceptable, so what you need to do is figure out what the best method would be for your situation. Discard assumptions. Really look at the problem you're trying to solve. Make sure that you fully understand every aspect of it. Then figure out the simplest way to solve it. Ask yourself: 'How, in general, in a perfect world, should this sort of problem be solved?'
To solve a complex problem doesn't mean solution has be complex. It does mean that you will have to work harder than usual to simplify code. If you're having trouble with a complex problem, write it down on paper in plain language or draw it out as a diagram. Most difficult design problems can be solved by simply drawing or writing them out on paper.
In some part of your system is too complex, there is a specific way to fix it - redesign the individual pieces, in small steps. Each fix should be as small as you can safely make it without introducing further complexity. More often, the steps involve splitting one complex piece into multiple simple pieces.
You must first conceive of a system that is simpler than the one you have now - even if just in a small way. Then you work toward that simpler system, step by step. Once you reach that simpler system, you again conceive an even simpler system and work toward that. You don't every have to conceive the perfect system, because there is no such thing. You just have to continuously work toward a system that is better than the one you have now and eventually you will reach a highly manageable level of simplicity.
You cannot stop implementing features to spend time on redesigning. The Law of Change tells us that the environment around your program will be continuously changing and thus your program's functionality must adapt. If you fail to adapt and improve from the user's perspective for any significant length of time, you risk the loss of your user base and project failure.
There are various ways to balance these two needs of writing features and handling complexity. One of the best ways is to do your redesigning purely with the goal of making some specific feature easier to implement and then implementing that feature. That way, you switch regularly between redesign work and feature work. This also helps your new design fit your needs well, because you're creating it with a real use in mind. Your system will slowly get less complex over time and you will still keep pace with your user's needs. You can even do this for bugs - if you see that some bug would be easier to fix with a different design, redesign the code before fixing it.
Making One Piece Simpler
Study design patterns, methods of dealing with legacy code and all the tools of software engineering in general. It can be particularly helpful to know multiple programming languages and be familiar with many different libraries, because each involves different ways of thinking about problems that could be applicable to your situation, even if you're not using those languages or libraries. Studying those materials will give you many options to choose from when you are faced with a complexity. Sometimes, thought, you many look at a piece of code and not know any tools to use to simplify it. Ask yourself: How could this be easier to deal with or more understandable? That's the key question behind every simplification. Any true answer to it is a valid way of making your code simpler; the tools and techniques of software design just help us come up with better answers.
When you are working on simplifying your system, you may find that some complexity is hard to avoid, like the complexity of the underlying hardware. If you run into an unfixable complexity like this, your goal is to hide the complexity. Put a wrapper around it that is simple for other programmers to use and understand.
You should only rewrite if all of the following are true:
- You have developed an accurate estimate that shows that rewriting the system will be a more efficient use of time than redesigning the existing system.
- You have a tremendous amount of time to spend on creating a new system.
- You are somehow a better designer than the original designer of the system or if you are the original designer, your design skills have improved drastically since you designed the original system.
- You fully intend to design this new system in a series of simple steps and have users who give you feedback for each step along the way.
- You have the resources available to both maintain the existing system and design a new system at the same time. Never stop maintaining a system that is currently in use so that the programmer’s can rewrite it. Systems must always be maintained if the are in use. Remember that your personal attention is also a resource that must be taken into account here - do you have enough time available in each day to be a designer on both the new system and the old system simultaneously, if you are going to work on both?