воскресенье, 11 апреля 2021 г.

Создание модуля в Golang: добавить тест

Теперь, когда вы привели свой код в стабильное состояние, добавьте тест. Тестирование кода во время разработки может выявить ошибки, которые обнаруживаются при внесении вами изменений. В этом посте вы добавите тест для функции Hello.

Примечание. Этот раздел является частью руководства, состоящего из нескольких частей, которое начинается с создания модуля Go.

Встроенная поддержка модульного тестирования Go упрощает тестирование на ходу. В частности, используя соглашения об именах, Go пакет testing и команду go test, вы можете быстро писать и выполнять тесты.

1. В каталоге greetings создайте файл с именем greetings_test.go.

Завершение имени файла _test.go сообщает команде go test, что этот файл содержит тестовые функции.

2. В greetings_test.go вставьте следующий код и сохраните файл.

package greetings

import (
    "testing"
    "regexp"
)

// TestHelloName вызывает greetings.Hello с именем, проверка
// для допустимого возвращаемого значения.
func TestHelloName(t *testing.T) {
    name := "Gladys"
    want := regexp.MustCompile(`\b`+name+`\b`)
    msg, err := Hello("Gladys")
    if !want.MatchString(msg) || err != nil {
        t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
    }
}

// TestHelloEmpty вызывает greetings.Hello с пустой строкой,
// проверка на наличие ошибки.
func TestHelloEmpty(t *testing.T) {
    msg, err := Hello("")
    if msg != "" || err == nil {
        t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
    }
}

В этом коде вы:

  • Реализуете тестовые функции в том же пакете, что и тестируемый код.
  • Создаете две тестовые функции для проверки функции greetings.Hello. Имена тестовых функций имеют вид TestName, где Name что-то говорит о конкретном тесте. Кроме того, тестовые функции принимают в качестве параметра указатель на тип testing.T пакета testing. Вы используете методы этого параметра для создания отчетов и ведения журнала из вашего теста.
  • Реализуете два теста:
    TestHelloName вызывает функцию Hello, передавая значение имени, с которым функция должна иметь возможность возвращать допустимое ответное сообщение. Если вызов возвращает сообщение об ошибке или неожиданное ответное сообщение (которое не включает имя, которое вы передали), вы используете метод Fatalf параметра t, чтобы вывести сообщение на консоль и завершить выполнение.
    TestHelloEmpty вызывает функцию Hello с пустой строкой. Этот тест разработан, чтобы подтвердить, что ваша обработка ошибок работает. Если вызов возвращает непустую строку или нет ошибки, вы используете метод Fatalf параметра t для вывода сообщения на консоль и завершения выполнения.

3. В командной строке в каталоге greetings запустите команду go test, чтобы выполнить тест.

Команда go test выполняет тестовые функции (имена которых начинаются с Test) в тестовых файлах (имена которых заканчиваются на _test.go). Вы можете добавить флаг -v, чтобы получить подробный вывод, в котором перечислены все тесты и их результаты.

Тесты должны пройти.

$ go test
PASS
ok      example.com/greetings   0.364s

$ go test -v
=== RUN   TestHelloName
--- PASS: TestHelloName (0.00s)
=== RUN   TestHelloEmpty
--- PASS: TestHelloEmpty (0.00s)
PASS
ok      example.com/greetings   0.372s

4. Сломайте функцию greetings.Hello, чтобы просмотреть неудачный тест.

Тестовая функция TestHelloName проверяет возвращаемое значение для имени, которое вы указали в качестве параметра функции Hello. Чтобы просмотреть неудачный результат теста, измените функцию greetings.Hello так, чтобы она больше не включала имя.

В greetings/greetings.go вставьте следующий код вместо функции Hello. Обратите внимание, что выделенные строки изменяют значение, возвращаемое функцией, как если бы аргумент имени был случайно удален.

// Hello возвращает приветствие для указанного человека.
func Hello(name string) (string, error) {
    // Если имя не было указано, возвращаем ошибку с сообщением.
    if name == "" {
        return name, errors.New("empty name")
    }
    // Создаем сообщение в произвольном формате.
    // message := fmt.Sprintf(randomFormat(), name)
    message := fmt.Sprint(randomFormat())
    return message, nil
}

5 В командной строке в каталоге приветствия запустите go test, чтобы выполнить тест.

На этот раз запустите go test без флага -v. Вывод будет включать результаты только тех тестов, которые не прошли проверку, что может быть полезно, когда у вас много тестов. Тест TestHelloName должен завершиться ошибкой - TestHelloEmpty все еще проходит.

