Уже 4 декабря в Слёрме стартует поток Go для инженеров. Мы продолжаем выпускать вводные материалы, которые помогут освоить азы языка и уже после — идти в глубину. Наш предыдущий материал по теме Go — дебажим Golang с помощью Delve.

А теперь к новому материалу! 

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

1. Читаемость кода имеет значение

Одним из основных принципов Go является читаемость. Код на языке Go должен быть легко читаем и понятен. Для этого требуется: 

Использовать понятные имена

Выбирайте осмысленные и понятные имена переменных и функций. Это облегчит другим (и вам в будущем) понимание вашего кода.

// Bad:
x := 42

// Good:
age := 42

Форматируйте свой код

Go поставляется со встроенным инструментом форматирования `gofmt`, который обеспечивает единый стиль кода. Используйте его для автоматического форматирования кода и поддержания единого стиля во всем проекте.

gofmt -w your_code.go

Делайте функции небольшими

Функции должны выполнять одну задачу и делать ее хорошо. Если функция становится слишком длинной или сложной, подумайте о том, чтобы разбить ее на более мелкие функции.

Вдумчиво комментируйте

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

2. Обработка ошибок

Обработка ошибок — важнейший аспект написания надежного кода на Go. 

Возврат ошибок

Функции, которые могут возвращать ошибки, должны возвращать значение типа `error`. Всегда проверяйте ошибки и обрабатывайте их соответствующим образом. Избегайте игнорирования ошибок с помощью `_`.

// Bad:
_, err := doSomething()
// Error ignored

// Good:
result, err := doSomething()
if err != nil {
 // Handle the error
}

Используйте ошибки в качестве значений

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

Добавляйте контекст к ошибкам

При передаче ошибок по стеку вызовов используйте `fmt.Errorf` или пакет типа github.com/pkg/errors для добавления контекста к ошибкам. Это помогает при отладке.

import (
 “fmt”
 “github.com/pkg/errors”
)
func someFunction() error {
 err := doSomething()
 if err != nil {
 return errors.Wrap(err, “failed to do something”)
 }
 return nil
}

3. Параллелизм

Примитивы параллелизма языка Go являются его отличительной особенностью. 

Используйте горутины

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

func main() {
 go worker1()
 go worker2()
 // Wait for the workers to finish
 time.Sleep(time.Second)
}

Общение через каналы

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

ch := make(chan int)
go func() {
 ch <- 42
 close(ch)
}()

4. Структуры и интерфейсы

Система типов Go проста и в то же время гибка. Для написания удобного кода:

Определите структуры

Используйте структуры для группировки связанных данных. Это делает ваш код более организованным и читаемым.

type Person struct {
 FirstName string
 LastName string
 Age int
}

Выбирайте интерфейсы

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

type Logger interface {
 Log(message string)
}
func DoSomething(log Logger) {
 log.Log(“Doing something…”)
}

5. Тестирование

Написание тестов имеет решающее значение для обеспечения надежности вашего Go-кода:

Используйте пакет testing

Go имеет встроенный пакет тестирования, который позволяет легко писать и выполнять тесты. Пишите модульные тесты для своих функций и интеграционные тесты для своих пакетов.

func TestAdd(t *testing.T) {
 result := add(2, 3)
 if result != 5 {
 t.Errorf(“Expected 5, but got %d”, result)
 }
}

Тесты, управляемые таблицами

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

func TestAdd(t *testing.T) {
 testCases := []struct {
 a, b int
 expected int
 }{
 {2, 3, 5},
 {0, 0, 0},
 {-1, 1, 0},
 }

for _, tc := range testCases {
 t.Run(fmt.Sprintf(“%d+%d”, tc.a, tc.b), func(t *testing.T) {
 result := add(tc.a, tc.b)
 if result != tc.expected {
 t.Errorf(“Expected %d, but got %d”, tc.expected, result)
 }
 })
 }
}

6. Управление зависимостями

Для управления зависимостями в Go введена система Go Modules. 

Инициализация модуля

Чтобы запустить новый модуль в проекте, выполните команду:

go mod init module_name

При этом создается файл `go.mod`, который отслеживает ваши зависимости.

Используйте go get и go mod tidy

Для добавления зависимостей в проект используйте команду go get . После добавления или обновления зависимостей выполните команду go mod tidy для очистки и подтверждения актуальности файлов go.mod и go.sum.

7. Избегайте глобальных переменных

Глобальные переменные могут привести к тому, что код будет трудно осмыслить и протестировать. Сведите их использование к минимуму и предпочитайте передавать значения в качестве аргументов в функции и методы.

8. Профилирование и оптимизация

Go предоставляет отличные инструменты для профилирования и оптимизации кода. Используйте такие инструменты, как `pprof` и `go tool pprof` для выявления узких мест и повышения производительности.

9. Контроль версий

Всегда используйте контроль версий, например Git, для отслеживания изменений в кодовой базе. Размещайте свой проект на таких платформах, как GitHub или GitLab, для совместной работы с другими пользователями и обеспечения надежности кода.

Комментарии (5)


  1. ht-pro
    24.11.2023 07:39
    +1

    Жиденько) Половина советов - общепринятые практики для любого языка.


  1. NeoCode
    24.11.2023 07:39

    В Go весьма своеобразно реализовано ООП - классических "методов классов" нет, есть только "методы-расширения". И непривычно здесь то, что все функции пишутся не внутри фигурных скобок класса, а в глобальной области. Это само по себе влияет на восприятие программы. Если в чистых ОО-языках классы стоят во главе всего, и создавая новую функцию, неизбежно мыслишь категориями классов, думаешь в каком классе ее разместить, то в Go это вроде как и необязательно. Ну подумаешь, какая разница - func(obj *Foo) bar() или func bar(obj *Foo). Программа становится больше похожей на код на Си. Пока не знаю что с этим делать:)


    1. Yak52
      24.11.2023 07:39

      Пока не знаю что с этим делать:)

      Забыть про С++ и использовать ту парадигму которую предлагает Go.


      1. Yak52
        24.11.2023 07:39

        А у минусаторов есть, что по делу сказать?


    1. micronull
      24.11.2023 07:39

      функции пишутся не внутри фигурных скобок класса, а в глобальной области

      В области пакета. Соответственно название пакета является неким namespace. Например slices.Sort, где slices является названием пакета и в тоже время namespace.