Principles of Productive Programming

Jay Butera
4 min readFeb 25, 2019

These are a few principles I’ve learned through many long, overextended projects. With many failures have come a few successes, and those successes have helped me to identify just what went wrong the other times. Learning to follow these closely has been like learning to bring a notebook to class; pivotal in introducing some order into an otherwise chaotic process.

  1. Keep a daily log to track changes and why they’re necessary

I’ll forget why I did something sometimes just 10 minutes after doing it. I don’t remember most decisions the day after. Even big design decisions where I changed the data structure or large portions of code, I won’t remember for too long after I’ve made the change. A log helps me to not make those same mistakes again in the future — when I may be wondering “Hm, why didn’t I just do this?” I skim the log and remember “Oh, that’s why”. In fact, I believe a designer’s log should be as expected in a project as a README.md file is on GitHub. It’s basically a map anyone can follow to see why the structure is set up the way it is, and why some other approach is not as good. This doesn’t need to be a complicated process. Just put an entry with a date-time stamp and explain in your own thoughts what you did or considered doing that day.

2. Be mindful of your emotional response to new problems
When I realize a flaw in my design that will require me to rethink the program’s structure, my first reaction is often, understandably, frustration. But that frustration in turn makes me feel like the problem is worse than it is. Often a fix is much more simple than I first think. The hardest part is getting through the initial frustration. Where I may have thrown my hands up in the air and taken a long break before, I’ll now try notice that frustration, and setting it aside to focus on a solution logically and with a level head. Usually the path becomes much more clear, very quickly.

3. Procrastination compounds
Temporarily building something a bad way, or not writing proper tests, or knowing there might be an issue but coding anyways until you hit it. It seems easy in the moment to just focus on making progress. But often that is progress down the wrong path, and though you may learn something from doing it, it can often be quicker play out in your head whether something will work or not. It may seem like a little problem — “this function is not referentially transparent, it implicitly depends on this mutable state, I’ll just remember that for now”. A few hours later you’re knees deep in the debugger, chasing some elusive panic state. Would you rather take the time to fix the small thing now? Or later after rediscovering it through some negative collision that rippled through your code.

It’s worth pointing out here the balance between getting something done in a timely manner and getting it done right. A system can always be improved so you don’t have to get it perfect on the first draft, but if you design it without any consideration for future scalability, you’ll end up with a program that needs to be completely re-written. If you feel unsure whether your design will meet all future requirements of the software, then you’re probably failing to consider principle #3. Spend the extra time to design a system that can be expanded upon, even if it seems to take longer to make progress at the start.

4. When designing something big, start from the end
Often we like to procrastinate big design decisions because just getting started seems so much easier. It’s very challenging to consider all the future obstacles and trade-offs that a specific decision will have on a design. If you hit this wall, write up a mock interface that a user might use for your system. For instance, if you’re building a graph library, write out how the user will define, build, and do the operations the library provides on that graph. Will those operations be methods of the graph or of the nodes in the graph? Will those methods take node identifiers as their parameters or references to node objects? The interface to your system is the sum of all your design decisions up to that point, so if you work backward, you can infer what those decisions should be based on the outcome you want.

let graph = Graph::new();
let n = Node { x: 5 };
graph.add_node(n);
n.is_ancestor_of(m);
// Vs
graph.is_ancestor_of(n,m);

Bonus principle

5. Don’t forget to breath

Literally. Next time you’re focused on a problem, notice your breathing. It’s probably very shallow which can be stressful on your body when you’re coding every day. Intentionally introduce a little breath back into your body. It should help your endurance and your overall mental health.

--

--