Лучшие практики для эффективного кода на Golang. Часть 1.
Введение
Потратьте всего 12 минут, чтобы писать эффективный код на Go. № 1: правильный отступ
С хорошим отступом код удобнее для восприятия. Используйте символы табуляции или пробелы последовательно, отдавая предпочтение символам табуляции, и следуйте стандартному соглашению Go по применению отступов. package main import "fmt" func main() { for i := 0; i < 5; i++ { fmt.Println("Hello, World!") } }
Форматируйте код автоматически с отступом по стандарту Go, запуская g
Лучшие практики для эффективного кода на 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: информативные названия переменных и функций
- Используйте содержательные названия, передающие назначение переменной.
- CamelCase: начинайте со строчной буквы, а первую букву каждого последующего слова в названии делайте заглавной.
- Для временных переменных с небольшой областью действия допустимы короткие, лаконичные названия.
- Избегайте непонятных аббревиатур и акронимов в пользу информативных названий.
- Сохраняйте единообразие именования во всей кодовой базе.
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 разработчикам рекомендуется обрабатывать ошибки явно. И вот причины:
- Безопасность: при обработке ошибок гарантируется, что неожиданные проблемы не приведут к панике или внезапному аварийному завершению программы.
- Четкость: с явной обработкой ошибок код удобнее для восприятия, проще определить места возникновения ошибок.
- Отладка: при обработке ошибок получается ценная информация для отладки и устранения проблем.
Создадим простую программу для корректного считывания файла и обработки ошибок:
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) }