воскресенье, 21 марта 2021 г.

Как писать Go код (с использованием модулей)

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

Примечание. В этом посте предполагается, что вы используете Go 1.13 или новее, а переменная среды GO111MODULE не установлена. Если вы ищете старую версию этого поста, предшествующую модулям, она находится здесь.

Организация кода

Программы Go организованы в пакеты. Пакет - это набор исходных файлов в одном каталоге, которые скомпилированы вместе. Функции, типы, переменные и константы, определенные в одном исходном файле, видны всем другим исходным файлам в том же пакете.

Репозиторий содержит один или несколько модулей. Модуль - это набор связанных пакетов Go, которые выпускаются вместе. Репозиторий Go обычно содержит только один модуль, расположенный в корне репозитория. Файл с именем go.mod объявляет путь к модулю: префикс пути импорта для всех пакетов в модуле. Модуль содержит пакеты в каталоге, содержащем его файл go.mod, а также подкаталоги этого каталога до следующего подкаталога, содержащего другой файл go.mod (если есть).

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

Путь к каждому модулю не только служит префиксом пути импорта для его пакетов, но также указывает, где команда go должна искать его для его загрузки. Например, чтобы загрузить модуль golang.org/x/tools, команда go будет обращаться к репозиторию, указанному https://golang.org/x/tools.

Путь импорта - это строка, используемая для импорта пакета. Путь импорта пакета - это путь к модулю, соединенный с его подкаталогом внутри модуля. Например, модуль github.com/google/go-cmp содержит пакет в каталоге cmp/. Путь импорта этого пакета - github.com/google/go-cmp/cmp. Пакеты в стандартной библиотеке не имеют префикса пути к модулю.

Ваша первая программа

Чтобы скомпилировать и запустить простую программу, сначала выберите путь к модулю (мы будем использовать example.com/user/hello) и создайте файл go.mod, который его объявляет:

$ mkdir hello # Или клонируйте его, если он уже существует в системе контроля версий.
$ cd hello
$ go mod init example.com/user/hello
go: creating new go.mod: module example.com/user/hello
$ cat go.mod
module example.com/user/hello

go 1.16
$

Первым оператором в исходном файле Go должно быть имя пакета. Исполняемые команды всегда должны использовать пакет main.

Затем создайте внутри этого каталога файл с именем hello.go, содержащий следующий код Go:

package main

import "fmt"

func main() {
	fmt.Println("Hello, world.")
}

Теперь вы можете собрать и установить эту программу с помощью инструмента go:

$ go install example.com/user/hello
$

Эта команда создает команду hello, создавая исполняемый двоичный файл. Затем он устанавливает этот двоичный файл как $HOME/go/bin/hello (или, в Windows, %USERPROFILE%\go\bin\hello.exe).

Каталог установки управляется переменными среды GOPATH и GOBIN. Если установлен GOBIN, в этот каталог устанавливаются двоичные файлы. Если установлен GOPATH, двоичные файлы устанавливаются в подкаталог bin первого каталога в списке GOPATH. В противном случае двоичные файлы устанавливаются в подкаталог bin GOPATH по умолчанию ($HOME/go или %USERPROFILE%\go).

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

$ go env -w GOBIN=/somewhere/else/bin
$

Чтобы отключить переменную, ранее установленную с помощью go env -w, используйте go env -u:

$ go env -u GOBIN
$

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

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

$ go install example.com/user/hello

$ go install .

$ go install

Затем запустим программу, чтобы убедиться, что она работает. Для дополнительного удобства мы добавим каталог установки в наш PATH, чтобы упростить запуск двоичных файлов:

$ export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
$ hello
Hello, world.
$

Пользователи Windows могут проконсультироваться в этом посте по установке %PATH%.

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

$ git init
Initialized empty Git repository in /home/user/hello/.git/
$ git add go.mod hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
 1 file changed, 7 insertion(+)
 create mode 100644 go.mod hello.go
$

Команда go находит репозиторий, содержащий заданный путь к модулю, запрашивая соответствующий HTTPS URL-адрес и считывая метаданные, встроенные в HTML ответ. Многие службы хостинга уже предоставляют эти метаданные для репозиториев, содержащих код Go, поэтому самый простой способ сделать ваш модуль доступным для использования другими - это, как правило, сделать так, чтобы его путь модуля соответствовал URL-адресу репозитория.

