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.