$ go test
--- FAIL: TestHelloName (0.00s)
    greetings_test.go:15: Hello("Gladys") = "Hail, %v! Well met!", <nil>, want match for `\bGladys\b`, nil
FAIL
exit status 1
FAIL    example.com/greetings   0.182s

В следующем посте вы увидите, как скомпилировать и установить код для его локального запуска.


Читайте также:


суббота, 10 апреля 2021 г.

Создание модуля в Golang: ответные приветствия для нескольких человек

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

Примечание. Этот раздел является частью руководства, состоящего из нескольких частей, которое начинается с создания модуля Go.

Но есть загвоздка. Изменение параметра функции Hello с одного имени на набор имен приведет к изменению сигнатуры функции. Если вы уже опубликовали модуль example.com/greetings, а пользователи уже написали код, вызывающий Hello, это изменение нарушит их программы.

В этой ситуации лучше написать новую функцию с другим именем. Новая функция примет несколько параметров. Это сохраняет старую функцию для обратной совместимости.

1. В greetings/greetings.go измените свой код так, чтобы он выглядел следующим образом.

package greetings

import (
    "errors"
    "fmt"
    "math/rand"
    "time"
)

// Hello возвращает приветствие для указанного человека.
func Hello(name string) (string, error) {
    // Если имя не было указано, возвращаем ошибку с сообщением.
    if name == "" {
        return name, errors.New("empty name")
    }
    // Создаем сообщение в произвольном формате.
    message := fmt.Sprintf(randomFormat(), name)
    return message, nil
}

// Hellos возвращает карту, 
// которая связывает каждого из названных людей
// с приветственным сообщением.
func Hellos(names []string) (map[string]string, error) {
    // Карта для связывания имен с сообщениями.
    messages := make(map[string]string)
    // Перебираем полученный срез имен, вызываем
    // функция Hello для получения сообщения для каждого имени.
    for _, name := range names {
        message, err := Hello(name)
        if err != nil {
            return nil, err
        }
        // В карте свяжите полученное сообщение с
        // именем.
        messages[name] = message
    }
    return messages, nil
}

// Init устанавливает начальные значения для переменных, 
// используемых в функции.
func init() {
    rand.Seed(time.Now().UnixNano())
}

// randomFormat возвращает одно 
// из набора приветственных сообщений. 
// Возвращаемое сообщение выбирается случайным образом.
func randomFormat() string {
    // Срез форматов сообщений.
    formats := []string{
        "Hi, %v. Welcome!",
        "Great to see you, %v!",
        "Hail, %v! Well met!",
    }

    // Возвращаем один из случайно выбранных форматов сообщений.
    return formats[rand.Intn(len(formats))]
}

В этом коде вы:

  • Добавляете функцию Hellos, параметр которой представляет собой срез имен, а не одно имя. Кроме того, вы меняете один из возвращаемых им типов со строки на карту, чтобы вы могли возвращать имена, сопоставленные с приветственными сообщениями.
  • Пусть новая функция Hellos вызовет существующую функцию Hello. Это помогает уменьшить дублирование, оставив при этом обе функции на своих местах.
  • Создаете карту сообщений, чтобы связать каждое из полученных имен (как ключ) со сгенерированным сообщением (как значение). В Go вы инициализируете карту со следующим синтаксисом: make(map[key-type]value-type). У вас есть функция Hellos, возвращающая эту карту вызывающей стороне.
  • Пройдете в цикле по именам, полученным вашей функцией, проверяя, что каждое из них имеет непустое значение, затем свяжете с каждым сообщение. В этом цикле for range возвращает два значения: индекс текущего элемента в цикле и копию значения элемента. Вам не нужен индекс, поэтому вы используете пустой идентификатор Go (подчеркивание), чтобы игнорировать его.

2. В коде вызова hello/hello.go передайте срез имен, а затем распечатайте содержимое карты имен/сообщений, которую вы получите.

В hello.go измените свой код так, чтобы он выглядел следующим образом.

package main

import (
    "fmt"
    "log"

    "example.com/greetings"
)

func main() {
    // Устанавливаем свойства предопределенного Logger,
    // включая префикс записи журнала и флаг отключения печати
    // времени, исходного файла и номера строки.
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    // Срез имен.
    names := []string{"Gladys", "Samantha", "Darrin"}

    // Запрашиваем приветственные сообщения для имен.
    messages, err := greetings.Hellos(names)
    if err != nil {
        log.Fatal(err)
    }
    // Если ошибок не было, распечатываем возвращенную карту
    // сообщения на консоль.
    fmt.Println(messages)
}

С этими изменениями вы:

  • Создаете переменную имен как тип среза, содержащий три имени.
  • Передаете переменную names в качестве аргумента функции Hellos.

