Beginner solution to basic quiz exercise, a la Gophercises
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
4
down vote
favorite
I just started golang development about two weeks ago and recently finished the recommended introduction book.
I'm now working my way through Gophercises - a sort of collection of exercises to improve beginner's understanding of Golang through small projects.
This is my solution to the first project: writing a quiz (cli-)application.
The requirements are simple:
Read a csv file, each line consisting of a question and an answer:
5+5,10
1+1,2
8+3,11
1+2,3
8+6,14
3+1,4
1+4,5
5+1,6
2+3,5
3+3,6
2+4,6
5+2,7Print the question to the user
Validate if the supplied answer is correct.
Print the correct answers.
Here is my solution to the problem:
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
)
type q struct
question, answer string
func (q q) ask() bool
fmt.Println(q.question, " equals: ")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
if scanner.Err() != nil
log.Fatal(scanner.Err())
if scanner.Text() == q.answer
return true
return false
func quizLoop(path string, verbose bool)
// Loop should:
// 1. Read records line by line
// 2. Ask the question (i/o)
// 3. Keep score.
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
reader := csv.NewReader(file)
for
record, err := reader.Read()
if err != nil
if err == io.EOF
break
log.Fatal(err)
q := qquestion: record[0], answer: record[1]
if q.ask()
if verbose
fmt.Println("Correct")
correct++
else if verbose
fmt.Println("Incorrect")
lines++
fmt.Printf("You had %d/%d correct answers!n", correct, lines)
func main()
// Setup flags.
p := flag.String("path", "problems.csv", "Specify the path to the quiz questions.")
v := flag.Bool("verbose", false, "A boolean value to check if you want the program to be verbose or not.")
flag.Parse()
// Invoke loop.
quizLoop(*p, *v)
As mentioned in my introduction I am rather new to the language, and couldn't see any caveats where it could have been beneficial to use things like interfaces or go routines in this particular project.
These are the things I'm the most interested in having reviewed:
- Best Practices
- Refactoring
- How to use more advanced functionality to solve it (ie. go routines or interfaces)
- Adding unit tests to it. What can be tested?
- Overall design
beginner programming-challenge csv go quiz
add a comment |Â
up vote
4
down vote
favorite
I just started golang development about two weeks ago and recently finished the recommended introduction book.
I'm now working my way through Gophercises - a sort of collection of exercises to improve beginner's understanding of Golang through small projects.
This is my solution to the first project: writing a quiz (cli-)application.
The requirements are simple:
Read a csv file, each line consisting of a question and an answer:
5+5,10
1+1,2
8+3,11
1+2,3
8+6,14
3+1,4
1+4,5
5+1,6
2+3,5
3+3,6
2+4,6
5+2,7Print the question to the user
Validate if the supplied answer is correct.
Print the correct answers.
Here is my solution to the problem:
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
)
type q struct
question, answer string
func (q q) ask() bool
fmt.Println(q.question, " equals: ")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
if scanner.Err() != nil
log.Fatal(scanner.Err())
if scanner.Text() == q.answer
return true
return false
func quizLoop(path string, verbose bool)
// Loop should:
// 1. Read records line by line
// 2. Ask the question (i/o)
// 3. Keep score.
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
reader := csv.NewReader(file)
for
record, err := reader.Read()
if err != nil
if err == io.EOF
break
log.Fatal(err)
q := qquestion: record[0], answer: record[1]
if q.ask()
if verbose
fmt.Println("Correct")
correct++
else if verbose
fmt.Println("Incorrect")
lines++
fmt.Printf("You had %d/%d correct answers!n", correct, lines)
func main()
// Setup flags.
p := flag.String("path", "problems.csv", "Specify the path to the quiz questions.")
v := flag.Bool("verbose", false, "A boolean value to check if you want the program to be verbose or not.")
flag.Parse()
// Invoke loop.
quizLoop(*p, *v)
As mentioned in my introduction I am rather new to the language, and couldn't see any caveats where it could have been beneficial to use things like interfaces or go routines in this particular project.
These are the things I'm the most interested in having reviewed:
- Best Practices
- Refactoring
- How to use more advanced functionality to solve it (ie. go routines or interfaces)
- Adding unit tests to it. What can be tested?
- Overall design
beginner programming-challenge csv go quiz
add a comment |Â
up vote
4
down vote
favorite
up vote
4
down vote
favorite
I just started golang development about two weeks ago and recently finished the recommended introduction book.
I'm now working my way through Gophercises - a sort of collection of exercises to improve beginner's understanding of Golang through small projects.
This is my solution to the first project: writing a quiz (cli-)application.
The requirements are simple:
Read a csv file, each line consisting of a question and an answer:
5+5,10
1+1,2
8+3,11
1+2,3
8+6,14
3+1,4
1+4,5
5+1,6
2+3,5
3+3,6
2+4,6
5+2,7Print the question to the user
Validate if the supplied answer is correct.
Print the correct answers.
Here is my solution to the problem:
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
)
type q struct
question, answer string
func (q q) ask() bool
fmt.Println(q.question, " equals: ")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
if scanner.Err() != nil
log.Fatal(scanner.Err())
if scanner.Text() == q.answer
return true
return false
func quizLoop(path string, verbose bool)
// Loop should:
// 1. Read records line by line
// 2. Ask the question (i/o)
// 3. Keep score.
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
reader := csv.NewReader(file)
for
record, err := reader.Read()
if err != nil
if err == io.EOF
break
log.Fatal(err)
q := qquestion: record[0], answer: record[1]
if q.ask()
if verbose
fmt.Println("Correct")
correct++
else if verbose
fmt.Println("Incorrect")
lines++
fmt.Printf("You had %d/%d correct answers!n", correct, lines)
func main()
// Setup flags.
p := flag.String("path", "problems.csv", "Specify the path to the quiz questions.")
v := flag.Bool("verbose", false, "A boolean value to check if you want the program to be verbose or not.")
flag.Parse()
// Invoke loop.
quizLoop(*p, *v)
As mentioned in my introduction I am rather new to the language, and couldn't see any caveats where it could have been beneficial to use things like interfaces or go routines in this particular project.
These are the things I'm the most interested in having reviewed:
- Best Practices
- Refactoring
- How to use more advanced functionality to solve it (ie. go routines or interfaces)
- Adding unit tests to it. What can be tested?
- Overall design
beginner programming-challenge csv go quiz
I just started golang development about two weeks ago and recently finished the recommended introduction book.
I'm now working my way through Gophercises - a sort of collection of exercises to improve beginner's understanding of Golang through small projects.
This is my solution to the first project: writing a quiz (cli-)application.
The requirements are simple:
Read a csv file, each line consisting of a question and an answer:
5+5,10
1+1,2
8+3,11
1+2,3
8+6,14
3+1,4
1+4,5
5+1,6
2+3,5
3+3,6
2+4,6
5+2,7Print the question to the user
Validate if the supplied answer is correct.
Print the correct answers.
Here is my solution to the problem:
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
)
type q struct
question, answer string
func (q q) ask() bool
fmt.Println(q.question, " equals: ")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
if scanner.Err() != nil
log.Fatal(scanner.Err())
if scanner.Text() == q.answer
return true
return false
func quizLoop(path string, verbose bool)
// Loop should:
// 1. Read records line by line
// 2. Ask the question (i/o)
// 3. Keep score.
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
reader := csv.NewReader(file)
for
record, err := reader.Read()
if err != nil
if err == io.EOF
break
log.Fatal(err)
q := qquestion: record[0], answer: record[1]
if q.ask()
if verbose
fmt.Println("Correct")
correct++
else if verbose
fmt.Println("Incorrect")
lines++
fmt.Printf("You had %d/%d correct answers!n", correct, lines)
func main()
// Setup flags.
p := flag.String("path", "problems.csv", "Specify the path to the quiz questions.")
v := flag.Bool("verbose", false, "A boolean value to check if you want the program to be verbose or not.")
flag.Parse()
// Invoke loop.
quizLoop(*p, *v)
As mentioned in my introduction I am rather new to the language, and couldn't see any caveats where it could have been beneficial to use things like interfaces or go routines in this particular project.
These are the things I'm the most interested in having reviewed:
- Best Practices
- Refactoring
- How to use more advanced functionality to solve it (ie. go routines or interfaces)
- Adding unit tests to it. What can be tested?
- Overall design
beginner programming-challenge csv go quiz
edited Jan 26 at 19:16
200_success
123k14143401
123k14143401
asked Jan 26 at 17:20
geostocker
39618
39618
add a comment |Â
add a comment |Â
3 Answers
3
active
oldest
votes
up vote
2
down vote
Two things I noticed:
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
Handle errors immediately, don't do other things first:
file, err := os.Open(path)
if err != nil
log.Fatal(err)
defer file.Close()
correct, lines := 0, 0Don't leave an opportunity to add more code later before the error handling by separating os.Open from if err != nil. Sooner or later you'll use file where it may be nil.
Creating a new scanner for each answer is slightly wasteful. Since you asked about potential tests, you can clearly test one or two question/answer cycles. To do that, you'll want to pass two io.Readers to quizLoop instead of a filename
func quizLoop(questions io.Reader, answers io.Reader, verbose bool)
You can pass the file and os.Stdin from main, and in tests you can pass bytes.Buffers, for instance, to supply test data.
I don't see an opportunity to leverage Go routines here. This program is inherently sequential. I'm sure you'll get an excercise for that later.
1
Even further:func quizLoop(questions io.Reader, answers io.Reader, output io.Writer, log io.Writer)
(withioutil.Discard
if no verbosity is wanted)
â oliverpool
Feb 2 at 9:20
add a comment |Â
up vote
0
down vote
If you really want to use goroutines and channels, you could read the csv file in a goroutine:
questions := make(chan q)
go pushQuestions(questions) // type: func(chan<- questions)
// it closes the channel when all questions are red
for q := range questions
q.ask()
...
add a comment |Â
up vote
0
down vote
Design
The q
structure has two roles:
- Container of a question-answer pair
- Handle user interaction
It would be better to separate these responsibilities:
the structure should not have the ask()
function.
Somewhat related to this,
there's no good reason to recreate a scanner in the ask
function.
One option could be to create a new ask(...)
function that takes as parameters a scanner, a question, and an answer.
With this approach the q struct
becomes pointless.
To make the q struct
legitimate,
you could write a func qreader(file *os.File, qs chan q)
that reads the CSV and pushes q
instances to the channel.
This qreader
could run in a goroutine,
while the main thread reads from the channel and handles user interaction.
Don't ignore return values
The program ignores the return value of scanner.Scan()
.
It could be useful to save an unnecessary check for scanner.Err()
.
It's good to develop the habit to look suspiciously at statements that don't return value (must be mutating state), or non-void statements whose return value is ignored.
Naming
Go encourages short names, but I think q
is too short and meaningless for a struct. At the minimum qa
would have been better, to capture the notion of a question-answer pair. I would call it questionAnswer
.
add a comment |Â
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
Two things I noticed:
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
Handle errors immediately, don't do other things first:
file, err := os.Open(path)
if err != nil
log.Fatal(err)
defer file.Close()
correct, lines := 0, 0Don't leave an opportunity to add more code later before the error handling by separating os.Open from if err != nil. Sooner or later you'll use file where it may be nil.
Creating a new scanner for each answer is slightly wasteful. Since you asked about potential tests, you can clearly test one or two question/answer cycles. To do that, you'll want to pass two io.Readers to quizLoop instead of a filename
func quizLoop(questions io.Reader, answers io.Reader, verbose bool)
You can pass the file and os.Stdin from main, and in tests you can pass bytes.Buffers, for instance, to supply test data.
I don't see an opportunity to leverage Go routines here. This program is inherently sequential. I'm sure you'll get an excercise for that later.
1
Even further:func quizLoop(questions io.Reader, answers io.Reader, output io.Writer, log io.Writer)
(withioutil.Discard
if no verbosity is wanted)
â oliverpool
Feb 2 at 9:20
add a comment |Â
up vote
2
down vote
Two things I noticed:
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
Handle errors immediately, don't do other things first:
file, err := os.Open(path)
if err != nil
log.Fatal(err)
defer file.Close()
correct, lines := 0, 0Don't leave an opportunity to add more code later before the error handling by separating os.Open from if err != nil. Sooner or later you'll use file where it may be nil.
Creating a new scanner for each answer is slightly wasteful. Since you asked about potential tests, you can clearly test one or two question/answer cycles. To do that, you'll want to pass two io.Readers to quizLoop instead of a filename
func quizLoop(questions io.Reader, answers io.Reader, verbose bool)
You can pass the file and os.Stdin from main, and in tests you can pass bytes.Buffers, for instance, to supply test data.
I don't see an opportunity to leverage Go routines here. This program is inherently sequential. I'm sure you'll get an excercise for that later.
1
Even further:func quizLoop(questions io.Reader, answers io.Reader, output io.Writer, log io.Writer)
(withioutil.Discard
if no verbosity is wanted)
â oliverpool
Feb 2 at 9:20
add a comment |Â
up vote
2
down vote
up vote
2
down vote
Two things I noticed:
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
Handle errors immediately, don't do other things first:
file, err := os.Open(path)
if err != nil
log.Fatal(err)
defer file.Close()
correct, lines := 0, 0Don't leave an opportunity to add more code later before the error handling by separating os.Open from if err != nil. Sooner or later you'll use file where it may be nil.
Creating a new scanner for each answer is slightly wasteful. Since you asked about potential tests, you can clearly test one or two question/answer cycles. To do that, you'll want to pass two io.Readers to quizLoop instead of a filename
func quizLoop(questions io.Reader, answers io.Reader, verbose bool)
You can pass the file and os.Stdin from main, and in tests you can pass bytes.Buffers, for instance, to supply test data.
I don't see an opportunity to leverage Go routines here. This program is inherently sequential. I'm sure you'll get an excercise for that later.
Two things I noticed:
file, err := os.Open(path)
correct, lines := 0, 0
if err != nil
log.Fatal(err)
defer file.Close()
Handle errors immediately, don't do other things first:
file, err := os.Open(path)
if err != nil
log.Fatal(err)
defer file.Close()
correct, lines := 0, 0Don't leave an opportunity to add more code later before the error handling by separating os.Open from if err != nil. Sooner or later you'll use file where it may be nil.
Creating a new scanner for each answer is slightly wasteful. Since you asked about potential tests, you can clearly test one or two question/answer cycles. To do that, you'll want to pass two io.Readers to quizLoop instead of a filename
func quizLoop(questions io.Reader, answers io.Reader, verbose bool)
You can pass the file and os.Stdin from main, and in tests you can pass bytes.Buffers, for instance, to supply test data.
I don't see an opportunity to leverage Go routines here. This program is inherently sequential. I'm sure you'll get an excercise for that later.
answered Feb 1 at 17:56
Peter
1433
1433
1
Even further:func quizLoop(questions io.Reader, answers io.Reader, output io.Writer, log io.Writer)
(withioutil.Discard
if no verbosity is wanted)
â oliverpool
Feb 2 at 9:20
add a comment |Â
1
Even further:func quizLoop(questions io.Reader, answers io.Reader, output io.Writer, log io.Writer)
(withioutil.Discard
if no verbosity is wanted)
â oliverpool
Feb 2 at 9:20
1
1
Even further:
func quizLoop(questions io.Reader, answers io.Reader, output io.Writer, log io.Writer)
(with ioutil.Discard
if no verbosity is wanted)â oliverpool
Feb 2 at 9:20
Even further:
func quizLoop(questions io.Reader, answers io.Reader, output io.Writer, log io.Writer)
(with ioutil.Discard
if no verbosity is wanted)â oliverpool
Feb 2 at 9:20
add a comment |Â
up vote
0
down vote
If you really want to use goroutines and channels, you could read the csv file in a goroutine:
questions := make(chan q)
go pushQuestions(questions) // type: func(chan<- questions)
// it closes the channel when all questions are red
for q := range questions
q.ask()
...
add a comment |Â
up vote
0
down vote
If you really want to use goroutines and channels, you could read the csv file in a goroutine:
questions := make(chan q)
go pushQuestions(questions) // type: func(chan<- questions)
// it closes the channel when all questions are red
for q := range questions
q.ask()
...
add a comment |Â
up vote
0
down vote
up vote
0
down vote
If you really want to use goroutines and channels, you could read the csv file in a goroutine:
questions := make(chan q)
go pushQuestions(questions) // type: func(chan<- questions)
// it closes the channel when all questions are red
for q := range questions
q.ask()
...
If you really want to use goroutines and channels, you could read the csv file in a goroutine:
questions := make(chan q)
go pushQuestions(questions) // type: func(chan<- questions)
// it closes the channel when all questions are red
for q := range questions
q.ask()
...
answered Feb 2 at 9:25
oliverpool
1,542425
1,542425
add a comment |Â
add a comment |Â
up vote
0
down vote
Design
The q
structure has two roles:
- Container of a question-answer pair
- Handle user interaction
It would be better to separate these responsibilities:
the structure should not have the ask()
function.
Somewhat related to this,
there's no good reason to recreate a scanner in the ask
function.
One option could be to create a new ask(...)
function that takes as parameters a scanner, a question, and an answer.
With this approach the q struct
becomes pointless.
To make the q struct
legitimate,
you could write a func qreader(file *os.File, qs chan q)
that reads the CSV and pushes q
instances to the channel.
This qreader
could run in a goroutine,
while the main thread reads from the channel and handles user interaction.
Don't ignore return values
The program ignores the return value of scanner.Scan()
.
It could be useful to save an unnecessary check for scanner.Err()
.
It's good to develop the habit to look suspiciously at statements that don't return value (must be mutating state), or non-void statements whose return value is ignored.
Naming
Go encourages short names, but I think q
is too short and meaningless for a struct. At the minimum qa
would have been better, to capture the notion of a question-answer pair. I would call it questionAnswer
.
add a comment |Â
up vote
0
down vote
Design
The q
structure has two roles:
- Container of a question-answer pair
- Handle user interaction
It would be better to separate these responsibilities:
the structure should not have the ask()
function.
Somewhat related to this,
there's no good reason to recreate a scanner in the ask
function.
One option could be to create a new ask(...)
function that takes as parameters a scanner, a question, and an answer.
With this approach the q struct
becomes pointless.
To make the q struct
legitimate,
you could write a func qreader(file *os.File, qs chan q)
that reads the CSV and pushes q
instances to the channel.
This qreader
could run in a goroutine,
while the main thread reads from the channel and handles user interaction.
Don't ignore return values
The program ignores the return value of scanner.Scan()
.
It could be useful to save an unnecessary check for scanner.Err()
.
It's good to develop the habit to look suspiciously at statements that don't return value (must be mutating state), or non-void statements whose return value is ignored.
Naming
Go encourages short names, but I think q
is too short and meaningless for a struct. At the minimum qa
would have been better, to capture the notion of a question-answer pair. I would call it questionAnswer
.
add a comment |Â
up vote
0
down vote
up vote
0
down vote
Design
The q
structure has two roles:
- Container of a question-answer pair
- Handle user interaction
It would be better to separate these responsibilities:
the structure should not have the ask()
function.
Somewhat related to this,
there's no good reason to recreate a scanner in the ask
function.
One option could be to create a new ask(...)
function that takes as parameters a scanner, a question, and an answer.
With this approach the q struct
becomes pointless.
To make the q struct
legitimate,
you could write a func qreader(file *os.File, qs chan q)
that reads the CSV and pushes q
instances to the channel.
This qreader
could run in a goroutine,
while the main thread reads from the channel and handles user interaction.
Don't ignore return values
The program ignores the return value of scanner.Scan()
.
It could be useful to save an unnecessary check for scanner.Err()
.
It's good to develop the habit to look suspiciously at statements that don't return value (must be mutating state), or non-void statements whose return value is ignored.
Naming
Go encourages short names, but I think q
is too short and meaningless for a struct. At the minimum qa
would have been better, to capture the notion of a question-answer pair. I would call it questionAnswer
.
Design
The q
structure has two roles:
- Container of a question-answer pair
- Handle user interaction
It would be better to separate these responsibilities:
the structure should not have the ask()
function.
Somewhat related to this,
there's no good reason to recreate a scanner in the ask
function.
One option could be to create a new ask(...)
function that takes as parameters a scanner, a question, and an answer.
With this approach the q struct
becomes pointless.
To make the q struct
legitimate,
you could write a func qreader(file *os.File, qs chan q)
that reads the CSV and pushes q
instances to the channel.
This qreader
could run in a goroutine,
while the main thread reads from the channel and handles user interaction.
Don't ignore return values
The program ignores the return value of scanner.Scan()
.
It could be useful to save an unnecessary check for scanner.Err()
.
It's good to develop the habit to look suspiciously at statements that don't return value (must be mutating state), or non-void statements whose return value is ignored.
Naming
Go encourages short names, but I think q
is too short and meaningless for a struct. At the minimum qa
would have been better, to capture the notion of a question-answer pair. I would call it questionAnswer
.
answered Mar 7 at 21:50
janos
95.6k12120343
95.6k12120343
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186067%2fbeginner-solution-to-basic-quiz-exercise-a-la-gophercises%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password