It has become an Internet habit: "Five things you must see before you die", or "Ten things that will blow your mind". For software developers, I will contribute "Some things that always go wrong" here and now, hoping to not "blow your mind" :-)
So what do we do wrong every day?
Maybe I repeat myself. It's a daily struggle to avoid these things, and to make other people realize them as being major obstacles.
Blowing Up Things
When we implement a new feature, we go inside the source and try to find the point where we can insert our solution. We add some parameters, a new loop in the method body, the class gets some new member fields, and that's it, devil-may-care, coffee machine! So coffee goes in and in and in, until the class has 3000 lines of code, and its state is no more controllable, consisting of 30 member fields, full of long methods. Same with packages. Same with modules, libraries, applications, tools. It turns out that the word "implement" is too near to "implant".
Why do we always blow up things from inside? Why don't we build on them, from outside, respecting their responsibilities? Why don't we reuse them, override them, call old methods from new ones, extend classes by creating sub-classes, put new fields into new delegate classes?
Blowing up from inside we violate one of the oldest C programming rules: one function should have just one purpose (single responsibility principle). Instead we should:
- Refactor until single responsibility is reached (→ "blow down")
- Implement new features always by new classes, packages, modules
- Make all new sources comply with single responsibility
What I found useful in real life:
- No class should have more than 400 lines
- No method should have more than 1 loop
- Even the largest method must fit into a 1024 x 768 screen
If you blow up classes to implement several responsibilities, you make redesign difficult up to impossible. Essentially you are breeding the monolith that way. You are doing the opposite of "divide and rule". But breaking down problems to smaller ones is the only way to cope with complexity. And complexity is the primary problem of software development!
Underestimating Names
Every day we create names for classes, functions, fields, variables, packages, modules, libraries, applications, tools, helpers, utilities, .... Does anybody care about these names? Aren't they just for the compiler or reference docs? Following sentence I hear frequently:
Doesn't matter how it's called if it works
So this is a strong sentence, and I need to find an even stronger one now that tells the exact opposite .... what about this:
If names don't matter, why do we use names?
So let's number things. Like they do in the army. Or use completely absurd names like the JS community.
We are still living in times of hand-written source code. Maintenance time for hard-to-read source code is high. Source code is communication, you write primarily for humans, only secondarily for machines. Good names are the key to understandable source code. Following are my advices for better naming:
- Don't use names that are not in common sense (you are the only one that would understand them)
- Don't use abbreviations and acronyms (they make reading and understanding much harder)
- Avoid too general terms like "service", "process", "system", "action", "data" (you may obscure the real intent, and can be misunderstood in many different ways)
Exceptions confirm the rule:
→ abbreviations that everybody uses are acceptable,
→ general terms that are used for high-level abstractions are also fine.
Words should be as precise as possible, and as short as possible.
Yes, this is a contradiction, and it is an art to achieve a compromise of both. You must exercise this for some time, then people will start to understand you fast and easily. Your source should look like a well-structured API manual, not like the code of enigma.
Fixing Symptoms Not Causes
The classic symptom is the NullPointerException. Easy and fast to fix, simply put an if-not-null condition in front. But then the problem occurs again in another place, and in another, and .... because this collection was expected to never be null! So we better should have fixed the cause. Of course the NullPointerException is not the only situation where a symptom may obscure a bug.
It's a little bit like the copy & paste story. As soon as some occurrences have been fixed, and then someone fixes the real cause, it will be not easy to say whether a symptom fix can be removed or not. We end up with code full of if-not-null conditions. Here is my advice how to avoid fixing just the symptoms:
- Write assertions:
- Assert parameters
- Assert returned values
- Keep methods short and classes small
This helps to narrow mistakes. Early revealed problems will not reach regions where the cause can not be guessed any more. Here is a strategy for finding causes:
- Make the bug reproducible
- Set a debugger breakpoint to where it happens, then reproduce it
- Carefully study the stack trace in the halted debugger, the cause is most likely not on top of the stack!
Understanding the source code around, and its user stories, is absolutely necessary to be able to find causes. Take yourself time for this.
Copy and Paste Coding
Fortunately a well known problem, but also a survival strategy for certain developers. Unfortunately these always write the new code!
Here is the copy & paste story:
- A buggy block of code has been duplicated 10 times
- The first bugfix will be done in just one place, the 9 duplicates will stay undetected
- Tests may uncover further duplicates, other developers will fix them, of course in different ways
- After 10 developers fixed the dups in 10 different ways, it will not be visible any more that this was all the same functionality
- The management will say "This software aged rapidly, let's hire some students to write a new one!"
To be fair, I've seen also skilled programmers achieve their feelings of success by copy & paste programming. It is the most wide-spread programming sin. It degrades source down to code. I just can advice to not repeat yourself, stick to DRY.
Conclusion
Even when programmers will be replaced by robots some day, the robots will have to be programmed by hand. Even when it will be possible some day to replace all hand written code by ready-made design patterns, these patterns will have to be programmed by hand. We can't escape. We (we!) have to be clever and avoid these things that always go wrong.