Лучшие практики для эффективного кода на Golang. Часть 1

Введение

Потратьте всего 12 минут, чтобы писать эффективный код на Go.

№ 1: правильный отступ

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

package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println("Hello, World!")
    }
}

Форматируйте код автоматически с отступом по стандарту Go, запуская gofmt:

$ gofmt -w your_file.go

№ 2: правильный импорт пакетов

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

package main

import (
    "fmt"
    "math/rand"
    "time"
)

№ 3: информативные названия переменных и функций

  1. Используйте содержательные названия, передающие назначение переменной.
  2. CamelCase: начинайте со строчной буквы, а первую букву каждого последующего слова в названии делайте заглавной.
  3. Для временных переменных с небольшой областью действия допустимы короткие, лаконичные названия.
  4. Избегайте непонятных аббревиатур и акронимов в пользу информативных названий.
  5. Сохраняйте единообразие именования во всей кодовой базе.
package main

import "fmt"

func main() {
    // Объявляем переменные с содержательными названиями
    userName := "John Doe" // CamelCase: начинаем со строчной буквы, а последующие слова с заглавной.
    itemCount := 10 // Короткие, лаконичные названия для переменных с небольшой областью действия.
    isReady := true // Избегаем непонятных аббревиатур и акронимов.

    // Отображаем значения переменных
    fmt.Println("User Name:", userName)
    fmt.Println("Item Count:", itemCount)
    fmt.Println("Is Ready:", isReady)
}

// Для переменных, описанных в спецификации пакета, используем mixedCase: начинаем со строчной буквы, а следующее слово с заглавной.
var exportedVariable int = 42

// Названия функций должны быть информативными
func calculateSumOfNumbers(a, b int) int {
    return a + b
}

// Сохраняем единообразие именования во всей кодовой базе.

№ 4: ограничение длины строки

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

package main

import (
    "fmt"
    "math"
)

func main() {
    result := calculateHypotenuse(3, 4)
    fmt.Println("Hypotenuse:", result)
}

func calculateHypotenuse(a, b float64) float64 {
    return math.Sqrt(a*a + b*b)
}

Примечание: есть мнение о необходимости ограничивать строки кода 120, 150 и даже 180 символами.

№ 5: константы для магических значений

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

package main

import "fmt"

const (
    // Определяем константу для максимального числа повторных попыток
    MaxRetries = 3

    // Определяем константу для времени ожидания по умолчанию в секундах
    DefaultTimeout = 30
)

func main() {
    retries := 0
    timeout := DefaultTimeout

    for retries < MaxRetries {
        fmt.Printf("Attempting operation (Retry %d) with timeout: %d seconds\n", retries+1, timeout)

        // ... Логика кода здесь ...

        retries++
    }
}

№ 6: обработка ошибок

В Go разработчикам рекомендуется обрабатывать ошибки явно. И вот причины:

  1. Безопасность: при обработке ошибок гарантируется, что неожиданные проблемы не приведут к панике или внезапному аварийному завершению программы.
  2. Четкость: с явной обработкой ошибок код удобнее для восприятия, проще определить места возникновения ошибок.
  3. Отладка: при обработке ошибок получается ценная информация для отладки и устранения проблем.

Создадим простую программу для корректного считывания файла и обработки ошибок:

package main

import (
    "fmt"
    "os"
)

func main() {
    // Открываем файл
    file, err := os.Open("example.txt")
    if err != nil {
        // Обрабатываем ошибку
        fmt.Println("Error opening the file:", err)
        return
    }
    defer file.Close() // По завершении файл закрываем

    // Считываем из файла
    buffer := make([]byte, 1024)
    _, err = file.Read(buffer)
    if err != nil {
        // Обрабатываем ошибку
        fmt.Println("Error reading the file:", err)
        return
    }

    // Выводим содержимое файла
    fmt.Println("File content:", string(buffer))
}

Примечание: пример совершенствуется добавлением обработки ошибок в функцию defer при вызове file.Close()  —  имеется возможность возвращения ошибки.

№ 7: глобальные переменные

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

Как избегать глобальных переменных, проиллюстрируем простой программой на Go:

package main

import (
    "fmt"
)

func main() {
    // Объявляем и инициализируем переменную в функции «main»
    message := "Hello, Go!"

    // Вызываем функцию, использующую локальную переменную
    printMessage(message)
}

// «printMessage» — это функция, принимающая параметр
func printMessage(msg string) {
    fmt.Println(msg)
}

№ 8: структуры для сложных данных

Объединяйте поля и методы связанных данных в структуры. Так объединяются соответствующие переменные, а код становится организованнее и удобнее для восприятия.

Вот полный пример программы с применением структур в Go:

package main

import (
    "fmt"
)

// Определяем структуру «Person» для представления информации о человеке.
type Person struct {
    FirstName string // Имя
    LastName string // Фамилия
    Age int // Возраст
}

func main() {
    // Создаем экземпляр структуры «Person» и инициализируем ее поля.
    person := Person{
        FirstName: "John",
        LastName: "Doe",
        Age: 30,
    }

    // Получаем доступ к значениям полей структуры и выводим их.
    fmt.Println("First Name:", person.FirstName) // Выводим имя
    fmt.Println("Last Name:", person.LastName) // Выводим фамилию
    fmt.Println("Age:", person.Age) // Выводим возраст
}

№ 9: комментарии

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

Однострочные комментарии начинаются с //, комментируйте ими конкретные строки кода:

package main

import "fmt"

func main() {
    // Это однострочный комментарий
    fmt.Println("Hello, World!") // Выводим приветствие
}

Многострочные комментарии помещаются между символами /* и */, эти комментарии длиннее или занимают несколько строк:

package main

import "fmt"

func main() {
    /*
    Это многострочный комментарий.
    Он может занимать несколько строк.
    */
    fmt.Println("Hello, World!") // Выводим приветствие
}

Комментарии к функциям добавляются для объяснения их назначения, параметров и возвращаемых значений, для этих комментариев используйте стиль godoc:

package main

import "fmt"

// «greetUser» приветствует пользователя по имени.
// Параметры:
// имя (строка): имя приветствуемого пользователя.
// Возвращается:
// строка: приветственное сообщение.
func greetUser(name string) string {
    return "Hello, " + name + "!"
}

func main() {
    userName := "Alice"
    greeting := greetUser(userName)
    fmt.Println(greeting)
}

Комментарии к пакету добавляются в верхней части файлов Go для описания назначения пакета, используйте тот же стиль godoc:

package main

import "fmt"

// Это пакет «main» программы Go.
// Он содержит функцию точки входа «main».
func main() {
    fmt.Println("Hello, World!")
}

№ 10: горутины для параллельного выполнения

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

Вот простой пример:

package main

import (
    "fmt"
    "time"
)

// Функция, запускаемая параллельно
func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Printf("%d ", i)
        time.Sleep(100 * time.Millisecond)
    }
}

// Функция, запускаемая в горутине «main»
func main() {
    // Запускаем горутину
    go printNumbers()

    // Продолжаем выполнение «main»
    for i := 0; i < 2; i++ {
        fmt.Println("Hello")
        time.Sleep(200 * time.Millisecond)
    }
    // Выполнение горутины обязательно завершается перед выходом
    time.Sleep(1 * time.Second)
}

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Golang Da: Golang Best Practices (Top 20)

Предыдущая статья4 ошибки при использовании useState в React, которых стоит избегать
Следующая статьяПодходы к созданию линейных графиков для iOS-приложений на базе фреймворка SwiftUI