How Prototyping Makes Me a Better Developer

Posted on:

Something that experimenting with as I build Adiungo is this new-to-me approach to scoping projects. I've done a lot of scoping in the past, and I keep finding smaller progressions in each iteration as I go.

When I first started programming, my process looked kind-of like this:

  1. Discover a problem
  2. Write code until problem is fixed.

And this sweet simplicity was effective, but unsurprisingly, riddled with buggy code and in-general just bad code that couldn't scale up well. As I got better at programming, and learned more about architecture and other complexities, I eventually upgraded to something a little better.

  1. Discover a problem
  2. Write a scope document that roughly describes how I will solve this problem
  3. Write code until problem is fixed

I think this approach only came to me out of necessity from customers, who would ask me for estimates on projects. I didn't want to build the entire damn project before giving them an estimate, but I wanted to get my estimate as accurate as I could. It turns out, however, that in taking a step back to think about the problem a little more before actually writing code resulted in me writing much better code.

Honestly, I've used this three-step approach for a few years now, and it's been pretty good for me. The problem, however, is that in both the first and second iteration of my process, I'm ultimately writing code one time, and changing it on the fly as I run into issues. This on-the-fly approach has worked great for me over the years, until I started to do test-driven development.

Test Driven Development

This on-the-fly approach tends to lead to a lot of re-writing, and as a result became very time consuming once I started committing to writing tests. Every line of code takes quite a bite more effort to write when you're working with unit tests, simply because I have to write tests for it afterward. While this is a worthwhile endeavor, and overall I'm writing much more stable and reliable code as a result, it's still very time consuming if you don't have the code written how you'd like it to be in the first place.

When I'm doing test-driven development, I've changed my process even-further. I'm essentially taking an agile approach:

  1. Discover a problem
  2. Write a scope for the initiative to solve the problem (or in Adiungo's case, a manifesto)
  3. Break that initiative down into smaller pieces, as individual projects
  4. Break those projects into individual tasks
  5. Write code described in tasks until problem is fixed

As I work with this approach, I find that step 3 is particularly difficult, and is the reason why in the past I said "screw it, I'm just going to write the code". At that point, you're getting detailed enough to basically need to know everything you're going to write before you actually write it in-order to be able to create the projects, and the tasks for those projects.

For a while, I was trying to take the same approach for all of these "project management" tasks as the approach I took when trying to write the scope. I would think through what needs to be built, and maybe build a very rough outline of the code, and use that as the basis for the entire project. This worked pretty well, but I inevitably discovered some kind of major oversight in what I was building that invalidates a lot of the tasks in the project. This would lead to me needing to go back, update the project scope, change the tasks to reflect the piece I missed, and resume work. It was awful.

What I needed was the best of both worlds. I needed to be able to be able to suss out how my code could be built in a way that felt familiar to me, but I also wanted to be able to ensure that tests were written. These two things are often at odds - the time it takes to write tests can make drafting code time consuming, and disruptive, but it's also an awful idea to write a bunch of code and say "I'll go back and write tests later" because I know that I will never go back and write tests later. It just doesn't happen.

Drawing Inspiration From Other Sources

My wife is currently in the process of writing a book, and as a result I've found myself reflecting on her process, which is quite similar to a typical writing process in-general, which has looked something like this:

  1. Discover a story idea
  2. Write an outline for the story
  3. Write a first draft "brain dump"
  4. Write a second draft that uses details discovered and considered in the first draft
  5. Continue writing drafts that polish, add context, and improve the novel

I realized that I really needed to have a process that is more akin to a traditional writing process. What I was trying to do was start with a vague sense of the problem, and continue to iterate on the solution to the problem until I have a solution that is stable, secure, and scale-able. I asked myself "how can I apply some of the practices that come from the writing process in my programming practice?"

I ended up with this:

  1. Discover a problem
  2. Write a scope for the initiative to solve the problem (or in Adiungo's case, a manifesto)
  3. Break that initiative down into smaller pieces, as individual projects
  4. Write the first draft of the code (I call it a prototype). Do not write tests.
  5. Break each method written in the prototype into individual tasks
  6. Work on each task, iterating on the prototyped method for that task, writing tests and proving that it works as-expected, and making adjustments as-necessary.
  7. If a significant problem arises while working on an individual task, prototype the changes, and move all of that into a new set of tasks for the project.
  8. Write code described in tasks until problem is fixed.

The key to this change is that it takes some of the practices of traditional writing, and embeds it into the process. It allows me to draft my code quickly, and freely, thinking through how to actually solve the problem I'm trying to solve without being encumbered by tests too early. Once I have a solid understanding, I can simply plop the resulting code into a new task, describe what that snippet of code is intended to-do, and move forward. I then iterate on that code in the actual task, writing tests and codifying it in the overall source. What I love about this, is that it forces me to touch any given piece of code at least twice, and each time, it's through a different lens.

The initial write is more big-picture focused, where I'm learning how all of the code interacts with the rest of the system. At this point, I'm mostly thinking about the architecture of the code. Is it scale-able? Are the approaches used consistent with how rest of the code is written? Does what I'm writing actually solve the problem? I believe in writing they say "edit with a chainsaw first", and this first draft enables me to do exactly that.

The second draft is zoomed way in. I'm not thinking about the architecture. Instead, I'm more focused on proving whatever method I'm looking at does what it's intended to-do, and I prove that by writing unit tests for it. At this point, I'll often break prototyped code into multiple methods just to make it easier to test, or even sometimes break it out into an entirely separate class. Edit with a knife second.

Conclusion

I don't take this approach for every project. It really depends on if the project is complex enough to warrant it, or if I'm writing unit tests for whatever I'm building. Unsurprisingly, there's a lot of agencies I work with who don't use unit tests because they're too time-intensive, so in those cases I find that the simple "on the fly" approach is appropriate. Regardless, it's nice to know I have a set of options at my disposal that determines how I approach projects when I'm working on them.