Kata Rules Overview0:00
Next up is the StringCalculator Kata. Let's go over the rules real quick. So we need to create a Calculator class with a method signature of add, and it accepts a string of numbers separated by a comma. So effectively, we're going to call an add method and do something like this. add(2, 3), and of course that should return 5. Okay. Here's some of the requirements. The add method can handle an unknown amount of numbers. All right. Allow the add method to handle new lines instead of commas, like this. Okay. We can support different delimiters. So rather than add(2, 3), maybe we do add(2; 3). That needs to be supported. And then finally, some basic guarding. We cannot call add with a negative number, and numbers bigger than 1000 should be ignored. So remember, in these situations, sometimes the rules for the kata are arbitrary. There's no reason why we should limit it to 1000,
First Test: Empty String0:50
bigger than a thousand should be ignored. So remember, in these situations, sometimes the rules for the kata are arbitrary. There's no reason why we should limit it to a thousand, other than the fact that you're following a set of requirements, just like any project you take on. So let's get started with our first test, beginning with, well, what if we pass an empty string? That should evaluate to zero. All right. It evaluates an empty string as zero. Okay. So if we instantiate some StringCalculator class, and you'll remember the method signature is add. That's all we're doing here. So if we add and then we provide an empty string, that should evaluate to zero. So assertEquals(0). And we'll give it a run. Okay. It fails because there is no StringCalculator. All right. That's our next step. Create the new class StringCalculator. All right. Come back and we need to import that.
run. Okay. It fails because there is no StringCalculator. All right. That's our next step. Create the new class StringCalculator. All right. Come back and we need to import that. And we'll give it, actually, let's clean that up and give it another run. Okay. So now there's no method add. All right. Let's create that. add. And then we accept a string of numbers. Give it another run. And at this point, it's returning green, but only because returning null is falsy, the same as 0 is falsy. So let's do assertSame in this case to make sure it's exactly 0 that is returned. Now it fails. Okay. So we're just going to force it to pass at the moment. Remember, the least amount of code to make the test pass. And now we get green. Okay. Let's do the next one. What if we call the add method and we provide a single number? So it finds the sum of a single number. All right. So let's say
Handle Single Number2:29
now we get green. Okay. Let's do the next one. What if we call the add method and we provide a single number? So it finds the sum of a single number. All right. So let's say add(5). And, of course, that should evaluate to 5. Okay. So we give that a run and it fails. All right. So let's see. So we might start by saying, okay, well, let's rewrite this. If what you gave us evaluates to false, then we're just going to return 0. But otherwise, why don't we just return whatever you gave us and we're going to convert it to an integer. So we convert the string '5' to the number 5. And I think that's enough to make it work right now. Yeah. And again, we know we're going to change this. We know it's not going to work for the next test. But you're trying to get yourself in the flow and in the mindset of what is the least amount of code I can write to implement this functionality.
Sum Multiple Numbers3:14
it's not going to work for the next test. But you're trying to get yourself in the flow and in the mindset of what is the least amount of code I can write to implement this functionality or this feature. Okay. Let's do another one. How about it finds the sum of two numbers? Finds the sum of two numbers. So if we were to say 5, 5, that should evaluate to 10, of course. So we'll give that one a run and it fails and we switch back and let's see. It's no longer enough to do this. We probably need to split the numbers, right? Because if we're thinking we have something like 5, 5, we need to split that into an array. Let's play around with this. Now we could do it in line and that's totally fine. But I like this tool called Tinkerwell by Marcel, who's very active in the Laravel community. You can check it out if you like. Anyways, we can do things like this to quickly
fine. But I like this tool called Tinkerwell by Marcel, who's very active in the Laravel community. You can check it out if you like. Anyways, we can do things like this to quickly evaluate php. Anyways, if we had a $string of five and five, let's just say that equals the $numbers. How can we split that into an array? Well, you could do explode using a comma as the delimiter. And that'll give you an array. You could do preg_split. Either one's fine. So let's try that. And now at this point, we would have an array of five and five. And then at this point, we need to find the sum of those numbers in the array. So why don't we change this to return and we can use the array_sum function. And run the test and it works. Okay, so let's go back and we're going to run the full class and all of those are passing at the moment. Okay, we know there's going to be more, but we're
the test and it works. Okay, so let's go back and we're going to run the full Calculator class and all of those are passing at the moment. Okay, we know there's going to be more, but we're at least getting started here. What is the next test we should write? Well, let's go back to our scratch pad here. So we've created the Calculator class. We've added the add method signature. It can take up to two numbers separated by a comma and it returns their sum. Okay. What else? Allow the add method to handle an unknown amount of numbers. Okay, so it originally said up to two, but now I assume it wants us to extend that to accept any number. However, I don't think we have to change anything there. We'll add a test just to make it clear that it can accept any number. So we'll say it finds the sum of any amount of numbers, but I don't think we'll have to change anything here. So we'll do 5, 5, 5, and 4
Support Newline Delimiters5:27
that it can accept any number. So we'll say it finds the sum of any amount of numbers, but I don't think we'll have to change anything here. So we'll do five, five, five, and four equals 19, right? And I think it'll just work. Cool. Let's go back. We have numberTwo. What else? Allow the add method to handle new lines between numbers instead of commas. So something like that. Okay, we have our next test. It accepts a new line character as a delimiter too. So let's try it again. We'll grab that. And this time we'll do five, new line, five. And let's change that to double quotes. All right, so if we give it a run, it now fails. And if we switch back, we're going to have to tweak this. Let's go back to Tinkerwell. So once again, we have our numbers, something like that. And yeah, before we were doing explode, but I know we're going to need a regular expression. So why don't we switch
Add Constraints and Custom Delimiters7:11
and run all the tests. And yeah, those are working as well. Great. What else? We've taken care of that. Support different delimiters. Okay, we're going to do that at the end. Let's skip ahead to these two real quick. Calling add with a negative number will throw an exception. So remember, in real life, you can add a negative number. This just represents an arbitrary requirement that we need to implement in the code. All right, so negative's not allowed. All right, we'll add a new test here. Negative numbers are not allowed. All right, let's do it again. And this time, let's get rid of that. If we were to say calculator->add(5, -4), that should throw an exception, right? So let's say expectException, like so. And we'll give it a run. It fails because no exception is ever thrown. All right, so yeah, at this point, this is an array of your numbers. Why don't we just peek
So that's done. Numbers bigger than a thousand should be ignored. So it shouldn't throw an exception, it should be ignored entirely. Okay, numbers greater than 1,000 are ignored. Completely arbitrary. Try not to overthink it. It's a generic rule for the kata. All right, so numbers greater than a thousand are ignored. It does not throw an exception, it is ignored entirely. So if I were to say add five and a thousand and one, that should equal five, because this is ignored. Assert::equals(five). And if we give that a run, of course it fails, because right now it's trying to add it. So let's implement our arbitrary rule, and we'll say, again, the simplest thing we can do here, let's filter the numbers down. So if a number is greater than a thousand, it gets stripped from the array entirely. And for that, we can use array_filter. So filter the numbers, and we'll just say we want the number included in the array.
beginning of the string must contain a separate line that looks like this. And that looks a little confusing. Let's remove that. That looks a little confusing, but it's basically this. If you want to add, for example, five and four, but you want to use a semicolon as the delimiter, it's telling us we must precede the string with two forward slashes, a semicolon, and a new line. So this is how we specify a custom delimiter. Again, it's an arbitrary rule. That's okay. It's just a rule for the kata. So if I wanted to use a colon, I would do something like this. All right, so let's copy that, and this will be our final test, and then we'll do a refactor and call it a day. It supports custom delimiters. Okay, so I'm going to save that, and let's say if we have a Calculator class, and then we try to add using a custom delimiter. So I'm going to grab this whole thing here. That should return nine.
and then we try to add using a custom delimiter. So I'm going to grab this whole thing here. That should return nine. I think that's right. Yeah. So let's give that a run, and of course it fails. So this will be the trickiest part. Let's see if we can figure it out. I'm going to go back to Tinkerwell to play around. So if our numbers was this, let's imagine that's what's passed to the add method. We need to check for that. So let's try starting with preg_match, and we need two forward slashes. So this will represent the boundaries of the regular expression, but then we need two forward slashes that need to be escaped. So one, two. Next, we need a delimiter of some form. So I'm not going to hard code : because it could be any character. So I will use a ., which represents any character. Finally, we end it with a new line. Okay, so if we were to match that when given
will be matches one. So now let me think. We just need to update the string here. Yeah, we figured out what the delimiter is. So let's now just remove it to return it to this. Okay, so let's say preg_replace, or actually it's hard-coded, so I think we can do str_replace now. And we'll just say, okay, look for the full match here and replace it with nothing using the $numbers string there, and then overwrite it. Yeah, so now we would have something like 5:4, and that's what we want. Okay, so now we can move on here where we split that string into an array. And you'll see up here we've set the delimiter, so we can't repeat ourselves. We'll change that to $delimiter. All right, let's run it. No ending delimiter colon in preg_split. Oh, I'm sorry, we forgot the forward slashes. Let's run that. Oh, it works. Cool. So let's run everything. Are we safe? Yes, we are. So now everything is working. And actually, let's
Refactor for Clarity15:29
disallowing negative numbers. So why don't we make that a method called disallowNegativeNumbers or negatives. That's fine. So now that'll create a new method here to store that logic and everything. Oh, that must be a type string, but we gave it an array. So that just needs to be updated to array or no type int at all. And that'll work as well. Okay, so that's one refactor. Next, what else? What else is popping out at me? I will often look for magic numbers. In this case, 1000 is a magic number. I don't know what it represents. I don't know why we're comparing against it. What is that? So again, often you will have a comment here, the maximum number allowed. If you need a comment there, it's not clear enough. So let's make it more clear and then remove the comment. So let's do that now. Let's add a constant called MAX_NUMBER_ALLOWED. We're doing a constant because it should never be changed, I'm assuming. So let's come back now.
remove the comment. So let's do that now. Let's add a constant called maxNumberAllowed. We're doing a constant because it should never be changed, I'm assuming. So let's come back now. We'll say max, or I'm sorry, self::maxNumberAllowed. And again, notice now the code and the comment are redundant. So I can remove this, rerun the test, and everything still passes. Okay, what else pops out? Well, this section right here. Six months from now, it's going to be confusing what I'm checking for. Like what is this? I can't even remember five minutes later what that is searching for. And that wastes energy. So let's make it a little more clear. This represents our custom delimiter check. So let's assign that. Okay, so now it's a little bit better. I can see, all right, we're checking for some kind of custom delimiter, and if so, we need to update things. So that still passes. But either way, it seems like all of this represents
bit better. I can see, all right, we're checking for some kind of custom delimiter, and if so, we need to update things. So that still passes. But either way, it seems like all of this represents the parsing of this parameter here. We're parsing that numbers string. So with that in mind, why don't we say this parseString. We're just extracting methods here. parseString. You gave us the numbers there, and we'll paste all of that in. That is what we ultimately return. This is going to fail though, isn't it? Yeah, undefined variable delimiter. So at the moment, we defined delimiter here, but now we're extracting functionality that wants to change that. So in situations like this, just introduce a property. So let's say we have protected delimiter will be a comma or a new line. And then if we need to change that at any point, like we do right here, we'll say this delimiter. So we can just update it on the fly. And that way,
protected delimiter will be a comma or a new line. And then if we need to change that at any point, like we do right here, we'll say this delimiter. So we can just update it on the fly. And that way, we don't have to return an array of values from a function or something weird like that. All right, so is that okay? No, disallowed negatives must be of type array. There we go. Okay, so that's one thing. So now we have a dedicated method that parses the string, and we don't have to think about it unless we need to change how that's parsed. And now we have a dedicated place to go. Let's keep going. So again, do this with me. We're just looking for anything out of the blue here. We parse the string. We disallow negative numbers. We ignore numbers greater than 1,000. And that can be here or, yeah, why don't we make that a method as well? Ignore greater than 1,000. Let's just be super clear because it sounds like that's a requirement.
So that was our first test. However, here's what I'm thinking. If I comment that out, I bet it still passes, and it does. Yeah, and that's because we ultimately switch to using ArraySum. And if ArraySum encounters an empty string, that's treated as zero. So for example, let's go to TinkerWell. If I were to say ArraySum, yeah, you could have one and two, and then I'll return three, of course. But if I have an empty string, that evaluates to zero. So if we had that and two, you can see how that works, which means that should be optional. And if I now run all of the tests, very cool. Even more simple now. I like that. Parse the string. You know what? Let's try this. Let's see how this feels. Sometimes I will inline it like this. It still works. Does it make it less clear? Disallow the negative numbers? You know what? I could go either way. I don't have a strong opinion. Let's keep it like that, though.
