Найти тему
Golang-news

User или *User — везде ли нам нужны указатели структур?

Перейдя с Java на Go несколько лет назад, я мог просто с комфортом украсить все свои типы символом a '*' и на этом закончить. И тем не менее, что-то продолжало меня беспокоить. Это была не страна Java, где все представляет собой замаскированный указатель, размещенный в куче. Разработчики Go, должно быть, сделали указатели явными, чтобы разработчик думал о том, когда их уместно использовать, а не просто размахивал ими, как золотым молотом.

Действительно, когда дело доходит до советов и советов сообщества, мы договорились, что чем больше мы сможем положить в стек (соответственно, чем меньше в кучу), тем лучше. Но код Go выглядит иначе. По большому счету указатели используются исключительно - везде и для всего. Хотя этот  подход «нет времени думать, нужно выполнить работу»  будет работать в любой ситуации, я считаю, что мы можем улучшить наше программное обеспечение, нарисовав простую линию. Вот о чем этот пост.

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

Goofer
Goofer

Структуры примитивны по своей природе

Вот дилемма, над которой я размышлял довольно долгое время. Go, как и его духовный предшественник, C имеет структуры. По своей природе структура представляет собой не что иное, как группировку вокруг нескольких переменных. Скажем, Config структура может иметь атрибуты RootFilePath, EmailServiceToken и Timout— все это простые строки. Другими словами, структура пространства имен этих переменных позволяет нам иметь несколько имен и возрастов и при этом легко различать их.

type Config struct {
RootFilePath string
EmailServiceToken string
Timeout time.Time
}

conf1 := Config{
RootFilePath: "/tmp/conf1", EmailServiceToken: "abcd", Timeout: time.Second*5
}
conf2 := Config{RootFilePath: "/timp/conf2", EmailServiceToken: "efgh"

}

Структура, как и другие примитивы Go, по умолчанию является  типом значения  . Он предназначен для работы как примитив по умолчанию. Если функция получает Config значение в качестве одного из своих параметров или возвращает Config значение в качестве результата, значение будет скопировано, то есть будут скопированы значения всех ее атрибутов.

Что бы это ни стоило, вы можете поделиться conf1 и conf2 даже не использовать указатель на любой из них, и все равно все будет в порядке. В настоящее время это просто  пакеты  (или контейнеры) для примитивных данных.

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

goofer
goofer

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

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

Независимо от парадигмы разработчика (ООП, FP, процедурная и т. д.), большая часть программного обеспечения имеет дело с той или иной формой  объектов  , которые разбрасываются, и функциями, которые работают с этими объектами. Таким образом, в остальной части обсуждения, когда я буду называть что-то объектом, я буду иметь в виду экземпляр некоторого пользовательского типа со своими свойствами и, возможно, методами. Пожалуйста, не принимайте это за то, что я называю Go языком ООП или чем-то в этом роде.

В каждой части программного обеспечения, с которой я сталкивался, я видел три типа объектов:

Объекты краткосрочной стоимости

Их много, но они небольшие и временные. Подумайте об этой Config структуре, которую мы использовали выше. Вы используете его один раз, чтобы настроить вещи, и можете его выбросить. Еще один отличный пример — всевозможные объекты передачи данных, которые мы используем при чтении данных из базы данных, возврате ответа JSON или передаче его в шаблон. Все это, по сути, одно и то же — пакет свойств, который вы заполняете, используете и тут же выбрасываете. Если те же данные не будут запрошены снова немедленно, нет реальной необходимости хранить их в течение долгого времени после того, как они выполнили свою задачу.

Чтобы ответить на вопрос из заголовка. Если User это структура, которую мы используем для чтения данных из users таблицы в базе данных, мы могли бы также назвать ее UserRow и обращаться с ее экземплярами как с объектами-значениями. Нет необходимости размещать их в куче; просто позвольте среде выполнения копировать их туда и обратно.

Долгоживущие односсылочные «служебные» объекты

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

Идеальным примером этого является ваше *DB соединение — вам нужен один его экземпляр, который используется во всем вашем приложении и предоставляет варианты взаимодействия с базой данных. Другим прекрасным примером может быть a *MailSender или a *StripeClient или a, даже вездесущая *App структура, которая обычно становится единственной точкой отсчета для всех других  объектов службы  в наших проектах.

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

Стоимостные «модели», представляющие структуру графа

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

Объект чистого значения, такой как наш, UserRow прост, содержит фиксированное конечное количество полей (имя, возраст и т. д.). Предположим, у нас есть такая же плоская PurchaseRow структура, содержащая покупку, сделанную пользователем. Все идет нормально. Что, если бы мы теперь захотели объединить оба в User объект с собственным фрагментом Purchase, каждый из которых ссылается на того, User кто их купил? Мы не можем сделать это, не введя где-нибудь ссылку. Тот факт, что наш User содержит фрагмент Purchase экземпляров, означает, что у нас уже есть эта ссылка. Таким образом, для согласованности и простоты понимания кода я всегда рассматривал более сложные объекты модели как типы указателей .