Импорт пакетов из вашего модуля

Напишем пакет morestrings и воспользуемся им из программы hello. Сначала создайте каталог для пакета с именем $HOME/hello/morestrings, а затем файл с именем reverse.go в этом каталоге со следующим содержимым:

// Пакет morestrings реализует дополнительные функции  
// для управления UTF-8 закодированными строками сверх того, 
// что предусмотрено в стандартном пакете "strings".
package morestrings

// ReverseRunes возвращает строку аргументов, 
// перевернутую по рунам слева направо.
func ReverseRunes(s string) string {
	r := []rune(s)
	for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return string(r)
}

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

Давайте проверим, что пакет компилируется с помощью go build:

$ cd $HOME/hello/morestrings
$ go build
$

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

Убедившись, что пакет morestrings собирается, давайте воспользуемся им из программы hello. Для этого измените исходный $HOME/hello/hello.go, чтобы использовать пакет morestrings:

package main

import (
	"fmt"

	"example.com/user/hello/morestrings"
)

func main() {
	fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}

Установите программу hello:

$ go install example.com/user/hello

Запустив новую версию программы, вы должны увидеть новое перевернутое сообщение:

$ hello
Hello, Go!

Импорт пакетов из удаленных модулей

Путь импорта может описывать, как получить исходный код пакета с помощью системы контроля версий, такой как Git или Mercurial. Инструмент go использует это свойство для автоматической загрузки пакетов из удаленных репозиториев. Например, чтобы использовать github.com/google/go-cmp/cmp в своей программе:

package main

import (
	"fmt"

	"example.com/user/hello/morestrings"
	"github.com/google/go-cmp/cmp"
)

func main() {
	fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
	fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}

Теперь, когда у вас есть зависимость от внешнего модуля, вам нужно загрузить этот модуль и записать его версию в файл go.mod.
Команда go mod tidy добавляет требования к отсутствующим модулям для импортированных пакетов и удаляет требования к модулям, которые больше не используются.

$ go mod tidy
go: finding module for package github.com/google/go-cmp/cmp
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.5.4
$ go install example.com/user/hello
$ hello
Hello, Go!
  string(
- 	"Hello World",
+ 	"Hello Go",
  )
$ cat go.mod
module example.com/user/hello

go 1.16

require github.com/google/go-cmp v0.5.4
$

Зависимости модулей автоматически загружаются в подкаталог pkg/mod каталога, указанного переменной среды GOPATH. Загруженное содержимое для данной версии модуля совместно используется всеми другими модулями, которым требуется эта версия, поэтому команда go помечает эти файлы и каталоги как доступные только для чтения. Чтобы удалить все загруженные модули, вы можете передать флаг -modcache для очистки:

$ go clean -modcache
$

Тестирование

Go имеет легкий фреймворк тестирования, состоящую из команды go test и пакета testing.

Вы пишете тест, создавая файл с именем, заканчивающимся на _test.go, который содержит функции с именем TestXXX с сигнатурой func (t *testing.T). Фреймворк тестирования запускает каждую такую ​​функцию; если функция вызывает функцию сбоя, такую ​​как t.Error или t.Fail, тест считается неудачным.

Добавьте тест в пакет morestrings, создав файл $HOME/hello/morestrings/reverse_test.go, содержащий следующий код Go.

package morestrings

import "testing"

func TestReverseRunes(t *testing.T) {
	cases := []struct {
		in, want string
	}{
		{"Hello, world", "dlrow ,olleH"},
		{"Hello, 世界", "界世 ,olleH"},
		{"", ""},
	}
	for _, c := range cases {
		got := ReverseRunes(c.in)
		if got != c.want {
			t.Errorf("ReverseRunes(%q) == %q, want %q", c.in, got, c.want)
		}
	}
}

Затем запустите тест с помощью go test:

$ go test
PASS
ok  	example.com/user/morestrings 0.165s
$

Что дальше

Чтобы ознакомиться с оcновами Go воспользуйтесь серией постов Основы Go.

Подборка Эффективный Go содержит советы по написанию понятного идиоматического Go кода.


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


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

Отправить комментарий