3. В командной строке перейдите в каталог, содержащий hello/hello.go, затем используйте команду go run, чтобы убедиться, что код работает.

Вывод должен быть строковым представлением карты, связывающей имена с сообщениями, примерно следующего вида:

$ go run .
map[Darrin:Hail, Darrin! Well met! Gladys:Hi, Gladys. Welcome! Samantha:Hail, Samantha! Well met!]

В этом посте представлены карты для представления пар имя/значение. Он также представил идею сохранения обратной совместимости путем реализации новой функции для новых или измененных функций в модуле.

Далее вы воспользуетесь встроенными функциями Go, чтобы создать модульный (юнит) тест для вашего кода.


Читайте также:


Создание модуля в Golang: возврат случайного приветствия

В этом посте вы измените свой код так, чтобы вместо того, чтобы каждый раз возвращать одно приветствие, он возвращал одно из нескольких предопределенных приветственных сообщений.

Примечание. Этот раздел является частью руководства, состоящего из нескольких частей, которое начинается с создания модуля Go.

Для этого вы воспользуетесь срезом Go. Срез похож на массив, за исключением того, что его размер изменяется динамически при добавлении и удалении элементов. Срез - один из самых полезных типов Go.

Вы добавите небольшой срез, содержащий три приветственных сообщения, а затем ваш код будет возвращать одно из сообщений случайным образом.

1. В greetings/greetings.go измените свой код так, чтобы он выглядел следующим образом.

package greetings

import (
    "errors"
    "fmt"
    "math/rand"
    "time"
)

// Hello возвращает приветствие для указанного человека.
func Hello(name string) (string, error) {
    // Если имя не было указано, возвращаем ошибку с сообщением.
    if name == "" {
        return name, errors.New("empty name")
    }
    // Создаем сообщение в произвольном формате.
    message := fmt.Sprintf(randomFormat(), name)
    return message, nil
}

// init устанавливает начальные значения для переменных, 
// используемых в функции.
func init() {
    rand.Seed(time.Now().UnixNano())
}

// randomFormat возвращает 
// одно из набора приветственных сообщений. 
// Возвращаемое сообщение выбирается случайным образом.
func randomFormat() string {
    // Срез форматов сообщений.
    formats := []string{
        "Hi, %v. Welcome!",
        "Great to see you, %v!",
        "Hail, %v! Well met!",
    }

    // Возвращаем случайно выбранный формат сообщения,
    // указав случайный индекс для среза форматов.
    return formats[rand.Intn(len(formats))]
}

В этом коде вы:

  • Добавите функцию randomFormat, которая возвращает произвольно выбранный формат для приветственного сообщения. Обратите внимание, что randomFormat начинается со строчной буквы, что делает ее доступной только для кода в собственном пакете (другими словами, она не экспортируется).
  • В randomFormat объявите срез форматов с тремя форматами сообщений. При объявлении среза вы опускаете его размер в скобках, например: []string. Это сообщает Go, что размер массива, лежащего в основе среза, можно динамически изменять.
  • Используете пакет math/rand, чтобы сгенерировать случайное число для выбора элемента из среза.
  • Добавите функцию init для заполнения пакета rand текущим временем. Go автоматически выполняет функции init при запуске программы после инициализации глобальных переменных.
  • В Hello вызовете функцию randomFormat, чтобы получить формат возвращаемого сообщения, а затем используете формат и значение имени вместе для создания сообщения.
  • Вернете сообщение (или ошибку), как и раньше.

2. В hello/hello.go измените свой код так, чтобы он выглядел следующим образом.

Вы просто добавляете имя Gladys (или другое имя, если хотите) в качестве аргумента при вызове функции Hello в hello.go.

package main

import (
    "fmt"
    "log"

    "example.com/greetings"
)

func main() {
    // Устанавливаем свойства предопределенного Logger, включая
    // префикс записи журнала и флаг отключения печати
    // время, исходный файл и номер строки.
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    // Запрос приветственного сообщения.
    message, err := greetings.Hello("Gladys")
    // Если вернулась ошибка, выводим ее в консоль и
    // выходим из программы.
    if err != nil {
        log.Fatal(err)
    }

    // Если ошибок не было, распечатываем возвращенное сообщение
    // в консоль.
    fmt.Println(message)
}

3. В командной строке в каталоге hello запустите hello.go, чтобы убедиться, что код работает. Запустите его несколько раз, заметив, что приветствие изменится.

$ go run .
Great to see you, Gladys!

$ go run .
Hi, Gladys. Welcome!

