Lessons in Software from James Waletzky
Editor’s note: This is a guest post from James Waletzky. James is a Development Lead at Microsoft and he maintains a blog about software engineering at http://blogs.msdn.com/progressive_development. James has shipped quite a few products and has worked on the Microsoft Engineering Excellence team, where he taught developers about agile and other software engineering practices and consulted with internal product groups to improve their engineering practices.
When J.D. asked me to share my thoughts on some top software development lessons I’ve learned throughout my time as a developer, I jumped at the chance. I have had successes and failures, and consulted with teams that share the same. Below is my list of 10 lessons I have learned through hard experience. This list is by no means definitive, but is gleaned from years of development experience.
Without further ado…
Ten Software Development Lessons
- Lesson 1. Keep it simple.
- Lesson 2. Define ‘done’.
- Lesson 3. Deliver incrementally and iteratively.
- Lesson 4. Split scenarios into vertical slices.
- Lesson 5. Continuously improve.
- Lesson 6. Unit testing is the #1 quality practice.
- Lesson 7. Don’t waste your time.
- Lesson 8. Features are not the most important thing.
- Lesson 9. Never trust anyone.
- Lesson 10. Reviews without preparation are useless.
Lesson 1. Keep it simple.
I lost count of the number of over-engineered, over-complicated designs that I have seen throughout the past few years. Software developers are ever in search of the most elegant solution to a problem. Guess what? Complexity causes problems – like prohibiting understanding of the design and code, causing maintainability issues, increasing the likelihood of bugs, generating bloated code, and often causing difficulty in testing. From the age old adage:
"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away." — Antoine de Saint Exupéry
Build in extensibility only when you need it. Accommodate change in your designs – don’t anticipate it. Keep class definitions small. Follow the Rule of Seven (i.e. 7 +/- 2 rule) when grouping concepts like methods on a class. Measure code complexity and refactor as necessary. There are many other strategies for keeping design and code simple. Use them.
Lesson 2. Define ‘done’.
Have you ever asked a developer how far along they are in their feature development? I am willing to bet that the most typical answer is 90%. Then next week you ask the same question and get the same answer. When they are eventually 100% ‘done’, do you know exactly what that 100% entails from the quality side? How exactly would you define "code complete"? I bet your answer would be different than mine, and different from J.D.’s.
It is important to have a clear, agreed-upon definition of ‘done’ at many different levels, including individual check-ins, component development, feature development, short iterations, milestones, and finally, release. On my team, code is ready for check-in when all unit tests pass, unit tests achieve 80%+ code coverage, code has been reviewed, design docs are in place, the code is free of memory leaks, and several other criteria. Our check-in checklist is arguably our most important tool. In fact, checklists are a great way to track these definitions. The meaning of ‘done’ should become commonplace and a part of your team’s vocabulary. Write it down so there is no confusion.
Lesson 3. Deliver incrementally and iteratively.
Unfortunately, my crystal ball is in the shop being repaired. Until I get it back, it is hard to predict the future and derive a detailed plan that I am sure will hold true for the development of many features . In the absence of that crystal ball, delivering software in a piece-wise fashion helps achieve success. Break your scenarios into pieces and deliver small chunks in short iterations of 2-4 weeks in length. Get feedback early and often. Fold the feedback into the next iteration and incrementally build upon the results of the previous iteration, refactoring as needed to keep the design clean. You will end up with a better result than if you swim down the river and fly over the waterfall.
Lesson 4. Split scenarios into vertical slices.
Assuming you are practicing scenario-based development, which could also easily make this list, to help deliver real business value in short iterations, it is important to break functionality into chunks. One method of chunking, assuming a typical architecture of data, logic and presentation layers, is to deliver the lowest level (data) followed by the middle layer (logic) followed by the user interface (presentation). The user does not care about the data layer and you miss the chance to gain valuable feedback if you deliver in this first. Instead, break things up vertically – deliver an end-to-end scenario with just enough data, logic and UI to support the scenario. The feedback you receive will factor into future scenarios and you adjust the design as you go. Additionally, you never write code that is not used, and adhere to the principle of YAGNI, or "You Ain’t Gonna Need It".
Lesson 5. Continuously improve.
Tightly coupled with delivering software in an iterative fashion is the idea of continuous improvement, often called "Kaizen". Nothing is ever good enough – at least, that is the way you should think. Work to constantly improve your processes, the way the team works together, your tools, and anything else that contributes to your software development. Step back early and often and do a retrospective on the previous iteration, feature delivery, or even past few days of work. What went well? Continue to do those things. What didn’t go so well? Get beyond the symptom to the root cause of why there were issues and come up with actionable ways to fix them. Put those actions into practice in your next iteration. Always strive to become a high performing team with the world’s best product.
Lesson 6. Unit testing is the #1 quality practice.
I often get asked the following question: if I could change one thing about software development to encourage improved early-development cycle quality, what would it be? Easy – improved unit testing. Historically at many companies, developers would write the code, run the "happy path" through the debugger, and throw the code over the wall to the test team for validation. Quality would be "tested in". On more recent teams we have been doing much more unit testing using code coverage as a feedback mechanism and quality has risen substantially. Additionally, unit tests give you the confidence to refactor your code at any moment in time leading to cleaner designs and more maintainable code. The icing on the cake is having the tests run as part of a daily build, so you always have quick feedback as to whether functionality is broken. The disadvantages are that unit tests take time to write and you add 50%+ more code to your product, but the investment is worth it.
Lesson 7. Don’t waste your time.
The agile development manifesto values working software over comprehensive documentation. This guideline has proven valuable. Several projects I have experienced went overboard on plans, requirements specifications, designs, test plans, process documentation, release plans, etc. Don’t get me wrong – there is value in these documentation artifacts. The key is to do "just enough". Know the audience for your documentation and do the minimum amount to meet their needs. Any more than that is waste. Every activity in the development cycle should add value to the business, product or end user. Spend your time on activities that count.
Lesson 8. Features are not the most important thing.
Yes, you heard correct – features are not the most important thing. Of course, if you are writing a v1 product, features are pretty important. However, in today’s software market, quality and fit and finish are just as important as features. The software needs to "just work". Quality attributes such as performance and reliability are huge satisfiers and are expected by customers. Fit and finish, or polish, on a product set it apart from competitors. A good example of fit and finish that could have been cut from the Apple development cycle are the rubber band effects on the list control on the iPhone. When I bought my iPod Touch I flicked that thing over and over because I thought the effect had a significant cool factor. It delighted me. I fell in love with the device. Of course, polish goes hand-in-hand with features and quality attributes – the device must do what I want it to do and not crash while doing it. The point, however, is that fit and finish is very important in today’s software world and should not be neglected.
Lesson 9. Never trust anyone.
Ok, not literally. I am not talking about trusting your teammates – that is extremely important, and if you ask Stephen Covey, "trust is the life-blood of an organization". Here I am talking about trusting calling code outside of your boundary (e.g. any public method). I have seen more security vulnerabilities than I can count resulting from a failure to validate input parameters. I have seen more bugs than I count that could have been prevented by programming defensively. Use assertions liberally in your code to validate internal state. Use trace statements strategically to dump out debugging state. Assume that some client with bad intentions will call into your code and handle all the error cases gracefully. One piece of advice that a good friend of mine and contributor to this blog, Corey Ladas, once told me: "write code as if the debugger doesn’t exist". That slight switch in mindset, coupled with a focus on unit tests, will make you a much more efficient developer reducing your time in the debugger, where you are generally least efficient.
Lesson 10. Reviews without preparation are useless.
If you ever get invited to a spec review or code review without having seen the document or code prior to the review, just say "no". In this case, you are about to violate lesson #7 and waste your time as well as everyone else’s. Code reviews are a valuable quality control technique that every software development organization should practice. The key to a successful review is receiving the artifact up-front and having that focused alone time to prepare and find issues. The meeting is simply used to gather the feedback and learn from one another. The meeting is not used to find more issues. It pains me to see many hours wasted in useless reviews. Don’t be a victim.
The above list of lessons learned in software development is the tip of the iceberg. There are many more lessons that could be added to this list to make us all more successful. I would love to learn from all of you as well, and hear about your top lessons learned. Care to share?
There are many resources for each of the lessons in the above list. For a gateway to many good resources, see the Progressive Development blog listed below, as well as many of the other postings on this site.