Показаны сообщения с ярлыком пакеты в Go. Показать все сообщения
Показаны сообщения с ярлыком пакеты в Go. Показать все сообщения

четверг, 11 июня 2020 г.

Руководство по стилю для пакетов Golang

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

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

Пакеты

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

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

Организация пакетов

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

Используйте несколько файлов

Пакет - это каталог с одним или несколькими файлами Go. Не стесняйтесь разделить ваш код на столько файлов, сколько логически целесообразно для оптимальной читабельности.

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

- doc.go     // пакетная документация
- headers.go // Типы и код заголовков HTTP
- cookies.go // Типы и код cookies HTTP
- http.go    // Реализация HTTP-клиента, 
             // типы запросов и ответов и т. д.

Держите типы близко

Как правило, держите типы ближе к месту их использования. Это позволяет любому сопровождающему (а не только первоначальному автору) легко найти тип. Хорошее место для типа структуры Header может быть в headers.go.

$ cat headers.go
package http

// Header represents an HTTP header.
type Header struct {...}

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

Организовать по ответственности

Обычной практикой в ​​других языках является объединение типов в пакет, называемый models или types. В Go мы организуем код по их функциональным обязанностям.

package models // НЕ ДЕЛАЙТЕ ЭТОГО !!!

// User represents a user in the system.
type User struct {...}

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

package mngtservice

// User represents a user in the system.
type User struct {...}

func UsersByQuery(ctx context.Context, q *Query) ([]*User, *Iterator, error)

func UserIDByEmail(ctx context.Context, email string) (int64, error)

Оптимизировать для godoc

Это хорошее упражнение - использовать godoc на ранних этапах разработки API вашего пакета, чтобы увидеть, как ваши концепции будут отображаться в документации. Иногда визуализация также влияет на дизайн. Godoc - это способ, которым ваши пользователи будут потреблять пакет, поэтому можно настроить вещи, чтобы сделать их более доступными. Запустите godoc -http=<hostport>, чтобы запустить сервер godoc локально.

Приведите примеры, чтобы заполнить пробелы

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

$ godoc cloud.google.com/go/datastore
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error)
...

NewClient работает с option.ClientOptions, но ни пакет datastore, ни пакет option не экспортируют все типы option.

$ godoc google.golang.org/extraoption
func WithCustomValue(v string) option.ClientOption
...

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

Примеры - это хороший способ улучшить видимость менее обнаруживаемого пакета. Например, пример для datastore.NewClient может ссылаться на пакет extraoption.

Не экспортировать из main

Идентификатор может быть экспортирован, чтобы разрешить доступ к нему из другого пакета.

main пакеты не импортируются, поэтому экспорт идентификаторов из main пакетов не требуется. Не экспортируйте идентификаторы из main пакета, если вы собираете пакет в двоичный файл.

Исключениями из этого правила могут быть main пакеты, встроенные в плагин .so, .a или Go. В таких случаях код Go может использоваться из других языков с помощью функции экспорта cgo, и для этого требуются идентификаторы экспорта.

Наименование пакета

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

Только строчные

Имена пакетов должны быть строчными. Не используйте snake_case или camelCase в именах пакетов.

Короткие, но представительные имена

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

Избегайте слишком широких имен пакетов, таких как "common" и "util".

import "pkgs.org/common" // НЕ ДЕЛАЙТЕ ТАК !!!

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

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

Очистите пути импорта

Старайтесь не раскрывать свою пользовательскую структуру хранилища пользователям. Избегайте использования разделов src/, pkg/ в ваших путях импорта.

github.com/user/repo/src/httputil   // НЕ ДЕЛАЙТЕ ЭТОГО, ИЗБЕГАЙТЕ SRC!!

github.com/user/repo/gosrc/httputil // НЕ ДЕЛАЙТЕ ЭТОГО, ИЗБЕГАЙТЕ GOSRC!!

Нет множественного числа

На самом деле, имена пакетов не во множественном числе. Это удивительно для программистов, которые пришли из других языков и сохраняют старую привычку использовать множественные имена. Не называйте пакет httputils, а используйте httputil

package httputils  // НЕ ДЕЛАЙТЕ ЭТОГО, ИСПОЛЬЗУЙТЕ ЕДИНСТВЕННОЕ ЧИСЛО!!

Переименования должны следовать тем же правилам