$ go run .
Hail, Gladys! Well met!

Далее вы воспользуетесь срезом, чтобы поприветствовать нескольких людей.


Читайте также:


четверг, 8 апреля 2021 г.

Создание модуля в Golang: возврат и обработка ошибок

Обработка ошибок - важная особенность надежного кода. В этом разделе вы добавите немного кода, чтобы вернуть ошибку из модуля greetings, а затем обработать ее в вызывающей программе.

Примечание. Этот раздел является частью руководства, состоящего из нескольких частей, который начинается с создания модуля Go.

В greetings/greetings.go добавьте код, выделенный ниже.

Нет смысла отправлять приветствие в ответ, если вы не знаете, кого приветствовать. Вернуть вызывающему абоненту сообщение об ошибке, если имя пусто. Скопируйте следующий код в greetings.go и сохраните файл.

package greetings

import (
    "errors"
    "fmt"
)

// Hello возвращает приветствие для указанного человека.
func Hello(name string) (string, error) {
    // Если имя не было указано, возвращаем ошибку с сообщением.
    if name == "" {
        return "", errors.New("empty name")
    }

    // Если имя было получено, возвращаем значение,
    // включающее имя в приветственном сообщении.
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message, nil
}

В этом коде вы:

  • Измените функцию так, чтобы она возвращала два значения: строку и ошибку. Ваш абонент проверит второе значение, чтобы увидеть, произошла ли ошибка. (Любая функция Go может возвращать несколько значений.)
  • Импортируете пакет errors стандартной библиотеки Go, чтобы вы могли использовать его errors.New функцию.
  • Добавляете оператор if, чтобы проверить недействительный запрос (пустую строку, где должно быть имя) и вернуть ошибку, если запрос недействителен. Функция errors.New возвращает ошибку с вашим сообщением внутри.
  • Добавляете nil (что означает отсутствие ошибки) в качестве второго значения в успешном возврате. Таким образом, вызывающий может увидеть, что функция выполнена успешно.

В файле hello/hello.go обработайте ошибку, возвращаемую функцией Hello, вместе со значением, не связанным с ошибкой. Вставьте следующий код в hello.go.

package main

import (
    "fmt"
    "log"

    "example.com/greetings"
)

func main() {
    // Устанавливаем свойства предопределенного Logger, включая
    // префикс записи журнала и флаг отключения печати
    // время, исходный файл и номер строки.
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    // Запрос приветственного сообщения.
    message, err := greetings.Hello("")
    // Если вернулась ошибка, выводим ее в консоль и
    // выходим из программы.
    if err != nil {
        log.Fatal(err)
    }

    // Если ошибок не было, распечатываем возвращенное сообщение
    // в консоль.
    fmt.Println(message)
}

В этом коде вы:

  • Настроете пакет log для печати имени команды ("greetings: ") в начале сообщений журнала без отметки времени или информации об исходном файле.
  • Присвоете переменным оба возвращаемых значения Hello, включая ошибку.
  • Измените аргумент Hello с имени Gladys на пустую строку, чтобы вы могли опробовать свой код обработки ошибок.
  • Найдете значение ошибки, отличное от нуля. В этом случае нет смысла продолжать.
  • Используете функции из пакета log стандартной библиотеки для вывода информации об ошибках. Если вы получили сообщение об ошибке, вы используете функцию Fatal пакета log, чтобы распечатать ошибку и остановить программу.

В командной строке в каталоге hello запустите hello.go, чтобы убедиться, что код работает.

Теперь, когда вы передаете пустое имя, вы получите сообщение об ошибке.

$ go run .
greetings: empty name
exit status 1

Это обычная обработка ошибок в Go: возвращать ошибку как значение, чтобы вызывающий мог ее проверить.

Далее, в следующем посте, вы будете использовать срез Go, чтобы вернуть случайно выбранное приветствие.


Читайте также:


среда, 7 апреля 2021 г.

Создание модуля в Golang: вызов своего кода из другого модуля

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

Примечание. Этот раздел является частью учебного пособия, состоящего из нескольких частей, которое начинается с создания модуля Go.

1. Создайте каталог hello для исходного кода модуля Go. Здесь вы напишете свой код, который будет вызывать greetings.

После создания этого каталога у вас должны быть каталоги hello и greetings на одном уровне иерархии, например:

<home>/
 |-- greetings/
 |-- hello/

Например, если ваша командная строка находится в каталоге greetings, вы можете использовать следующие команды:

cd ..
mkdir hello
cd hello

2. Включите отслеживание зависимостей для кода, который вы собираетесь написать.

