Exercises For Programmers: Exercise 3- Printing Quotes

The purpose of this exercise is to display a quote and prompt the user for the person who said the quote. If the quote is correct, you output the answer and the speaker.

I think the stated goals for this exercise assume that you are using a language like Haskell that does not have strong user interface component. I adapted the requirements somewhat for iOS.

The UI elements I need for this exercise are a label asking who said the quote, a text view to hold the quote, a text field to accept the answer, a button to submit the answer, and another text view to display the response.

What you start off seeing when you launch the app.

What you start off seeing when you launch the app.

I briefly thought about how to create a label that would allow for multiple lines of text, when I remembered the best object for that is a text field.

I tried to make the layout adapt to different lengths of quotes. I thought if I just pinned my elements under one another but did not specify the height of each element that it could expand and contract according to how long the quote was. This does not quite work. The text field is stuck at one size and height. If the text is longer than the text field, the element adopts a scroll view to expose the hidden text.

It might be possible to use programmable constraints to get the effect I would like. I am interested in figuring this out in the future, but I will leave that challenge for another time.

There are a few challenges associated with this exercise:

  • Display a quote
  • Check the user input to see if there is an answer
  • If there is an answer, is the answer correct?
  • Customize the output depending on what the user submitted

Like the previous exercises, there are three different possible outputs. Unlike the previous examples, the three are not all connected. If there is no answer, then you have one output. If there is an answer, then you have to process it further to see if it is the correct answer.

I wanted to hard code as little in this application as possible. It was indicated that later in the book we will revisit this application and refactor it to pull multiple quotes and authors from a data structure. I could set the quote and the answer in the view controller, but then if the requirements change and it’s possible that there are more than one quote, I will have to change things. I want this to be set up for that possibility and I want the entire application to be testable, which means moving anything that is not view controller code into a helper functions class.

Since I believe I will later need to have access to multiple quotes, the first function I created was one to return the quote:

func quoteText() -> String {
    return "You can lead a horticulture, but you cannot make her think."
}

Right now we only have one quote, but in the future the requirements may change where we may possibly have more and this can be refactored to determine what that is without having to change any code in the view controller.

Next I want to determine if the response is correct or not. Since the answer is going to be used in multiple places and won’t be passed in from the UI, I made the answer a global variable.

Incorrect response.

Incorrect response.

I need to pass in the response from the user and check it against this answer. It is either correct or incorrect, which means we are returning a Bool:

let answer = "Dorothy Parker"

func isTheAnswerCorrect(response:String) -> Bool {
    if response == answer {
        return true
    } else {
        return false
    }
}

Depending upon whether the response is correct or not, the output string will change. If the answer is correct, we output a congratulatory message. If not, then we let the user know they were incorrect:

func responseOutput(isResponseCorrect:Bool, response:String) -> String {
    if isResponseCorrect == true {
        return "Correct! (response) said (quoteText())"
    } else {
        return "Sorry, (response) did not say that. Try again."
    }
}

Again, I want to future proof this function by not hard coding anything that might change. I am using the response as an interpolated String in both output messages because it will be the same either way. There’s no reason to hard code the correct response, thus making it more difficult to go back and change later.

Correct response

Correct response

So far we’re assuming that the user has entered a response. If they did not enter a response, I would like to handle that contingency as well.

I need to not only check to see if there are no characters in the string, but also to have an output message for the user if it is empty. I want this string to be optional because it is probable that there will be a response and I want this to be a fail-safe:

func isResponseEmpty(response:String) -> String? {
    if response.characters.count == 0 {
        return "Please enter a response!"
    } else {
        return nil
    }
}

Empty Response

Empty Response

My Helper Functions file is now complete. Each piece is small and testable. Now I need to put these pieces together to complete my functionality in the view controller:

@IBAction func answerQuestion(sender: AnyObject) {
    let response = answerTextField.text
        
    if let noResponse = isResponseEmpty(response!) {
        responseTextView.text = noResponse
    } else {
        let checkResponse = isTheAnswerCorrect(response!)
        responseTextView.text = responseOutput(checkResponse, response: response!)
    }
}

I need to pull in the response from the user and store it in a variable.

Since the failsafe for no response is an optional, I need to wrap it in an if-let statement. This puzzled me for a while because I kept getting a compiler error when I did this initially because I had an optional that was not unwrapped. I knew I didn’t want to force unwrap the return value from isResponseEmpty() because most of the time it would be nil. At that point I realized I was using response rather than response! The value from the text field is always presented as an optional. In the first exercise I realized that when you don’t enter anything in the text field, it doesn’t return nil, it just returns an empty string, so I knew it was safe to force unwrap the response.

After I check to verify that there is not an empty string, I need to check to see if the answer is correct. I run the isTheAnswerCorrect() function and pass that value into the responseOutput() function. That function returns a string and that string is set to the response text view.

I ran the code and it appears to work properly. Just to make sure, I went ahead and wrote a unit test for each function in the Helper Functions file. Since each function is small and does a very specific unit of work, it doesn’t take too long to verify each possible output.

Conclusion

I suppose it’s possible that I have overengineered this application. Some of the functions are probably overkill for the requirements as they are now, but I wanted to make this flexible for if those requirements change.

I probably could have shoved all my programming logic into one function, but I really like having everything spelled out and having each function do one small thing. When I was taking classes with Eric Knapp, he said that if you are talking about a function’s job and your description includes “It does this AND…” then you need another function. Each function should do one thing. I like this philosophy and I have been trying to implement it in my own code.

A link to this project can be found here.