I have always been concerned about quality when it comes to software development. Being a Computer Engineering student I remember some of my fellows told me: “if it works, don’t touch it!”. I could understand that point but I never agreed.
I always had the sense that code has not only to work but also to look organized, well written. Once, a friend of mine told me: “Cesar, yes I know, you like well-made things, but…” as an apology for handing me a little crappy piece of code. We passed our joint assignment and got a good mark, after all, his code worked.
It turns out that, in the academic context, writing good code and not code that just works, is not that important because, as a student, you usually create small, short-lived pieces of software that focus on solving a specific challenge and you’ll be graded according to how well your solution performs concerning that challenge. Code quality is well below in the list of criteria to get your grade, if at all.
I remember a particular assignment in which we had to design a heuristic search algorithm in a problem space that was not too big (around a million states average). Granted, coming up with a good heuristic was key to solve the challenge and I empirically proved that mine was quite good. However, as execution time would also play a key role to impress the professor, I decided to write the program in C and I used several hacks (manually unrolling loops, copy-pasting pieces of code to minimize function call overhead, extensive use of the inline keyword, tweaking code according to the generated assembly and execution time, etc).
My solution was twice as fast as the next fastest solution. I got the best mark, but my code was obscure. Nobody, including the professor, paid attention to that fact. My code was written to be efficiently executed by a machine, but not to be read by another person, just the opposite of the famous Harrold Abelson quote: “Programs must be written for people to read, and only incidentally for machines to execute”.
Don’t get me wrong, there are some specific situations in which you must trade code clarity for efficiency, but these are very uncommon and need to be identified and isolated. But for the rest of the codebase, what you want is a clean code that can be maintained and evolved by many people.
Apart from the ephemeral nature of the college assignments and its focus on specific topics, there are additional reasons to overlook good programming habits:
- Most instructors never left the academic community and thus never have come across the need to write maintainable code themselves.
- There is not enough time to learn everything. You are supposed to learn several fundamental concepts and techniques that will allow you to write powerful software such as data structures, algorithms, structured programming, object-oriented programming, functional programming, dynamic programming, recursion, software patterns, refactoring, you name it. But the fact is that, as a student, you barely have time to put all of them into practice, especially those that only make sense when dealing with a large codebase.
I’m not going to blame college or a particular university for not producing good programmers. After all, college gives you the foundations, a way of thinking, but does not give you the experience. It’s like getting your driver’s license, you are supposed to know how to drive, but actually, you learn to drive on the road.
Programming is an art
Programming is an art. Although it has its roots in science and computer engineering, programming is a skill acquired by experience. Programming requires paying attention to what you do, creative and critical thinking, discipline, and artistry. And, like in any other craft, craftsmen require techniques, tools, and, above all, experience.
A photographer needs to know how to compose a frame and how to adjust the exposition according to what she wants to achieve. She also requires a decent camera and lenses; and perhaps flashes, filters, a tripod, and other tools. Good tools help to achieve better results but are knowledge, good taste, spatial vision, aesthetic sensibility, which distinguishes a regular from an outstanding photographer. And you learn to become a better photographer by taking pictures, by reviewing the work of others and your own, by working with other photographers, by getting reviews about your work.
Following the analogy, a programmer also requires knowledge to apply the foundations of her craft. For instance, how to judiciously use specific algorithms, data structures, recursion, and software patterns. Needs to embrace the best practices, such as test-driven development (TDD), clean code, refactoring, code reviews, and pair programming. Needs to use the best tools available such as version-control software, continuous integration systems, and Integrated Development Environments (IDE) which provide a bunch of helpers to improve productivity and code quality.
There are, however, fundamental differences between photography and programming. Whereas photography is usually a solitary activity, programming is usually a team effort. In photography, once you’ve taken a picture you move to the next one. There would be some post-processing, and perhaps you need to maintain the coherence across a set of photos, but, in general, that’s it. When programming, you are building a product that, apart from being used, will need to grow and to be maintained. Therefore, if a team is going to develop a piece of software it better has the experience, otherwise it is too easy to screw things up.
Software quality is a broad subject in which multiple properties and measures can be used to assess it, such as conformance with specifications and expectations, usability, reliability, efficiency, security, maintainability, and size. I like to distinguish between those properties that users (and customers) can easily perceive and others that not. If you ship a piece of software that does not do what is expected to, users will notice almost immediately. If your application is hard to use, users might complain about that, as well as if your software looks messy or crashes often. However, if you make a mess under the hood, but the software “works fine” is harder for users and customers to detect that. This is especially true for greenfield projects.
In these projects, at the beginning, the team progresses and delivers value fast. If users and customers are happy with the perceived quality of the software, everything is fine. However, if the team does not care to refactor and clean the code; if there is no guidance on how to architect the software; if new features are added one on top of another, chaos will slowly creep in. Technical debt starts accumulating and the project is heading to become a Big ball of mud.
At that point, users and customers might start noticing some issues such as a feature that used to work is now broken or the effort to add a new feature has increased. Over time the perceived quality of the software decreases and the team slows down its pace dramatically because they struggle to work with a tangled mass of code. The customer asks the project manager to fix the situation and he usually does the only thing that can you, throw more people at the project which, by Brook’s law, still slows down further the team several weeks until the staff can do some useful work on the big ball of mud. Eventually, the project is canceled or is restarted from scratch.
“We do not have time”
I remember a specific project in which I was not part of the development team but I played the role of Product Owner. That project would solve some problems that might benefit a specific group of people and there was no other product on the market that addressed such particular issues. A quite large number of users and other stakeholders were involved and ready to provide specifications and testing. Scrum had been chosen as the agile framework. The project had a reasonable budget. Everything seemed fine, and the overall expectations were high.
The lead developer was also in charge of gathering requirements and feedback and providing support to users and other stakeholders. He was a young man, very attentive, very smart; a guy with great potential. In the beginning, requirements were turned into software features quite fast and users started testing. The iterative approach of Scrum started working. Everything was fine, everybody was happy.
Time went by and users started noticing the firsts issues. The software crashed under certain circumstances. Some features that used to work now were broken. On each new release, the application was harder to use. Hopefully, the lead developer was still there to fix the issues and provide support. At that time I came across one of the developers, and I asked him about refactoring and cleaning the code, his answer was: “we do not have time to do that, we focus on adding new features”. At that point, I realized that the project was doomed.
After several months, development has almost stalled. New releases were delayed and new features were almost impossible to be added. The development team was struggling to work with the codebase. Eventually, the project was canceled, only a few users were still using the software, that was the official reason. Nobody was happy.
But, what happened? Why did such a promising project end up so disastrously? We wanted to delve into this to see if there were any chance to resurrect the project. I was given access to the source code and, as I suspected, it was a complete mess. No signs of an attempted architecture, no unit tests, main functionality built around a God object, spaghetti code, etc. The efforts to try to refactor this would have been far greater than starting again from scratch and reusing some bits and pieces of the legacy code.
Some months later I had the opportunity to have a relaxed conversation with the lead developer. He apologized for how the project ended up. I told him that he was not the one to blame. He has just finished college and this was his first “big” project. Despite that, he was appointed as a lead developer and scrum master, with almost no mentoring or training. As smart the guy was, he managed to move on the project, but due to the lack of experience, it ended up being unmaintainable. He was brilliant but lacked experience.
Scrum is one of the most popular frameworks in the industry to manage software projects. Therefore, it didn’t come as a surprise when it was chosen for this project. The Scrum process is centered around project management but does not enforce any strong technical practices, as opposed, for instance, to eXtreme Programming. XP includes elements such as code reviews, unit testing, and pair programming. In Scrum, it is assumed that the team already knows and follows the best industry practices. Unfortunately, in this case, the team was composed of brilliant people but without strong technical expertise. They end up applying what Martin Fowler calls FlaccidScrum.
High-quality code does not always matter. For toy projects, proofs of concept, or solving a narrow challenge, you might live with a little mess of code. After all, you are going to throw it when finished. However, if you’re concerned about the future maintainability and stability of your project, then you better aim at producing high-quality code from the very beginning.
Producing quality code is not that hard, but it requires discipline, good foundations, and, above all, skills learned through experience. When you write good code you show respect for your customer and other programmers that work (or will work) on that code. Conversely, delivering your customer poorly written software, concealing the technical debt, is like selling a house or a car with latent defects. Everything is fine until the defects, the debt, bite you.
Currently, our industry is in a growing need for developers. This means that the average age, and thus experience, is decreasing thus posing a great challenge if we want to keep our professional standards.
Practices such as code reviews, pair programming, or having experienced programmers at reach to get feedback and answer questions quickly are great for learning and knowledge transfer.
Given the pace at which our industry evolves, continuous learning, and self-education are also important. When I find myself working with some pieces of code written by me some years ago, I realize how poorly written it was. But looking from another perspective, helps me assessing how much I improved over time.