···11+---
22+title: Doing projects alone
33+subtitle: "Though I can't claim to be an expert"
44+date: 2025-04-25
55+tags:
66+- teamwork
77+- personal
88+author: Cam
99+---
1010+1111+It should be noted that I have a very poor ratio of completed to abandoned projects.
1212+For every project I complete (or even, continue to a point where it more closely resembles
1313+what it's supposed to than it does Hello World), there are probably 10 more projects that lie
1414+abandoned as barely more than an empty repository. That is to say, by no means am I particularly
1515+successful in setting out to build something and actually following through with building it.
1616+Yet there are some similarities between projects that had any sort of continued progress,
1717+as opposed to projects that did not.
1818+1919+The main observation worth making is that __working alone is slow__. There's no way
2020+around it. Particularly for those of us who have become accustomed to working on a team of
2121+developers, each committing 40 hours per week to the project, the effort of one person for
2222+*maybe* 10 hours a week in the evenings doesn't feel like much. It just isn't. It's a quarter
2323+(or less) of the time, a quarter (or less) of the people, and pretty much none of the
2424+consistency (if you go on vacation, everything just stops).
2525+2626+The trick to success is to recognize this and compensate accordingly. What it comes down to
2727+is really one thing: __don't repeat yourself__. Avoid having to do anything twice. Don't
2828+make a decision twice, don't fix a bug twice, don't make yourself repeat any process twice.
2929+Having to repeat things leads to errors, frustration, and a shifting design, all of which
3030+eventually result in giving up.
3131+3232+To this end, there are a few strategies that have provided me with some success.
3333+3434+### Make a plan and write it down
3535+3636+Easily the first and most important factor in succeeding at building a project is knowing
3737+where that project is going. If you don't know the features you need, or what the final
3838+goal is, how can you build towards it? Easily, the leading cause for my giving up a project is
3939+not really knowing what the end goal is: I'll start something, play with it a bit, and
4040+quickly lose interest because I don't have a clear picture of what the next step should be.
4141+Without confidence in the end product, why bother to work on it at all.
4242+4343+If you make as many decisions as you can up front, _and write them down_, that's a bunch of
4444+decisions you won't need to make again. When it comes time to build, all you need to do is
4545+refer to what you decided earlier and trust in the vision.
4646+4747+This is true for any project, not just individual projects, but I would argue it is especially
4848+important for solo work to have a plan documented explicitly and clearly. Where a large team
4949+project, such as those at work, will almost always have at least a few people with it fresh in
5050+their minds at any given time. There is likely to be a pretty decent coverage of the knowledge
5151+of the project, even without writing it down, just in people's active working memory. Meanwhile,
5252+a solo project has only one contributor, and if they forget something, that knowledge is gone
5353+for good.
5454+5555+My two longer term projects of relative success ([ConArtist][] and [Trilogy][]) both started with
5656+documentation well beyond what I might have thought necessary, and in both cases, it was the
5757+existence of that documentation that enabled the projects' lifetimes to span many years. Because
5858+the documents existed, I couldn't accidentally forget what the goal was, and I was able to pick
5959+up after taking a break.
6060+6161+For ConArtist, this was a Figma project covering every feature I intended for each of the apps to include.
6262+Having this design meant that while building, I did not have to think about entire classes of
6363+problems, and could focus instead on purely the implementation. From minor inconsequential details
6464+(font sizes, colours) to larger considerations that would have major effects on data model or API
6565+structure (which pages show which information), when it came time to build I only had to refer to
6666+the document and do as I was instructed. This was true in the early days, and after every time I
6767+had to take a month or two off the project.
6868+6969+Meanwhile Trilogy has a whole [language specification][spec], which was written before starting any code.
7070+For such a deeply technical project as a programming language, this document proved to be very useful.
7171+As life got in the way, there were multiple months, even a full year, during which I hardly looked at
7272+Trilogy at all. Without the document, when faced with some particular edge case of how some control flow
7373+construct interacted with another, I would have had no option but to make some choice and hope it was
7474+consistent with the rest of the (barely remembered) implementation. Instead, on many occasions, I opened
7575+the document to find that the problem was already solved, significantly reducing the amount of effort it
7676+would take to get back into the project.
7777+7878+A lot of advice out there says "if you want to get a project done, you should aim to work on it a little
7979+every day." Although this is entirely true and great advice, it is also not always possible. Accommodating
8080+for the fact that you will have to take some breaks is a good compromise in my experience.
8181+8282+[ConArtist]: https://github.com/foxfriends/conartist
8383+[Trilogy]: https://github.com/foxfriends/trilogy
8484+[spec]: https://github.com/foxfriends/trilogy/tree/main/spec
8585+8686+### Keep the plan up to date
8787+8888+There is no way for the pre-implementation document to be fully accurate. There are always going
8989+to have been edge cases that you discover, or great new feature ideas that come in as you experience
9090+the product as it comes to exist. While it is tempting to simply cover those cases and add those
9191+features on the fly (it only takes a day anyway!), it is still valuable to hold yourself to updating
9292+the document before implementing, for two reasons.
9393+9494+The first is that there may be details that were previously considered but currently forgotten. Depending
9595+on how long since the document was written, this can be very likely. It may be an edge case that you already
9696+considered and planned the solution for, so is actually not an issue after all. It may be an assumption
9797+you made previously, and already implemented something under, that you are about to overlook and violate.
9898+By referring to the document, and amending it with your most up to date information, you ensure that you
9999+don't contradict a previous choice.
100100+101101+The second reason is that you need to ensure that you won't re-contradict this new choice later when you
102102+run into this same edge case yet again. If you implement something without amending the document, it is hard
103103+to know which is the actual intended behaviour. Someday you will again forget this edge case, see the
104104+code that does not align with your expectation, attempt to "fix" it, and instead just introduce bugs.
105105+106106+### Write the unit tests
107107+108108+Truly I always used to skip this, but it has come back to bite me so many times. Even for a small side
109109+project, unit tests are such a powerful tool for preventing unexpected breakages. Especially for small
110110+side projects, which may someday earn a small adjustment far in the future, the unit tests will protect
111111+you from all the things you once knew and forgot. Much like the plan document is like a "pre-described"
112112+goal, the unit tests are a "post-validated" version of that goal, locking in the intended functionality
113113+as a different flavour of project specification. This doesn't replace updating the planning document
114114+though, as if the tests and the plan do not align, which one is to be trusted?
115115+116116+I won't go into too much detail on unit testing today, that being such a vastly covered topic already.
117117+I will note, however, that ConArtist, without any unit tests, experienced enough deprecated dependencies
118118+that I recently had to replace them in order to have any hope of maintaining the project, resulting in
119119+a handful of (thankfully small) regressions which went undetected for a few days due to the lack of tests.
120120+There have also been more small bugs discovered while using it over the years than I would have liked, some
121121+of which might have been found and fixed earlier had I written tests at the appropriate time.
122122+123123+Meanwhile, Trilogy has a large enough test suite that I have been able to add or replace whole pieces
124124+of syntax, multiple times, and rarely have issues as a result. In fact, upon setting up the latest
125125+parts of the testsuite, I've felt that development velocity has *improved* significantly, despite
126126+also spending time writing tests, simply because there are so few bugs being introduced as I add
127127+each new feature, and so few previously-implemented features that I have to revisit later, having
128128+become newly broken. Multiple major refactors to the LLVM generation code for very complex control flow
129129+changes have gone over smoothly, and I have no doubt that will continue to be the case.
130130+131131+### Accept that tools are product too
132132+133133+Often when working on something, I run into a recurring but small point of friction. Something that doesn't
134134+take much time to manually resolve whenever it comes up, but it does disrupt from the flow of the work
135135+and make working that much less pleasant.
136136+137137+The options in this situation are:
138138+1. Continue to focus on what's "important", and brush this issue under the rug one more time.
139139+2. Give up a day or two of making "progress", and build something that solves the issue once and for all.
140140+141141+I would argue that, despite it feeling like you're taking a break or falling behind, the answer is always #2.
142142+143143+My latest example of this goes back to the unit tests for Trilogy: the reason it took so long to
144144+start adding tests was that there was no way to run those tests, until I built the tooling. In
145145+this case, what I needed to test was that, given a Trilogy program, does it produce the correct output.
146146+Though that sounds simple, there are a currently a bunch of steps that are required to compile and run
147147+a Trilogy program: build the Trilogy compiler, compile the Trilogy code to LLVM IR, compile the LLVM code
148148+to an actual executable, then run it. None of this was something that could be handled out of the box with
149149+Rust's standard testing functionality, and even if it were, that would be a lot of boilerplate to have to
150150+write in any testing environment.
151151+152152+After struggling in a test-less world, I spent a day setting up the first draft of the testsuite: a
153153+[shell script][test.sh] that would apply all those steps to a folder of Trilogy programs, and compare
154154+their outputs to the expected result. This day that felt like it was a waste of time at first became
155155+probably the largest contributor to the velocity at which language features could be implemented.
156156+157157+Eventually even this shell script was not enough, as it just took too long to run the growing set of
158158+tests, taking 10 seconds for around 80 tests, but this time I had no reservations as I learned about
159159+Rust's support for custom test harnesses and rewrite the script as a multithreaded [Rust program][test.rs]
160160+that plugs in to `cargo test`, allowing my custom testsuite to be run automatically alongside the rest
161161+of the unit tests, taking just over 1 second for the same. These times were worse (double?) before I
162162+upgraded my computer, so it was more dramatic of an issue at the time.
163163+164164+Now that these tests are such low friction to both write and run, it's reasonable to run the
165165+whole testsuite constantly as I iterate on each new feature, which significantly reduces turnaround
166166+time on finding and fixing all the bugs I have inevitably introduced.
167167+168168+[test.sh]: https://github.com/foxfriends/trilogy/blob/6b98c0d7eda323bf8fb7f8bd45d5b72af46ba64b/testsuite-llvm/test.sh
169169+[test.rs]: https://github.com/foxfriends/trilogy/blob/a0f8883ea07a9808fc1d5d2044293e8861dfbea5/trilogy/test.rs
170170+171171+In these cases it's often, but not limited to, testing-related tools. Scripts for managing data,
172172+UI for designing levels, improved logging and tracing, hot-reloading, or even just installing
173173+better linters and formatters are all tooling tasks that take a day but so quickly pay off in
174174+the time saved on chores and debugging later. Plus, it can be a refreshing change of pace
175175+to build a tool, instead of always grinding away at the "real" work.