Чтобы включить отслеживание зависимостей для вашего кода, запустите команду go mod init, указав ей имя модуля, в котором будет находиться ваш код.

Для целей этого руководства используйте example.com/hello в качестве пути к модулю.

$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello

3. В текстовом редакторе в каталоге hello создайте файл для записи кода и назовите его hello.go.

4. Напишите код для вызова функции Hello, а затем распечатайте возвращаемое значение функции.

Для этого вставьте следующий код в hello.go.

package main

import (
    "fmt"

    "example.com/greetings"
)

func main() {
    // Получаем приветственное сообщение и распечатываем его.
    message := greetings.Hello("Gladys")
    fmt.Println(message)
}

В этом коде вы:

  • Объявляете main пакет. В Go код, выполняемый как приложение, должен находиться в main пакете.
  • Импортируете два пакета: example.com/greetings и пакет fmt. Это дает вашему коду доступ к функциям в этих пакетах. Импорт example.com/greetings (пакет, содержащийся в модуле, который вы создали ранее) дает вам доступ к функции Hello. Вы также импортируете fmt с функциями для обработки ввода и вывода текста (например, вывода текста на консоль).
  • Получаете приветствие, вызвав функцию Hello пакета greetings.

5. Отредактируйте модуль example.com/hello, чтобы использовать локальный модуль example.com/greetings.

Для производственного использования вы опубликуете модуль example.com/greetings из его репозитория (с путем к модулю, который отражает его опубликованное местоположение), где инструменты Go могут найти его для загрузки. На данный момент, поскольку вы еще не опубликовали модуль, вам необходимо адаптировать модуль example.com/hello, чтобы он мог найти код example.com/greetings в вашей локальной файловой системе.

Для этого используйте команду go mod edit, чтобы отредактировать модуль example.com/hello, чтобы перенаправить инструменты Go с его пути к модулю (где модуля нет) в локальный каталог (где он находится).

В командной строке в каталоге hello выполните следующую команду:

$ go mod edit -replace=example.com/greetings=../greetings

Команда указывает, что example.com/greetings следует заменить на ../greetings с целью определения зависимости. После запуска команды файл go.mod в каталоге hello должен содержать директиву replace:

module example.com/hello

go 1.16

replace example.com/greetings => ../greetings

Из командной строки в каталоге hello запустите команду go mod tidy, чтобы синхронизировать зависимости модуля example.com/hello, добавив те, которые требуются кодом, но еще не отслеживаются в модуле.

$ go mod tidy
go: found example.com/greetings in example.com/greetings v0.0.0-00010101000000-000000000000

После завершения команды файл go.mod модуля example.com/hello должен выглядеть следующим образом:

module example.com/hello

go 1.16

replace example.com/greetings => ../greetings

require example.com/greetings v0.0.0-00010101000000-000000000000

Команда нашла локальный код в каталоге greetings, а затем добавила директиву require, чтобы указать, что example.com/hello требует example.com/greetings. Вы создали эту зависимость, когда импортировали пакет greetings в hello.go.

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

Для ссылки на опубликованный модуль в файле go.mod обычно опускается директива replace и используется директива require с помеченным номером версии в конце.

require example.com/greetings v1.1.0

6. В командной строке в каталоге hello запустите свой код, чтобы убедиться, что он работает.

$ go run .
Hi, Gladys. Welcome!

Вы написали два функционирующих модуля.

В следующем посте вы добавите обработку ошибок.


Читайте также:


понедельник, 5 апреля 2021 г.

Создание модуля в Golang

Это первая часть учебного пособия, которое знакомит с некоторыми фундаментальными особенностями языка Go. Если вы только начинаете работать с Go, обязательно ознакомьтесь с Быстрое начало с Go, в котором представлены команда go, модули Go и очень простой код Go.

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

Последовательность этого руководства включает семь кратких тем, каждая из которых иллюстрирует разные части языка.

  • Создание модуля - напишите небольшой модуль с функциями, которые вы можете вызывать из другого модуля.
  • Вызовите свой код из другого модуля - импортируйте и используйте свой новый модуль.
  • Возврат и обработка ошибки - добавьте простую обработку ошибок.
  • Возврат случайного приветствия - обработка данных в срезах (массивы Go с динамическим размером).
  • Возврат приветствия для нескольких людей - храните пары ключ/значение в карте.
  • Добавить тест - используйте встроенные функции модульного тестирования Go для тестирования вашего кода.
  • Скомпилируйте и установите приложение - скомпилируйте и установите код локально.

