2
\$\begingroup\$

This is the first exercise from https://gophercises.com/.

Basically the idea is to parse a CSV file in a form of 'question:answer' and count the number of correct answers. Also you have a time out range in which you must finish it.

I would appreciate any comments, good or bad :) Thanks a lot !

package main

import (
        "encoding/csv"
        "os"
        "fmt"
        "flag"
        "time"
)

const Ready = "yes"

var correctAnswers int

func getFileContent(filename string) (records[][] string, err error) {
        fd, err := os.Open(filename)

        if err != nil {
                exit("Problem with opening the file")
        }

        reader := csv.NewReader(fd)
        return reader.ReadAll()
}

func outputResult(totalQuestions, correctAnswers int) {
        fmt.Println("Number of total questions:", totalQuestions)
        fmt.Println("Number of corrected answers:", correctAnswers)
}

func exit(message string) {
        fmt.Println(message)
        os.Exit(3)
}

func provideQuestions(questions[][] string, done chan bool) {
        for index, value := range questions {
                question, answer := value[0], value[1]
                fmt.Println("Question N:=", index+1, "=>", question)

                var userAnswer string
                fmt.Scanf("%s", &userAnswer)

        if userAnswer == answer {
            correctAnswers ++
        }
        }

        done <- true
}


func main() {
    fileName := flag.String("name of csv file", "problems.csv", "The name of the csv file - default to 'problems.csv'")
    allowedTime := flag.Int("allowed time", 10, "The duration in seconds for which the quizz must be finished.")
    flag.Parse()

    var readyToStart string
    fmt.Println("Type 'yes' if you are ready to start")
    fmt.Scanf("%s", &readyToStart)

    if readyToStart != Ready {
        fmt.Println("Come back later if you are ready")
        os.Exit(0)
    }

    records, err := getFileContent(*fileName)


    if err != nil {
            exit("Reader problem.")
    }
    

    done := make(chan bool)
    timer := time.NewTimer(time.Duration(*allowedTime) * time.Second)
    
    go provideQuestions(records, done)

    select {
    case <- done:
            fmt.Println("Great job")
            fmt.Println("You finished with all questions.")
    case <- timer.C:
            fmt.Println("Time expired")
        }

    outputResult(len(records), correctAnswers)

}
\$\endgroup\$
3
\$\begingroup\$

Your function returns error, but actually you don't return it and exit in the function body when opening the file:

func getFileContent(filename string) (records[][] string, err error) {
        fd, err := os.Open(filename)

        if err != nil {
                exit("Problem with opening the file")
        }

        reader := csv.NewReader(fd)
        return reader.ReadAll()
}

From the point of error handling it will be more consistent to return error:

fd, err := os.Open(filename)
if err != nil {
   return nil, err
}

The file was open, but not closed: defer fd.Close().

I don't remember flag package, but if it is not ensure the presence of parameter you need to check filename != nil:

records, err := getFileContent(*fileName)

Here you have created new goroutine, but returns the result via global variable correctAnswers:

done := make(chan bool)
go provideQuestions(records, done)

It stops working if you decide to improve your game and allow more than one person to play on the same time. You will have race condition on this variable. Much better return the value from goroutine using channel.

You already have such channel - done, just change its type to channel of int and return the number of correct answers using it.

\$\endgroup\$
1
\$\begingroup\$

Improving the quiz

Parsing user input

You should parse all user input, including the csv file, by stripping them from white-spaces and making it lower-case. This ensures that user supplied data is correctly handled and the user doesn't fail a Question while it should be correct.

Let's take for example the following quiz in the csv:

What is the capital of the UK, london

The user might have it correct but it could be stated as incorrect in the following ways:

  1. You don't Trim the csv, and it's easy to forget a white-space in there
  2. A user could answer with London (Capital L)

Secondly there could be a malformed csv file, with only 1 item in the line, this would break the code without giving any indication why to the user.

Randomise

It would be nice if the problems were shuffled to avoid repetition.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.