I agree with all the points of this article and would like to add one: Have a quick feedback loop. For me, it's really motivating to be able to make a change and quickly see the results. Many problems just vanish or become tangible to solve when you playfully modify your source code and observe the effect.
They also have the advantage that you can A) refactor pretty much everything underneath them without breaking the test, B) test realistically (an underrated quality) and C) write tests which more closely match requirements rather than implementation.
I can see a place for this, but these are no longer e2e tests. I guess that’s what “hermetic” means? If so it’s almost sinister to still call these e2e tests. They’re just frontend tests.
> A) refactor pretty much everything underneath them without breaking the test
This should always be true of any type of tests unless it’s behavior you want to keep from breaking.
> B) test realistically (an underrated quality)
Removing major integration points from a test is anything but realistic. You can do this, but don’t pretend you’re getting the same quality as a colloquial e2e tests.
> C) write tests which more closely match requirements rather than implementation
If you’re ever testing implementation you’re doing it wrong. Tests should let you know when a requirement of your app breaks. This is why unit tests are often kinda harmful. They test contracts that might not exist.
This is why unit tests before e2e tests.
It's higher risk to build on components without unit tests test coverage, even if the paltry smoke/e2e tests say it's fine per the customer's input examples.
Is it better to fuzz low-level components or high-level user-facing interfaces first?
IIUC in relation to Formal Methods, tests and test coverage are not sufficient but are advisable.
Competency Story: The customer and product owner can write BDD tests in order to validate the app against the requirements
Prompt: Write playwright tests for #token_reference, that run a named factored-out login sequence, and then test as human user would that: when you click on Home that it navigates to / (given browser MCP and recently the Gemini 2.5 Computer Operator model)
Quick feedback with unit tests can help. It can be a pain to decouple stuff so you can test them better, but it’s worth it IMO.
When I want to try/fix something, if the setup itself takes hours, I lose heart and move on.
Thats why I love lisp (or anything with a decent Repl). Instant gratification.
The second you lose motivation the whole thing poofs into non-existence, so making it enjoyable is almost the most important facet.
This resonates with me. Sometime i want to "turn off" my brain and write shitty code.
Back in the day, i made a lot of toy project. Sometime all the source code is in single file. No respect to modularity. But it was fun, and it worked. Now just try to finish a toy project seem much harder than ever.
His 'No Silver Bullet' theory may or may not stand up to what AI is doing today as well.
"there is no single development, in either technology or management technique, which by itself promises even one order of magnitude [tenfold] improvement within a decade in productivity, in reliability, in simplicity."
If he had also included "volume" then AI would have disproved him! More anecdotally, the hands-on experiences of senior+ developers seem to firmly fall into the accidental camp, with the essence of software problem solving getting marginally easier as you'd expect with a new, powerful tool, but far from "solved".
I do believe order of magnitude improvements in productivity and reliability are possible, but they don't come from technology or management technique, they come from simplicity. The simplest possible thing that gets the job done can be infinitely more reliable than whatever baroque contraption comes out of typical fog-of-war enterprise environments. The trick is having the judgement to understand what complexity is essential and how to distill things down to the most valuable essence. This is something AI will never be able to do, because the definition of value is in the eye of the human beholder.
It's a playful environment with low stakes too, compared to working in a startup, so really advice new programmers to participate in order to learn faster.
> My goal with the early sub-projects isn't to build a *finished* sub-component, it is to build a good enough sub-component so I can move on to the next thing on the path to a demo.
This is so enlightening. And I realized that to do this, one has to "skip" something. Other folks mention they ignore code modularity when doing this, I don't think I will do that, keeping code clean and reading/working in such a codebase actually make me satisfied and motivated. For me, I am going to "skip" algorithms, data strucuture and performance.
So the point here is probably, we should skip things, but if a thing motivates you, it should not skipped?
I do this as part of the work on my own game engine:
https://github.com/zinc-framework/Zinc.Demos/tree/main/Zinc....
This sounds like his approach working on personal projects. I'm really curious about large technical team projects though. What's the best approach to getting stuff done and making sure everyone is working towards the same goal etc.
After 15 years I have yet to see a technical project that hasn't run over budget, over time, under delivered or burnt people out.
I'm sure there are people out there with counter examples who know exactly how to deliver projects at a massive scale. Any links/suggested reading would be appreciated!
TBH these are fine in my book.
"over budget" is only an issue if there's really no more money, and I think it's pretty rare for IT projects. Most of the time it's just someone complaining the estimate was off.
"Over time" is the same, it's an issue if there was a real deadline, but the best practice is not plan unflexible events (e.g. do a huge PR campaign with the date on it) before it's basically done.
"under delivered" is a matter of expectations, the real pointis that it was delivered at all.
> or burnt people out.
This one is not like the others. People shouldn't burn out over bullshit deadlines.
Over the years, I’ve learned that true success starts with clearly understanding why you’re building something. Without clear goals, it’s impossible to prioritize or even know where to start. That clarity drives better sequencing, and sometimes the wisdom to not build something at all.
The next critical factor is empathy. You have to see through your customer’s eyes and validate that what you’re building actually solves their problem. That doesn’t mean giving them everything they ask for, but rather understanding their pain points well enough to deliver real value.
Ultimately, most projects go over budget or underdeliver because teams spend too much time building the wrong things. If we instead focused on continually steering toward desired, valuable functionality (things people genuinely want or will pay for), more software projects would appear like successes.
How do I build this on an unmodified system with unmodified popular and trusted libraries?
How do I make sure that users don't have to modify my project to use it, unless they are going to be contributors?
If I'm truly just building for myself, which almost never happens, I look at resuability. If I have to learn a new tool, what's the most general one, that I'm likely to be glad I learned when I'm doing some other project?
My approach to building large technical projects - https://news.ycombinator.com/item?id=36161397 - June 2023 (27 comments)
Building software for yourself can help you solve a problem you have. If you're also using the software yourself, you can fix bugs in the software. I found some bugs in a web server I'm building by trying to use it myself.
That combined with launching an MVP first rather than the "complete" vision. Shipping early meant we avoided the trap of spending months perfecting features no one actually used.
I think this is where the choice of language makes a big difference. In Clojure, the difference between a "component" and a separate library/application is literally just adding a `deps.edn` file and then pointing to the directory from the parent project.
I think breaking the project in to small achievable goals is very sensible. But if you take the extra time to make the component stand on its own as a mini-lib .. it's very satisfying. For instance I had to write a "component" that would read some GeoJSON and segment it (it's took me a couple of days and was mostly a wrapped around GDAL or something). I could have hacked together a solution to rush to a demo - but instead I made a small little library out of it. When I was done with it, I had a sense of "I made a thing". To be clear.. it's still kind of ugly and I would be horrified if someone else tried to use it and submitted PRs.. but it's also not a coupled tangle of code in my larger codebase.
as he says "Build for yourself" - the library/application should only do what you need
By contrast, if I was working in C++ making an API and decoupled library would be such a chore that'd never bother
The most important aspect is that this all ends up not just much more satisfying at every step - but it makes your code incredibly decoupled and refactorable. The more you rush to a demo the more your code is coupled and hard to refactor.
How do make this into a sexy image for management. Sure, business logic is stubbed, but my carefully crafted strongly typed interfaces all mesh together! Imagine the future dividends!
So in essence we have: Empirical Process Control, Self-Organization, Collaboration, Value-Based Prioritization, Time-Boxing, and Iterative Development.
Is this just solo-SCRUM or am I missing something here?