Предпосылки

  • Некоторый опыт программирования. Код здесь довольно простой, но он помогает кое-что узнать о функциях, циклах и массивах.
  • Инструмент для редактирования вашего кода. Любой текстовый редактор, который у вас есть, будет работать нормально. Большинство текстовых редакторов хорошо поддерживают Go. Наиболее популярными являются VSCode (бесплатно), GoLand (платно) и Vim (бесплатно).
  • Командный терминал. Go хорошо работает с любым терминалом в Linux и Mac, а также в PowerShell или cmd в Windows.

Запустите модуль, который могут использовать другие

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

Код Go сгруппирован в пакеты, а пакеты сгруппированы в модули. Ваш модуль определяет зависимости, необходимые для запуска вашего кода, включая версию Go и набор других модулей, которые ей требуются.

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

1. Откройте командную строку и перейдите в домашний каталог.

В Linux или Mac:

cd

В Windows:

cd %HOMEPATH%

2. Создайте каталог greetings для исходного кода вашего модуля Go.

Например, из вашего домашнего каталога используйте следующие команды:

mkdir greetings
cd greetings

3. Запустите свой модуль с помощью команды go mod init.

Запустите команду go mod init, указав ей путь к вашему модулю - здесь используйте example.com/greetings. Если вы публикуете модуль, это должен быть путь, по которому ваш модуль может быть загружен инструментами Go. Это будет репозиторий вашего кода.

$ go mod init example.com/greetings
go: creating new go.mod: module example.com/greetings

Команда go mod init создает файл go.mod для отслеживания зависимостей вашего кода. Пока что файл включает только имя вашего модуля и версию Go, которую поддерживает ваш код. Но по мере добавления зависимостей в файле go.mod будут перечислены версии, от которых зависит ваш код. Это обеспечивает воспроизводимость сборок и дает вам прямой контроль над тем, какие версии модулей использовать.

4. В текстовом редакторе создайте файл для написания кода и назовите его greetings.go.

5. Вставьте следующий код в файл greetings.go и сохраните файл.

package greetings

import "fmt"

// Hello возвращает приветствие для указанного человека.
func Hello(name string) string {
    // Возвращаем приветствие, включающее имя в сообщение.
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}

Это первый код для вашего модуля. Он возвращает приветствие любому вызывающему абоненту, который его запрашивает. На следующем шаге вы напишете код, вызывающий эту функцию.

В этом коде вы:

  • Объявляете пакет greetings для сбора связанных функций.
  • Реализуйте функцию Hello, чтобы вернуть приветствие.
    Эта функция принимает параметр name, тип которого - строка. Функция также возвращает строку. В Go функция, имя которой начинается с заглавной буквы, может быть вызвана функцией из другого пакета. Это известно в Go как экспортируемое имя.
  • Объявляете переменную message для хранения вашего приветствия.
    В Go оператор := - это ярлык для объявления и инициализации переменной в одной строке (Go использует значение справа для определения типа переменной). Пройдя долгий путь, вы могли бы написать это как:

    var message string
    message = fmt.Sprintf("Hi, %v. Welcome!", name)
    

  • Используете функцию Sprintf пакета fmt для создания приветственного сообщения. Первый аргумент - это строка формата, и Sprintf заменяет значение параметра name на команду формата %v. Вставка значения параметра name завершает текст приветствия.
  • Возвращаете отформатированный текст приветствия вызывающему абоненту.

На следующем шаге вы вызовете эту функцию из другого модуля.


Читайте также:


суббота, 3 апреля 2021 г.

Основы работы с Protocol Buffer в Golang

Это руководство представляет собой базовое введение в работу с protocol buffer в Golang с использованием версии proto3 языка protocol buffer.

Зачем использовать protocol buffer?

Пример, который мы собираемся использовать, представляет собой очень простое приложение "адресной книги", которое может считывать и записывать контактные данные людей в файл и из файла. У каждого человека в адресной книге есть имя, идентификатор, адрес электронной почты и номер контактного телефона.

Как вы сериализуете и извлекаете подобные структурированные данные? Есть несколько способов решить эту проблему:

  • Используйте gobs для сериализации структур данных Go. Это хорошее решение для среды Go, но оно не работает, если вам нужно обмениваться данными с приложениями, написанными для других платформ.
  • Вы можете изобрести специальный способ кодирования элементов данных в одну строку - например, кодирование 4 целых чисел как "12:3:-23:67". Это простой и гибкий подход, хотя он требует написания разового кода кодирования и анализа, а анализ требует небольших затрат времени выполнения. Это лучше всего подходит для кодирования очень простых данных.
  • Сериализуйте данные в XML. Этот подход может быть очень привлекательным, поскольку XML (отчасти) удобочитаем, а библиотеки для работы с XML существуют для многих языков. Это может быть хорошим выбором, если вы хотите поделиться данными с другими приложениями/проектами. Однако, как известно, XML занимает много места, и его кодирование/декодирование может значительно снизить производительность приложений. Кроме того, навигация по дереву XML DOM значительно сложнее, чем обычная навигация по простым полям в классе.

