首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基本测验练习的初学者解决方案,la Gophercises

基本测验练习的初学者解决方案,la Gophercises
EN

Code Review用户
提问于 2018-01-26 17:20:49
回答 3查看 362关注 0票数 5

大约两周前,我开始了高丽的开发,最近完成了推荐的入门书。

我现在正在学习Gophercises --一种通过小项目来提高初学者对Golang的理解的练习集。

这是我对第一个项目的解决方案:编写一个测试(cli-)应用程序。

要求很简单:

  1. 读取一个csv文件,每一行包含一个问题和一个答案: 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
  2. 将问题打印给用户
  3. 验证所提供的答案是否正确。
  4. 打印正确的答案。

以下是我解决这个问题的方法:

代码语言:javascript
复制
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 := q{question: 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)
}

正如我在介绍中提到的,我对该语言相当陌生,在这个特定的项目中使用接口或go例程可能是有益的,但我看不出任何警告。

以下是我最感兴趣的评论:

  • 最佳实践
  • 重构
  • 如何使用更高级的功能来解决这个问题。去例程或接口)
  • 向它添加单元测试。什么是可以测试的?
  • 总体设计
EN

回答 3

Code Review用户

发布于 2018-02-01 17:56:15

我注意到两件事:

代码语言:javascript
复制
    file, err := os.Open(path)
    correct, lines := 0, 0

    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
  1. 立即处理错误,不要先做其他事情:文件,如果err != nil { := ( err )} later file.Close() file.Close()正确,那么请不要在错误处理之前留下添加更多代码的机会,方法是将os.Open与if err != nil分开。迟早,您将使用可能为零的文件。
  2. 为每个答案创建一个新的扫描器有点浪费。因为您询问了潜在的测试,所以您显然可以测试一个或两个问答周期。要做到这一点,您需要将两个io.Readers传递给quizLoop,而不是文件名func quizLoop(问题io.Reader、答案io.Reader、冗长的bool),您可以从main传递文件和os.Stdin,例如,在测试中可以通过bytes.Buffers来提供测试数据。

我看不出有机会利用这里的围棋套路。这个程序本质上是连续的。我相信你以后会有个节选的。

票数 3
EN

Code Review用户

发布于 2018-03-07 21:50:42

设计

q结构有两个角色:

  • 问答对的容器
  • 处理用户交互

最好将这些职责分开:结构不应该有ask()函数。

与此相关的是,没有充分的理由在ask函数中重新创建扫描仪。

一个选项可以是创建一个新的ask(...)函数,该函数将扫描器、问题和答案作为参数。通过这种方法,q struct变得毫无意义。

要使q struct合法,您可以编写一个读取CSV并将q实例推送到通道的func qreader(file *os.File, qs chan q)。当主线程从通道读取并处理用户交互时,这个qreader可以在一个goroutine中运行。

不要忽略返回值

程序忽略scanner.Scan()的返回值。为scanner.Err()保存不必要的检查可能是有用的。

养成怀疑地查看不返回值的语句(必须是变异状态)或其返回值被忽略的非空语句的习惯是很好的。

命名

Go鼓励短名,但我认为q太短,对结构来说没有意义。至少,qa会更好地捕捉问题回答对的概念。我会叫它questionAnswer

票数 1
EN

Code Review用户

发布于 2018-02-02 09:25:11

如果您真的想使用goroutines和通道,可以在goroutine中读取csv文件:

代码语言:javascript
复制
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()
    ...
}
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/186067

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档