I started my morning with some cowboy coffee; which is coffee brewed in a saucepan, since I seem to have misplaced my aero press, as I listened to Go for beginners, and discovered Gophercises, a set of coding exercises in Go aimed at people who’ve done a few tutorials but have kinda lost steam. I felt like that described me, so I’ve decided to tackle these exercises while journalling my progress and learning in this blog.

/ I have a peculiar writing style because I write in tandem with coding, so there’s a lot of tense switching, so apologies in advance. All comments and criticisms are encouraged, no matter how harsh or abrasive, as long as they are constructive!/

I’m going to tackle the first challenge, creating a Quiz Game for the command line.

So the first thing I need to do is make a new go project. I create a new repository on Github, clone it into my $GOPATH/src/github.com/adilw3nomad , and made a simple main.go file that prints Hello! when it's run.

I run go build && ./GopherQuiz , which compiles my program into a binary called GopherQuiz and runs said file. Hello! was printed to the command line. Success! I now have something to work with.

Lets go over the exercise details; I’m going to make the points which I feel are important to the domain in bold.

Create a program that will read in a quiz provided via a CSV file (more details below) and will then give the quiz to a user keeping track of how many questions they get right and how many they get incorrect. Regardless of whether the answer is correct or wrong the next question should be asked immediately afterwards. The CSV file should default to problems.csv (example shown below), but the user should be able to customize the filename via a flag.

I like to break down stuff like this into bullet points;

There’s a quiz, which has questions.

Questions can be either right or wrong, ergo they have an answer.

There’s a score, or at least a concept of a score

The quiz is created from a CSV file with questions and answers

The CSV file can be chosen by the user

That’s the domain of the application; now let’s have a think about the behaviour. The application has a single dependency; it requires a CSV file to create the quiz. Without a CSV file, there is no quiz, and the app is useless. So the first thing we need to do is to get our app to load and read a CSV file. I won’t worry about the filename flag just yet, or even the content of the CSV. All I want to focus on is loading and reading a CSV file into the application.

So once I’ve done that, this is how I imagine the program to work;

Create array/slice to hold quiz items. (Question, Answer) Read each line of the CSV and create a quiz item from it. Iterate through the slice, printing the question to stdout Accept an answer from the user If answer matches the user’s input, increase score counter by one.

I’m going to do this in the worst way possible; with no tests, all in the main function. Then I'll write some tests for it, and then I'll refactor, knowing that my code still works thanks to the tests.

So this is what I have so far; I open the file using the os package, and do not close it until the program stops running. I then create a 2 dimensional array of each of the CSV elements using csv.Reader.ReadAll . Finally, to see what I'm working with, I loop through the records and print each one. Protip; PrintLn is useful for just printing out whatever as long as it's printable.

So I now have my CSV input, what do I want to do with it? I want to

Ask each question

Accept an user’s answer

Compare it to the correct answer

Increase a counter if they are the same.

What does it mean to ask a question in a command line app? It means writing to stdout. What does it mean to accept an answer? Reading from stdin. I’ve already got writing to stdout covered by using PrintLn , and in order to read from stdin, I'm going to use the bufio package.

What we’ve done here is loop through the lines of the CSV, print the question out, then instantiate a new buffered I/O reader which uses the os.Stdin interface. Next we print an instruction to the user, and use the input.ReadString method to read their input. The parameter passed to ReadString is the delimiter that lets the reader know when to stop reading. Then a simple conditional statement checks to see if the answer is correct.

Pretty sweet, except it doesn’t work! I suspect it’s because I am not making the assertion correctly. Let’s do some debugging!

I posit that the type of variables I am comparing ( text and records[I][1] ) are not the same.

Let's find out the types of each of these by printing them.

We know (thanks to VScode’s Go package), that input.ReadString returns a string. What does csv.Reader.ReadAll return? A 2-dimensional slice of with strings. Hm. Using this little bit of nasty code, I can confirm they are both the same type. So what is going on!

fmt.Println(fmt.Sprintf(“%T”, text)) // string fmt.Println(fmt.Sprintf(“%T”, line[i][1])) // string

