Every new requirement has various aspects that we need to take under consideration and it is really easy to omit important bits and pieces. Throughout my career I've came across two mnemonics that were supposed to help you remember all different aspects that one should consider when developing new piece of software: CUPRIMDSO & FURPS. The issue with these mnemonics is that they mean.... nothing and are a bit old hence a bit outdated. So here is a handy mnemonic:
"No-one wants to work with a MISCAPTURED story"
This tool can be used when capturing a story to see if we understand how to approach particular aspect but also as a definition of done helping to check if we've done all that we wanted, or consciously decide that given aspect is not relevant.
First lets demystify the mnemonic and then we will deep dive into each aspect
M - maintainability
I - installability
S - security, serviceability, scalability
C - configurability, customization
A - accessibility, agility
P - performance, portability
T - testability
U - usability, ubiquity
R - reliability, recoverability
E - evolvability
D - documentation, distributed, deletability
Maintainability, MobilityThere are couple of additional question we need to ask when talking about maintainability:
- Does the new requirement fit into current model?
- Can we implement it without huge amount of "if"s?
- Are responsibilities of different classes/modules/services well defined?
We are leaving in a mobile world, hence we need to think how the new feature will be presented on various hand-held devices. How to fit all we need to fit on the screen?
InstallabilityWhen it comes to installability, there are two most important questions:
- Assuming that you have a one-click-deployment, will it remain this way after implementing the story?
- If you have manual steps during deployment, will the story bring you closer to one-click-deployment?
- Fresh install - vanilla case, default values
- Re-install (the same version)
- Upgrade (new version over old one)
- Downgrade (old one over newer)
Security, Scalability, Serviceability
SecuritySecurity is a really deep topic, I will propose just few more questions to think about when talking about how given story/requirement influences security of our application:
- Are we adding any sensitive data to our application?
- Are roles currently defined in our application sufficient for the new story?
- Are interfaces required by the story secured?
ScalabilityWhen talking about scalability, I obviously have in mind concept of scaling-out and not scaling-up. Let's briefly cover the difference between these two models:
- scale-up - bring more computational power (stronger CPU, more/faster RAM, SSD instead of HDD, etc.)
- scale-out - spin-up more instances of your service, so that they do the computation in parallel
- Does the story require implementing some complex/heavy algorithms?
- How well the story fits our current scaling-out model?
- If we don't scale-out (e.g. it is not yet needed), do we have an idea how the story could have been scaled-out?
ServicabilityWe are creating a perfect software that always works as intended.... until it doesn't. Then the first thing we usually do is to look into logs and see what has happened. When implementing new story we need to think about information that we want to log (consider various logging levels: debug, info, warn, error). This might seem obvious but often when implementing new story a difference between debug and info or warn and error might be pretty blurred.
I found following question somewhat helpful when deciding if given log should be debug or info:
- INFO - contextual information needed to understand what is currently happening in our system, stated in a business language
- DEBUG - low level technical information, needed for debugging
When it comes to distinguishing between error and debug I use such simple rule:
- WARN - we have an invalid situation but application knows how to handle it and does not require admin intervention
- ERROR - we've encountered and erroneous situation that might require admin manual intervention
- FATAL - it's so bad that application is about to commit "suicide"
A few generic comments about logging:
- Enough context information - when your application is processing some messages, put for example an id of the processed message, if user is performing some actions put id of a user, etc.
- Log all relevant pieces of information in one line - as a rule of thumb, do not log a multiline messages - it is really hard then to grep all information that you might be needing when investigating given problem
- Be friendly to the tools you use to analyse logs - consider following log message "processing of message (id: 123, version: 1) started" and "processing of message started (id=123, version=1)". Both contain the same information but the former one is splunk-friendly. Splunk will automatically recognize that there are variables "id" and "version". It will even allow you to use them in queries (e.g. "'processing of message started' AND version>2 | stats count by id"). Basically think about how you will be then searching through your logs.
- Security aspects - who is going to read your logs? Don't log sensitive data.
- Log message consistency - especially when two log messages are related e.g. "processing of message started (id=123, version=1)" and "processing of message finished (id=123, version=1)"
Configurability, CustomizabilityWhen it comes to configurability, think about two aspects:
- Things that will change between environments - these certainly needs to be configurable, as before your software reaches Production environment, it will surely go through Continuous Integration environment and probably some User Acceptance Testing (Staging) environment - each of them will need to have different configuration (e.g. different url to a services which our application is using)
- Things that support engineer (you :-)) would like to change in order to tune the application without a need of building a new artefact and going through procedure to deploy new artefact to Production environment (unless in your ecosystem it is really cheap to deploy stuff on Production - then you can be a bit more relaxed about this rule)
Accessibility, AgilityAccessibility has at least two different aspects:
- Disabled people - it is good if given information is conveyed to a user using at least two different ways (e.g. if you want to show an error message to a user you can show a message in a red frame, but what if a user is colour-blind? Hence you might not only put the message into a red frame but also put there a red cross sign)
- How easy it is for a regular user to access given feature - A/B testing might be really helpful to assess how easy it is
- Team's business knowledge/intuition .vs. Product owner availability - even though it seems at the beginning that you know how to implement/test the story I can guarantee that unexpected questions will arise. Having said that it is important that either team has a great deal of business knowledge so that they themselves are able to answer these questions or Product owner is highly available.
- Team understands the value that story delivers so that they are able to come up with a good-enough solution
- Story is fairly short so that it fits into an iteration or, if you use kanban, it does not plug your pipeline
- Team has enough knowledge to estimate the story (using whatever units)
- Team knows how to demonstrate the story
PerformanceQuite obvious point I think:
- Does the story require adding a long-lasting activities (e.g. report generation)?
- Is there a possibility to do given operation in an asynchronous mode?
- What is more important latency or response time?
- Is it possible to scale the problem out?
- Do we need to add/amend performance scenario?
- Do we need to add/amend data used for performance tests?
- How users will be using this new functionality (often, once per week/month, etc)?
TestabilityTwo most important things:
- Do we know how to test the story on various levels (at the beginning end-2-end would be the most important, but we need to think about lower level tests as well)?
- Will we need additional resources (e.g. test DB) for automated tests?
- unit - avoid static methods, separate object creation from usage
- integration - define clear (easy to cut) contexts with well defined entry/exit points
- end-2-end - define business interfaces that are required to assert if application is working properly
Usability, UbiquityWhen it comes to usability the question is fairly simple: is our application user-friendly? There are various more fine-grained metrics we can use to assess the user-friendliness:
- How efficient it is to achieve a goal in our application?
- How much time does it take?
- How many clicks user needs to make?
- How many screens user needs to go through?
- How intuitive is our application?
- How much time user spends trying to figure out how to use our application?
- How many screens has the user seen before finding what she wanted?
- How much do users like our application?
- Should we add a new definition to our glossary?
- Should we amend existing definition?
- Should we remove existing definition?
Reliability, RecoverabilityAs I alerady said I know that the software we create is fault-less, however production environment is a dangerous place. Various things may happen:
- Server on which our application is deployed unexpectedly goes down
- Disc gets unmounted
- When trying to log some vividly important message, we run out of disk space
- Service which we are using becomes unreachable
- Our database that is located on a separate server becomes unreachable
- Having experienced a failure, will our application recover by itself?
- Assuming that it will recover, can I relay on the information that will be presented to me?
EvolvabilityI really like the division into two different quality aspects:
- Quality of a feature - does it fulfil users' expectation?
- Quality of a design - is it easy to evolve the architecture further?
Documentation, Distributed, DeletabilityWhen talking about documentation we need to distinguish three different aspects:
- User documentation - it should be treated the same way as any other product we deliver to the customer (when agile manifest talks about "working software over comprehensive documentation" - this is not user documentation that it has in mind).
- Architectural documentation - this one should be kept as close to the code as possible as it tends to be outdate and rot when noone looks at it. A good practice is to keep it as readme file in the code repository. It is easy to exaggerate with it so think twice before putting an information to the readme - even though it is close to the code, IDE not always will recognize that when refactoring your solution you also need to refactor your readme.
- Self-documenting code - the best book I've read so far about self documenting code is the "Clean code" by Robert C. Martin
- Method/Class names - the main idea here is that one should be able to find in a code business domain.
- Executable documentation - tests are one of the ways of documenting the behaviour of the software, especially with various BDD frameworks which allow us to write sentences in a natural language which are then translated into executable pieces of test code, which is run against our application.
- Project documentation - project charters, high level design, low level design, test plans, test execution S-curves, etc. - keep it as small/short as possible - this is the "comprehensive documentation" that we value less then "working software". Let's be honest, it is needed to some extend - especially if you are working in an environment that needs to go through various Audits - but the goal is to use it, if you create a document which noone reads a red sign should appear in your head.
- From implementation perspective we need to take care of fallacies of distributed computing
- From testing perspective we need to ensure that the contract between two services (provider and consumer) is automatically tested and that we will not accidentally break it - Consumer driven contract come to rescue here
- Am I able to add the new requirement in such a way that it will be easy to remove it, or will I tangle it into the existing solution?
- Features that are easy to remove are relatively easy to understand as they tend to:
- have clear interfaces
- be less coupled with the existing solution
- Since they are relatively easy to understand, they are also relatively easy to refactor