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

Я занимаюсь разработкой EDR-решений 7 лет.

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

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

Использовать карты как набор

Нам часто нужно проверить наличие чего-либо. Например, мы могли бы захотеть проверить, был ли ранее посещен путь к файлу / URL / ID. В этих случаях мы можем использовать map[string]struct{}. Например:

Использование пустой структуры struct{} означает, что мы не хотим, чтобы часть значения карты занимала какое-либо пространство. Иногда люди используют map[string]bool, но тесты показали, что map[string]struct{} работает лучше как по памяти, так и по времени.

Также стоит отметить, что обычно считается, что операции с картой имеют O(1) временную сложность (StackOverflow), но время выполнения go не дает такой гарантии.

Использование chan struct {} для синхронизации горутин

Каналы могут передавать данные, но это не обязательно. Иногда они нужны нам просто для синхронизации.

В следующем случае канал несет тип данных struct{}, который представляет собой пустую структуру, не занимающую места. Это тот же трюк, что и в предыдущем примере карты:

Использовать близко к трансляции

Продолжая предыдущий пример, если мы запускаем несколько go hello(quit), то вместо отправки нескольких struct{}{} на quit мы можем просто закрыть канал quit для трансляции сигнала:

Обратите внимание, что закрытие канала для трансляции сигнала работает с любым количеством горутин, поэтому close(quit) также применяется в предыдущем примере.

Использование нулевого канала для блокировки выбранного случая

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

Что мы хотим улучшить:

  • отключите case s.dispatchC, когда len(pending) == 0, чтобы код не паниковал
  • отключите case s.eventSource при len(pending) >= maxPending, чтобы не выделять слишком много памяти

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

Предупреждение: будьте осторожны, чтобы не отключать все случаи одновременно, иначе цикл for-select перестанет работать.

Неблокирующее чтение из канала

Иногда мы хотим предлагать услуги по принципу «максимальные усилия». То есть мы намеренно хотим, чтобы канал работал с потерями.

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

  1. Отправка другим получателям вовремя
  2. Избегайте выделения слишком большого количества памяти для ожидающих событий

Анонимный Struct

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

Случай 1: Конфигурация

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

Итак, вместо этого:

Ты можешь сделать это:

Обратите внимание, что struct {...} - это тип переменной Config - теперь вы можете получить доступ к своим значениям конфигурации через Config.Timeout.

Случай 2: контрольные примеры

Допустим, вы хотите протестировать свою причудливую Add() функцию вместо того, чтобы писать множество операторов if-else вроде этого:

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

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

Определенно есть другие сценарии, в которых вам могут пригодиться анонимные структуры. Например, если вы хотите проанализировать следующий JSON, вы можете определить анонимную структуру с вложенными анонимными структурами, чтобы вы могли анализировать ее с помощью библиотеки encoding/json.

Варианты упаковки с функциями

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

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

Используя приведенный выше пример, у нас есть 2 необязательных поля (порт, прокси), которые пользователи могут указать при создании экземпляра Client:

Такой способ упаковки упрощает использование и, что более важно, удобство чтения:

Заключение

Итак, мы говорили о

  • Использование map[string]struct{} как установлено
  • Использование chan struct{} для эффективной синхронизации горутин и использование close() для широковещательной передачи сигналов произвольному количеству горутин.
  • Установка переменной канала в nil, чтобы отключить отдельные случаи
  • Создание каналов с потерями по шаблону select-default
  • Использование анонимных структур для группировки значений конфигурации и тестовых случаев
  • Параметры упаковки как функции

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

Go - очень мощный язык, конструкция которого сильно отличается от большинства знакомых нам языков (например, C / C ++, Python, PHP, Java и т. Д.). Поэтому очень важно правильно использовать его красивый синтаксис, иначе вы можете столкнуться с очень неприятными ошибками, которые либо трудно запустить, либо вы можете не знать, откуда они взялись.

Я попытался изобразить суть Go с помощью приведенных выше шаблонов кода, но они далеки от завершения. Чтобы узнать больше, я бы порекомендовал посмотреть отличные выступления от Google.

Дальнейшее чтение