Protocol buffer - это гибкое, эффективное, автоматизированное решение для решения именно этой проблемы. С помощью protocol buffer вы пишете .proto-описание структуры данных, которую хотите сохранить. Исходя из этого, компилятор protocol buffer создает класс, который реализует автоматическое кодирование и анализ данных protocol buffer с эффективным двоичным форматом. Сгенерированный класс предоставляет геттеры и сеттеры для полей, составляющих protocol buffer, и заботится о деталях чтения и записи protocol buffer как отдельной единицы. Важно отметить, что формат protocol buffer поддерживает идею расширения формата с течением времени таким образом, чтобы код все еще мог читать данные, закодированные в старом формате.

Пример кода

Наш пример представляет собой набор приложений командной строки для управления файлом данных адресной книги, закодированным с использованием protocol buffer. Команда add_person_go добавляет новую запись в файл данных. Команда list_people_go анализирует файл данных и выводит данные на консоль.

Вы можете найти полный пример в каталоге примеров репозитория GitHub.

Определение формата вашего протокола

Чтобы создать приложение адресной книги, вам нужно начать с файла .proto. Определения в файле .proto просты: вы добавляете сообщение для каждой структуры данных, которую хотите сериализовать, затем указываете имя и тип для каждого поля в сообщении. В нашем примере файл .proto, определяющий сообщения, называется addressbook.proto.

Файл .proto начинается с объявления пакета, что помогает предотвратить конфликты имен между различными проектами.

syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";

Параметр go_package определяет путь импорта пакета, который будет содержать весь сгенерированный код для этого файла. Имя пакета Go будет последним компонентом пути импорта. Например, в нашем примере будет использоваться имя пакета "tutorialpb".

option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";

Затем у вас есть определения сообщений. Сообщение - это просто агрегат, содержащий набор типизированных полей. Многие стандартные простые типы данных доступны как типы полей, включая bool, int32, float, double и string. Вы также можете добавить дополнительную структуру в свои сообщения, используя другие типы сообщений в качестве типов полей.

message Person {
  string name = 1;
  int32 id = 2;  // Уникальный идентификационный номер этого человека.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;

  google.protobuf.Timestamp last_updated = 5;
}

// Наш файл адресной книги - только один из них.
message AddressBook {
  repeated Person people = 1;
}

В приведенном выше примере сообщение Person содержит сообщения PhoneNumber, а сообщение AddressBook содержит сообщения Person. Вы даже можете определять типы сообщений, вложенные в другие сообщения - как видите, тип PhoneNumber определяется внутри Person. Вы также можете определить типы перечисления (enum), если вы хотите, чтобы одно из ваших полей имело одно из предопределенного списка значений - здесь вы хотите указать, что номер телефона может быть одним из MOBILE, HOME или WORK.

Маркеры " = 1", " = 2" на каждом элементе определяют уникальный "тег", который поле использует в двоичной кодировке. Номера тегов 1-15 требуют для кодирования на один байт меньше, чем более высокие числа, поэтому в целях оптимизации вы можете решить использовать эти теги для часто используемых или повторяющихся элементов, оставив теги 16 и выше для менее часто используемых дополнительных элементов. Каждый элемент в повторяющемся поле требует перекодирования номера тега, поэтому повторяющиеся поля являются особенно хорошими кандидатами для этой оптимизации.

Если значение поля не задано, используется значение по умолчанию: ноль для числовых типов, пустая строка для строк, false для bool. Для встроенных сообщений значением по умолчанию всегда является "экземпляр по умолчанию" или "прототип" сообщения, для которого не задано ни одно из полей. Вызов метода доступа для получения значения поля, которое не было явно задано, всегда возвращает значение этого поля по умолчанию.

Если поле repeated (повторяющееся), поле может повторяться любое количество раз (включая ноль). Порядок повторения значений будет сохранен в protocol buffer. Думайте о повторяющихся полях как о массивах динамического размера.

Вы найдете полное руководство по написанию файлов .proto, включая все возможные типы полей, в Руководстве по языку protocol buffer. Однако не ищите средства, подобные наследованию классов - protocol buffer этого не делают.

Компиляция вашего protocol buffer

Теперь, когда у вас есть .proto, следующее, что вам нужно сделать, это сгенерировать классы, которые вам понадобятся для чтения и записи сообщений AddressBook (и, следовательно, Person и PhoneNumber). Для этого вам нужно запустить protocol buffer компилятор protoc на вашем .proto:

