How do you ensure quality in the software you create?
Many years ago I was asked to send a response to this question to a recruiter for an interview prescreening. I decided to put a little more thought into my answer than the few bullet points you might typically expect.
This is a great, albeit a very loaded, question. One could easily write a series of books to address just this one topic alone. That said, I’ll attempt to be brief and concise, yet comprehensive in my answer. To begin, I believe it’s worth mentioning that the definition of quality is contextual. For example, an executive manager will view quality differently than a team lead, and a team lead might view quality differently than an end user. I have had a wide breadth of experience in my career, and these are some of the things that I endeavor to do to ensure quality when building a software system.
Achieve a full understanding of the business vision, goals, and requirements of the system
It is important to have a full understanding of why you are building software. Most software is built to increase the net income of a business, either by increasing revenues or by reducing costs. It is vitally important to understand such objectives when designing a new system so that it is not over engineered or under engineered. Over engineering software can make it very robust and sophisticated, but if the costs exceed the financial gain, then the system has failed to meet a fundamental business goal. Alternatively, if a system is under engineered, there are a plethora of issues which can arise such as it being very brittle and buggy, unable to scale and handle user load, or it can be very difficult to extend and add new functionality. To that end, I feel it is very important to understand why you are building a particular piece of software so that it may be designed and implemented with those goals in mind.
Achieve a full understanding of the technical environment
Develop a user experience / design optimized for the end users
I’ve built a lot of systems that were amazing on the back end, yet simply failed to meet the expectations of the users because the UI was built for how the managers thought it should work instead of how the users thought it should work. I feel that is a critical component of successful software design to ensure you are carefully considering the profiles of the end users and how they will be using that system. The users don’t care how cool the back end is; they care that the system helps them meet their needs easily and efficiently. To this end, I always try to ensure that the UX component of systems are a forethought instead of an afterthought.
Design and build the system for scalability and extensibility
With the caveat I mentioned above about not over engineering systems, if they are designed properly with the goals of scalability and extensibility in mind, software can be built to scale to the increasing demands of a growing user base and can also be easily extended to meet evolving business and functional needs. One of the primary goals here is that existing systems shouldn’t need to be rebuilt to do this. To that end, I feel that there are some basic architectural principles which should be followed to achieve this goal. Those principles are listed below.
Design and implement an N-Tier architecture
I normally build at least 3 tiers into the systems I design. These canonical tiers, Data, Business, and Presentation, can almost always be separated to support scalability and extensibility without over engineering the system. The fourth tier, Services (between Business and Presentation), has also become increasingly more important with the proliferation of mobile devices.
Adhere to the SOLID architectural principles
Bob Martin (“Uncle Bob”) came up with the SOLID acronym as basic architectural guideline for application architecture. I feel these five principles, i) single responsibility, ii) open closed, iii) Liskov’s substitution, iv) interface segregation, and v) dependency inversion, are very important in building quality software because when they are followed, they help create systems which are not brittle, are scalable, and are extensible. To say that is an over simplification of its benefits, however, is an understatement at best. I endeavor to follow these five principles in my code.
Always be mindful of DRY
“Don’t repeat yourself”! This one is very simple, but often takes disciple to follow. A large aspect of building good software is refactoring. You can’t build everything perfectly the first time around and you don’t always know how to initially break up classes and functionality, but it’s very important to recognize when you are duplicating a piece of code and have an opportunity to abstract it out so that it is reusable. It’s easy to fall into the copy and paste trap when you are under strict deadlines, but technical debt never gets repaid and it’s always a good idea to adhere to DRY so that you only have one place to fix bugs and implement logic.
Always be mindful of YAGNI
“You aint gonna need it”! This one is also hard for developers because we love building castles in the sky. There is a fine line between over engineering and building for extensibility, and this applies to not just architecture but to functional implementation. Don’t build functionality you’re not going to need. If the business hasn’t thought of something, instead of building it surprising them with the good news later, simply talk to them about it and they can decide if they want to spend the time and money developing it.
Implement appropriate design patterns
Design patterns create not just a common language for us to use in the industry, but also provide solutions to problems which have already been solved. I’m a big believer in not reinventing the wheel, so I look to patterns to help me solve problems. That said, I also try to stay very practical in my application and study of patterns. I often have no idea that I’m even using an established pattern until I read about it later and have the “oh, that’s what that’s called” moment. It’s easy to go overboard with patterns, so I try to be pragmatic with them.
Conduct and participate in regular code reviews
I think code reviews are critically important in developing quality software. Having an extra set of eyes on things is always a good idea. Doing regular and structured code reviews not only pushes developers to write great code and adhere to team standards, but it catches bugs early. And the quicker you catch a bug, the less expensive it is to fix.
Achieve 100% conformance to StyleCop rules
I’ve lost track of how many quasi-religious discussions I’ve been a part of regarding style and coding standards. Due to its subjective nature, style is one of the hardest things to standardize upon. Words can’t express how happy I was to discover StyleCop so many years ago. To me, it’s the answer to all code standard discussions. Microsoft set the baseline standards and used this tool internally across all of their teams before ultimately turning it over the community. I endeavor to achieve 100% conformance to StyleCop in all of my projects. I feel like this is critically important because it takes the subjectivity out of the conversation and allows the computer to enforce the rules instead of peers doing it during code reviews, which in turns make code reviews far more productive and valuable.
Achieve 100% conformance to MS Code Analysis rules
I use MS Code Analysis (formerly known as FX Cop) for the exact same reason I use StyleCop, except that MS Code Analysis checks my functional implementation instead of my style. It’s fantastic and is like having a computer (who is a lot smarter than I am) do paired programming with me. I’ve actually learned a lot of new techniques by using this tool. Like StyleCop, I attempt to achieve 100% conformance to the full Microsoft rule set.
Resharper is fantastic. I use it regularly for refactoring, but it also works amazingly well with StyleCop and MS Code Analysis.
Write XML comments as I work
The best developers I’ve worked with write XML comments as they are writing their code. It doesn’t take long to do this, and if you say that you’ll come back and do it later, you never do. To that end, I write XML comments as I work.
Ensure that my code is self-documenting
I am not a big fan of comments being peppered throughout code. To me, I can “see” things in C# and it’s hard to do that when English is thrown in there. If you have to write a comment in English to explain what you’re doing, then one could argue that your code is not written well. So to that end, I work to ensure my code is somewhat self-documenting and it makes sense to the reader. That said, I am a firm believer in commenting code when appropriate. Commenting what you’re doing shouldn’t be necessary, but commenting why you’re doing it often is. And lastly, if a method is going to be fairly complicated, I’ll often write pseudo code at the top of the method in a multi-line comment to explain what’s going on.
Always study and follow best practices
I always attempt to stay abreast of best practices created by the community and by Microsoft. The Patterns and Practices team has a wealth of knowledge and regularly publishes guidelines for the community, and quite frankly, those developers are better developers and architects than I will ever be, so it makes a lot of sense to follow their lead.
Follow a Test Driven Development methodology when possible
TDD is hard, and I’m just learning it. I say it’s hard because it requires a complete paradigm shift to do properly. It’s just as much about design as it is about tests, which I never realized before I started doing it. It’s hard to write an API first that doesn’t compile and to fix it as you go. I like TDD a lot, however, because it forces the YAGNI principle and results in great tests and a great system.
Always build tests for bugs
When a bug is reported in a system I’ve built, I always try to build a test to recreate the bug before I fix it. This ensures that I’ll never re-introduce the same bug again more than once.
Write automated unit tests
I am in love with test automation. I don’t know how I ever lived without it. I always try to write some level of unit tests for my systems so that as I refactor and extend code, I am very comfortable that my changes are not breaking existing functionality. It’s a fantastic feeling to be able to gut a class and refactor it without worrying about what you’ve just broken. Love it. Love it. Love it.
Write automated acceptance tests
Automated acceptance tests are just like unit test to me. I love them, arguably more so than unit tests, because they encapsulate all layers of testing- unit, integration, system, and acceptance. They are the highest level of testing and having a full suite of automated acceptance tests is fantastic when you need to do a full regression test of your systems. It’s a great feeling to know that you can delegate hours, or even days, of work to a computer who won’t complain about having to re-test everything again and won’t make any mistakes. I try to build automated acceptance tests whenever I can and use frameworks like SpecFlow and Selenium to implement them.
Follow a Behavior Driven Development methodology when possible
Behavior Driven Development is TDD on steroids. I haven’t had a chance to practice it extensively, but I would do it everywhere if I could. I love the concept of functional requirements being your tests and staying 100% up to date.
Always use build automation
Build automation is a critical component to building quality software. I always like to ensure that we have an automated build and deployment system in place to remove the human factor from deployment scenarios. Being able to click a few buttons and queue up a build of 30 projects to your DEV, TEST, and UAT environments is a great feeling. Or better yet, knowing that when you come in the next day all of your environments will be refreshed with the latest and greatest code is a great feeling as well.
Always use continuous integration
Along with build automation, continuous integration is a must. I always ensure we have proper CI in place. I very much like gated check-ins. It’s a great feeling to know that the code you’re about to check in isn’t going to break the build for the rest of the team.
Fail fast whenever possible
Agile methodologies are fantastic. I am a huge believe in Scrum and its benefits. While ALM is a little out of scope for the original question (albeit, not by much), I will say that one of the biggest advantages is the short feedback loop with the business. If you’re going to fail, fail fast! This gives you the chance to mitigate your risks and correct them early. I’m a passionate believer in all aspect of Scrum and try to practice them anywhere I can.
Always refactor when you can make a piece of code better and provide value
Refactoring has almost become a four letter word to managers. This is a bad thing. Developers shouldn’t be afraid to refactor code, especially when there are safety nets like automated unit and acceptance tests in place to catch mistakes. We are always learning new ways to make software better, strong, and faster. I like to refactor code when I see a benefit to the system or to the business, whether it results in better performance, a more stable system, less maintenance, or any number of things. When a refactoring opportunity is discovered which will result in a lower total cost of ownership to the business, I like to take it. That said, I don’t like to refactor just because there is a different way to do something; there must be a value in doing so.
Always eat my own dog food
Microsoft uses the term “dog fooding” a lot, and I believe in it wholeheartedly. It simply means to use your own systems. Doing so provides a great source of feedback! Hopefully the systems I build allow me to perform business tasks more effectively and efficiently.
Never try to optimization the system prematurely
I am always very mindful of not trying to prematurely optimize a system. You might spend days refactoring something that increases performance by a few hundred milliseconds, but there was really no reason to do the optimization. If you build your systems with sound principles in mind, you should generally achieve good performance without having to tweak everything. Follow the 80/20 rule here and only optimize the 20 percent of things which actually need to be optimized and provide a true value to the business.
Attempt to never reinvent the wheel
Reinventing the wheel is silly and wastes time and money. I always like to try to figure out if someone else has already solved the problem I am working on before I do it on my own.
Keep it simple
Again with the caveats mentioned above, I always try to keep things simple. A piece of software should never be more complicated than the problem you are trying to solve.
I always try to keep learning; I am a software craftsman
The software development industry is dynamic. It is always changing and improving. Every day new platforms and techniques are being developed and introduced. If it is even possible to stay that you’ve achieved a mastery in our craft, (and with the exception of an elite group of people like Anders Hejlsberg, I don’t think it is), then you can really only make that claim for a few years before your mastery becomes antiquated. I think it is very important to stay plugged into the community and keep learning constantly. I try to listen to podcasts every chance I get, read as many technical books as I can, go to as many user groups as I can, and go to as many trade events as I can. To me, this is a necessity in being able to build quality software.
Elegant Software Solutions