How to Approach Problems in Development (and Pretty Much Everywhere Else)
When you plan a feature, write some code, design a user interface, etc., and a non-trivial problem comes your way, you have more or less the following options:
- Attack – invest time and effort, give it all to make it happen, no matter what!
- Sneak around – identify alternative options, or find a way to drastically limit the scope to reduce the amount of work.
- Postpone – do nothing and wait until a later point in time; if the problem hasn’t become irrelevant by then, you know that it’s worth to be dealt with.
- Don’t do it – sometimes you have to make the hard decision to not do something.
As obvious as this seems to me now that these options exist: When I look back at my younger self more than twenty years ago, fresh from university, I was all about attacking problems head-on, because problem-solving was fun! Which sometimes lead to me spending too much energy at the wrong location, at the wrong time. In this blog post I try to write down some words of wisdom which my younger self may have found useful – maybe it helps somebody else out there.
Do a minimum of research
You want to work on the problem, you are eager to write code, but hold on for a second. Don’t dive into it head first. Always imagine a meeting in the future where one of the following questions is asked:
- “What exactly do you know about the problem?”
- “What have other people done so far?”
- “Are there samples/libraries/frameworks? What can we at least learn from them?”
- And of course the killer question: ”Why didn’t you think for a second about X?” (with “X” being super-obvious in your context)
You definitely want to know as early as possible about critical obstacles, e.g.
- the only choice for an algorithm for your envisioned approach has exponential complexity,
- you require hardware performance that your target system simply cannot provide,
- you have nowhere near enough pixel space in your user interface to display an envisioned feature, or
- your application only makes sense if your users behave in a completely unrealistic way in the actual usage context.
if you don’t do anything else, gain at least a basic understanding what you don’t know. You can deal with a “known unknown” on a meta-level, prioritize it, think of possible risks, talk to other people about it. An “unknown unknown”, on the other hand, can sneak up to you and hit you at the worst possible moment.
Do not confuse means and ends
As developers, we’re problem solvers – that is a good thing. But it sometimes makes us concentrate on the means (i.e. the technical solution to the problem) instead of the ends (what exactly do we want to achieve?). We all know these situations in meetings where no progress is made until somebody broadens the focus.
Be that somebody and ask: In which way does solving that one specific problem contribute to the overall goal? And: Is this the easiest way to achieve the desired result?
Think of the (in)famous “sword fight” scene from Raiders of the Lost Ark: Indiana Jones is facing a bad guy with a sword. Indy, on the other hand, does not have sword. With a narrow focus on the problem of not being able to participate in a (deadly) sword-fighting competition, finding a sword would be the next step. But the actual goal, though, is to get rid of the attacker as quickly and with as little risk as possible – which can be achieved by shooting the bad guy with a gun.
Here’s a real-life example that I often present in my UI/UX talks:
My software for the LED advertising system in the local sports arena uses pixel shaders for controlling the brightness of the displayed images and videos. The UI consists of a slider (0-100%) which I move until the apparent LED brightness looks right for the lighting in the arena (which is not constant due to necessary warm-up/down-down phases of the old lamps).
It was very easy to modify the pixel shaders to influence opacity or displacement of pixels, which gave me the idea for smooth transitions between different content sources for the LEDs. My first choice for the UI: the same as for the brightness, a slider. Copy/paste, simple as that. And from a UI point of view, using a slider promised full flexibility in regard to timing and dynamics of the transition. But what was the actual goal? Perform a smooth transition to the next image or video to be shown. For that, flexibility was much less of a concern than consistency and reliability. And that could be achieved with a simple “Next” button that triggered a transition with a fixed duration of 0.5 seconds.
Tackle problems as early as necessary, as late as possible
How do you decide which problem to be solved first? Of course, if one problem depends on a solution for another, that dictates a specific order. Often enough, tough, you have a certain freedom to choose.
If that is the case, ask yourself whether you have to solve a specific problem now. If you postpone working on something that is more or less isolated, there’s always a chance that priorities change. Something that was essential to reaching a specific goal suddenly becomes obsolete because the goal no longer exists.
On the other hand, be careful when delaying work, especially if you don’t know much about possible challenges. If part “A” of a system depends on part “B”, young developers like to work “bottom up”, first building “B” into a nice foundation that will make things easier when it comes to working on “A” (I have been there, too). But you definitely want to avoid a situation where the thing that was supposed to use your framework has known or unknown “unknowns“ that turn out to be insurmountable obstacles. So better make sure to have a working proof of concept and work from there.
This is related to the conflict between the (deliberately) very narrow scope of work items, tasks, user stories, etc. on one hand, and the question when the broader architecture of a system should be planned and implemented. Possible approaches range from BDUF (big design up front) in an almost “waterfall” fashion up to doing only the absolute minimum at a given time combined with continuous refactoring whenever needed. The truth is somewhere in between.
The secret is to find the right balance between…
- You ain’t gonna need it (YAGNI): Do not implement things when you just foresee that you need them. For example: extension points in your software that later are never used (but have to be maintained nevertheless). Or architectures with swappable components on a fine-grained level – if you’re not 100% sure that this is vital for the success of the software.
…and…
- Do it or get bitten in the end (DOGBITE): While you can get away with not doing something for a long time (all in with the best intentions of delivering value to the user of your application), postponing some work items can really come at a high cost. For example, if you want to have an undo-redo feature in your finished product. In theory, you could add that with a refactoring at a later time. In practice, good luck with getting the budget for stopping all feature work for weeks of refactoring late in the development.
In my experience, consider YAGNI as the rule, but don’t use it as “kill all”. Missing a DOGBITE situation is bad and will haunt you for years.
Ask yourself:
- Do you have a rough idea how you would add a feature at a certain point? If yes, that’s good enough, don’t add code as a preparation.
- Are you doing things now that actively prevent adding a feature in the future, or at least make that very expensive? Try to avoid that whenever possible.
- If you consider something DOGBITE, would you be able to defend it if somebody would grill you on that? You better be, because that’s what will happen at some point.
Heed the universal truths
- Everything is more complicated than it appears at first sight. If it doesn’t, you haven’t asked the right questions yet.
- Truly generic, reusable solutions are hard. First get the job done, then (maybe) think about reuse.
- “Good enough” is perfectly fine most of the time.
- Leaving things out is usually the better choice than adding too much up front. Once something (an API, a feature or some UI) is in your software, removing it will make somebody unhappy.
One more thing…
The Wikipedia article comparing the Amundsen and Scott Expeditions is very interesting. You’ll quickly understand why I mention it here.