All about the executable specification lens

In this episode, I introduce the executable specification lens, its questions, and its goal of getting to runnable, testable code as quickly as possible.


[00:00:00] All about the executable specifications lens.

[00:00:03] Hello, my name is Eric Normand, and this is my podcast. Welcome.

[00:00:09] So I'm writing a book. It's about executable specifications as a way to do domain modeling, and I've organized the book into lenses. Each lens expands a perspective that lets you gain more information about the context of your software so that you can make better design decisions.

[00:00:33] And this lens is all about executable specifications. So I like executable specifications. I think that it's an approach to software development that is not used enough. It's fun and iterative and interactive. Gives you lots of great feedback as you're developing it. So let me tell you what it is.

[00:01:06] Executable specifications is writing your domain model in the implementation language so that you can run it right away.

[00:01:19] At the very least, you can see if it compiles. If you're using a language with compilation step, you can run unit tests on it. You can also write property based tests. But you can also write little scenarios and maybe even do some simple visualizations of them so that you can check to make sure it does what you think it should.

[00:01:49] One of the problems that we face when we do domain modeling is that we can overthink things, over-engineer them, and just come up with a million ideas and we have nothing concrete to show somebody. And because we're just thinking about it, there's always ideas. There's always problems that come up that you don't think of while you're imagining how it would work.

[00:02:24] And you don't get to face those problems until you start coding. So if you spend a lot of time thinking and designing, without bringing up these problems, you lose out on all that rich information.

[00:02:41] Now, an opposite problem you could have is you start coding right away with no design, and it kind of works for a while. And then you find a problem where it doesn't work anymore, or maybe it kind of still works. You can figure out a workaround, but you're going down this path. You're investing more and more time and effort on code. It's working. It's working. It's working until you hit a place where you're stuck.

[00:03:18] Now, often people say you have to do this. Really the only way to design is to do it on the second iteration, or even the third is where you get it to be in a good place. And that could be true, like this is a necessary investment, but I think it is true that you have to start over, right? That one. It is not in question. If you wanna get the designs, the stuff you learned along the way into your code, you have to start over.

[00:03:52] What I think executable specifications and the rest of the book is trying to do is find a middle path between the two. On the one hand, you want something concrete so that you can show people and it's real code, so it's checked. On the other hand, you want to be working at a high level. You don't want to commit to any specific implementations yet that will crystallize and become the nucleus of your code. And how do you do that?

[00:04:28] Well, we've talked a little bit about it, about how after a little bit of analysis, you can start writing out function signatures for your use cases. So those function signatures are real code. They can be checked. You can write a function with an empty body. Just write the signature part. And the signature contains the name, which usually isn't checked by the compiler, but it also has the argument types and the return type, and that lets you start to build your code. Have the type checker involved, the compiler involved. You're dealing with real stuff, but you haven't committed too much. So if you find out, Hey, these type signatures aren't exactly what I need, you haven't invested a whole bunch in it, you can just rework them, recompile.

[00:05:31] Now, that's not an executable specification. In executable specifications, what we're asking is how do I, as quickly as possible, get this thing to where I can run it so that I can observe it, I can check that it's working the way I think it should be, and gain a lot of information as early as possible so that you make changes as early as possible, before you've committed? You wanna make these changes sooner rather than later because the longer they're in the code, the more stuff is built on top of those assumptions and things.

[00:06:14] So one way to do this and, and it's one of the ways I'm gonna talk about in the book is, you want to do an in-memory version first. In-memory is always gonna be easier to work with than something like in the database or on disc. Just work in-memory, figure out the data structures, the operations, how they work, and just simulate it in memory. That'll let you work so much faster and learn a lot before you've done like the hard work of like making database tables or figuring out your file schema, stuff like that.

[00:06:56] That stuff is super important, but once you start locking in database tables it's really hard to change those things. Work in memory where you can iterate really quickly.

[00:07:09] Often you can cheat your way to a prototype . So an example is, if you're making a vector graphic system, I've actually given a talk about, this is why I use it as an example. You don't have to figure out your representation right away. One trick you can do is to use functions as your representation. It's kind of complicated to explain on a podcast, but just look at the parts of a function. And we'll use a, a closure. So it has an environment that's closed around it. So there's the arguments, there's the return value, and there's the closed over variables. Everything that you can refer to within the body.

