While SOLID is the known standard when it comes to software design and architecture, it may be hard to apply for some as it limits developers to specific techniques in going about it.
In this episode, Daniel Terhorst-North, originator of Behaviour-Driven Development (BDD) and Deliberate Discovery, presents his arguments on why SOLID isn't exactly the best bet when if comes to design principles, offering an alternative that can help against the misapplications of various agile practices.
- Daniel Terhorst-North answers why he challenges the principles of SOLID and presents his arguments.
- How does CUPID improve on the principles of SOLID?
- Despite Test-driven Development being considered an "evolutionary approach to development," what are the problems associated with it?
- What is Behaviour-driven Development (BDD)?
- How has the development community responded to SOLID and BDD?
Welcome to Episode 67 of the Coding Over Cocktails podcast! My name is Kevin Montalbo. Joining us from Sydney, Australia is Toro Cloud CEO and Founder, David Brown. Good day, David!
Good day, Kevin!
Hello! All right, let me introduce our guest for today, who helps business and technology leaders find simple, pragmatic solutions to business and technical problems, often using lean and agile techniques. He has thirty years of experience in IT, and is a frequent speaker at technology and business conferences worldwide. The originator of Behaviour-Driven Development (BDD) and Deliberate Discovery, he has published feature articles in numerous software and business publications, and contributed to The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends/ and 97 Things Every Programmer Should Know: Collective Wisdom from the Experts.
Joining us for a round of cocktails is Daniel Terhorst-North. Hi Dan, welcome to the show!
Hello, thank you very much for having me! By the joy of time zones, it is morning here. So, unfortunately, I'll be doing the coding. When I heard “Coding Over Cocktails,” I was thinking, is that like one of the, you know, the fifth agile value?
Well, if you value cocktails.
Well, [we’re an] Australian company, so it’s a manifest. We always end the week with beer o'clock. It's a tradition. So yeah, we don't want to promote alcoholism. I can't wrap my head around it. But yeah, there is a bit of an ongoing theme there and of course all our products are named after cocktails as well. So, we have Italian cocktails in all our product names.
But look, Dan, I want to start off with SOLID. Now you have a problem with SOLID. So, I think we should, first off, cover what is SOLID. So, the SOLID principles are introduced by Michael Feathers. You've been up on the work of Robert Martin in his paper, Design Principles And Design Patterns, published back in 2000. Now that those SOLID principles have served us well for over 20 years in terms of object oriented programming, you are now challenging these principles. Before we get into your alternative, maybe you can run us through what is SOLID and what's your problem with it?
So, the phrase “served us well” is an interesting one. They've been around for a long time, certainly. As you said, I've been writing software for 30 years. Like, as a full-time, hands-on programmer for the first 20 of those, for the last 10 or so I've been a consultant. So, I'm a cause of programming for other people. I still code when I can. I love programming. These days, I tend to do a lot more reviewing code, looking at programming strategy, architecture strategy, that kind of thing. So, helping teams write better software.
So, SOLID. SOLID's interesting. I think of SOLID as a useful historical artefact. Okay. When Robert Martin was describing all these principles, he was collecting things, you know? He wasn't just sitting there and making pronouncements. These are things that he'd seen in the wild and he thought they were useful things to bring together. So, there was a good intent behind it. But some things stand the test of time and some things don't and I attest from development. We're gonna talk about that later. I came across that over 20 years ago, it was decades old. By the time I came across it, it's still completely relevant today. It's one of the most useful software design techniques I've ever encountered. SOLID, not so much. So, if you look at each of the five principles you can see – and this is what I did, I wrote an article before any of the CUPID work or wrote an article [that was] kind of deconstructing, if you like. SOLID has only five principles. Now there are over a hundred of these things in his books.
And as you say, I just happened to notice the first slide, if you change the order a bit, you could get the acronym SOLID. You can also get “LOIDS” and “DOILS” as well. “IDOLS,” oh, there you go. “IDOLS.” So, you can look at them as “IDOLS” and perhaps that makes them an iconic class. I don't know. So, the problem I have with them is, let's look at them one by one. The Single Responsibility Principle says code should have one reason to change. That's the technical definition. So in other words, you shouldn't conflate different aspects of code. You shouldn't have things like business logic with view logic or data access logic with business logic.
And, you know, that kind of makes sense. That seems like a pretty good rule for life. And it kind of makes sense in an age when making changes to any code was a high transaction cost activity. I was working on big C++ systems, multi-million lines of C++ systems in the 90s. I wrote a Europe-wide telecom billing platform, which was fun, lots of multicurrency and fun edge cases. Like, you had to have one bill per customer, but one of the customers was First Telecom, which itself was a reseller. So, it might have tens of millions of call records in a month. And the system – the original system – wasn't designed for that. So, I was working on big systems and especially C++, it’s become a much bigger, more complicated language at that time. It was a pretty reasonable language for doing those kinds of systems. This is before Java.
And so, you’d make changes in your code. And compiling was pretty quick. I could write from C++ code and then run and then the compiling stage was a few minutes. There's then a state called linking, and linking is where a linker resolves all of the various bits of your code and makes sure they all connect to each other. That could take literally hours. So in other words, one line change could have a multi-hour build. Not building a running test. We won't do automated testing much. Then just getting a binary that you can run was a multi-hour endeavour. And when the transaction cost is high, what you tend to do is do more work.
So, you make bigger changes because if the cost of a change is high, you shove in a load of stuff. You don't want to keep doing that every few lines of code. So, that means that you then have an exponentially higher likelihood of introducing bugs. So, this is the context you're working in. And so what we’re saying is, look, if you are doing changes to business logic, for crying out loud, don't make changes to the view at the same time. Don't put them in the same code because that's just gonna be a nightmare.
If you play that forward to now, we have like the equivalent language to something like C ++, which is your business systems language [like] Java or C# or maybe Go, sometimes. These days Rust is coming up, but for a language like Java or C#, I'm in my IDE, I'm typing, the program is compiling behind my fingertips. In real time, I've got a hot reloading of apps, right? So, my transaction costs for making a change is N so that's the first change.
The second change is, the code itself is a lot more malleable. And I think Java was the language that made this mainstream, small talk is where it started. But this idea of refactoring, if I'm writing C or C++, it's kind of like bricklaying, right? So, you write a file, something dot C and then you're done with that file. Then you get the next file, something dot C or something do C CPP or whatever it is. And, and you write the files, you park them. Okay? And so, you wouldn't generally go back and make a sure change to a file, or if you did, you were making a change within that file.
And so the prevalent version control system, for instance, CVS at the time, if you renamed a file, you lost all the history, because the history was file based. So, if you renamed a file in CVS, you just lost all of its change history. That wasn't a bug in CVS. That's like a consequence of how uncommon that action was. And now Java comes along and Java couples the name of a class to the name of a file and the name of a package to an entire directory structure. Right? So now suddenly, when you have refactoring tools in Java, changing file names and changing entire directory, hierarchies are common. It becomes a single keystroke. And so suddenly, tools like CVS are breaking and that's where subversion came from. It was the next generation of CVS that was refactoring-aware.
That was the big thing it did. The change of a bunch of files was atomic instead of pefile. And we play that forward and we get tools like it. So, the mindset, that change, that the code would only change in one place, just became nonsense. Code changes all over the place. And also, the tools from navigating code became much, much richer, right? So instead of using VI or EAX or whatever you're using for editing your C++ code, which has pretty good code navigation… There's a brilliant thing, it called C tags that they would both use, that would allow you to navigate code. But I can just define usages across a file, across a code, basically, with hundreds of thousands or millions of files in it. And it goes here. They are right here. And I can guarantee there's no false positives because I can build a deterministic AST from my code, right?
So, there's the whole context. So, now let's take a look at single responsibility. When I first start writing a new piece of code in some language in Python, in Rust, in Go, in whatever it is, why do I want an arbitrary separation of all the bits that are changing together? Because they're in my head right now, the UI doesn't change on its own apart from, for trivial UI changes. If you're going to make that button blue, that's a UI change, right? Usually a UI changes because I've added some more information or I've changed the information. And I changed the information in the business layer. So, that means that changed in correspondence with that. And that probably changed because there was some data change or some event change.
So, actually all these things do change together. And so, now what I've got is a bookkeeping exercise, where I've got to get the set, to get the set, to get the set, to get the set. If you ever did any enterprise Java back in the day, most of your life was copying and pasting code between getters and setters, right? This is madness, except when you get Rails, right? And Rails has the same thing. You have models and views and controllers in separate directories because that's what single responsibility says they have to. They don't say they have to live in separate places, but that's what software craftsmen do, it's that they take these things to the end degree. And now we're stuck with this model where in order to make a change to code, I have to go ferreting around in all different files. I've never done that. I managed to get by 30 years without using the single responsibility principle.
So, again, I describe it as neither necessary nor sufficient. If you do it, I mean, it might, maybe. The other thing, and I'll shut up, because I know we've got a lot more bearing on the S. The other thing to bear in mind is you can think of it as a premature optimisation of code structure. So, if I am duty bound on or bound to separate my code into areas that only have one reason to change, that means that I'm not allowing myself other things. Right? So, for instance, what I might have is a bunch of code that's to do with billing and a bunch of code that’s to do with customer management and a bunch of code that's to do with, you know, with buying or something, whatever my product is, a bunch of things to do with search. And those areas of functionality, that might be a much more sensible way to arrange my code, but I don't get to do that because I'm already in models and views and controllers as my top level view of the world.
Well, I might ask you, I mean, you came up with a newer acronym called CUPID as your alternative to SOLID. So, as you’re going through these SOLID principles, and you're talking about the single responsibility principle of SOLID at the moment, maybe you can run us through the CUPID alternative and how it resolves these issues you have with SOLID.
You look at the code and you're like, 'I get this. And I'm about to have fun.'
So, that's a great question. I'm going to counter with something a little different. So, originally, here's the backstory. I just poached the bear of SOLID, in a fun talk about five years ago. It was literally a five-minute talk in a pub with alcohol. And it was like an Ignite-style of fun, silly talks, and it was a post-conference event. And I thought I'd poke at SOLID. And I did. It was a five-minute talk. So, for each of the five principles, I go 15 seconds describing it, 15 seconds describing why I don't like it, and 15 seconds saying what I would do instead and thinking, I would have exactly the answer to your question. And as I wrote the talk, I realised that the thing I would do instead was the same thing every time, which is write simple code.
Now clearly, “simple code” is itself a bit vague. So, I needed to clarify what I meant by “simple.” And my working definition of simple, is it's not about my head. It's code that fits in someone else's head. So, Kevin can come along, see this code and go, “Yeah, I get what's going on here.” And my qualifications for that person's head is that they understand the language, the idiomatic code in that style of language. So, I'm not going to take someone who's never seen Go code before and say, “What does this do?” So, the idea is that someone who's familiar with the language, its ecosystem, its core libraries, its run time, its build chain. And also some familiarity with the domain that we're working in.
So, if you have all those things and you can look at some code and it's obvious to you, then that's simple. If it's not obvious to you, it means that there's some tacit knowledge that's in Kevin's head that's about how this module connects to that other module, because you have to know about this other secret module over here. That's now not obvious. So, how do we make that tacit knowledge explicit? For each of these five, basically, my go-to was to write simple code. So some years later, this is where CUPID came from. Someone challenged me and said, “Well, you’re clearly, you're not a fan of SOLID. What would you replace it with?” And that led me to, “Are there a set of principles? Is there a universally applicable set of principles to software? Or is it such a vast field that it doesn't make any sense?”
And I got to thinking about it and I ended up with properties. I said I wouldn't describe any principles as universal. They're all contextual, but there are properties of code that I think make it joyful to work with, that make coding fun. You know, I've been into code bases. I've been super lucky in my 30 years. I've been into code bases more than once. Not often, but more than once, and just smiled. You look at the code and you're like, “I get this. And I'm about to have fun.” Right? I'm about to make a change here. And I know I'm gonna do it confidently. I know I'm gonna not mess things up because someone else who came before me put this together in such a way that it just couldn't be more self-explanatory. And also, it makes you super careful. Like, when you're in there, I don't want to leave it like this.
Yeah, they've set benchmarks. So, what are those five properties of CUPID?
Well, my counter is this. It’s that I don't have a one-to-one correspondence with SOLID because I think individually, each of them is silly. Each of them is, again, neither necessary nor sufficient. They were useful at the time, right? In the mid-nineties, which is a generation ago, they were good. They were good, sound advice, but they haven't aged well. So, what would I describe instead? I’d describe properties of code. So very briefly, CUPID is an acronym. So, I started with a word and then assigned properties, which is great fun and it was deliberate as well. You know, Cupid is like the god of love and is about, you know, joy and romance and stuff. And it's like the opposite of the kind of “bro” culture around software craftsmanship.
And so, I wanted something deliberately controversial. And it also means that you get to have candidates. Like, there's loads of things that begin with “C.“ I'm gonna write, actually. I've got it in my head a series of articles of the runners up. And there were loads of “I,” and I ended up with “idiomatic,’ but there were several for “I.“ And what I discovered delightfully ,as an accidental consequence, is that [for] all of the runners up, you can derive from the five that made it. So, the five that made it are kind of foundational and then anything else you can say of these things, you're going to necessarily get that.
You like Wordle, don’t you? You’re a huge Wordle fan?
My working definition of "simple," is it's not about my head. It's code that fits in someone else's head.
I'm not, no. I've never played Wordle. I’ve seen it but I've never been inclined to play Wordle. I have plenty of Wordle on my Twitter Timeline but I don't actually play. So, “C” is “composable.” So, this is the idea, the principle of comparing, preferring composition over inheritance. But, the idea with all of these properties, is not just at a line of code level. So, the idea is that I want composable code. I want composable methods. I want composable classes or objects, but then I want composable modules and I want composable subsystems. So, when you're designing an API, there’s composability there. When you are thinking about how you're going to build your code, there’s composability. So, for instance, one of the aspects of composability is dependencies, right? So if you've given me a lovely, lovely API, and it drags in half the internet through Maven, I'm much less likely to use it. So, composability requires you to think small, requires you to be very, very discerning about what dependencies you absolutely need and what you can't roll on your own. So that’s “C.”
“U” is “Unix philosophy,” which is “do one thing, and do it well,” which kind of sounds a bit like single responsibility doesn’t it? Except, very deliberately, the CUPID properties are in the eye of the beholder, right? Beauty and joy is in the eye of the beholder. So this, it does one thing for you as a consumer, whereas the SOLID principles are from the perspective of the code. So, it has one reason to change. So it may do one thing and have one reason to change. But from a consumer perspective, that lump of business logic is no use without the view on it and without the data on it. So, that is [to] do one thing. So, with the component piece, with the “be composable” and “do one thing,” you've basically got Unix. So all of the Unix shell commands, the core commands, they're all about composability and they're all about doing one thing well.
So “cat” gives me contents of files, “curl” gives me contents of web pages, et cetera. And then from that content, I can then filter that content using “grep.” I can then transform that content using “sed” or “or.” And then I can filter out what's left using “uniq” for instance, or “sort.” So, just having really simple primitives for text processing, I can build very sophisticated pipelines because everything is composable and each thing does one thing really comprehensively. So that's the “U.”
“P” is about predictability. So, “predictable” is, the way I'm describing it, a generalisation of testable, right? So Mike Feathers, in his lovely book, “Working Effectively with Legacy Code,” describes legacy as anything without tests. So, I went broader than that, because I've seen legacy code bases without tests that were joyful, that were brilliant to work in. And I've also seen entirely test-driven code bases that were an absolute rat’s nest, and I wouldn't want to do work in them. So, again, having full test coverage, which we'll get to later is neither necessarily nor efficient. So, predictable is I know what it does. I can tell by looking at it what it does. So it's about intention revealing. It's about [how] it does what you think it will do. So, code that does what you think it will do. And also, that it is easy to determine that it does what you think it will do.
And that's not necessarily with automated tests. It may be through some kind of checker. It may be by running it. It may be by looking at it. But if I can make code that is easy to instrument, that has its own telemetry, that tells me when things are wrong, that fails in a well-known way. So, predictable code isn't just about that. It follows the happy path every time. It's that when it fails, it fails in well known ways. And, we often don't think about that. So, does it fail silently? Does it fail silently and corrupt data? Does it fail catastrophically? And I need to know this, and this allows me then to use your API, as the API is idiomatic, and this is a really big deal. Idiomatic means you write code like everyone else would write code. So, it's a lot about egoless programming, right? If I go into a code base and I can see that this is David's code, and I can see that this is Kevin's code, then props to you for having your own style. But it means that now, I've got more cognitive load. I've got to not only think about the pricing algorithm problem I'm trying to solve. I've also got to think about how David would've coded this and where he might have put this and his weird naming convention for stuff, and his punch on for different testing from everyone else. Right?
So, all those lovely, personal quirks. Idiomatic code says, “Write code like everyone else.” If the language itself has an idiomatic style… Clojure is a massive idiomatic language. They all write code that looks the same. They all write this lovely, listy style of code. And you can tell when someone's new to Clojure, because they're trying to write an imperative, kind of procedural code in a lovely, functional, listy language. And it takes a while to get that. Likewise, Go has Go format, which takes all of your syntax highlight, syntax discussions off the table. It also has a massive standard library where everything looks the same. Right? And I hate their naming conventions. Something that does reading and writing is called a “reader writer.” You're like, “Really? That's the best you could do?” And something that's reading and writing and printing, guess what that's called?
Then, what happens is this. As you look through the core library, if you've done anything with the HTTP library, you've written to HTTP code, and then you go to use the FTP library, man is this familiar. It's like I've already been here because whoever designed the FTP library made it look just the same as the HTTP library and all the errors fail in the same way. And then you look at the SMTP library, guess what? Right? Any network protocol, libraries have all been designed with a similar flavour. And so what they have, they bake that idiomaticity, they bake that into the language and into all of the example code. And along with that, you get this effective Go, which is a beautiful document that says, “Here's how we write Go around here.”
I'm not advocating for Go. I think it's a really ugly language. It's very good for a small subset of things. But boy, are they idiomatic. Because they know how many developer hours, days, millennia have been lost to pointless arguments about style. And how many really subtle bugs have come in, because I thought I was in some of Kevin's code and actually I'm in some of David's code and he does error handling differently. And my thing just suddenly failed.
So, it's layered. Right? So you have idiomatic [as] what the community does, then idiomatic [as] what we do around here, like in this organisation, and then idiomatic [as] how our team writes code. If you kind of layer those things on, it's pretty hard to write code that doesn't look like the other kids. But if you don't set those standards, if you don't set those lines in the sand, when you start, you just end up with 10 different styles of code and it makes change much harder and much less joyful.
“D,” the last one [is] “domain-based,” which is, and again, this goes right back to SOLID, the S conversation we were having earlier, single responsibility conversation. So, I'm a massive fan of domain driven design, domain driven design was one of the core ingredients to BDD back in the day, in 2003 or something. And I'm a huge fan of Eric Evans. I think he's a giant in our industry. So, domain driven design is the idea of writing software to solve business problems isn't the point, right? Understanding the domain and solving problems in that domain is the point. And writing software is like a useful byproduct of that.
So, you know, I'm trying to understand. His background is shipping, so logistics. And so what he does is his friends, ages and ages working with people, understanding what they mean by a manifest, what they mean by moving cargo around, what they mean by bonded warehouse and business rules that flow from that and how we do work. And software just kind of seems to appear while he is having these conversations. But the names in the code represent things like, if you've got a bonded warehouse, there'll be a bonded warehouse thing in the code. But you can't not know that this code is about bonded warehouses or this code is about derivative pricing, or this is about patient healthcare management, because it just says it. You've got a patient ID or something and you can't not know.
And so anyway, strong domain naming is just, again, another way of reducing cognitive load. But then when I talk about his domain structure as well, and this goes right back to things like Rails and all these kinds of frameworks that have, like views here, controllers here, models here, helpers here, managers here, whatever it is. And like, I don't care about all that. I mean, I know I'll need those things, but what I want at the top level is patient management, right? [Or] order booking. Like, I want my broad bounded context, my broad subdomains at the top of my code base. And within those, the subdomains in there. And yeah, there'll be models in views and controllers in there, but I'm an adult and I can find them. So, I don't want you, I want to see the framework. I want to see the solution.
You briefly mentioned BDD, which is behaviour driven development, when you were talking about mind driven design. You developed behaviour driven development as a response to the “confusion and misunderstandings,” in your own words from various agile processes, such as test driven development. So, I'd like you to dive into, if you may, behaviour driven development, what does that mean? And what, what is it developing response to?
I don't have beef with TDD. I love TDD. I still think it's the most powerful model I've ever come across.
So, it's interesting, I think, with a lot of these techniques. And TDD is a really good example, there are several others as well where you get, I think a genuinely game-changing technique that gets let down by a poor name. Kent Beck is an amazing programmer. He really is an amazing programmer. He isn't great at naming things, or rather, there are better names. And so, test driven development suggests that there's tests. And when he described it, he said, “Right, we're going to start with a test. We're going to start with a failing test.” And I started doing this. I picked this up around the late nineties again, so 20-something years ago. And I was fascinated by it. So, you write code for code that doesn't exist yet. So what we do, it's like a model client. You're like, you're writing a model client for some code that doesn't exist.
Let's take an example. So, you and I, we're going to go and get some code out of a database. Okay? So, ready, sleeves up. We get some code of a database. Let's write an API for that. So, let's see. The API could be “connect a database, run a query, close connection,” alright? That's the three things we need to do. Okay.
So, let's write a client for that. Our client would be “connect a database, run a query, close connection.” And you look at me and you go, “You know what's gonna happen here, Daniel, don’t you?” [I’m] like, “What's that?” “They're going to forget to close the connection.” “Yeah, you're right. You're right.” Okay. Try, “open database, run query.” Finally, “close connection.” Okay. I'm happy with that. Fine. What about if the “connect” fails? “Oh yeah.” I think about what kind of failure we're going to have. Okay? And so what we're doing is we're now designing the API.
While we're designing the API, and then you look at me and you go, “Who else in our world is ever going to try, finally?” And we look at each other and we go like, “No one. Right?” So, what if we put that behind the API? And what if we had just run a query as the entire API and in our implementation of “run query,” we're going to do that “try finally” thing. So, it's a bit more work for us, but much easier to use.
So we've just had a design discussion in writing a model client. We've taken a model client from three lines of code and a complicated construct to one line. Right? Go us, because hopefully many more people are going to use our code than they're going to write it in the first place. So, what we've just done is simplified the API.
That's test driven development. Did you hear me use the word test in there at all? No, because I'm writing a model client. I'm not writing a test right now. It turns out that if I use this model client to then exercise the thing I built and I can maybe put some assertions in there about how I think it should behave, then it becomes a test. And this is the linguistic shift. It’s that your model clients, your code examples become a test after they were useful. Right? I wrote the code to design the API. That was the value. I then get this handy byproduct that I can run in the future to see that it still works. Score. But the problem is all the spotlight went on the second bit and not on the first bit, including in Kent's head where he talks about it as a test.
It's not a test. It's a model client. Even if you're Kent Beck, you're still writing a model client. Right? And so as an experiment, this is 2003, I remember vividly, I remember the team I was on, that I was trying to pitch TDD to them. I was like, it's the best way to watch software. It's brilliant. You end up with really good design. I've been doing it for years. And they're like, “No, we're programmers. We don't write tests. We have testers to write tests.” And this is very much the culture of, you know, [that] testers are cheap programmers or, you know, they’re people who aren’t good enough to be programmers, that kind of mindset. And I was like, “It's not really a test,” “But you called it a test. It's called test-driven development. You said we're going to write a test.”
I was like, “Oh crap. How else do I do this?” Meanwhile, the testers caught wind of this. And they're like, “Are you getting the programmers to write tests?” I was like, “Kinda.” And they said, “That's our job.” And it was partly like a fear of losing their job, but it was also, we know these programs and they're rubbish at writing tests. Let me tell you. And so I thought, “Well, how can I describe this differently?” And so I said, “Alright. I'm not going to use the word ‘test.’ I'm going to talk about behaviour. I'm going to say, ‘We are going to describe the behaviour of the code before we write the code.’” It's like a spec. You'd write a functional spec, right? And in the functional spec, you'd say, “This is our expected input and output,” and say “yes,” so we're going to call it a specification. So we're going to write an executable specification. And that executable specification will document the expected behaviour of our code. Right? And then here, this became the sell. If you do this, David, you don't have to write a functional spec, right? Shut up and take my money.
So, either you do TDD with Dan, or you can write functional spec with Dan. And so now, people start doing it and then suddenly, all the questions went away, you know? “Yeah. Let's do it because what we're doing [is] we're writing behaviour ahead of time.” And then, oh, by the way kids, as a cool side effect, these things make pretty handy tests. And that was where BDD came from. It was an attempt to reframe TDD, because I don't have beef with TDD. I love TDD. I still think it's the most powerful model I've ever come across, the technique I've ever come across for design in the small. It doesn't give you design in the large. You still need to have a picture of your architecture that you're growing into. You need to make some decisions about, “Are we event-driven? Are we three tier? Are we client-server? Are we message-based? What sort of thing are we building?”
But in the small, when I'm building this piece of it, TDD is absolutely a brilliant way to come up with good design, especially with pair programming. They work so well together. So, you and I sat down and we had that conversation in real time about that database API and we smashed it. Right? We put all the handling where it should be. Now that it's a single method, by the way, we are owning the “try-finally,” we can also do things like connection pooling. That's cool. We just got that for free because we put that behind the API and all of that came out of, “Will anyone else ever do try-finally?” and you looked at me and went, “No, no they won't. Let's do that for them.”
So, it is a brilliant design technique. So there we are with BDD and then, well, I remember this vividly as well. In 2004, I was talking to a business analyst buddy at ThoughtWorks. Chris Matts, he said, “What's this BDD thing? Because I've been hearing this going around ThoughtWorks about BDD.” And I said, “Oh, basically you describe the behaviour you want in terms of an automated way. And then you implement the behaviour, and then you check [that] what you got is what you wanted.” And he looked at me and he said, “You know, you just described business analysis.” “What do you mean?” He said, “Well, [in] business analysis, you describe what you want. You write some software, and then you check that it does what it’s supposed to.” And we both just went, “Oh, maybe this works at multiple levels.”
Maybe we could use this BDD model to describe behaviour. And there was an index card kind of template going around ThoughtWorks at the time. It's [by] a lovely guy, Ivan Moore. And what you would write on an index card, you'd write your stories on an index card because you’re old school. And if it doesn't fit on like a five-by-three inch index card, then it's too big. You need to write less words. Write fewer words, and on the back of the index card, he wrote a line down on it and two headings, “I do this,” and “this happens,” right? “I do this, this happens.” And so you go, “I do this; log in with a password. This happens; I see the landing page. Bad password. This happens; error message. Bad password three times, this happens; locked out.” Right?
So, Chris looks at this and he goes, “Yeah, that's not going to work” “What do you mean?” He said, “Well, I do this, any number of things could happen. [It] depends on the context, doesn't it?” Like, let's say you log in with a correct password and the system's down. Do you get in? No, you got a 501, don't you? It’s got nothing to do with you. I was like, “Oh, whoa. You're right.” And so he added a third column at the beginning, which is “context.” And that's where “given/when” then came from. So, given some context when I do this, then this happens. And so now, we have this really lightweight template for capturing those examples. So, now at a code level, and to this day, 20 odd years later, if I write any BDD at all these days, I use things like PITest or JUnit. You know, real simple, what we would think of as unit testing frameworks. There are very few times I will reach for something like Cucumber. It has its place, the plain text runners have their place, but it's a much, much smaller space than you think. Anyway, that’s a whole different conversation.
So yeah, “given-when-then,” so I go “hash given, hash then, hash when,” or “slash-slash given, slash-slash when, slash-slash then,” and then I start writing my model client. And then I trade from there. And so Chris and I kind of buddied up and we had this thing called “Behaviour and Development,” which we started talking about at conferences. So, my beef with TDD has never been with TDD. I love TDD. I heart TDD. I'm not obsessive about it. It has a place.
And the other big risk with TDD – I wrote a big article about this like last year, I don't write that many articles – but I had a thing out in my head for literally years that finally came out. It’s that 20-something, 30-something years on, it’s still nothing to do with testing. And what I mean by that is this; let's say you and I pair on something and we do TDD and we end up with a bunch of code examples and we say, “Right, we've got tests now.” Now let's look at those tests. Those tests were the smallest number of model clients that triangulate allowance. So, we probably just started with one to get the basic API. And then we're going to try one that's going to test maybe some narrow conditions or something, and maybe one that's going to put in some bad input. And we’re confident that what we've built is probably robust. And we go, “Yep. I'm down the pub,” or “out for coffee.” If you're a tester looking at that, you're just going to break out in hives. That's not tested. No, it's not. It's a minimal viable assurance that you probably haven't messed it up too badly, but it's not testing.
So, I could take that test. I could say one of the tests that puts a value in, and I could write a generative test based on that, that puts in hundreds of millions of values and just keeps them running all the time. And every now and then in bits, because it finds a failure mode. And it turns out that if you have a very, very small negative number close to zero, we're due to some floating point quirks in the Java library that it breaks our pricing. Right? Neither of us would ever have thought of that. Luckily, we’re just slamming it with random numbers and one of them broke. When it breaks, we can figure out why, but we are unlikely to break it. Right? That's testing, assuming that it might break.
I'm going to have to catch you short Dan. I think this is our longest podcast and you warned us beforehand that you had plenty of material on this subject matter and you were right, because I haven't even asked you half the questions I had written down to ask you. But one thing I’ll ask before we wrap up is, what has been the community's response to CUPID and BDD? You know, did you get a lot of flack? Did you get accolades? You know, what has been the response?
That's a brilliant question. I think with BDD, I was amazed. I was amazed at the adoption of BDD. For me, I never meant it to be a thing. It was genuinely a coaching hack. I was convinced that this team would benefit from TDD and I was convinced that the pushback I was getting about testing was totally irrelevant, but I couldn't convince it was irrelevant. So, I just started over. I said, “We're not going to do that thing. We're not going to do TDD. What we're going to do instead is write an executable spec.” But then at ThoughtWorks, this [was] back in the early days of ThoughtWorks, we had these things called “away day,” you know, and the whole company would get together and do stuff and give talks.
And I gave a talk about it and this wonderful lady, Liz Keogh, got very excited about it and I said, "Right, how do I turn this into a thing?" And it kind of went sort of viral within ThoughtWorks. People started finding that this technique was a great way for them to introduce TDD as well. And then suddenly, the world picked it up and I had no plan that was going to happen.
And then sometime later in the Ruby community, especially some folks that aren't me. Some folks wrote, I think [it's] called RSpec, which was like a BDD-style, like a "given-when-then" structure for Ruby. And I was writing something in Ruby and I wanted something at a higher level than RSpec, a code level thing. And I wanted like a "given-when-then" thing. So, I wrote a "given-when-then" thing and I called it RBehave. And then the RSpec folks, which is David Chelimsky, like some other folks folded that into RSpec. And then as Slack went and basically rewrote it as Cucumber, and this thing just exploded. Right? So, Cucumber suddenly became I think one of the most downloaded software products of all time or something.
And, so no one knew this was going to happen. And then I think my head finally exploded when it had its own conference. Wow. I got its own conference. I was like, wow. I've given keynotes of my own things. There's a lovely phrase, I learned many years ago that I try and live by, which is if you want an idea to travel a long way, don't try and travel with it. Okay? And I've never tried to be the BDD person, right? The BDD community is rich, exciting, vibrant, creative, and way, way, way bigger than me. And that delights me.
But QID is much more deliberate. I'm going to do something here. I'm going to talk about this thing. I've spoken about it at conferences. I've written this article which appeared on Hacker News. In fact, the backstory appeared on Hacker News and then the article appeared on Hacker News and that's a nice bit of amplification, but then, and this came from totally left field. I discovered I'm in the latest version of the ThoughtWorks Tech Radar. QID is now a thing to explore or something. It's not adopt. I can't remember what it's called, but like the thing where they're saying, we've become aware of this thing and you might want to have a look at it. And I'm like, that's nice. Wow. That's probably the shortest time from anything.I've done to anything that's appeared the ThoughtWorks tech writer. And the feedback I've had from CUPID has been universally positive. There's a bunch of, of course, SOLID program craftsman type who are just rattling pitch forks. But they are the same people who rattle pitch forks when I did the talk with the original, you know, poking-the-bear-talk five years ago. Yeah. I'm kind of over them.
But other than that, there's been a lot of love, a lot of positivity and I'm super excited about CUPID. So yeah, I'm not sure where it goes, but well I unpacked the article. It's a 5,000 word article. I unpacked it onto its own website called CUPID.dev. It lays out all of the properties. And my my aspiration at this stage is to start collecting case studies and putting them up on the website as well. So people can see other people using CUPID and as a tool, as a review construct, as a set of maybe design principles, that kind of thing.
And your social channels, Dan, you are more active on Twitter, LinkedIn, on the DanNorth.net website, where should they follow you?
So, I'm on Twitter (@tastapod) far more than I should be. Okay? That's my main channel. Like, I'm not on Facebook or Instagram or anything. I'm on Twitter a lot. I occasionally go to LinkedIn, but I find LinkedIn tends to be where messages go to die for me. So I think I use LinkedIn as like a big phone book. So Twitter definitely. And DanNorth.net is where I publish things. But typically, what I'll do is I'll publish something onDanNorth.net and then I'll tweet about it. And that's how you find out.