article.svx
  1  ---
  2  title: Doing projects alone
  3  subtitle: "Though I can't claim to be an expert"
  4  date: 2025-04-25
  5  tags:
  6  - teamwork
  7  - personal
  8  author: Cam
  9  ---
 10  
 11  It should be noted that I have a very poor ratio of completed to abandoned projects.
 12  For every project I complete (or even, continue to a point where it more closely resembles
 13  what it's supposed to than it does Hello World), there are probably 10 more projects that lie
 14  abandoned as barely more than an empty repository. That is to say, by no means am I particularly
 15  successful in setting out to build something and actually following through with building it.
 16  Yet there are some similarities between projects that had any sort of continued progress,
 17  as opposed to projects that did not.
 18  
 19  The main observation worth making is that __working alone is slow__. There's no way
 20  around it. Particularly for those of us who have become accustomed to working on a team of
 21  developers, each committing 40 hours per week to the project, the effort of one person for
 22  *maybe* 10 hours a week in the evenings doesn't feel like much. It just isn't. It's a quarter
 23  (or less) of the time, a quarter (or less) of the people, and pretty much none of the
 24  consistency (if you go on vacation, everything just stops).
 25  
 26  The trick to success is to recognize this and compensate accordingly. What it comes down to
 27  is really one thing: __don't repeat yourself__. Avoid having to do anything twice. Don't
 28  make a decision twice, don't fix a bug twice, don't make yourself repeat any process twice.
 29  Having to repeat things leads to errors, frustration, and a shifting design, all of which
 30  eventually result in giving up.
 31  
 32  To this end, there are a few strategies that have provided me with some success.
 33  
 34  ### Make a plan and write it down
 35  
 36  Easily the first and most important factor in succeeding at building a project is knowing
 37  where that project is going. If you don't know the features you need, or what the final
 38  goal is, how can you build towards it? Easily, the leading cause for my giving up a project is
 39  not really knowing what the end goal is: I'll start something, play with it a bit, and
 40  quickly lose interest because I don't have a clear picture of what the next step should be.
 41  Without confidence in the end product, why bother to work on it at all.
 42  
 43  If you make as many decisions as you can up front, _and write them down_, that's a bunch of
 44  decisions you won't need to make again. When it comes time to build, all you need to do is
 45  refer to what you decided earlier and trust in the vision.
 46  
 47  This is true for any project, not just individual projects, but I would argue it is especially
 48  important for solo work to have a plan documented explicitly and clearly. Where a large team
 49  project, such as those at work, will almost always have at least a few people with it fresh in
 50  their minds at any given time. There is likely to be a pretty decent coverage of the knowledge
 51  of the project, even without writing it down, just in people's active working memory. Meanwhile,
 52  a solo project has only one contributor, and if they forget something, that knowledge is gone
 53  for good.
 54  
 55  My two longer term projects of relative success ([ConArtist][] and [Trilogy][]) both started with
 56  documentation well beyond what I might have thought necessary, and in both cases, it was the
 57  existence of that documentation that enabled the projects' lifetimes to span many years. Because
 58  the documents existed, I couldn't accidentally forget what the goal was, and I was able to pick
 59  up after taking a break.
 60  
 61  For ConArtist, this was a Figma project covering every feature I intended for each of the apps to include.
 62  Having this design meant that while building, I did not have to think about entire classes of
 63  problems, and could focus instead on purely the implementation. From minor inconsequential details
 64  (font sizes, colours) to larger considerations that would have major effects on data model or API
 65  structure (which pages show which information), when it came time to build I only had to refer to
 66  the document and do as I was instructed. This was true in the early days, and after every time I
 67  had to take a month or two off the project.
 68  
 69  Meanwhile Trilogy has a whole [language specification][spec], which was written before starting any code.
 70  For such a deeply technical project as a programming language, this document proved to be very useful.
 71  As life got in the way, there were multiple months, even a full year, during which I hardly looked at
 72  Trilogy at all. Without the document, when faced with some particular edge case of how some control flow
 73  construct interacted with another, I would have had no option but to make some choice and hope it was
 74  consistent with the rest of the (barely remembered) implementation. Instead, on many occasions, I opened
 75  the document to find that the problem was already solved, significantly reducing the amount of effort it
 76  would take to get back into the project.
 77  
 78  A lot of advice out there says "if you want to get a project done, you should aim to work on it a little
 79  every day." Although this is entirely true and great advice, it is also not always possible. Accommodating
 80  for the fact that you will have to take some breaks is a good compromise in my experience.
 81  
 82  [ConArtist]: https://github.com/foxfriends/conartist
 83  [Trilogy]: https://github.com/foxfriends/trilogy
 84  [spec]: https://github.com/foxfriends/trilogy/tree/main/spec
 85  
 86  ### Keep the plan up to date
 87  
 88  There is no way for the pre-implementation document to be fully accurate. There are always going
 89  to have been edge cases that you discover, or great new feature ideas that come in as you experience
 90  the product as it comes to exist. While it is tempting to simply cover those cases and add those
 91  features on the fly (it only takes a day anyway!), it is still valuable to hold yourself to updating
 92  the document before implementing, for two reasons.
 93  
 94  The first is that there may be details that were previously considered but currently forgotten. Depending
 95  on how long since the document was written, this can be very likely. It may be an edge case that you already
 96  considered and planned the solution for, so is actually not an issue after all. It may be an assumption
 97  you made previously, and already implemented something under, that you are about to overlook and violate.
 98  By referring to the document, and amending it with your most up to date information, you ensure that you
 99  don't contradict a previous choice.
100  
101  The second reason is that you need to ensure that you won't re-contradict this new choice later when you
102  run into this same edge case yet again. If you implement something without amending the document, it is hard
103  to know which is the actual intended behaviour. Someday you will again forget this edge case, see the
104  code that does not align with your expectation, attempt to "fix" it, and instead just introduce bugs.
105  
106  ### Write the unit tests
107  
108  Truly I always used to skip this, but it has come back to bite me so many times. Even for a small side
109  project, unit tests are such a powerful tool for preventing unexpected breakages. Especially for small
110  side projects, which may someday earn a small adjustment far in the future, the unit tests will protect
111  you from all the things you once knew and forgot. Much like the plan document is like a "pre-described"
112  goal, the unit tests are a "post-validated" version of that goal, locking in the intended functionality
113  as a different flavour of project specification. This doesn't replace updating the planning document
114  though, as if the tests and the plan do not align, which one is to be trusted?
115  
116  I won't go into too much detail on unit testing today, that being such a vastly covered topic already.
117  I will note, however, that ConArtist, without any unit tests, experienced enough deprecated dependencies
118  that I recently had to replace them in order to have any hope of maintaining the project, resulting in
119  a handful of (thankfully small) regressions which went undetected for a few days due to the lack of tests.
120  There have also been more small bugs discovered while using it over the years than I would have liked, some
121  of which might have been found and fixed earlier had I written tests at the appropriate time.
122  
123  Meanwhile, Trilogy has a large enough test suite that I have been able to add or replace whole pieces
124  of syntax, multiple times, and rarely have issues as a result. In fact, upon setting up the latest
125  parts of the testsuite, I've felt that development velocity has *improved* significantly, despite
126  also spending time writing tests, simply because there are so few bugs being introduced as I add
127  each new feature, and so few previously-implemented features that I have to revisit later, having
128  become newly broken. Multiple major refactors to the LLVM generation code for very complex control flow
129  changes have gone over smoothly, and I have no doubt that will continue to be the case.
130  
131  ### Accept that tools are product too
132  
133  Often when working on something, I run into a recurring but small point of friction. Something that doesn't
134  take much time to manually resolve whenever it comes up, but it does disrupt from the flow of the work
135  and make working that much less pleasant.
136  
137  The options in this situation are:
138  1. Continue to focus on what's "important", and brush this issue under the rug one more time.
139  2. Give up a day or two of making "progress", and build something that solves the issue once and for all.
140  
141  I would argue that, despite it feeling like you're taking a break or falling behind, the answer is always #2.
142  
143  My latest example of this goes back to the unit tests for Trilogy: the reason it took so long to
144  start adding tests was that there was no way to run those tests, until I built the tooling. In
145  this case, what I needed to test was that, given a Trilogy program, does it produce the correct output.
146  Though that sounds simple, there are a currently a bunch of steps that are required to compile and run
147  a Trilogy program: build the Trilogy compiler, compile the Trilogy code to LLVM IR, compile the LLVM code
148  to an actual executable, then run it. None of this was something that could be handled out of the box with
149  Rust's standard testing functionality, and even if it were, that would be a lot of boilerplate to have to
150  write in any testing environment.
151  
152  After struggling in a test-less world, I spent a day setting up the first draft of the testsuite: a
153  [shell script][test.sh] that would apply all those steps to a folder of Trilogy programs, and compare
154  their outputs to the expected result. This day that felt like it was a waste of time at first became
155  probably the largest contributor to the velocity at which language features could be implemented.
156  
157  Eventually even this shell script was not enough, as it just took too long to run the growing set of
158  tests, taking 10 seconds for around 80 tests, but this time I had no reservations as I learned about
159  Rust's support for custom test harnesses and rewrite the script as a multithreaded [Rust program][test.rs]
160  that plugs in to `cargo test`, allowing my custom testsuite to be run automatically alongside the rest
161  of the unit tests, taking just over 1 second for the same. These times were worse (double?) before I
162  upgraded my computer, so it was more dramatic of an issue at the time.
163  
164  Now that these tests are such low friction to both write and run, it's reasonable to run the
165  whole testsuite constantly as I iterate on each new feature, which significantly reduces turnaround
166  time on finding and fixing all the bugs I have inevitably introduced.
167  
168  [test.sh]: https://github.com/foxfriends/trilogy/blob/6b98c0d7eda323bf8fb7f8bd45d5b72af46ba64b/testsuite-llvm/test.sh
169  [test.rs]: https://github.com/foxfriends/trilogy/blob/a0f8883ea07a9808fc1d5d2044293e8861dfbea5/trilogy/test.rs
170  
171  In these cases it's often, but not limited to, testing-related tools. Scripts for managing data,
172  UI for designing levels, improved logging and tracing, hot-reloading, or even just installing
173  better linters and formatters are all tooling tasks that take a day but so quickly pay off in
174  the time saved on chores and debugging later. Plus, it can be a refreshing change of pace
175  to build a tool, instead of always grinding away at the "real" work.