[00:08:03] In the most functional languages, you can't change the values of those closed over variables, right? Like in Clojure if they're locals, they are immutable. You in some languages you probably could, but we're gonna act like they're immutable, okay? So what they are is a set of things that don't change from one call of the function to the next.

[00:08:34] Then there's the arguments. Arguments can change from one call to the next. You can pass in different arguments each time. And then there's the return value, which is the answer, the result.

[00:08:46] Instead of using a data structure, you use a closure and you divide up, well, these are the things that are gonna stay constant and these are the things that are going to change each time. There you go. You got it.

[00:09:01] So what I do in this talk where we do a vector graphics system, is we represent a picture as a function, and the things that can change are its rotation and its translation.

[00:09:17] So you can't change the color of a circle. A circle is one of the pictures that you can make. You can't change the color or the size, but you can move it around and you can rotate it. Okay. Now this is all based on the model. You can do whatever you want. You can make the scale change also. But I didn't do that in this talk. I just wanted to keep it simple.

[00:09:40] But now you have these functions and functions can do anything your programming language can, right? You have infinite freedom within that. So if you represent them as functions, you don't have to write some kind of dispatch to figure out which one to do. And it's an open system. You can always add a new function to it. So you can have a function that draws a circle, a function that draws a square function, that draws a triangle. So it's an open set.

[00:10:10] Because it's a function in Clojure or JavaScript you can do side effects. So you can draw to a canvas or something so you're not restricted to some return value like an SVG or something like that. You can just draw directly. So at the end of it, once you've built this function that's the picture you want , you just execute the function.

[00:10:38] But what that lets you do is because you're using a function to represent the thing, it lets you skip over a whole bunch of steps, like what does my data structure look like and how am I going to get the behavior from this data structure and translate it into concrete side effects that I'm calling. You can just do it all in one step.

[00:11:03] But it still has a lot of the great properties. You're looking at what can change, what can't change, what are the types that I need to keep track of.

[00:11:15] In the talk, I go over how, even though it's not the final implementation, you can draw the thing for real. Might be slower than the real thing. It might not store it to the format you want. Like maybe you want it in SVG or maybe you want it in a P N G. Like it doesn't do all that stuff, but it can draw it. And you can see what it does. And in the talk I show an example of how you can animate it.

[00:11:49] It reveals that, wait, this isn't what I expected. I actually wanted something a little different. And that's what you want. You want to be able to learn that you are wrong sooner so you can go back to the drawing board, debug it, figure out how, cuz debugging is the key word here.

[00:12:10] I think it was Nicholas Negroponte paraphrasing Seymour Pappert. Nicholas Negroponte said that Seymour Papper said that the closest thing we have to learning is programming, and the closest thing to learning how to learn is debugging. It's a cool quote, but the idea is you're trying to take this idea in your head and code it into software, and then when you find that the software doesn't do what you expect, and you start to say there's a bug, so you debug it. You actually figure out how you were wrong and you can correct your own model of how things work. We've all experienced this when we're programming, but you're doing it about something that's explicitly a domain.

[00:13:08] And so when I do this, it's fun. It's a lot of fun because there's so much feedback. You get into flow much better. I do it in Clojure at the REPL, so it's super fast feedback, very rich. I can tell if it's working pretty quickly. And then back out a better data model once I understand exactly what data I need.

[00:13:38] And then also if you can set up live coding, once you've got the executable specification, you're not going through a translation step. You're coding directly in something executable. That's why it's called executable specifications. So instead of most specifications, let's say, are written in natural language, they're written in English or whatever language that you happen to use, and it's hard to tell if they're correct or complete. And then, to run them, you have to interpret them into code. So there's a long process before you can tell if it's gonna work.

[00:14:26] What executable specification says is we can write our specification in a natural way, in a formal way. In this case, formal means it's a programming language with a well-defined semantics. And then you can run it and see and test it. Does this do what we need? Does it have all the properties that we want?

[00:14:55] That's the idea. You don't have to translate it into some other language. You just write it directly in your implementation language.

[00:15:06] All right. My name is Eric Normand. This has been another episode of my podcast. Thank you for listening, and as always, rock on!