четверг, 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.


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


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

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