Lesson 5 of 21

Simplicity is Complicated

Duration: 11:37 Watch on YouTube (external link, opens in new tab)

Markdown version

In this lesson

Roy Osherove judges how simple a solution is by how close it is to the beginning of the method and how little it is indented. He test-drives a parse method on the string calculator (external link, opens in new tab), starting with an empty string that returns zero, and treats each hard-coded answer as an assumption that the next failing test, such as a single number, proves wrong. He connects these small steps to incremental development in micro iterations, with new working code that could be released every day.

Transcript

1,682 words

[00:03] (external link, opens in new tab) And I want to talk about simplicity. Sometimes when we solve things in the simplest way (external link, opens in new tab), what we want to do is to solve it visually. We can measure how to actually measure something in terms of simplicity. And the only way I found to say whether code is simpler or not is whether the solution that I have is closer to the beginning of the method and whether it's more indented or less indented. So if it if it has more indentation like inside a loop or an if, it's more complicated than if it's not inside a loop or an if.

[00:35] (external link, opens in new tab) Agreed? Also if I have to put something as a solution to make a test pass as the third line of code in a production code, that's not as simple as making it the first line of code. Maybe that makes a little less sense. So let me explain. If your code ends up here instead of here, that means that it that to actually execute your code, all the following things just above it had to execute, right? So that means that your code has to assume that something's happened before.

[01:09] (external link, opens in new tab) That makes it more complicated or at least to understand the solution would be more complicated. If your code is the first line of code, if the if to make a test pass, you just add one line of code at the beginning of the method, that's the simplest thing because you don't make any assumptions about what happened before. You just do something in the simplest way possible and almost blindly. For example, clean up the inputs or things like that. And that's an example of simplicity. And we're going to find that simplicity is actually a bit difficult to achieve when we do the string calculator (external link, opens in new tab).

[01:45] (external link, opens in new tab) Now when I do test-driven development (external link, opens in new tab) I basically practice the idea that I remove as many assumptions from my code as possible but I might write my code with as many assumptions as I can about the inputs. So I'll give you an example. Let's say that the string calculator has a method called add public void. Let's call it parse

[02:20] (external link, opens in new tab) And parse can take a string with multiple numbers and those numbers can be divided with commas. Okay. And then the idea of parse is it will parse the numbers and sum them up and give you the number. Okay. So if I wanted to test parse and parse doesn't exist yet. I want to start with something then I have to start and I might start there are many ways I could start this test right but I want to start with the simplest test possible as well. So I want to start with the simplest inputs.

[02:51] (external link, opens in new tab) For example I can assume that the input is an empty string in which case I would just say no numbers. let's return zero. That's better than sending in three numbers because that's something that I can incrementally work towards adding more and more features. And I can go ahead and say parse add parse an empty string returns zero or I can say returns a default. In this case I would say zero

[03:24] (external link, opens in new tab) Summation. So I can say make calc extract a variable. Oh, that's not it. Extract a variable and say string calculator dot what was it? String calculator dot parse. And here I'm just going to give it an empty string.

[04:01] (external link, opens in new tab) What I'm going to say now is that we basically have int result equals u parse of an empty string. And here I would say assert equals what do I expect? What is the expected value from an empty string? If you're not sure, just look at the name of the test. What is it? Thank you. Zero from result.

[04:37] (external link, opens in new tab) Now if I just run this, this will of course not compile. But I can go ahead and say create the method string numbers and now I can say just return minus one because I want to see the test fail. Remember I'm just going to run the tests. Expected one that was minus one. That's what I wanted.

[05:07] (external link, opens in new tab) Now I'm going to just make the test pass. So now the question is if I wanted to make the test pass in the simplest way possible, what can I send in? What should I write? Do I have to check the string? No, I can make an assumption. I can just say, hey, you know what? I'm the laziest developer in the world. I'm just going to return zero. How's that? Return zero. And I know that feels horrible.

[05:39] (external link, opens in new tab) But that's kind of the point of making too many assumptions because now it feels horrible. All the tests pass and I'm back to the first rule of TDD. You want to change it. You cannot without a failing test, right? You if you think something's wrong, prove the assumption wrong. So now I get to write a new test that says you can't assume that the input is always empty. Here's an example. And I would go and I say, okay, let's do this.

[06:16] (external link, opens in new tab) I'm going to write this test. Parse single number because single number is easier than multiple numbers, right? Returns that number. Here I'm going to say give me what number should I send in? One. Thank you. Not 42. That's right. And we expect Z one. And now I'm just going to run my test again. Expected one actual zero. And now I've proven that the assumption is wrong.

[06:46] (external link, opens in new tab) So it's like an onion. And you start with implementing the code with as many layers of assumptions as you can. Then every new test you basically peel out one layer of the says, "Oh, that can't be true. That can't be true." So now I get to go and say, "Okay, it can't always be zero. So what's the next simplest thing that I can assume if I have to make both of the tests pass in the same time? So I might say look let's just do an if if numbers length is

[07:19] (external link, opens in new tab) Zero then return zero otherwise hey let's just return one because obviously if it's not an empty string it has to be the number one right that's an assumption right there let's do that do all the tests pass damn So now we're back in the same crappy situation we were. We don't like the code, but we don't have a failing test. So we can go back and say, "No, you can't make that assumption as well, dude.

[07:50] (external link, opens in new tab) So let me go back and say, actually, if you send it with a different number, so I'm just going to send in a different number." So I just have the exact same test. I'm going to send in two. I'm going to send in two. And I'm going to run the test again. Of course, that fails. Now I know I can't just return one. I can I have to do like a integer parse int

[08:20] (external link, opens in new tab) Numbers. Notice that I I assume that there's only one number obviously. Now here's a question. Oh, maybe I can just get rid of all these things. Do I can I? Let's see what happens. Oops, I can't. But how long did it take me to figure that out? A few seconds. And that's kind of the another point is that when you have the tests, you can experiment and say, can I get rid of that?

[08:57] (external link, opens in new tab) Can I get rid of the loop? And you have less fear in your heart when you do that. So now I know these two things have a reason to exist. And guess what? My code works incrementally. So I have two of three features working. I have zero numbers working and I have one number working and I did it within a few minutes. I can actually demo my code and instead of saying my feature either works or it doesn't, I can say that my feature partially works and I can show examples of what works and what doesn't.

[09:29] (external link, opens in new tab) That's an example of incremental development. I basically did micro iterations. So with every minute or every 10 minutes I have a new feature sub feature working. That means that every day I have new functionality in the application working with tests. If every day I have new functionality I can basically take the code and release it every day if I wanted to. I don't have to wait two weeks until the feature is either finished or not. I can release every day because I always have something new passing, new working.

[09:59] (external link, opens in new tab) And that's an example of how you you make short iterations work is because if you have micro iterations in your code, then a release date is just a date and iteration and end of an iteration demo is just another day because you can demo on the first day of the iteration and in the middle day and every day because you always have new features and you're working incrementally instead of taking your build a big bulk of code and making pass.

[10:32] (external link, opens in new tab) Here you're making very little increments. And you also have much better feeling because you have a lot of small wins. You see green and seeing green instead of red for two weeks is a much better feeling.

[11:04] (external link, opens in new tab) So that's the idea of making the assumptions and remove the assumptions one by one from your code. So if I have an input, it will always be empty. Nope, that's wrong. Here's a test that proves it. And it also helps if you think about it wearing two hats. One hat is the person who makes who writes the tests and the other hat is for the person who's the laziest developer in the world who has to go home in five minutes. What would they do? And then they do it in the simplest way and then the other guy says, "Oh, no, you can't do that.