What is Signature-Driven Development?

This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.

Subscribe: RSSApple PodcastsGoogle PlayOvercast

Signature-Driven Development means starting with function signatures before you implement them. I also discuss why we implement the hardest function first.

Transcript

[00:00:00] What is Signature Driven Development?

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

[00:00:13] So my friend Bert, he was actually the editor of Grokking Simplicity, he helped me work through a lot of the ideas. And at some point I was showing him some of the functions that I was gonna put in the book, and he commented that he could see how to implement them once I wrote out the signatures. It seemed kind of obvious how to implement them, but that he would never think to write those signatures. That his training as an object-oriented programmer, he would just never think about [00:01:00] writing those signatures.

[00:01:03] And so he came up with this idea of signature driven development, and I think that there's a lot to it. There's a point in your skill level as a programmer where once you have the signature, you can just fill in the body of the function and implement it. But then a lot of the skill, the design work, the hard choices are in what signatures do you write.

[00:01:40] And this becomes more important as you move from data models to operation models. And then certainly as you move into the third level, which is algebraic modeling. The signature carries so [00:02:00] much information

[00:02:03] Let me just be specific, what I mean by the signature. If you're writing a JavaScript function, it's basically the first line of the function, right? You have the name of the function, you have a list of the arguments in parentheses, and those arguments have types that you can infer if you squint, usually based on their names, but often not.

[00:02:33] If you had a typed language, the types would indicate a lot of the information about the arguments. A language like Haskell where you have the type signature and the name of the function as a type declaration, it's gonna have the entire signature in it. It's got the types of all the arguments, the type of the return value, and the name of the function. [00:03:00] Because the types do not tell you the whole story, but they tell you a lot of the story. And the name can do a lot of work at the level of disambiguating different functions that happen to have the same argument types and return type.

[00:03:16] As an example, if I might have a function that adds toppings to a pizza, it's called addTo(). And it takes a pizza and a topping and it returns a new pizza. Now, I might have another function called removeTopping() that takes a pizza and a topping and returns pizza. The types are the same, but they're totally opposite semantics. So the types can't tell you everything and you wouldn't want them to. There's gotta be some room for human level interpretation in there.

[00:03:51] I wanna talk about this because, when you get into operation modeling, there's this level two, you're [00:04:00] operating basically at the level of functions, at function signatures, not even implementation. You want to get those function signatures right before you think about your data model.

[00:04:17] When you're at level two, you're thinking, How am I going to use this? How is the user going to use this? The different users of your domain. You just list out all the use cases and all the operations that need to be part of those use cases. A user's going to have a pizza and they're gonna add a topping, add another topping, choose the size.

[00:04:43] They're gonna change their mind, they're gonna change it to a different size. So you have all these different operations that you need to support, and you write them all down without implementing them. You just write their signatures. [00:05:00] You write addTopping(), and you say it's gonna take a pizza, it's gonna take a topping, and it's gonna return a pizza.

[00:05:09] It's important to name those types, because if you don't name them, it's easy to be ambiguous about what they are, what they're gonna mean. You're operating at this level of function signatures. And you're making sure things make sense in terms of those signatures, before you start worrying about, oh, is it an array or a hash map? Or, you know, do I have an alternative here? And that kind of thing. That stuff is going to be informed by the operations.

[00:05:49] You write out all these operations, and then here's the trick. We often start at the wrong side. The trick is: [00:06:00] You want to do the most complicated operation first. You wanna write that one because that one is the most constrained, and so it will determine the most about your data model by itself, meaning it feels harder, but it actually makes the problem easier because it's more constrained.

[00:06:21] You'll have fewer choices. But the constraints will guide you to make the right choices, to make good choices within the few that remain.

[00:06:32] An example of this: We'll write our getters and our setters that have zero logic in them. We'll write those first. Let me get those out of the way because I know how to do those real easy. I don't have to think. But then we save that hard one till last. And then it turns out that the way we wrote the getters and the setters and the fields that we put in there, now that has to change because this [00:07:00] hard one is telling us, No, that's a very awkward way to do it.

[00:07:04] It's better to start with the hard one first. You can write getters and setters in your sleep. So why are you writing those first? Use your brain power on the hard ones first.

[00:07:18] Here's an example. I've talked about this before, but it's worth repeating. I remember when Apple Contacts on the Mac and on the iPhone started to sync. They would overwrite each other. If you changed it on your phone, then your phone would sync, and it would say, oh, well I'll use the one from your computer and, and overwrite your phone. And I just changed it. I just wrote this person's phone number and I lost it. It messed up. Of course, yeah, it's a bug. You know, that's what you could call it.

[00:07:54] But I always saw something deeper, which was that they made a nice app [00:08:00] on the phone and they made a nice app on the computer, and they're very simple. They're just basically databases of key value pairs. So you have a contact, and then you can say, well, the key is name and the value is their name, and the key is is phone number and the value is their phone number and the key is address and the value is their address.

[00:08:23] And they basically wrote all the getters and setters as very easy database to write, but then they wanted to implement this operation of sync. Merge two databases that may have changed since you last saw the other one. Well, that's a really complicated operation to write and it's hard to get right.

[00:08:49] They didn't get it right probably because they weren't keeping enough information. They weren't keeping like the date that it changed and what device it changed on, [00:09:00] and which one was the last one you saw, and things like that.

[00:09:03] They didn't think of that until it was too late. People had already been writing their contacts in their phones. And if they had started with the sync first, they would've known all of the stuff that they had to keep track of besides just key value pairs.

[00:09:22] Now, of course, maybe the story is a little wrong because they probably weren't thinking about sync yet because they wrote contact in the early two thousands, and that wasn't on their mind, and they just wanted to get something out there, and they had no idea that they would ever sync up. Yeah. And so they wrote a simple thing. Yeah, that's fine, that's fine. That ruins my story.

[00:09:46] But the real story is if you have to write something today that you know has to sync, that is really hard, so do that one first. Make sure that works right. The getters [00:10:00] and the setters are easy to write no matter what data structure, data model you use, no matter what database you use. The getters and the setters are not the part where you should start because you could implement them easily. If the underlying data model has to change because when you get to sync, it doesn't work anymore, you wasted all that time.

[00:10:23] So do the hard ones first. That's the big secret of operation modeling. Write out all the operations, figure out which one is most constraining on your data model and then implement that one first. Then everything else kind of falls into place. You have to be good at data modeling, though. You have to already know how to do it, but don't start with the data modeling once you understand operation modeling.

[00:10:56] All right, well, that's all I had to say about [00:11:00] Signature Driven Development. My name is Eric Normand. This has been another episode of my podcast. Thank you for listening and as always, rock on!