My blog (https://blog.eldridge.cam)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

another article that i found halfwritten and finished sort of lazily

+191 -16
+175
article/20250425-doing-projects-alone/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.
+16 -16
package-lock.json
··· 1349 1349 "license": "MIT" 1350 1350 }, 1351 1351 "node_modules/@types/node": { 1352 - "version": "22.14.1", 1353 - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", 1354 - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", 1352 + "version": "22.15.2", 1353 + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", 1354 + "integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==", 1355 1355 "license": "MIT", 1356 1356 "dependencies": { 1357 1357 "undici-types": "~6.21.0" ··· 1548 1548 "optional": true 1549 1549 }, 1550 1550 "node_modules/bare-fs": { 1551 - "version": "4.1.2", 1552 - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.2.tgz", 1553 - "integrity": "sha512-8wSeOia5B7LwD4+h465y73KOdj5QHsbbuoUfPBi+pXgFJIPuG7SsiOdJuijWMyfid49eD+WivpfY7KT8gbAzBA==", 1551 + "version": "4.1.3", 1552 + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.3.tgz", 1553 + "integrity": "sha512-OeEZYIg+2qepaWLyphaOXHAHKo3xkM8y3BeGAvHdMN8GNWvEAU1Yw6rYpGzu/wDDbKxgEjVeVDpgGhDzaeMpjg==", 1554 1554 "license": "Apache-2.0", 1555 1555 "optional": true, 1556 1556 "dependencies": { ··· 1913 1913 } 1914 1914 }, 1915 1915 "node_modules/detect-libc": { 1916 - "version": "2.0.3", 1917 - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", 1918 - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", 1916 + "version": "2.0.4", 1917 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", 1918 + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", 1919 1919 "license": "Apache-2.0", 1920 1920 "engines": { 1921 1921 "node": ">=8" ··· 4115 4115 "license": "MIT" 4116 4116 }, 4117 4117 "node_modules/parse5": { 4118 - "version": "7.2.1", 4119 - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", 4120 - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", 4118 + "version": "7.3.0", 4119 + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", 4120 + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", 4121 4121 "dev": true, 4122 4122 "license": "MIT", 4123 4123 "dependencies": { 4124 - "entities": "^4.5.0" 4124 + "entities": "^6.0.0" 4125 4125 }, 4126 4126 "funding": { 4127 4127 "url": "https://github.com/inikulin/parse5?sponsor=1" 4128 4128 } 4129 4129 }, 4130 4130 "node_modules/parse5/node_modules/entities": { 4131 - "version": "4.5.0", 4132 - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 4133 - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 4131 + "version": "6.0.0", 4132 + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", 4133 + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", 4134 4134 "dev": true, 4135 4135 "license": "BSD-2-Clause", 4136 4136 "engines": {