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 https://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?
Additional Resources
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.
- Progressive Development (James Waletzky’s blog)
- Lean Software Engineering (Blog)
You Might Also Like
Top 20 Best Software Engineering Books of All Time
Architectural Styles in Software Engineering
Lessons in Software Development from Mike de Libero
Great run down, you really nailed some key concepts.
My favorite 3 are
– Define ‘Done’
– vertical slices
– continuously improve
– Defining done should be the first thing any team does. Without it you’re going to have pain and churn. I’ve seen so many teams burn hours because of missed expectations and bad communication. Define it up front, publish and put mechanisms in place to re-inforce it. Our status reporting mechanism prevents any task from going to 100% before it gets code reviewed and test reviewed. During our scrums its a natural conversation of “why isn’t x done” “I should have the reviews done today and then I can close it out”.
– Splitting scenarios into vertical slices is a good step toward “customer focused delivery”. In todays world you have to be focused on delivering more value to your customers sooner, better and cheaper. If you don’t someone will and then you’ll be looking for a new customer (or job). Face it, we’re geeks, we love doing geeky things. Historically that means building cool uber solutions that “NEVER SEE PRODUCTION!”. We fear customer contact so its easy to hide it IT and architect the coolest thing every and marvel at our own intelligence. We do this instead of what would bring us real success. Learning the business domain, identifying the customer’s pain points and building incremental (via vertical slices) functionality that reduces pain and improves customer productivity. Focusing on vertical slices allows you to deliver incremental value and identify real cross cutting concerns and opportunities for patterns/frameworks. I see so many times a project gets started and everyone starts building out “the plumbing” which usually gets thrown away a couple of times in the next year. If you deliver real value regularly your customer will give you time to refactor and extend the architectural pattern becuase they believe you really have their best interests at heart.
– Continuously improve. This is HUGE but takes real strength. Not everyone is capable of being introspective. I personally think introspection is a key trait/practice that any team lead can have. You can get the suckiest problem and dysfunctional team in the world and if you’re introspective and lead your team through service and incremental improvement you can turn any effort around and deliver more than anyone thought possible. Identifying where to improve is just the first part. Communicating and introducing change is the real art. Additionally, EVERYTHING is open to improvement including YOU.
Thanks for a great list James.
#8 and #4 are my favorite – the rest are great too but these two are less emphasized in the field. In fact everybody runs after features forgetting many quality attributes along the way, but when the features are ‘done’ … the quality, the lack of it strikes back
Loved it
Very neat set of lessons. Define ‘done’ is a very important lesson for me. I can see the impact it will have on a team. Thank you.
I used to be a Feature-perfect freak quite some time back. Once I started growing in the ranks I realized how important it was for the product as a whole to be rich, and not just its features. Thanks for pointing it out. I’m particularly impressed with the phrase “fit and finish” – kind of summarizes the whole thing.
Thanks for the kind words, guys!
I could have gone on with > 10 lessons, but J.D. told me to shut up. Just kidding, J.D. ;-).
Although there are some fairly “typical” lessons on that list, I wanted to hit the ones that made the most impact on software I have shipped. From the project side, defining ‘done’ is still one we keep getting wrong. It’s a hard one for people to appreciate until they have messed it up over and over. Emphasizing it frequently bounces off as not being important enough to worry about in the grand scheme of things. Far from the truth.
No one has yet said that #6 resonated with them. I hope that’s because everyone is sold on unit testing as a best practices and it’s part of your culture. I still run across teams where unit testing is viewed as a “tester’s” job. It’s those cases where quality always comes into question. Unit tests have a ton of advantages and coupled with a good test framework, will make your life easier as you write the code and change it later.
On the slicing, this is another one that really is misunderstood. Just think about delivering real business and customer value with each chunk you build. Although it can be challenging from the design side, don’t be afraid to refactor and ‘continuously improve’ your software. It’s not just for teams anymore!
Again, appreciate the comments.
James.
well,Information provided is very informative especially lesson 6 and lesson 7 are fine.update information about systematic testing
Great list James. The trouble with #6 is that unit tests have no intrinsic value. They are a means to an end, and they are a workaround for programming languages that can’t be verified by more efficient and reliable means. We use dynamic programming languages because they are expressive, but the ease of use of these languages comes at a cost of safety. Then we try to put the safety back in using a labor-intensive and unreliable method. Like most such things, it should be viewed as a tradeoff. My hope for the long run is that verifiable languages will eventually become popular.
Hey Corey – nice to hear from you!
I would agree in principle, but in practice with languages like C++ and C# (without extensions like Spec#, or whatever the name changed to), unit tests provide that safety net we need to refactor confidently and validate our thinking, as well as drive the design. Verifiable languages would be a nice addition to the arsenal of development weaponry, and even catching with C# catching up to other languages that natively support Design By Contract as a start would help with validation.
Would love to hear your other ideas, Corey!
This is a very impressive, meaningful list! If someone were watching as I read your bullets & text they would have seen my head shaking vigorously & heard me repeatedy saying, “yes”, “Yes”, “YES!”
In my job, I parachute in very late in the dev cycle–unfortunately too late to implement these best practices. However, what I can do–& will doo–is evangelize them in the hope that they’ll be used in the next project.
James,
Great article, lessons #1 (Keep it simple), lesson #2 (Define Done) and lesson #6 (Unit Testing) are my favorite for sure.
Regarding keeping it simple, I find it odd whenever developer relish at how complicated their code, design, etc. is. Simple designs happen to be the easiest to understand, manage and in my personal opinion they are the most elegant 🙂 (I am infinitely more proud of my designs that are simple and get the job done, than the ones that get the job done but in a contrived, ‘academically superior’ way 🙂
Define done. For myself, and the developers I contract to build our product, I always define what done means (except I call them success metrics). Keeps the project within budget and on time.
Finally, on unit tests. I never appreciated these until I left MS to start my own business. If you think about it, unit tests capture the objective of the code (meeting some very specific requirement). We should all be doing this, start with an objective and aim to research that objective. Best analogy I know of, when you go to the airport you start with a destination in mind and you take the appropriate flight to get you there, not show up at the airport and jump on any old plane 😉
Great stuff, MS needs to start cloning you 🙂
–Kevin
Jimmy: Glad you enjoyed the article! I would be curious on any tips that you can share for people in your position. How do you approach fixing a project that is in trouble? It is a different problem, that’s for sure.
Kevin: I love the analogy with the airport. That’s why I like TDD so much. You start with where you want to get to and then derive the starting point, so to speak. Flipping around our typical line of thinking typically leads to better designs, and adherence to the simplicity rule. I feel the same way with you with a design – if it is straight-forward, easy to understand, and meets our quality metrics (e.g. performance), all the better. Complex designs cause time and money down the road.
Thanks for the kind words everyone! I love hearing your ideas as well.
James.
good blog….. 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
I love the analogy with the airport. That’s why I like TDD so much. You start with where you want to get to and then derive the starting point, so to speak. Flipping around our typical line of thinking typically leads to better designs, and adherence to the simplicity rule. I feel the same way with you with a design – if it is straight-forward, easy to understand, and meets our quality metrics (e.g. performance), all the better. Complex designs cause time and money down the road.