These are the slides and comments of a presentation I held for bbv Software services AG.
The presentation is about how we get quality into our code.
Buzzwords: Fokus, frequent measurements, strong team, clean code, pair programming, test driven development, acceptance tests, continuous integration, collective code ownership, team learning.
Senior Software Architect
bbv Software Services AG
urs.enzler _at_ bbv.ch (replace _at_ with @)
Copyright © 2010 bbv Software Services AG
How to get quality into source code – that’s the question I’ll try to answer in this document.
You’ll see what we do at bbv Software Services to get code that is built with inherent quality and why it is important to think about quality throughout the whole development process.
There exists a lot of software that does not impress with high quality. The software crashes, is slow or simply not user friendly.
It seems that getting it wrong is really simple. Reasons for this are:
- Lack of know-how in the problem domain and technology
- Lack of good engineering practices
- Lack of a quality culture throughout development
But, why care about quality anyway?
Only a happy customer will sponsor your next project. By delivering superior quality, you build a strong product binding between your customer and users to your application and ultimately to you.
On the other hand there are the developers to be considered. No developer likes fixing bugs in old versions of the software. Furthermore, if as a consequence of having to do so your developers start quitting their jobs, will there be enough know-how left to maintain and further develop the software?
A high quality product will lead to stronger customer and developer binding.
First, we have to agree on what quality is.
The car in the picture sure looks like a quality car.
But why? Because it looks nice? Or because of its engine specs?
In software there exist two kinds of quality:
- External and
- Internal quality
External quality consists of everything an end user gets in touch with:
- Look & Feel
If you again consider our car as an example, you certainly expect it to get you from A to B at an adequate speed while using a reasonable amount of fuel. The seats should be comfortable and you’d like the car to be painted in your favorite color.
However, there is more to quality than what can be seen from the outside: Internal quality.
If you take a look under the hood then you might be surprised. The battery might have seen better times, or the engine does not match the expectations raised by looking at the bodywork of the car.
The same applies to software. Internal quality refers to the way the sub-systems, components, classes, methods and so on are built. Are they in shape? Do they fulfill the promise made by the user interface or retail packaging?
There is a one way dependency between internal and external quality.
Bad internal quality will sooner or later influence external quality.
If you don’t take care of your car then it will at some point be as rusty as the one in the picture above. And it for sure won’t take you from A to B anymore.
In software, lack of internal quality will result in either less external quality or increased cost of maintaining a high level of external quality.
Therefore, internal quality has to be a prime factor during software development and maintenance.
If you want to know the level of external and internal quality of your software, you have to be able to measure quality.
There exist a lot of metrics and other measurement methods for software quality. However, I consider most of them quite useless. For me, the real measurement for quality is as follows.
The only real measure for external quality is whether the software actually works:
- The user can do with it was she is supposed to
- with acceptable performance and
- with acceptable user interface experience (Look & Feel)
Internal quality has a single purpose: keeping the code changeable. While I consider this the highest goal to follow for achieving good internal quality, it consists of several individual aspects:
- Readability: only easily readable code can easily be understood and therefore changed
- Simplicity: simple is always easier to change than complicated
- Testability: only automatically tested code can be changed without fear of braking existing behavior.
Quality does not happen spontaneously. A lot of effort is needed to get a quality product.
For me, there are three fundamental key factors that influence quality in a positive way.
The first key factor for achieving quality is focus. In order to reach high code quality, you need to be able to focus on a single task at once.
Like the golf player in the picture, you need a clear goal. This goal has to be well defined and within sight.
If you don’t know what you should build (due to an unclear goal)you in all likelihood won’t produce a high quality application.
If you have a clear goal but it is far away (space or time) then it is likely that you won’t hit it – at least not without some detours.
If you have ever watched an attempt to set a new world record in a 10’000 meters race in athletics, you know what so called “pace makers” are. The job of a pace maker is to run the first couple of laps in a certain amount of time. The time of the competing athletes is measured every 400 meters and compared to the “plan” required to get the new record. This gives both the athletes and the spectators an indication whether the record will be broken or not.
The same holds true for software. If you want quality in the end, quality has to be measured frequently and depending on the results, adequate actions have to be taken.
A lot of different skills are needed to get a quality product: UI, database, multi-threading, communication, testing, automation, process, social skills, and so on.
The complexity of today’s software projects requires several persons to take part in it. There is a need for specialists for the different aspects of the project; however, if you want quality, a bunch of specialists alone won’t help much. What you need is a team – a team in which everyone can fill in for everybody else. Otherwise, it is highly likely that one of the specialists will become a bottleneck sooner or later. This will result in either delays or decreased quality.
The next sections cover what we do in our projects at bbv Software Services AG to build quality into our software.
These are all practices that have proven helpful in real world projects.
As stated above, the basis for code that is changeable is readability, simplicity and testability.
We have established principles and rules for developing so-called clean code:
- Principles of object oriented design
- Principles about the development environment
- Coding style guidelines
- Naming guidelines
- Exception handling guidelines
- Unit testing principles
This clean code definition is a guideline for all developers to reach a common understanding of source code quality.
We are convinced that basic quality checks have to be performed with tools. We don’t want to waste our energy and focus on something that a tool can do for us.
Therefore, we use StyleCop to check our coding style guidelines. Actually, StyleCop is our coding style guideline. Furthermore, FxCop is used to check basic design guidelines.
These tools eliminate the need for reviews focusing on coding style and basic design. They allow us to put our energy into higher concepts of quality.
Code reviews provide an environment to make a step back from coding and compare the code at hand with our clean code guidelines and other quality goals.
They are a periodic inspection of selected parts of the code. Normally, only critical code is reviewed extensively because reviews incur a rather high cost.
We practice a lighter form of code review whenever a developer wants to check-in code into the source code repository. The developer walks a second team member through all changes. This results in three things:
- The original writer thinks through her changes once again, sometimes finding code needing additional refactoring
- The reviewer checks whether the changes are understandable and follow coding guidelines
- Know-how transfer is established to the reviewer
However, there exists yet another form of code review: real time code reviews, or pair programming.
Pair programming is a key practice to quality. Pair programming has the following advantages:
- The code is already reviewed by a second person.
- The code is built with the combined technical excellence of two developers. This includes the advantages that it is more likely that alternatives are discussed and a better solution results than in the case of a single developer working on the task.
- Know-how is transferred between the pairing developers. Know-how about the problem domain, technical excellence and even tool usage is shared.
We develop our code test-driven. This means we write unit tests and production code at the same time. It’s like in a tight race, most of the time the unit tests lead, sometimes the production code takes over. But they are always within sight.
The tests we write act as a sparring partner for us to write the production code.
This fast paced switching between writing a bit of a test and writing just enough production code to fulfill the test allows us to get a very strong focus on the task at hand. Furthermore, the tests are our measure of working code. Whenever an already existing test fails due to changes made during new development, this is discovered almost immediately when running all unit tests.
Therefore, Test Driven Development helps us a lot in getting working software.
Once the new feature I develop is working, the refactoring phase of TDD starts. The goal of refactoring is to get code as clean as possible. We can really focus our energy on refactoring because whenever we introduce an error, one or more failing unit tests will inform us about it.
Because it is hard to do two things at the same time, TDD enables us to first focus on getting working code and then to make it clean and changeable by refactoring.
The unit tests we get from Test Driven Development give us the certainty that the many parts our system is built of work correctly. However, we need something different to show that the system as a whole is working: acceptance tests.
Acceptance tests do not test a single part of the system in isolation; they test the system in the same way users (human or non-human) interact with it.
The sum of all acceptance tests constitutes the specification that describes the functionality provided by the system.
Acceptance tests should be automated whenever possible. Only automated acceptances tests can be run over and over again and therefore provide us with frequent measurements of the external quality of our software.
As we have seen, Test Driven Development and acceptance tests give us working code. But this code will be changed during development and existing features might be broken in the process.
Therefore, it is important to check the current code base as often as possible against all existing unit and acceptance tests. This course of action surfaces breaking code changes almost immediately.
Therefore, whenever a developer commits/checks-in code into the source control system, the continuous integration server starts checking the code. A typical scenario consists of building the source code, running all automated unit and acceptance tests, checking coding guidelines automatically as far as possible (StyleCop, FxCop, …) and finally creating an installer package that can be used to deploy the application on a test system for additional manual testing.
Continuous integration requires that as many tests as possible are automated. Otherwise, manual testing will become a bottleneck to the overall development process.
So far, you have seen what we do to get working and changeable code. However, requirements will change and new features will be added. With time, the architecture and the design of our code will begin to “rot”; they will no longer be as simple and clean as possible.
We have to invest some effort to counteract this loss of internal quality. Otherwise development and maintenance cost will increase dramatically. This is often referred as technical debt.
A high level of code coverage by unit- and acceptance tests allows us to refactor the code without breaking the individual features and overall behavior.
To minimize peaks in refactoring work, we refactor our code continuously. Whenever a developer identifies a piece of code that does no longer fulfill our quality expectations, it is refactored as soon as possible.
Whenever you write a text of any importance you will probably have it prove-read by someone else. Otherwise, a lot of typos will remain undetected because we can’t see errors we make ourselves.
Therefore, developers do not exclusively work on their favorite part of the application. Ideally, every developer works on all parts of the software. They may have their main working area but they should extensively interact with other developers inside and outside this area.
When the team is working together, its whole technical excellence can be applied to all parts of the software.
Whenever there is a problem with quality – whether external or internal – it is a team responsibility to get it straight. There is not to be any finger-pointing. The team should have done pair programming or performed a code review on the code in question to prevent the issue in the first place.
Whenever a problem arises, it is important to identify the root cause. As long as only symptoms are dealt with, the problem will in all likelihood pop up again and typically do so at the worst possible moment.
Furthermore, it is important to resolve problems quickly. Whenever you decide to postpone a root cause analysis, you take the risk of developing code you have to throw away in the end because of the impact of the problem resolution.
Whenever I have to walk on an icy path, I take care that every single step is safe. This results in me taking small, well considered steps.
The same applies to coding software. Small steps allow me to focus and not to get lost in the mass of work to be done. Small steps give you the opportunity to measure quality frequently. Also, if something goes terribly wrong, it happened within a small step which can be reverted without too much pain.
However, a small step is importantly still a complete step. This means that a single development step should comprise a complete feature. The feature may be very small but should provide value. At the level of Test Driven Development, this means a step cannot be smaller than a single test, which shows that the code under test provides a certain feature. At the level of acceptance testing, a step cannot be smaller than an end-to-end feature (normally including user interface, business logic, database access, …).
A complete step is required to enable feedback on it.
This document describes the state of software development today at bbv Software Services AG. When my current project started three years ago, we had no Test Driven Development, no Continuous Integration, and no Pair Programming. We had to learn all of that. Therefore we established a learning culture.
Improvement happens in several ways:
- Technical excellence: getting better at coding and handling tools
- Process: adapt your process to better support quality
- Team work: becoming a high performance team
At bbv Software Services AG, we do a lot to foster individual learning as well as team learning. Besides reading books and going to conferences we meet once a month for a so-called Engineering Roundtable to discuss technical topics (e.g. technologies, user interface usability) companywide. We hold Coding Dojos to improve our engineering skills (e.g. TDD, design skills, tool handling). Furthermore, we pursue personal annual goals and individual technical projects besides the regular day-to-day work to master special topics. And finally, once a year we spend a whole week at a retreat dedicated to improving knowledge of a certain area (e.g. TDD, Scrum) in a highly focused way.
Getting quality into software is not easy. A lot of effort in various ways is needed to get it right.
Developing software iteratively supports us by measuring where we are earlier and more frequently – thus allowing us to improve where necessary.
Developing software incrementally helps accomplish better focus and prevents the feeling of being lost due to the lack of a clear goal within sighting distance.
Finally, quality has to be continuously taken care of. Quality cannot be added at the end, it has to be built in from the start. Therefore we design, test, write user stories and do everything needed on a daily basis.
If you have any questions or feedback, please feel free to add a comment.
Images: http://www.istockphoto.com/index.php, http://www.sxc.hu/