Beginner solution to basic quiz exercise, a la Gophercises

 Clash Royale CLAN TAG#URR8PPP
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,7
- Print 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,7
- Print 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,7
- Print 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,7
- Print 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, 0- Don'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)(with- ioutil.Discardif 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, 0- Don'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)(with- ioutil.Discardif 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, 0- Don'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)(with- ioutil.Discardif 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, 0- Don'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, 0- Don'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)(with- ioutil.Discardif 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)(with- ioutil.Discardif 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