Если вы импортируете более одного пакета с одинаковым именем, вы можете локально переименовать имена пакетов. Переименования должны следовать тем же правилам, которые указаны в этом посте. Нет правила, какой пакет вы должны переименовать. Если вы переименовываете стандартную библиотеку пакетов, было бы неплохо добавить префикс go, чтобы сделать самодостаточным документом, что это пакет "стандартной библиотеки Go", например, gourl, goioutil.

import (
    gourl "net/url"

    "myother.com/url"
)

Обеспечить vanity URL

go get поддерживает получение пакетов по URL, который отличается от URL репозитория пакета. Эти URL-адреса называются vanity и требуют, чтобы вы обслуживали страницу с конкретными метатегами, которые распознают инструменты Go. Вы можете обслуживать пакет с пользовательским доменом и путем, используя vanity URL.

Например,

$ go get cloud.google.com/go/datastore

за кулисами извлекает исходный код из https://code.googlesource.com/gocloud и помещает его в свое рабочее пространство в папке $GOPATH/src/cloud.google.com/go/datastore.

Учитывая, что code.googlesource.com/gocloud уже обслуживает этот пакет, можно ли получить пакет с этого URL? Ответ - нет, если вы введете vanity URL.

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

package datastore // import "cloud.google.com/go/datastore"

Пакетная документация

Всегда документируйте пакет. Документация пакета - это комментарий верхнего уровня, непосредственно предшествующий предложению пакета. Для неосновных пакетов godoc всегда начинается с "Package {pkgname}" и следует с описанием. Для основных (main) пакетов документация должна объяснять двоичный файл.

// Package ioutil implements some I/O utility functions.
package ioutil

// Command gops lists all the processes running on your system.
package main

// Sample helloworld demonstrates how to use x.
package main

Используйте doc.go

Иногда пакетные документы могут быть очень длинными, особенно если они содержат подробную информацию об использовании и рекомендации. Переместите godoc пакета в файл doc.go.


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


четверг, 27 февраля 2020 г.

Пакеты в Golang: объявление, импорт, загрузка, документирование

Каждая программа Go состоит из пакетов, и каждый пакет имеет путь импорта:

"fmt"
"math/rand"
"github.com/yourbasic/graph"

Пакеты в стандартной библиотеке имеют короткие пути импорта, такие как "fmt" и "math/rand". Сторонние пакеты, такие как "github.com/yourbasic/graph", обычно имеют путь импорта, который включает в себя хостинг (github.com) и название организации (yourbasic).

По соглашению имя пакета совпадает с последним элементом пути импорта:

fmt
rand
yourbasic

Ссылки на определения других пакетов всегда должны иметь префикс с именами их пакетов, и только заглавные имена из других пакетов доступны.

package main

import (
    "fmt"
    "math/rand"

    "github.com/yourbasic/graph"
)

func main() {
    n := rand.Intn(100)
    g := graph.New(n)
    fmt.Println(g)
}

Объявление пакета

Каждый исходный файл Go начинается с объявления пакета, который содержит только имя пакета.

Например, файл src/math/rand/exp.go, являющийся частью реализации пакета math/rand, содержит следующий код.

package rand
  
import "math"
  
const re = 7.69711747013104972
…

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

Конфликты имен пакетов

Вы можете настроить имя, под которым вы ссылаетесь на импортируемый пакет.

package main

import (
    csprng "crypto/rand"
    prng "math/rand"

    "fmt"
)

func main() {
    n := prng.Int() // псевдослучайное число
    b := make([]byte, 8)
    // криптографически безопасное псевдослучайное число
    csprng.Read(b) 
    fmt.Println(n, b)
}

Точки в импорте

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

package main

import (
    "fmt"
    . "math"
)

func main() {
    fmt.Println(Sin(Pi/2)*Sin(Pi/2) + Cos(Pi)/2) // 0.5
}

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

Загрузка пакета

Команда go get загружает пакеты, названные путями импорта, вместе с их зависимостями, а затем устанавливает пакеты.

$ go get github.com/go-sql-driver/mysql

Путь импорта соответствует хранилищу кода. Это уменьшает вероятность будущих конфликтов имен.

Пакетная документация

На веб-сайте GoDoc размещена документация для всех общедоступных пакетов Go на Bitbucket, GitHub, Google Project Hosting и Launchpad:

https://godoc.org/fmt
https://godoc.org/math/rand
https://godoc.org/github.com/go-sql-driver/mysql

Команда godoc извлекает и генерирует документацию для всех локально установленных программ Go. Следующая команда запускает веб-сервер, который представляет документацию по адресу http://localhost:6060/.

