How I approach software features.
Let me know if this series of events sounds familiar:
- Start a project by writing some simple, abstract functionality
- Attempt to build up brick by brick
- Find out you didn’t account for everything you needed in the original plan
- Add duck tape and paper clips, only to find your API or UI overcomplicated and requiring pages of documentation or in person communication.
For years I found myself in that cycle, because the first two bullets seem like a good practice. Some things that come to mind with those items:
- “Don’t bite off more than you can chew.”
- “You can always add more later.”
However, for some projects it can be better to develop in “reverse”. Let me explain…
That’s a fancy name I’ve made up, but it does describe what I’m after. A thought experiment for this term:
When starting a feature or project, envision that you’ve already completed the task and the API, UI components, or otherwise are already at your disposal. Imagine that it’s incredibly simple to use and you only need a few commands (or maybe even just one) to make your runtime sing.
With that, and your project, in mind, start with those last few commands.
Let’s say we’re building an app the queries video game statistics from a web API and then generates/updates a widget.
results = pollkit.execute()
widget = pollkit.get_or_create_surface('my_screen_id')results.render_to(widget)
I have written extremely simple commands for
pollkit (which doesn’t exist yet). There’s no over engineering here, just simple, easy to read code.
With that, move towards the beginning of your feature and start a scaffolding. Try not to develop too much into these early components.
api_endpoint = 'vidstats.com/api/lol/' def execute(self):
# ... def get_or_create_surface(self, id):
Once you have some of the basics go toward the proverbial “middle” of your implementation and see how many paths it will require to go from your scaffolding to the simple commands you’ve already written and love.
Then repeat this process, filling in wholes by jumping about to the “middle” of each location, leaning towards the end result so you know what you have to build underneath.
When you have a clear target that is simple to use it can be be more methodical for development to see everything orchestrating together rather than building bricks without knowing what shape the roof should be.
When you meet resistance, avoid changing the end commands. You wrote those commands because they were simple and easy to digest; Because you weren’t burdened by what your app couldn’t do; Because it couldn’t do anything! That means other developers/users will feel the same way.
That doesn’t mean you can’t adjust your initial sketch, but doing so with care is fruitful for you and future maintainers.
Obviously this approach has the challenge of taking longer to get off the ground. So I advise, when starting to use this paradigm, after the “end commands” are written, learn toward the beginning as you bisect to have incremental results.
As your experience grows, you can maintain a better mind map and go for longer stretches without needing to debug and you’ll trust your code more.
At some point, it will feel natural to take this approach and you’re end users will thank you for it.