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

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

Что такое пакет?

В Go код организован в пакеты. Каждая папка, содержащая код Go, является пакетом. Запускаемые программы должны иметь пакет с именем main, который действует как точка входа в программу. Все остальные пакеты могут называться (почти) как угодно, и они экспортируют код, который можно использовать в других пакетах и ​​исполняемых программах. Пакеты такого типа, которые невозможно запустить, мы по соглашению называем пакетами библиотека.

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

Эмпирические правила

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

1. Скрыть внутренние функции

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

package classifier

// ClassifyImage classifies images as "hotdog" or "not hotdog"
func ClassifyImage(image []byte) (imageType string) {
	return hasHotdogColors(image) && hasHotdogShape(image)
}

func hasHotdogShape(image []byte) bool {
	// internal logic that the application doesn't need to know about
	return true
}

func hasHotdogColors(image []byte) bool {
	// internal logic that the application doesn't need to know about
	return true
}

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

2. Не меняйте API пакета

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

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

3. Не экспортируйте функции из основного

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

4. Пакеты не должны знать о зависимостях

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

package classifier

// ClassifyImage uses a slightly different algorithm if
// the image comes from qvault.io
func ClassifyImage(image []byte, isQvaultImage bool) (imageType string) {
	return hasHotdogColors(image) && hasHotdogShape(image)
}

Вот пример явного нарушения этого правила. Классификатор изображений не должен знать об «образе Qvault», который, как мы можем сделать вывод, является приложением, зависящим от этого пакета. Автор должен был сделать разные типы классификаторов для общего пользования, и тогда иждивенцы пакета смогут выбрать правильный. Два приложения, зависящие от одного и того же пакета, могут не знать друг о друге.

Спасибо за чтение

Напишите мне в твиттере @wagslane, если у вас есть какие-либо вопросы или комментарии.

Переулок на Dev.to: wagslane

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