Обложка: Как стать разработчиком на Go: 10 шагов

Как стать разработчиком на Go: 10 шагов

Никита Сапогов
Никита Сапогов

Руководитель backend-разработки в Ситилинк и ментор Solvery

Ментор Solvery и руководитель backend-разработки в Ситилинк рассказал, с чего начать, чтобы стать разработчиком на GO и как написать свою первую программу.

1. Пройдите Tour Of Go

Чтобы стать разработчиком Go, для начала перейдите на сайт tour.golang.org/, чтобы узнать основы. Советую пройти там все задания. Этот тур познакомит вас с особенностями и синтаксисом языка.

2. Начните писать первую программу на Go

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

Не задумывайтесь о код-стайле, уже есть go fmt, после запуска которого ваш код будет оформлен правильно.

3. Обрабатывайте ошибки

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

Представим себе простейший складыватель чисел:

func main() {
	sum := 0
	for i, arg := range os.Args[1:] {
		n, err := strconv.Atoi(arg)
		if err != nil {
			log.Fatalf("can't parse arg #%d '%s'", i, arg)
		}

		sum += n
	}

	log.Printf("sum is %d", sum)
}

В этом простом примере указано, что конвертация из строки в число может закончиться с ошибкой. И вроде бы это и так ясно, но уже в данном маленьком примере кода можно задать себе вопрос, а как я буду действовать, если передали неправильный аргумент? Завершить приложение? Взять это значение, как 0? Перестать перебирать аргументы?

Как много вопросов, благодаря одной проверке ошибки, не правда ли? Именно в этом сила обработки ошибок в Go, вы всегда обладаете полной информацией о поведении метода. Для вас как разработчика Go никогда не будет сюрприза или неявного поведения, вы всегда выбираете сами, как обработать ту или иную ошибку, начиная от конвертации строки в число, заканчивая превышением одновременного кол-ва запросов к ресурсу.

4. Пишите простой и ясный код

Хотя эта истина присуща любому языку программирования, но для Go — часть философии. В нём все сделано так, чтобы вы писали код как можно проще и яснее. Создатели языка не добавляют новые фича, если их результата можно достичь уже существующими возможностями языка. Например, сделать метод map/reduce для map’ы, ведь это уже можно сделать обычным for/if.

5. Конкурентность и Параллелизм

Если взять слова concurrency и parallelism, и попробовать перевести, то в обоих случаях в переводе получите «параллелизм», но это два разных понятия. Параллелизм — просто запуск двух операций одновременно, а конкурентность — это про архитектуру приложения, когда два (и более) одновременно работающих потока начинают общаться друг с другом, и это по-настоящему сложно. Для параллелизма у разработчика Go есть горутины, а для конкурентности — каналы, пакет sync с Mutex, RWMutes, WaitGroup, пакет Atomic, такие понятия как гонка данных.

6. Мьютексы для синхронизации доступа, каналы для оркестрации

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

Но как понять, когда что использовать? Как говорил Роб Пайк, один из создателей языка Go: «Используйте мьютексы для синхронизации доступа, а каналы для оркестрации». То есть, если у вас есть конкурентный доступ к любой области памяти, используйте мьютекс, а если вы хотите обмениваться сообщениями, делать «сигнализацию», то используйте каналы.

7. Выучите определение конкурентного доступа

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

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

  1. Имеют ли несколько потоков (горутин) доступ к этой области памяти (переменной, свойству структуры и так далее)?
  2. Изменяет ли один из потоков (горутин) эту область памяти?

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

8. Используйте гексагональную архитектуру

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

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

  • регулярно попадаете на ошибку циклической зависимости пакетов;
  • трудно/невозможно написать тесты.

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

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

Определите что является сутью вашего приложения, в чём его настоящая ценность, и поместите это в центр/ядро вашей архитектуры и вокруг этого ядра реализуйте его части при помощи других технологий mysql, postgre, mongodb, tarantool, memcache, http, grpc, сторонних API и так далее.

9. Генератор кода — ваш лучший друг

Конечно, начнём издалека и поговори про generic’и, ведь это такая нужная и важная вещь в любом языке программирования (нет), что жить и программировать нельзя на языке без generic’ов (тоже нет). Но почему часть людей жалуется на отсутствие generic’ов, а часть уже живет с ними счастливо и не знают бед? Ответ прост —генерация кода.

Разработчики Go настолько часто используют генерацию кода, что даже в официальном инструментарии есть команда go generate! Помимо стандартных решений проблемы генерацией кода во всяких фреймворках, вы будете применять генерацию кода для решения следующих проблем:

  • генерацию клиента/сервера из всяких спецификаций (swagger, grpc);
  • генерация моков для тестирования;
  • генерация типов для эмитации generic’ов;
  • генерировать супер оптимизированные (un)marshal json’а, да-да, самый быстрый способ превратить структуру в строку в виде json просто конкатенировать строку, именно этим и будет заниматься сгенерированный код.

Я даже слышал, что при помощи генерации кода делают di container в которых сохранена строгая типизация, а не применяется повсеместно пустой интерфейс(interface{}).

10. Стек и куча — стоит призадуматься

Прежде всего стоит вспомнить, что:

  • в Go при вызове функции каждый аргумент копируется и передается в вызываемую функцию;
  • в Go применяется сборщик мусора, который сам очищает heap от ненужных сущностей.

Применять указатели или не применять указатели? И вроде ответ очевиден — применяйте указатели, ведь копировать придется всегда N байт независимо от размера структуры, экономия на лицо! Но не все так просто.

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

Как только вы решили использовать указатель и передать его извне функции, происходит сразу две вещи:

  • выделяется память в куче, что занимает на порядок больше времени, нежели в сделать это в стеке.
  • увеличивается объём работы сборщика мусора, который теперь вынужден следить на новой сущностью в куче.

Есть большое количество статей на эту тему, с исследованиями, бенчмарками и так далее, тут сделаем небольшую выжимку:

  • переменную надо изменить — передавайте по указателю.;
  • структура состоит из 100+ полей — передавайте по указателю;
  • переменную копировать нельзя (внутри используется mutex или другие причины) — передавайте по указателю;
  • во всех остальных случаях передавайте по значению.

Если короче, где без указателя никак, передавайте по указателю, иначе —по значению.

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