$ godoc -http=:6060


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


Купить gopher

четверг, 11 июля 2019 г.

Списки пакетов и шаблоны в Golang

Многие команды применяются к набору пакетов:

go action [packages]

Обычно [packages] представляет собой список путей импорта.

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

В противном случае путь импорта P обозначает пакет, найденный в каталоге DIR/src/P для некоторого DIR, указанного в переменной среды GOPATH (более подробно см. go help gopath).

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

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

- "main" обозначает пакет верхнего уровня в отдельном исполняемом файле.

- "all" распространяется на все пакеты, найденные во всех деревьях GOPATH. Например, 'go list all' перечисляет все пакеты в локальной системе. При использовании модулей "all" распространяется на все пакеты в основном модуле и их зависимости, в том числе зависимости, необходимые для тестирования любого из них.

- "std", как и все, распространяется только на пакеты в стандартной библиотеке Go.

- "cmd" распространяется на команды репозитория Go и их внутренние библиотеки.

Пути импорта, начинающиеся с "cmd/", соответствуют только исходному коду в репозитории Go.

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

Чтобы сделать общие шаблоны более удобными, существует два особых случая. Во-первых, /... в конце шаблона может соответствовать пустой строке, так что net/... соответствует и net, и пакетам в его подкаталогах, например, net/http. Во-вторых, любой разделенный косой чертой элемент шаблона, содержащий подстановочный знак, никогда не участвует в совпадении элемента "vendor" в пути к вендорному пакету, так что ./... не соответствует пакетам в подкаталогах ./vendor или ./mycode/vendor, но ./vendor/... и ./mycode/vendor/... соответствуют. Однако обратите внимание, что каталог с именем vendor, который сам содержит код, не является вендорным пакетом: cmd/vendor будет командой с именем vendor, и шаблон cmd/... соответствует ей.

В пути импорта также можно указать пакет для загрузки из удаленного хранилища. Запустите go help importpath для получения подробной информации.

Каждый пакет в программе должен иметь уникальный путь импорта. По договоренности, это организовано путем запуска каждого пути с уникальным префиксом, который принадлежит вам. Например, все внутренние пути в Google начинаются с 'google', а пути, обозначающие удаленные репозитории, начинаются с пути к коду, например, 'github.com/user/repo'.

Пакеты в программе не обязательно должны иметь уникальные имена пакетов, но есть два зарезервированных имени пакета со специальным значением. Имя main указывает на команду, а не на библиотеку. Команды встроены в бинарные файлы и не могут быть импортированы. Имя documentation указывает на документацию для не-Go программы в каталоге. Файлы в документации пакета игнорируются командой go.

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

Каталог и имена файлов, начинающиеся с "." или "_" игнорируются инструментом go, как и каталоги с именем "testdata".


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


воскресенье, 23 июня 2019 г.

Спецификация Go: инициализация пакета

Внутри пакета переменные уровня пакета инициализируются в порядке объявления, но после любой из переменных, от которых они зависят.

Точнее, переменная уровня пакета считается готовой к инициализации, если она еще не инициализирована и не имеет выражения инициализации или ее выражение инициализации не зависит от неинициализированных переменных. Инициализация продолжается путем многократной инициализации следующей переменной уровня пакета, которая является самой ранней в порядке объявления и готова к инициализации, пока не будет готовых переменных для инициализации.

Если какие-либо переменные все еще неинициализированы после завершения этого процесса, эти переменные являются частью одного или нескольких циклов инициализации, и программа недействительна.

Порядок объявления переменных, объявленных в нескольких файлах, определяется порядком, в котором файлы представляются компилятору: переменные, объявленные в первом файле, объявляются перед любой из переменных, объявленных во втором файле, и так далее.

Анализ зависимостей не основывается на фактических значениях переменных, только на лексических ссылках на них в источнике, анализируемых транзитивно. Например, если выражение инициализации переменной x относится к функции, тело которой относится к переменной y, то x зависит от y. В частности:

  • Ссылка на переменную или функцию - это идентификатор, обозначающий эту переменную или функцию.
  • Ссылка на метод m - это значение метода или выражение метода в форме t.m, где (статический) тип t не является типом интерфейса, а метод m находится в наборе методов t. Неважно, вызывается ли результирующее значение функции t.m.
  • Переменная, функция или метод x зависят от переменной y, если выражение или тело инициализации x (для функций и методов) содержит ссылку на y или на функцию или метод, который зависит от y.

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

