Done as a concept is something that all of us are familiar with, yet so difficult to define. If you perform a quick search on Merriam-Webster's online dictionary you will find six different definitions:
1 arrived at or brought to an end, "One more question and we're done."
2 doomed to failure, defeat, or death
3 gone by, over, "The day of the circus big top is done."
4 physically exhausted
5 food: cooked sufficiently, "Check to see if the meat is done."
6 conformable to social convention, "not the done thing"
In our daily work lives, we work towards the first definition of "done," but how do we know we have reached the end? A designer will tell you there is always more they can do to improve a design, an engineer will tell you there is always more enhancements that can be made, and a product owner will tell you there will always be more features that could be added. For a project to be successful, the end — this elusive "done" — is something that everyone on the team must define, communicate, and agree to.
While I was still in school working on my Bachelor's degree in Computer Science, I remember reading some bits and pieces of Algorithms in a Nutshell. At the beginning of the book, there was a scenario where a developer was tasked with improving search functionality and implemented one of the many sorting algorithms covered in the text.
At the end of the scenario, the author explained how the developer could have continued working to improve the algorithm and thus improve the search speed, but the developer closed out the task. They had drastically improved the speed of the search utility by using the appropriate algorithm, and any more enhancements would not yield nearly the same amount of benefit. It is extremely wasteful to continue working on something once the "done" state has been reached.
In order to reach a "done" state on a feature we must:
- Define the requirements
- Meet the requirements
- Meet/exceed quality expectations
Defining requirements is often easier said than done (no pun intended). Requirements can be functional, like the fact that users need the ability to log in. Requirements can be non-functional, like the application being secure or scalable. There are also other requirements that are more difficult to define, like creating an intuitive user experience or setting the right tone. Furthermore, once the requirements are gathered, they need to be organized in a way where they can be estimated, prioritized, and then worked.
Functional requirements are the easiest to define and are usually written as job stories. An example job story for a login feature could be:
As a user I want to use my email address and password at a login form So that I can gain access to the system.
For engineers, this tells them they need the ability to track user accounts. Authorization is granted with the correct combination of an email address and a password. Finally, it communicates that without authorization, users should not be allowed to use part of the system.
This job story also communicates to designers that the system will need to have styled form inputs and buttons. However, designers will need to ask more questions and create mockups for review. Furthermore, some thought will need to go into how errors should be handled.
Non-functional requirements are much more difficult to define and tend to be more vague. Stating that an application needs to be scalable, secure, or have a good user experience can often cause engineers and designers to follow rabbit holes. For engineers, it's important to state your expectations so they can perform the appropriate amount of research to architect a solution that best meets your use case.
Requirements for user experience can be very tricky to define. More often than not, I hear clients request "I want it to work like X." This is a good starting point, but this may conflict with your brand and the image that you are trying to meet. If your software is meant to be used by mechanics, then it probably won't need to look and feel like Instagram, but it should be easy to use and direct users on how to use it. Having a brand guide and an overall vision for the software will aid designers in defining the overall user experience for the software.
An important part of defining requirements is defining what you will not know at the start of the project. It is highly likely there are use cases you will not know about until the software is in the customer's hands. While it is possible to learn some things at the beginning of the project, it is highly likely most of them will be learned during the course of the project. It is important to identify these risks as early as possible and wait until the last responsible moment to address them.
Users today have a certain standard for the design of the platform. Therefore it is important to have some minimum design expectations. A style guide is pivotal in helping inform the colors, fonts, assets, etc. that should be used when designing HiFis (high-fidelity designs). This also helps communicate user expectations of how a platform by your brand should look and feel.
In an ideal world, designers work slightly ahead of the engineers by building static prototypes, conducting user tests, and gathering feedback from the team to help make the functional requirements crystal clear. Everyone should have input into how the software should look and feel. Once the design is tested and approved, requirements will be updated so engineers will know the target they are trying to achieve.
It is important to define the minimum functionality needed for a feature to help ensure that it is not over developed. It is extremely easy for engineers to run down rabbit holes for improving performance or adding more features. Therefore there should be over-communication between stakeholders, engineers, and designers to ensure the requirements are well defined and all questions about expectations are answered.
Engineers will ask many questions so that it becomes crystal clear what they are building. It may seem like some of these questions can be safe assumptions, but it is extremely important that engineers understand the business domain they are writing software for. In addition, each client in the same business domain will have their own business processes that need to be followed. Therefore it is important these business rules are communicated to the engineering team.
If the requirements are clear, then developing and designing the solution should be rather easy. However, if the requirements are vague, then the desired outcome will be difficult to achieve.
Designers and engineers will oftentimes take notes on their requirements and make task lists that will help them outline all of the work they need to do in order to meet the requirements. In my experience, the features that take longer than estimated usually do not have these task lists defined. For meeting the requirements, it is ok for the first attempt to be quick and dirty because working software is more important than non-functioning software. Once the requirements have been met, it is time to clean up the code so that it is high quality.
There are many factors that define quality software. Here are the attributes that I look for when writing software and performing technical audits:
- It has to be clean, easy to use, and easy to maintain. This addresses both technical quality and user quality. An application can be of high technical quality, but if the user interface is cluttered and difficult to use, then users will not see it as high-quality software. On the other hand, the software needs to be easy to maintain so that changes in one area of the code do not break other areas.
- Development and user experience needs to adhere to best practices. I cannot state how important it is to follow best practices. Following them helps protect against security concerns, properly separate concerns in business logic, keep track of why and when changes were made, ensure other developers can join and contribute to the project, etc... Best practices and conventions are defined so you can learn from the mistakes of others.
- It should be documented. The code should be documented or written in a self-documenting manner. It is so much easier to maintain a codebase when the existing modules and functions are documented so you know how to use them. Not writing documentation is like asking someone to cook a dish they've never made before without giving them a recipe. It is extremely wasteful and costly to forgo documentation.
- It should have an automated test suite. Automated tests are something that every developer knows they should do, but all too often they'll let it slip through the cracks. It is important to have automated tests so that you can ensure your code does not have unintended side effects. It is even more important with larger teams where everyone only touches a small portion of the codebase. Tests help you refactor in confidence to ensure you do not lose expected functionality. They also act as documentation in code for how the platform should behave and the features it should have. Whether you chose TDD, BDD, or go exploring and write tests for your final functionality, just write tests.
- It should be bug-free. You would think this is pretty self-explanatory, but it isn't. Some people think that "bug-free" means that no errors occur, but that's not the case. People are imperfect and will make mistakes. Likewise, hardware and software can fail. It is entirely possible that a user will lose their network connection, the hardware serving the application may fail, etc... It is important to handle situations like this gracefully. Let the user know they lost connection, tell them how some data they entered was invalid, tell them they tried to do something that isn't allowed. But don't give them error messages meant for developers. Give them an action they can take so they have a good experience with your software.
- It should behave the same way in production as it did during development. I feel like this one is a quote from Captain Obvious, but this situation does arise more than developers care to admit. In development, you'll install a different set of dependencies, such as testing frameworks, or write experimental features that may not get deployed to production. However, you should always be able to check out the code at the same commit that is deployed to production and be able to recreate any scenario that a user is experiencing.
With all of the requirements, special cases, and quality considerations that go into software development, it is easy to see how developers wander down rabbit holes. This is why it is so important to establish these practices early on during a project so the codebase has a good foundation to grow, thus making fewer rabbit holes to go down. While there is always a little bit more work that can be done on a feature, one of the most important skills for product development teams to learn is knowing the work they need push off for another time.