But then it says "Steel Threads is better. Instead of switching out a part of your monolith, you... switch out a part of your monolith but it's a smaller part. That's not as hard". Which is true but isn't that the same thing? It's not clear to me what the qualitative difference is that requires the introduction of new terminology?
In the end you have three states two deal with with the code before the new feature, the chimeric code and then the code with the new code enabled.
With the steel thread approach the feature exists on its own and is used and tested in production at the very beginning, although with limited traffic first. Important to note that the author seems to assume a micro-service architecture.
In the tweet, he said that when coding, he'd start by the smallest possible PoC, and code it entirely front to back. That'd give the general structure, and then he'd build upon that. (this is what I remember of it fwiw).
I do this all the time too, which I think is a vastly superior approach to TDD, which assumes how an API is going to be used, without actually writing the actual thing that's going to use it.
public class Foo
{
public static void main( String[] args )
{
System.out.println( "Done" );
}
}
And he was really struck at first, like "Why would you write Hello World when we're building a $WHATEVER? My response was that the very first thing I always want to see, in any new project, is something compiling, packaging and executing. It's my way of ensuring that at a minimum the environment is set up, cosmic radiation hasn't fried my CPU or RAM, etc. And once I have that trivial program running, I just start building up from there.I more or less follow that same pattern for everything I write to this day. At most, the slightly more complex version I start with is something like a "template" or "skeleton" project. For example, I keep a sample Spring Boot project around that as a pom file, the directory structure, a package named something like org.fogbeam.example, and a simple controller that just returns "Hello World". Once I can build and run that, and hit localhost:8080 and see my "Hello World" page I start iterating from there.
I can't tell you exactly how I developed this habit over the years, but it's worked well for me.
The book that really helped me to start working in this way is Growing Object Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce [0]. The book has been around for a few years at this point and tech has moved on since then, as have some of the techniques, but it's still a very interesting read. (Disclosure: I was lucky enough to briefly work with one of the authors a few years ago but I was a fan of the book long before then.)
I try to make the features, structure, everything as simple as possible. As you do this you'll see things that should be probably be abstracted, things you'll want to do as soon as this next part is in.
Don't do it yet, wait for that actual feature to be written, then you refactor, make a system, etc.. Don't do it prematurely. You want to wait because the longer you wait, the chance the features parameters or use case will be different, or it won't even exist. Half the requirements they think they need at the start will be side thoughts by the end.
The real problem is getting to the point where the initial solution you imagine is close to the initial PoC.
IMHO Carmack's point is closer to the standard advice given to writers (which is also true and good):
Just get something on paper that you know is somewhat workable, and then reshape it from there.
Especially in team situations (as opposed to solo coding), the effect is magical. Endless meetings and weeks of technical flailing can be skipped by just having something instead of nothing.
Although, lately I've been finding I like this approach for solo coding too. Last night I opened up a Terminal, along with a browser tab for my new coding buddy, ChatGPT. The code I got from ChatGPT was absolutely horrendous for my needs, but at least it was something — and, an hour later, I'd scrapped and rewritten everything except for a couple function names.
There's something just plain nice about keeping things moving along — about getting some more clay on the table, some more paint on the canvas, and not being shy about reworking it after that.
I think TDD tries to capture this (especially in its pair-programming ping-pong-style implementations) — but, in its haste to come up with a one-size-fits-all system, TDD glosses over the soul of what we actually do. You're getting at exactly why — it over-constrains the freedom to reshape things, and it slaps those constraints in too early.
Then later in the cycle you have high-level structure so it's easier to start with the test.
We teach TDD and a lot of Software Engineering practises largely to beginners to make them productive for the maintenance of software - as that is about 95% of or work. So the flows that are stressed are those suitable to maturing/mature code not to completely new systems.
A complex system that works is invariably found to have evolved from a simple system that works.
That’s not TDD. It’s a common misconception that you’ve fallen for, and are now spreading further.
TDD is a series of small steps where you write a bit of test code—about five lines—and then a bit of production code—about five lines—then refactor, then repeat.
Here’s a free chapter in my book that describes how it works:
https://www.jamesshore.com/v2/books/aoad2/test-driven_develo...
Here’s a video that demonstrates it:
https://www.jamesshore.com/v2/projects/lunch-and-learn/incre...
Most of the time, any major features that require refactoring are usually around the data model and its representation in code (the existing control flow and overall flow of a request through the system is generally fine).
I will build out what I believe the new data model should be, and then just work front-to-back, updating any references and refactoring the shared state and responsibility into the new data model, clearly separating out concerns and encapsulating responsibility.
This method has proved itself time and again, and I recommend it to anyone who needs to make large changes to and existing code base. That is, start with how the kernels of data, state, and responsibility should look, and everything grows from there.
So I when someone suggests “we can build X” I say yes but first we need to build “hello world”. The discussion about what constitutes Hello World is often valuable, but often ends up with examples like the article, eg "can we write a message and the recipient gets it?" "can we do one trade?" etc. These sometimes seems like trivial goals but implementing them can be surprising.
I think this is a function of how much you're designing up front, not of TDD itself. The stuff you design, you write tests for and then build. How much you choose to design up front (maybe almost nothing) is up to you.
Mathematicians and physicists make a career out of this, but it works with almost anything, including sports.
Read the fibonaci example at the end of Becks book. He does in fact start with the smallest possible PoC.
I think you misunderstand TDD.
If you write tests first, you assume that the test is going to match how you're going to use the stuff in a real situation, which you generally don't know.
After reading the linked article, I'm actually more convinced that removal was the correct course of action.
They make it somewhat easy to do using Yet Another Reverse Proxy (YARP). I’m in the middle of it for a .NET 4 to 6 migration. The challenge for me is that it introduces complexity. Developers have to think “do I fix this bug in v1 code or v2?” We have to host two backends instead of one. They both touch the same db, so that adds complexity- what if v2 does something in data that breaks v1? All solvable problems but just thought I’d share a from the trenches take. I do still think it is the right approach for this scenario.
Edit: the advantage of this is that any systemic problems will become apparent quicker; your earlier tasks become a proof of concept for the viability of the project as a whole.
I'll join others here by saying I haven't heard of it as well and it would seem "tracer bullet" did just fine in The Pragmatic Programmer published much earlier.
The author doesn't state who came up with the term "steel thread" and I'm suspecting it was the author.
That's a weird type of suspicion. A simple search yields some prior usage of this term:
Step 1: Rehash a bunch of existing ideas together (PoC, vertical slice, strangler pattern).
Step 2: Give it a flashy new name.
Step 3: Market your consulting services as an expert for flashy new name. Sell to companies how they can build high performing organizations with this new technique.
Con: Early design decisions are the hardest to change. For example, retro-fitting security (or parallelism) onto a system that was not designed with it in mind is a fool's errand.
> A minimum viable product (MVP) is a version of a product with just enough features to be usable by early customers who can then provide feedback for future product development.
Also, referred to as a "steel thread". Runs the length of the application architecture (front-to-back, top-to-bottom, whatever) of what you are building. Each new top-to-bottom feature is a new thread. Steel threads wrapped together incrementally form a cable stronger than an equivalent diameter solid cable extruded all at once. Thread akin to string, as in string testing. - NormanECarpenter
https://wiki.c2.com/?SpikeSolution (too bad the c2.wiki has modernized the UI, its now almost unusable)So identify as narrow a case as you can, implement that first, and once that's good, build out from there.
That is, break down your problem into manageable chunks. Nothing new...
the part that might not be obvious (there are many ways to break down a problem, after all) is the idea to fully deliver a narrow case.
Seems pretty reasonable to me.
MBA: "we have a new technique, you'll love it, it's called a 'steel thread'"
"So like, on a bolt?"
"..."
"Or do you mean more like,,, a wire?"
"...Steel. Thread."
"Excellent sir. I shall be adding the term 'steel thread' to my next quarterly report. Give my best to the missus."
It's arguable, but I'd say the definition of a good software design is that it makes the above straightforward (e.g. testing, DRY, ... are means to that end)
Not sure I agree with the steel thread metaphor
Pros: Gives a real work indicator of performance with very low risk. Data could be truncated and then backfilled before the final release.
Cons: not always possible depending on complexity or feature. Requires implementing the parallel path which carries some risk in itself.
Why would I do that instead of building good configurable monoliths?
You may start with 20 options but people will demand more and more options if you don't stop them very early. Those options will have side effects that other options are supposed to fix and those will also have side effects and in the end you have 1000 options, all doing various things nobody knows. The worst part was: since every option was global, people used options for things they weren't supposed to be used for across multiple modules.
I worked at a company with over 15,000 options in their on-prem monolith. Nobody can know about all of them and each consultant demanded more and more customer-specific options.
It was a nightmare and we tried to get the mess sorted with a plugin system where each plugin had their own options that couldn't pollute the global configuration. But it was very hard, very painful, and in the end we could only eliminate a few thousand global options.
Not all the options are equal.
It's possible to soundly define, verify and instantiate big modular contexts.
Also optimize in-process before going out-of-process
> A steel thread is a very thin slice of functionality that threads through a software system. They are called a “thread” because they weave through the various parts of the software system and implement an important use case.
This sounds awfully like spaghetti code.
I think it has benefits but you also hit on the biggest risk, if you aren’t careful you’ll end up writing spaghetti code, except now your spaghetti is made of steel which is way harder to untangle.
Not sure steel threads is a very good name
and probably a bunch more.