Например, с учетом объявлений

var (
    a = c + b
    b = f()
    c = f()
    d = 3
)

func f() int {
    d++
    return d
}

порядок инициализации d, b, c, a.

Переменные также могут быть инициализированы с использованием функций с именем init, объявленных в блоке пакета, без аргументов и без параметров результата.

func init() { … }

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

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

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

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


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


суббота, 22 июня 2019 г.

Спецификация Go: пакеты

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

Организация исходного файла

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

SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .

Пункт package

Пункт package начинает каждый исходный файл и определяет пакет, к которому принадлежит файл.

PackageClause  = "package" PackageName .
PackageName    = identifier .

PackageName не должно быть пустым идентификатором. Пример:

package math

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

Import объявления

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

ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec = [ "." | PackageName ] ImportPath .
ImportPath = string_lit .

PackageName используется в квалифицированных идентификаторах для доступа к экспортированным идентификаторам пакета в исходном импортирующем файле. Это объявлено в блоке файла. Если PackageName опущено, по умолчанию используется идентификатор, указанный в предложении пакета импортируемого пакета. Если вместо имени появляется явный период (.), все экспортированные идентификаторы пакета, объявленные в блоке пакета этого пакета, будут объявлены в блоке файла импортирующего исходного файла и должны быть доступны без квалификатора.

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

Ограничение реализации: компилятор может ограничивать ImportPaths непустыми строками, используя только символы, принадлежащие общим категориям Unicode L, M, N, P и S (графические символы без пробелов), а также может исключать символы !"#$%&'()*,:;<=>?[\]^`{|} и символ замены Unicode U+FFFD.

Предположим, что мы скомпилировали пакет, содержащий выражение пакета package math, которое экспортирует функцию Sin, и установили скомпилированный пакет в файл, обозначенный как «lib/math». В следующей таблице показано, как обращаться к Sin в файлах, которые импортируют пакет после различных типов объявлений импорта.

Import declaration          локальное имя Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin

Декларация импорта объявляет отношение зависимости между импортирующим и импортированным пакетом. Запрещается импортировать сам пакет, прямо или косвенно, или напрямую импортировать пакет без ссылки на какие-либо экспортируемые идентификаторы. Чтобы импортировать пакет исключительно для его побочных эффектов (инициализация), используйте пустой идентификатор в качестве явного имени пакета:

import _ "lib/math"

Пример пакета

Вот полный Go пакет, который реализует конкуррентный отсеиватель простых чисел.

package main

import "fmt"

// Отправляем последовательность 2, 3, 4, … в канал 'ch'.
func generate(ch chan<- int) {
  for i := 2; ; i++ {
    ch <- i  // Отправляем 'i' в канал 'ch'.
  }
}

// Копируем значения из канала 'src' в канал 'dst',
// удаляем те что делимы на 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
  for i := range src {  // Проходим в цикле по значениям 
                        // полученным из 'src'.
    if i%prime != 0 {
      dst <- i  // Отправляем 'i' в канал 'dst'.
    }
  }
}

// Отсеиватель простых чисел: 
// Гирляндный фильтр работает вместе.
func sieve() {
  ch := make(chan int)  // Создаем новый канал.
  go generate(ch)       // Запускаем generate() 
                        // как подпроцесс.
  for {
    prime := <-ch
    fmt.Print(prime, "\n")
    ch1 := make(chan int)
    go filter(ch, ch1, prime)
    ch = ch1
  }
}

func main() {
  sieve()
}


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


четверг, 14 марта 2019 г.

Go Code Review Comments: Имена пакетов

Все ссылки на имена в вашем пакете будут выполняться с использованием имени пакета, поэтому вы можете опустить это имя в идентификаторах. Например, если вы находитесь в пакете chubby, вам не нужен тип ChubbyFile, который клиенты будут записывать как chubby.ChubbyFile. Вместо этого назовите тип File, который клиенты будут записывать как chubby.File. Избегайте бессмысленных имен пакетов, таких как util, common, misc, api, types и interfaces.


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


вторник, 19 февраля 2019 г.

Go FAQ: Как создать многофайловый пакет в Go?

Поместите все исходные файлы для пакета в каталог самостоятельно. Исходные файлы могут ссылаться на элементы из разных файлов по желанию; нет необходимости в предварительных декларациях (forward declarations) или заголовочном файле (header file).

Помимо разделения на несколько файлов, пакет будет скомпилирован и протестирован как однофайловый пакет.


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