Too often technicians are struggling with logical problems and managers are tinkering with design problems. This article gives an overview of all kinds of errors and problems which occur in software development. It further explains how to identify, distinguish and solve errors on different levels.
I recently answered a question on stackoverflow which seems contradictory to my recent blog post on cyclic dependencies. This made me realize that many software developers are not aware that there are different kinds of errors from which software can suffer. In this article I categorize the errors which can occur during software development. I describe how they differ from each other and how to identify and solve them.
In a second article I’ll explain how they correlate to each other, why some errors are more severe than others and how to transform them into each other.
Software Development Process
This article describes errors you can encounter down the road when developing software. Errors can be made in each of these four stages of the process:
The steps contain the first phases of the classical waterfall model. While agile fans often argue otherwise (cf. scrumalliance.org), I’m convinced that each of the above activities is done to develop software. Some of these activities can be done very inconspicuously or badly, but you can’t get around doing them.
This article describes errors for each of these activities: Management Errors, Logical Errors, Design Problems and Technical Errors.
These problems seem trivial, but many people are not really aware of them. I often see technicians struggling with logical problems or managers tinkering with design problems. One needs to become aware of the various levels to which the errors can belong and solve them accordingly.
The first level errors can occur at is the management level. Management errors are mostly project management errors, but may also be caused by other management areas, like strategic, business or quality management.
Examples of Management Errors
- No clear priorities.
- Not enough time to implement features.
- No experienced team members or a Council of Elders.
- No clear responsibilities in the team.
- A subordinate complains about problems, but is ignored.
- Applying any management Anti-Pattern, like Feature Creeping, Micromanagement, Death March or the Not-invented-here-syndrome.
Identifying and solving Management Errors
There is no single solution to all management problems at once, so I won’t offer solutions for all contingencies in this overview. If you don’t know how to solve them, get an experienced consultant. But management errors can cause further errors on all of the levels described below. These errors fall onto the heads of people who are not responsible for management themselves. Those people will likely try to solve and work around the problem within their area of responsibility. Therefore, one of the main responsibilities of management is to listen to their staff’s (subtle) messages which helps optimize the work process. In turn, this will cause an increase in productivity.
In my second category are the logical errors. If you (or your team leader) didn’t understand the problem domain correctly, you have one of these problems. They are often expensive to repair, especially if they are discovered after implementation.
Examples of Logical Errors
- You want to develop a software which calculates tomorrow’s lottery numbers.
- You want to legally access secret user data.
- You want a third-party website to call your app.
- You want to start a task without having all necessary information/inputs.
- You have an infinite loop, though all stop criteria are implemented as specified (like in the stackoverflow example).
Identifying Logical Errors
They are hard to identify, since they can occur in many points of the software development life-cycle and can be very distributed.
If you talk to the client and don’t understand a feature request completely, don’t be shy: ask what is meant. Does the feature contradict another feature? Clients are often not aware of what they really want.
If you write down some specification for developers and don’t exactly know what to write, you probably don’t understand the problem domain well enough.
A logical error could become obvious for the first time only after the requirements are specified. If your design diagram contains a cyclic dependency which is caused by the requirements, this may be a logical error. If your design requires third-party tools (e.g. the google API) to depend on your code, you may have a logical error. If you write code, but can’t fulfill the requirements, no matter how you modify the code, then you have a logical error.
Agile approaches can help identify logical problems early. Do some quick prototyping and show the results to your clients as often as possible.
Solving Logical Errors
You have to track the problem back to the root, which is a wrong understanding of the problem domain. Then you need to take more time, investigate and discuss in more depth in order to understand the problem properly. Often you need to inform your team leader, requirements engineer or clients about the problems you discovered.
In the worst case scenario, it’s not solvable, like the lottery example above. In other cases, it’s easier to solve them. E.g. missing inputs just need to be acquired beforehand somehow. Analysis-Patterns can help understand and model the domain properly.
Sometimes customers or managers mix up their requirements with wishes for specific technologies. If they do so, figure out what they want to achieve with this technology. Propose better options, if you know some. Technologies are (mostly) not logical issues, but solutions to some fundamental requirements.
Agile processes can handle these kinds of errors better than heavy-weight development processes. Regardless of process, errors are always harmful, especially if discovered late. Discovering that your whole Scrum-Sprint was useless at the end of the Sprint is painful. In classic Waterfall, this problem is a real hell: the problematic requirement can’t be accomplished at all. In any case, you can discover and prevent them early through intense communication and courageously voicing concerns.
My third class are design problems. Assuming your requirements are correct, you can still introduce conceptual errors before coding, while developing the architecture and design.
Customers or managers may interfere with architecture or design. It’s (normally) not their responsibility to mess up your design, but they probably have their reasons. Figure out what they want to achieve. Propose better options, if you know some.
We can further divide design problems into design flaws, which still result in software fulfilling the requirements, and design errors, where the design violates the requirements or specification.
In my experience, design flaws are a very common problem in practice, for several reasons: it’s not mandatory to solve them, they are often vague and one needs experience to discover and avoid them. Also fixing them is only useful in the long-term and who cares about the future?
Examples of Design Flaws
- Having no design or architecture at all is the flaw I see most often. Problems are solved on code level, instead of design level (cf. advantages of graphical notations).
- Being unable to draw your designs without crossing edges or overlapping elements. This is my rule of a thumb to decide if further fine tuning is urgent.
- Having front-end dependencies in the back-end.
- Using no Interfaces or Packages in bigger projects.
- Applying any well-known architectural or design Anti-Pattern, like Big Ball of Mud, God object or Sumo Marriage.
- Having Code Smells like Feature Envy or Data Classes. They are forms of design flaws, though the name Code Smell indicates something else.
- Violating basic design principles. E.g., having a class with multiple responsibilities violates the Single Responsibility Principle.
Identifying Design Flaws
Recognizing them needs some experience in software design and architecture. You should develop some intuition to identify them, which isn’t easy to teach.
For beginners, I’d recommend reading about software architecture and design. Design principles, code smells and anti patterns (see examples above) are a good starting point. Also learning from experienced architects and designers is very helpful.
After all, if the programmers complain a lot or become slower and slower, then it’s clear that there are too many design flaws.
The more experienced you become, the easier it gets to find the flaws. The problem shifts from simply identifying flaws, to prioritizing flaws and avoiding gold plating.
In any case, communication helps a lot when it comes to design. Talk about your design with developers and designers. It reveals many more flaws than doing design on your own.
Solving Design Flaws
Just as identifying, solving them requires experience, too. A mentor can help, as well as reading about design. Especially knowledge about architectural and design patterns is useful here. There are so-called Refactorings which help you improve your design step by step, while preserving functionality.
When you become more experienced, you won’t need those beginner tools anymore. Instead, common sense and good judgement are your best weapons. But with every solution you’ll introduce the next flaws. The key is accepting some minor flaws in order to avoid gold plating and over-engineering.
In agile processes, changing design is not a big deal normally. In more classical approaches, a bad design may result in code which just doesn’t implement the specified design. Introducing bigger architectural changes after implementation is problematic in both agile and non-agile approaches.
Design Errors are less common and easier to solve. They occur if the correct requirements are misunderstood and a wrong design is created as a result. Skipping the design causes an immediate implementation of a wrong solution, which can result in higher costs.
Examples of Design Errors
- Every person has 1 to n parents.
- Your Car class has a swim method.
- One thread requires data which another thread didn’t deliver yet, a.k.a. race condition or race hazard.
- Security issues.
Identifying Design Errors
To identify them, always argue on design level, not on code level. Use graphical notations instead of textual or pure verbal communication. And communicate a lot. Doing designs with others reveals much more errors than doing it alone.
Learn a lot of diagram types. They are not only good to visualize your design and to communicate it. They also provide you with new (better) ways of thinking about certain problems. UML diagrams are a good starting point to learn. If you don’t know any better diagram, free form diagrams are often sufficient, too. The better your chosen diagram matches the problem, the easier you can discover design errors.
In very agile environments there is no need to identify and prevent design errors early. Just wait for the programmers to get stuck, or for the customers to complain about the prototype. Then fix it.
In classical processes it can be a real pain to identify design errors early. You need to thoroughly read the requirement specifications and apply design reviews to ensure no mistakes were made.
Solving Design Errors
Design errors are often very easy to solve on a design level and hard to solve on a code level (if you skipped explicit design). I’ll demonstrate how easy it is for the examples above:
- Person with n parents. → change it to have exactly 2 parents
- Car with a swim method. → (re-)move the swim method.
- Concurrency issues. → make your concurrency design explicit, instead of just coding threads from your head. Use UML activity diagrams with forks and joins.
- Deadlocks. → use data flow diagrams, watch out for typical deadlock patterns.
- Security issues. → can’t be solved in this general discussion, they differ strongly for different areas of security.
Finally, there are technical errors, also known as bugs. All errors before were conceptual errors in someones head, or on paper. Technical errors appear in some program. They are much more obvious, because the program gives you explicit signs.
Some problems here are hard and expensive to solve. If you discover one of these problems, ask the management or customer to reconsider the importance of the corresponding feature. Requirement changes are often easier than expected.
Technical errors can be subdivided into the following categories.
The first thing a programmer learns about a programming language is its syntax. What are the available keywords? What brackets to use? How to arrange the code? Most IDEs and some text editors help you find syntax errors by underlining them. Fixing them is very easy: just correct the typo or add the bracket.
They occur if you do some illegal type casts or access private data. To many programming beginners they are annoying, while I think they are not the root of the problem. Normally, when a static error occurs, it’s caused by an overseen logical or design error.
Usually IDEs allow displaying them, while text editors don’t. Anyway, they shown up the moment you compile code with static errors. To solve them, you need to rethink the problem domain or design and determine what exactly you want.
In addition to IDE and compiler, there are many other tools which support you with static analysis. They discover memory leaks, check pre and post conditions, concurrency and security issues.
Compile Time Errors
Compile time errors are very similar to static errors, but IDEs don’t show them the moment you type them. Newer languages don’t have this phenomenon, as far as I know. But, for example in C++, your IDE may show some errors only after you compiled the code, instead of doing it “on the fly”.
Dynamic errors (also called run time errors) occur when the program is already running. There are many possible reasons for them. Some special cases may not have been considered yet, or some technical issue prevents the program from running properly (e.g., an unavailable server).
Popular examples of dynamic errors are the Null Pointer Exception, Division by Zero and infinite loops.
Some of these errors are very hard to discover and stay hidden for many years, before someone stumbles across them. Bug trackers can help remember bugs that have already been discovered. Tests can help discover them, too, but Dijkstra’s law applies here:
Testing shows the presence, not the absence of bugs
– Edsger W. Dijkstra
To solve a dynamic error, it must be reproducible, otherwise it’s impossible to know if you fixed it. Because the cause of dynamic errors can vary widely, there is no single solution. Use a debugger to figure out their cause, then use your brain to fix them.
Semantic errors are caused by implicit type systems and implicit type casts. They are often static or dynamic errors which slipped through to the semantic level, because your language isn’t strict enough to find them.
For example, when a user types 12 into a text field and we have the following pseudo code:
print(userInput + 5)
Different things maybe printed, depending on your language: 125, 17, a static error or a dynamic error, depending on your language and text field. The first of these results is a semantic error, the later two are other error types.
One could argue that these errors are a kind of logical error, but they are not caused by a misunderstanding of the problem domain. Instead, they occur because the technology is used incorrectly, thus I categorized them as technical errors.
Semantic errors can best be found by using tests or by running the program and employing common sense. To fix them, debug the program to find out what’s causing the problem.
In this post we covered different kinds of errors and problems that can occur during software development. We learned how to identify and solve them individually.
In the second blog, post I’ll explain how these errors relate to each other, how to convert them into each other and why some of them are worse than others.
Looking forward to your comments and opinions,
Part 2 – Relation & Severity (Under Construction) >>