1. Если вы не установили компилятор protoc, время это сделать.

2. Выполните следующую команду, чтобы установить Go protocol buffers плагин:

go install google.golang.org/protobuf/cmd/protoc-gen-go

Плагин компилятора protoc-gen-go будет установлен в $GOBIN, по умолчанию в $GOPATH/bin. Он должен быть в вашем $PATH, чтобы компилятор protoc нашел его. Обновите свой PATH, чтобы компилятор протоколов мог найти плагин:

$ export PATH="$PATH:$(go env GOPATH)/bin"

3. Теперь запустите компилятор, указав исходный каталог (где находится исходный код вашего приложения - текущий каталог используется, если вы не указали значение), целевой каталог (где вы хотите, чтобы сгенерированный код перемещался; часто то же самое, что и $SRC_DIR) и путь к вашему .proto. В этом случае вы должны вызвать:

protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

Поскольку вам нужен код Go, вы используете параметр --go_out - аналогичные параметры предоставляются для других поддерживаемых языков.

Это создает github.com/protocolbuffers/protobuf/examples/go/tutorialpb/addressbook.pb.go в указанном вами целевом каталоге.

Protocol Buffer API

Создание addressbook.pb.go дает вам следующие полезные типы:

  • Структура AddressBook с полем People.
  • Структура Person с полями для Name, Id, Email и Phones.
  • Структура Person_PhoneNumber с полями для Number и Type.
  • Тип Person_PhoneType и значение, определенное для каждого значения в перечислении Person.PhoneType.

Вы можете подробнее узнать о том, что именно генерируется в руководстве Go Generated Code, но по большей части вы можете рассматривать их как совершенно обычные типы Go.

Вот пример из модульных тестов команды list_people того, как вы можете создать экземпляр Person:

p := pb.Person{
        Id:    1234,
        Name:  "John Doe",
        Email: "jdoe@example.com",
        Phones: []*pb.Person_PhoneNumber{
                {Number: "555-4321", Type: pb.Person_HOME},
        },
}

Запись сообщения

Вся цель использования protocol buffer - сериализовать ваши данные, чтобы их можно было проанализировать где-нибудь еще. В Go вы используете функцию Marshal из библиотеки proto для сериализации protocol buffer данных. Указатель на структуру protocol buffer сообщения реализует интерфейс proto.Message. Вызов proto.Marshal возвращает protocol buffer, закодированный в его проводном формате. Например, мы используем эту функцию в команде add_person:

book := &pb.AddressBook{}
// ...

// Записываем новую адресную книгу обратно на диск.
out, err := proto.Marshal(book)
if err != nil {
        log.Fatalln("Failed to encode address book:", err)
}
if err := ioutil.WriteFile(fname, out, 0644); err != nil {
        log.Fatalln("Failed to write address book:", err)
}

Чтение сообщения

Чтобы проанализировать закодированное сообщение, вы используете функцию Unmarshal(b []byte, m Message) из библиотеки proto. Вызов этой функции анализирует данные в b как protocol buffer и помещает результат в m. Итак, чтобы проанализировать файл в команде list_people, мы используем:

// Прочитать существующую адресную книгу.
in, err := ioutil.ReadFile(fname)
if err != nil {
        log.Fatalln("Error reading file:", err)
}
book := &pb.AddressBook{}
if err := proto.Unmarshal(in, book); err != nil {
        log.Fatalln("Failed to parse address book:", err)
}

Расширение Protocol Buffer

Рано или поздно после того, как вы выпустите код, использующий protocol buffer, вы, несомненно, захотите "улучшить" protocol buffer определение. Если вы хотите, чтобы ваши новые буферы имели обратную совместимость, а старые буферы - прямую совместимость - а вы почти наверняка этого хотите - тогда вам нужно соблюдать некоторые правила. В новой версии protocol buffer:

  • вы не должны изменять номера тегов каких-либо существующих полей.
  • вы можете удалять поля.
  • вы можете добавлять новые поля, но вы должны использовать свежие номера тегов (т.е. номера тегов, которые никогда не использовались в этом protocol buffer, даже в удаленных полях).

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

Однако имейте в виду, что новые поля не будут присутствовать в старых сообщениях, поэтому вам нужно будет сделать что-то разумное со значением по умолчанию. Используется значение по умолчанию, зависящее от типа: для строк значением по умолчанию является пустая строка. Для логических значений значение по умолчанию - false. Для числовых типов значение по умолчанию равно нулю.


Читайте также: