Beginner solution to basic викторины exercise, имеет Gophercises


Я только начал golang развития около двух недель назад и недавно закончил рекомендуется введение книги.

Я сейчас работаю мой путь через Gophercises - своего рода коллекция упражнений для улучшения понимания новичок в Golang в рамках небольших проектов.


Это мое решение для первого проекта: написание тест (строки)приложения.

Требования простые:

  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. Напечатать правильные ответы.


Вот мое решение проблемы:

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)
}

Как отметил в своем вступительном слове я довольно новыми для языка, и не вижу моментов, где это может быть выгодно, чтобы использовать такие вещи, как интерфейсы или пройти процедуры в данном конкретном проекте.

Эти вещи я наиболее заинтересован в рассмотрев:

  • Лучшие Практики
  • Рефакторинг
  • Как использовать дополнительные возможности, чтобы решить его (т. е. пройти процедуры или интерфейсы)
  • Добавление модульных тестов к нему. Что можно проверить?
  • Общий дизайн


Комментарии
3 ответа

Две вещи, которые я заметил:

    file, err := os.Open(path)
correct, lines := 0, 0

if err != nil {
log.Fatal(err)
}
defer file.Close()


  1. Обрабатывать ошибки сразу же, не первые прочем:

    file, err := os.Open(path)
    if err != nil {
    log.Fatal(err)
    }
    defer file.Close()

    correct, lines := 0, 0

    Не оставляйте возможность добавить больше кода позже перед обработкой ошибок путем разделения ОС.Работает с если индикатор ERR != шь. Рано или поздно вы будете использовать файл, где он может быть nil.


  2. Создание нового сканера для каждого ответа-это немного расточительно. Поскольку вы спросили о возможных тестов, вы можете четко проверить один или два Вопрос/ответ цикла. Чтобы сделать это, вы захотите пройти два Ио.Читателям quizLoop вместо имени

    func quizLoop(questions io.Reader, answers io.Reader, verbose bool)

    Вы можете передавать файл и ОС.Вывод из основной, и в тесты вы можете пройти байт.Буферы, например, для подачи тестовых данных.


Я не вижу возможности использовать пройти процедуры здесь. Эта программа по своей сути является последовательной. Я уверен, что вы получите упражнения Для что поздно.

2
ответ дан 1 февраля 2018 в 05:02 Источник Поделиться

Если вы действительно хотите использовать горутины и каналы, вы могли прочитать файл CSV в горутина:

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
ответ дан 2 февраля 2018 в 09:02 Источник Поделиться

Дизайн

В q структура имеет две роли:


  • Контейнер для вопроса-ответа пара

  • Управления взаимодействием пользователей

Было бы лучше разделить эти обязанности:
структура не должна иметь ask() функция.

Несколько связанных с этим
нет никаких оснований, чтобы воссоздать сканер в ask функция.

Одним из вариантов может быть создание новых ask(...) функция, которая принимает в качестве параметров сканера, вопрос и ответ.
При таком подходе q struct становится беспредметным.

Для того чтобы сделать q structзаконным,
вы могли бы написать func qreader(file *os.File, qs chan q) который читает CSV и толкает q экземпляры на канал.
Это qreader может работать в горутина,
в то время как основной поток читает из канала и обработки взаимодействия с пользователем.

Не игнорировать возвращаемые значения

Программа игнорирует возвращаемое значение scanner.Scan().
Это может быть полезно, чтобы сохранить ненужные проверки scanner.Err().

Хорошо выработать привычку с подозрением смотреть на заявления, которые не возвращают значение (должно быть мутирует государства), или не-void заявления, возвращаемое значение игнорируется.

Именования

Перейти призывает короткие имена, но я думаю q слишком короткие и бессмысленные структуры. На минимальном qa было бы лучше, чтобы захватить понятие вопрос-ответ пара. Я бы назвал это questionAnswer.

0
ответ дан 7 марта 2018 в 09:03 Источник Поделиться