This post was originally published in my blog smartpuffin.com.
Last week a very nice thing happened with me. Two of my colleagues asked my advice on analyzing corner cases.
Lately our team had several tasks where we forgot to think about some important corner cases.
The feature is coded and rolled out, almost ready to be shown to the users (we always implement our features as a/b experiments, so until you start the experiment, the feature isn't "live" yet, even though the code is rolled out on production already). Developer gives it out for testing, and there is a bug in it. A very simple bug, like when there are more than 5 items loaded from the database, the page design breaks.
Developer fixes the bug, pushes and rolls out again. But there is another bug. In case there are no items at all, the design breaks.
Developer sighs, fixes the bug, pushes and rolls out. But they forgot about RTL languages. And in case the user is logged in, the items are not aligned. And the color is supposed to change when the device is in the night mode, but it doesn't. We delay the release again.
The loop goes on.
In retrospective, these bugs are easily avoidable. But how do we find them faster?
So Many Questions
We all know this feeling. You present your idea, and instead of fame and glory you get a seemingly endless flood of questions:
- What's going to happen if X?
- How are we going to deal with Z?
- What if the user input is "ABC"?
- If condition M happens, the page looks differently; does our feature still work?
- In case service N reboots, how do we deal with that?
- Every Saturday we get twice more K than on average, what do we do?
- Service P also uses this API, will it break if we change it?
- We also have R type of users, what do we show them?
- How do we calculate Q when the timezone is T, the currency is O, and the moon is in Pisces?
Asking these questions is a skill. Not everyone is born with it, and, of course, sometimes you forget about them. So what happens if no one asked them?
Is it bad if we forget?
If no one asks these questions, and a system or a feature gets implemented without thinking of different cases, one of the following happens.
1. It fails
Developer forgets to remove the "delete" button when there is nothing to delete. User presses the button. App crashes. User is angry.
Developer forgets there can be no data and still tries to process it. Division by zero or a null-pointer exception happens. Service crashes.
Developer forgets other services use the same API, changes it, services fail. Developer needs to wake up in the middle of the night and fix the API.
2. It looks bad
Developer forgets there can be zero results and shows an empty box. Layout breaks.
Developer forgets there can be more than 5 items. There are 10 items. They take up all screen space. Layout breaks.
3. It inflicts damage
Developer forgets some orders cannot be cancelled. User cancels. Company loses money.
4. Bad user experience
Developer forgets that there is a "sort by price" mode. Inserts an extra item which doesn't comply with sorting. User is confused.
Developer forgets there is a night mode. Hardcodes the text color. Dark blue text looks nice on white background; on black background it is invisible.
5. Nothing bad happens
An icon is displayed when there is a discount. The new icon "best choice" is displayed in the same place. When there is a discount on the best choice item, two icons overlap. Doesn't look pretty, but it's not too bad.
How to avoid?
We have listed several things that happen when developers forget to think of corner cases. The consequences are not too nice! Now the question is: how do we avoid them?
When my colleagues asked me for my input, we sat down and came up with this list of three steps to improve our process.
1. Think about different cases in advance
As a developer with a decade of experience and a tiny bit of obsessive- compulsive disorder, I try to think of as many cases in advance as possible. By doing that, I avoid many of the listed problems on the stage of designing the system or feature.
This means: on the stage of designing a product, a feature, or a system - think as much as you can, and ask as many questions as you can.
Very likely you design for "the best case". You should start thinking: what other cases are there? When would my app behave differently than usual?
Sometimes I write down lists, looking somewhat like that:
- in case <3 elements: don't show the block;
- in case > 10 elements: show only first 10 and add "show more" link;
- if server fails: catch the exception and display a message, don't crash;
- device in landscape mode: put button on the right.
A good idea is to document all this for everyone to read. The best place to put it is right next to the original task description - say, in your Jira ticket. Documenting will help you remember your decisions while implementing, and it will help while testing - a lot.
2. Ask someone for help
The earlier - the better. Ask someone if they see any flaws in your idea.
Ask several people if you can. Different people will think of different things. (This is one of the reasons I value diversity.)
Sometimes people will start talking about alternative solutions right away. In this case, try to work backwards and figure out what's the problem they see. What exactly are they trying to solve?
Of course, you don't have to incorporate all their ideas, and sometimes you may decide to go for a different solution than they propose. Still, you should feel happy and grateful for their input.
After talking, document the decisions, just like in your previous step - say, in your Jira ticket or on your wiki page.
Talking with someone is a great opportunity to learn more about the system you're working on. Next time you may already remember all these cases by yourself and won't need that much help.
Try to understand their thought process. How do they list all these cases so methodically? What's their secret? Next time you can try to mimic them. That's a nice opportunity to learn more about someone else's corner-case-searching approach.
Last tip: QA engineers and experienced developers are trained to think comprehensively about the task. Ask them for their valuable input.
3. Test early
This step depends on your process very much, and I'm sure you can figure out the best way in your environment. Here are some options to start you thinking.
If it is possible, ask someone to take a look at your feature as soon as you put the last semicolon in your code. Way before committing, pushing, rolling out.
Invite them to your laptop, or send them a link to your test machine. If they find something - you can fix it right away, and they just need to refresh the page or restart the app to see it working.
This shortens the feedback loop from weeks to minutes.
If you have QA engineers, and you can ask them to take a quick look - that's great. If you have a user nearby - that's even better.
Agile methodologies help as well. When presenting the results of your sprint to the customers, you may find out many interesting things you forgot.
Code reviews are a nice way to help solving the problem. You will get someone else's input from looking both at the feature and at the code. It's a double attack on corner cases! And of course, code reviews have other benefits as well.
Finally, writing tests really makes you to think comprehensively. And if you manage to write tests before code, you don't need my advice.
Random tips instead of a conclusion
I often see people lacking the skill of foreseeing problems in advance. Meanwhile, it is an important skill for a developer. It is a part of problem-solving skills. These 5 consequences won't fix themselves!
You don't have to implement your feature for all cases. Sometimes, it's beneficial to not implement something for a particular corner case. Sometimes, you may make a trade-off: you explicitly decide not to care about a certain condition. Still, you will make an informed decision, and not simply go with the flow.
Good knowledge of the system you're working on really helps. When starting working on a new project, spend some time learning about it. Try to play with it like a user. Read some documentation. Do some dogfooding.
Thinking of corner cases in advance is a skill, and it's trainable. Don't worry if you can't do it perfectly the first time - you'll learn.
(open source and trusted by devs everywhere ❤️)