Validating and Normalizing User Input13:17 with Craig Dennis
Let's take care of a bug we found surrounding uppercase and lowercase guesses.
So we ran into that slight little issue that popped up 0:00 when we added an uppercase value as input. 0:03 The uppercase value T is different than the lowercase value of T. 0:05 So we need to ensure a specific case in the letters of our data. 0:09 So when dealing with input, what our end user is providing us through our prompts, 0:13 like those letters, there are a few things that we can and should always do. 0:16 Firstly, we should check the correctness of the input. 0:21 This is called validation. 0:23 Input that has been checked is said to be validated. 0:25 Another option is to modify or transform the value so 0:28 that it becomes a valid value. 0:32 This process is called normalization, and the data is said to be normalized. 0:34 So in our, hey, you already guessed that letter exception example, 0:38 we validated the input and let the caller know that the validation had failed. 0:42 Now, we're looking at a place where we need to normalize the input. 0:47 We need to normalize alphabetic case of our values. 0:50 So, what we'll do is when we start the game with new Game("Treehouse"), 0:54 we'll ensure that the value is lowercase. 0:58 And then we'll also make sure that any time someone calls applyGuess on the game 1:00 object like so, we'll take whatever they gave us and make it lowercase. 1:05 A great practice to make sure things are working as you anticipate is to write what 1:10 are known as tests. 1:14 Tests exercise your code and 1:15 make sure that your code works exactly as you expected it to. 1:16 We, unfortunately, are not going to get into writing tests in this course. 1:20 But we do offer content that covers testing, check the Teacher's Notes. 1:24 So for now, while we're coding up this validation and normalization a bit, 1:28 we'll do some quick manual spot check testing. 1:32 I'd like you to get a feel for what kind of tests we might actually write, and 1:35 show you why they're such a critical part of writing applications. 1:39 Let's get to it. 1:42 Okay, so let's get to work on this bug. 1:44 I'm gonna pull this over into In Progress column. 1:46 And first off, 1:50 let's make sure that the answer that's passed in is always lowercase. 1:51 And we can do that by this, toLowerCase, right? 1:55 Remember that on strings toLowercase. 1:59 Now to tackle the guest side of things, 2:01 one thing that we haven't even checked is if our user is even giving us a letter. 2:04 So let's go explore the character class, the documentation for the character class. 2:09 So if we go here and we go Java 8, and we search for Character. 2:13 So this first result here. 2:19 So the character class is a wrapper class for our primitive data type char. 2:21 Now much like how we saw integer wrapper for a class for ints, right, 2:26 where we used integer.parseInt. 2:30 Well since a primitive char doesn't have any methods, 2:32 this class is used to provide helper methods for chars. 2:35 And it does it through, guess what., let's go look at the methods here, 2:38 through static methods. 2:43 So, here's some common getters and things. 2:45 But what we're looking for is we're looking for something that says, 2:47 is this a letter? 2:51 So, if we look through here and we say, is letter. 2:52 Hey, look at that, it takes a char and it returns a boolean of true or false. 2:55 So, let's try it out. 3:00 Let's pop over to jshell. 3:01 So, we come in here and we'll say, Character.isLetter, and 3:02 I sure hope that a is. 3:07 And boom, it returns true, cool, just like we thought. 3:09 So what happens about the character 2 or a weird @ or something? 3:12 Okay, I'll submit it works for uppercase too right? 3:17 Great, so I wonder if there's a toLowerCase for chars, 3:21 just like we looked at for the strings, I wonder if that exists. 3:24 So let's come here and I'm just going to do a search. 3:28 And a look for to, look at that, there it is, toLowerCase. 3:30 And it's a static method off of the char and 3:33 it converts the character argument to lowercase, cool. 3:35 So we could also for over here we could say a character too low and 3:38 just tap complete and boom there it is to lower case and 3:42 if we give it an a That should return as a lowercase A, beautiful. 3:46 Okay, so with that information, we should be able to get a valid lowercase letter 3:51 and throw an exception if in fact we don't. 3:56 You know what? Now is a great time to show off a concept 3:59 that we haven't touched on yet, and that is private methods. 4:01 So our game object here currently is doing some validation inside of this applyGuess. 4:05 Let's go ahead and let's make a new method and 4:11 we'll kinda group our logic together there. 4:13 So we'll make it private. 4:16 And by making this private, it will not be accessible to anyone but the class itself. 4:18 So we'll say private is gonna return a chart that has been normalized. 4:23 All right, so let's say normalizeGuess. 4:28 And it's gonna take a char and it's gonna return a char. 4:33 So again, because it's private, the prompter, like for instance, the prompter 4:37 can't say game.normalizeguess, only our code here can. 4:41 So this method is a good way to group common functionality and 4:45 not clutter up your other methods. 4:49 Here let's build it. 4:51 So first we wanna know if it is not a letter, right? 4:53 So if it is not a letter, gonna pass on letter. 4:58 Ideally this would kinda be a custom exception, but for 5:05 now we'll just do a throw new IllegalArgumentException, right? 5:09 They passed in a illegal argument to make a guess. 5:13 So we'll say a letter is required. 5:16 So now that we know it's a letter, we can transform it to lowercase. 5:22 So we can say letter =, again using that same character wrapper class, 5:25 toLowerCase(letter). 5:30 Awesome, great, now we already have some validation and applyGuess, 5:35 why don't we put that in here in the normalizeGuess. 5:39 So I'm going to cut that out, and I'm gonna paste it here. 5:42 And then finally our normalizeGuess needs to return the letter, great. 5:46 So now in applyGuess we can just call our private method there. 5:52 We'll say letter = normalizeGuess(letter). 5:56 See how it keeps things readable? 6:02 We could very easily just jam all of these lines in this applyGuess method here. 6:04 But see how nice and concise this is? 6:08 Methods provide a great way to name grouped logic. 6:10 So how about we do this, let's add a little smarts to our prompter object. 6:14 If we come over to prompter object and we look here at promptForGuess. 6:18 What if we made this thing just keep trying until it got a valid guess? 6:21 Sounds good, right? 6:26 So there's a couple ways to do this approach. 6:27 But first let's move this isHit up the top here, let's get this stuff down here. 6:29 So we move isHit to the top. 6:36 And I'm gonna bring this scanner definition to the top too. 6:38 Let's go ahead and do that. 6:42 It's kinda common to the whole method, so we'll bring him to the top. 6:43 We'll set up a new variable there. 6:47 And let's do a trick here. 6:49 Let's store a new value that we're gonna use to keep track of state in this method. 6:51 Did we get an acceptable value? 6:58 So we're gonna set that to default to start with. 6:59 Okay, so like we said, we wanna do a loop here. 7:02 We wanna loop through things, and we wanna make sure that it happens once. 7:05 So while we don't have an acceptable guess, but 7:09 we wanna make sure it happens once, so that's a do while loop. 7:12 So again, that starts like this, you say do. 7:15 And then we kinda wanna run all this stuff, don't we, to right about here. 7:18 And then we're gonna close that do loop, see how it's highlighted? 7:22 And we'll say while, we'll go not isAcceptable. 7:25 Speaking of not acceptable, look at this lineup of this code. 7:31 Let's go ahead, and I'm gonna highlight this. 7:35 And I'm gonna do a command bracket or a control bracket, right bracket. 7:38 And it will move, you can move left or right with that. 7:45 There we go. 7:49 So what we wanna do is we wanna make sure that we flip or 7:51 change the isAcceptable value to true here. 7:56 And what's happening is if it comes through here, 8:02 we know that it's valid, otherwise it's coming here. 8:04 And now, since we're looping, we can try to give a cleaner message. 8:07 So let's switch this to a printf. 8:10 And we'll put a format string here of, we'll say, here's your error message. 8:12 Please try again. 8:16 The famous Please try again. 8:19 All right, so we're gonna pump in there, just like we were before, 8:21 the exception message that we set, okay. 8:26 And we're still returning the isHit. 8:31 So this is gonna loop and will return isHit, okay. 8:33 Let's mentally walk this really quick. 8:36 So this method, promptForGuess is called. 8:39 And we set up a variable to know if it's a hit and 8:43 we set a variable to know if it's acceptable. 8:45 Our first iteration through the loop, 8:49 which will always happen because we're using a do while loop. 8:51 It's going to get an input. 8:55 It's gonna pull off that input. 8:58 And because our input returns a string, we have to use this charAt. 9:01 Sure would be nice if applyGuess took a string for us, but it doesn't. 9:07 So anyway, applyGuess takes the guess, and in here, it does some normalization. 9:10 So we pass that single charge through and it has a way of normalizing our char. 9:17 Let's take a look at that one more time. 9:21 As we come through applyGuess, it comes in here, 9:23 it calls this private method normalizeGuess. 9:25 And we pass that original guess through, 9:28 it comes through here it does all of our checks that we want. 9:30 If it makes it all the way through here, we have a valid letter. 9:33 If not, it throws an IllegalArgumentException. 9:36 Now because one of those exceptions will break out of this try block, it will run 9:39 this message, and this isAcceptable will never be flipped to true. 9:43 So therefore, when it comes back down here to check, it will go again, and 9:47 again, and again. 9:52 So let's walk that really quick with one. 9:54 So if the person entered in a 1 here, and they came and guessed, and 9:56 the guesser says, trying to be tricky for some reason, put in a 1. 9:59 They hit applyGuess, they come on over to game. 10:03 It would go to normalizeGuess, isLetter, false, a letter is required. 10:05 Here it comes, a letter is required, please try again. 10:08 And notice that it jumped over the isAcceptable. 10:12 So isAcceptable is still false. 10:15 So take a second and think about an app that you've used that takes your input. 10:18 Now this loop is pretty common right? 10:22 Whether it be on the web or on an app on your phone or tablet. 10:24 Do you follow it? 10:27 It is totally understandable for you not to grasp this immediately. 10:29 This very well may be the first time that you're thinking about 10:32 this side of the application. 10:35 But I know that you've filled out web forms before. 10:37 So you've had this experience that we're creating. 10:39 So if you need to, go ahead and pause me, and try to walk through each line there 10:41 on your own in your head, or out loud if you aren't in public. 10:45 Rewind me a bit if you wanna hear me explain it all again. 10:48 Okay so, let's give this a test run. 10:53 I am going to do a clear && javac Hangman.java && jave Hangman. 10:56 So let's make sure that we can't guess a number, right? 11:10 That was our first exception. 11:12 A letter is required, beautiful. 11:14 Nice, and there it is, it'll keep going and keep on asking me, 11:16 it's pretty insistent there. 11:19 So that works. 11:20 So what happens if I give it a capital H? 11:21 Awesome, it lowercases it. 11:24 You know what, actually we moved the code that tests the checks of duplicate 11:26 guesses into that normalizeGuess method. 11:30 So we'd better test that too, to make sure it's working. 11:32 So I'm gonna guess an h again. 11:35 Cool, it says h has already been guessed. 11:36 And then I'm gonna guess t, and then Guess it again. 11:39 Cool, and we're still at seven tries. 11:41 Nice, we did it. 11:44 Okay, great, and I just wanna point out if we had actually written a test for 11:45 all that, we'd know that we didn't break it. 11:50 Because we could just run a series of tests. 11:52 But as it stands, we have to test that every single time we change our code. 11:54 We would write tests for one, and we'd write tests for uppercase, and 11:57 we'd make sure that that worked. 12:00 In fact, we'd also test what happens if somebody just came here and pressed Enter. 12:02 No, look, it's trying to get the first 12:07 character out of a string that doesn't have any characters. 12:10 Well that code was the code we were wanting to clean up anyway. 12:14 So let's go ahead and let's close that issue. 12:16 And unfortunately, lets make a new one that says, 12:20 BUG: Sending no values on a guess causes a crash. 12:26 There we go. 12:33 And let's label that, let's give that a red label. 12:36 All right, here we go. 12:39 Nice job on the looping and exception handling. 12:42 Doing that manual testing or QA, which stands for quality assurance, 12:44 helped us to uncover another issue that we should probably fix here shortly. 12:48 If you don't enter a letter, the application encounters a critical error. 12:52 Are you beginning to see how handy it would be to be able to run 12:57 a bunch of tests and make sure everything is working at all times, 13:00 especially if you change a bit of code? 13:03 You would just run the test to make sure everything still works. 13:05 As it stands, we have to do that ourselves every single time the code changes. 13:08 All right, let's refactor in clean up that code right after this exercise. 13:13
You need to sign up for Treehouse in order to download course files.Sign up