Time for a new hypothesis; but first, some refactoring. I’m finding it a bit difficult to make sense of what I’m writing, mainly because of all these horrible names, like line[i][1] . I'm going to turn these record's into what they are; items in a quiz. How, you say? By making a struct!

Go’s structs are typed collections of fields. They’re useful for grouping data together to form records, and are pretty essential if you want to do OOP in Go. Go isn’t a pure object oriented language, but through methods and structs, you can write object-oriented code.

Here’s how to define a struct in Go, the aptly named quizItem

type quizItem struct {

question string

answer string

}

We start with the keyword type , since Go is statically typed, and we are defining a new type of variable. Followed by the name of the struct, quizItem , and finally the keyword struct . In the body of the curly braces, we define the structs attributes, followed by their type.

Here’s how the quiz loop looks now that we’ve added a struct

As you can see on line 24, instantiating the struct is pretty straight forward, and now the code is a lot more readable. Now that it’s easier to read, time to make a new hypothesis as to why the answer comparison does not work. I hypothesise that perhaps I’m making an assumption on what the value of text is going to be.

Let’s print it out and check! I’ll print the user’s answer before running the comparison check, so now the code looks like this

Let’s build it and try it out!

$ go build && ./GopherQuiz

Question: 5+5

Enter your answer now:10 Your answer is: 10

WRONG! Answer is: 10

Question: 7+3

Enter your answer now:

Something here is odd; there is a new line after printing the user’s answer. Let’s investigate it by looking at the base16 value of the string. We can do this by using fmt.Printf("%x", text) , where %x is a format modifier signifying that I want the string to formatted in base16. Let's write some code to test this out;

fmt.Println("Your answer is:", text) fmt.Printf("Your answer in base16 is %x

", text) fmt.Printf("The expected answer in base16 is %x

", quizItem.answer) $ go build && ./GopherQuiz

Question: 5+5

Enter your answer now:10

Your answer is: 10 Your answer in base16 is 31300a

The expected answer in base16 is 3130

Aha! My suspicions are confirmed; there / is/ a difference between the two. The user input ends in 0a , which is hex code for linefeed.

What’s happened here is that when reading from stdin, we took everything up to and including

, which is the reason why there was a new line after printing the user's input.

Now that we know the issue, how can we solve it? The simplest way would be to remove the linefeed character from the input before comparing. Let’s use the strings package, which is a library of tools for manipulating strings. A quick test:

text = strings.TrimSuffix(text, "

") fmt.Printf("Your answer trimmed in base16 is %x

", text) fmt.Printf("The expected answer in base16 is %x

", quizItem.answer) $ go build && ./GopherQuiz

Question: 5+5

Enter your answer now:10

Your answer is: 10



Your answer trimmed in base16 is 3130

The expected answer in base16 is 3130

Success! So what we’ve done here is use the TrimSuffix method, passing it the text we want to apply the trim to, and the suffix we wish to trim. With this, the quiz should work! Here's a quick reminder of how the entire program looks;

It’s always nice to build something that works; there’s a rush of endorphins that course through your body when achieve your goal and your program works as expected. However, making something that works today is not the goal of a programmer; the goal of a programmer is to write code that is flexible and robust in the face of uncertainty and change.

What would happen if the CSV file were to be 1000 lines long? The program would still work, but it would be severely inefficient. The clue to this mystery lies innocently on line 25.

We are using csv.Reader.ReadAll; this is a function that does exactly what it says on the tin; it reads the entire file. In order to do this, it must load all of the lines that will be read into memory, and thereafter read each record, not stopping until it is done. The more lines in the file, the more memory is needed. Needless to say, there’s a better way to do this, which will make our code more performant, as well as a bit neater and easier to follow.

The biggest change here is that we are not looping through all the CSV lines as a collection, but rather processing each line in an infinite loop until we reach the end. In this way, we only allocate memory for a single CSV line, rather than the entire slice of CSV lines.

Here is the final product in action!

So the next things we need to do are;

Keep track of the score, and output how many are correct/wrong

Add a flag that allows the CSV file to be customised.

But before we go any further, we must have tests! Having tests helps you understand your code, saves you time from having to manually test things, and also gives you a little rush every time it all goes green.

Follow me on twitter if you wanna ask me questions or have any suggestions!