Iteration FTW

Does It Do the Thing?

A refinement-based approach to software in 5 easy steps

Brooklyn Zelenka
Radio Free Monad
Published in
5 min readMar 8, 2016

--

Over the past several months, I’ve had a few developers that are earlier in their careers want to emulate the cleanliness of their more seasoned colleagues, and have asked what the secret is. Honestly, this isn’t some dark art, so I pair programmed with them to show how I approach a problem. The number one comment has consistently been surprise at how much I go back and tweak, rephrase, and refactor code constantly, both in the small (single lines or functions) and in the large (modules, classes).

Code always grows organically, even if the initial pass had full volumes of granular specs and UML diagrams. Code needs constant maintenance, especially while writing it. More features will emerge while you’re writing that first one (features beget features), but not just end-user features: internal features. Utility functions, edge cases, external service integration, to name a few. Since your code will develop its own local idioms (a “regional accent”, even), sticking to the plan can be difficult, and keeping the code consistent can be difficult if you don’t have a process.

Code always grows organically [and] needs constant maintenance, especially while writing it

To account for the above considerations (and some more), I evaluate the success of software in a hierarchy. This “refinement-based approach” is used by many developers, writers, and composers the world over.

TL;DR

Write a rough draft that works, even if it’s ugly. Refine the code from short-term needs to long term needs. Consider both small (local) and large (global) organizing units.

Step 1: Does it do the thing?

“You had one job”

My colleagues often hear me ask “does it do the thing?” Needless to say, I was thrilled when someone forwarded this to me:

“What is the thing?”, you ask. Well, the thing is whatever peice of functionality that you’re working on adding to the system. If it doesn’t actually complete the task that it was written for, then the code is simply no good. The most elegant chunk of code is useless if it doesn’t do the thing.

Features are the beadrock of your application. They literally don’t function without them, and even a messy solution is better than no solution. If you have a plan, then great, but if you’re stuck, then power through it. We can refine it later.

Step 1 is focused on:

  • The feature works!
  • It’s shippable now, if it really comes down to it
Sage advice

Beware

Only focusing on the thing without going further along the hierarchy leads to horrible, horrible cruft! It’s the shortest-term, needs-based portion. It has the additional virtue of encouraging you to be pragmatic, and not get stuck optimizing an algorithm until it’s functioning.

Step 2: How maintainable is it?

How easily can you make it do more things?

With what level of confidence would you be able to hand the project off to another developer, and have them add features to the code?

Many pixels have been spilled over the issue of maintainability, especially in the Lisp and Ruby communities.

Step 2 focuses on:

  • Making idiosyncratic code more idiomatic
  • Extensibility
  • Clarity (structure and naming)
  • Comments & documentation
  • Best practices
  • “Classic” testing (ex. BDD doubling as docs)

Step 3: Does it perform well?

How fast does it do the thing?

If execution speed wasn’t important, we wouldn’t have computers. These days it may mean parallelizing your code, or even optimizing perceived speed to combat variables out of your control.

Many developers get bogged down at this phase, so it’s best to say vigilant of diminishing returns. It can be rewarding to see the improvement of the asymptotic complexity of your code, because it’s easily quantifiable.

Step 3 focuses on:

Step 4: Is it correct?

Does it always do the thing?

Correctness isn’t talked about nearly as much as it deserves. Are there edge cases? Is the function total? Is there unreachable code? Do the types unify?

Correctness is a very desirable property of code, but it can be a lot of work. It’s in fourth place, because it provides the longest-term benefits, with almost none up-front. This is the the section that will keep your code from biting you 16 months from now, behaving unexpectedly in production. It gives you a very high level of confidence that your code does in fact do the thing every time.

Correctness is a very desirable property

Static analysis tools like Dialyzer and the type checkers in ML-family languages are aimed very much towards correctness. It’s what makes these tools so powerful.

Step 4 focuses on:

Step 5: Wash, Rinse, Repeat

Do all the things!

Just remember, if in doubt, make it do the thing. You can worry about the rest later. This is an iterative process. That one liner that you’re working on? Does it do the thing? Is it maintainable? Great. Now do the function. Need to reorder the module to have it make sense? Rename some functions even? Awesome.

By repeating these same steps in the small, and in the large, you keep the code coherent, and your strategy focused. Your software is always shippable, and refinement keeps it that way over longer periods of time. You can come back to your code in 10 months and feel good about what’s there. Your clients are happy because the code functions well, has few bugs, and is extendable. Everybody wins!

--

--