Программирование на языке Go: полезные советы. Часть 2 | OTUS
⚡ Подписка на курсы OTUS!
Интенсивная прокачка навыков для IT-специалистов!
Подробнее

Курсы

Программирование
Python Developer. Professional
-3%
Разработчик на Spring Framework
-5%
iOS Developer. Professional
-8%
Golang Developer. Professional
-6%
Базы данных
-12%
Agile Project Manager
-5%
Android Developer. Professional
-11%
Microservice Architecture
-5%
C++ Developer. Professional
-5%
Highload Architect
-6%
JavaScript Developer. Basic
-8%
Kotlin Backend Developer
-9%
C# Developer. Professional
-9%
Team Lead
-6%
Алгоритмы и структуры данных Разработчик программных роботов (RPA) на базе UiPath и PIX Unity Game Developer. Basic Разработчик голосовых ассистентов и чат-ботов Vue.js разработчик VOIP инженер NoSQL Супер-практикум по использованию и настройке GIT Symfony Framework iOS Developer. Basic Супер-интенсив «СУБД в высоконагруженных системах» Супер-интенсив "Tarantool"
Инфраструктура
DevOps практики и инструменты
-12%
Базы данных
-12%
Network engineer. Basic
-10%
Network engineer
-4%
Экcпресс-курс «ELK»
-10%
Инфраструктурная платформа на основе Kubernetes
-6%
Administrator Linux.Basic
-10%
Экспресс-курс «CI/CD или Непрерывная поставка с Docker и Kubernetes»
-30%
Дизайн сетей ЦОД
-13%
PostgreSQL
-8%
Разработчик программных роботов (RPA) на базе UiPath и PIX Reverse-Engineering. Professional Внедрение и работа в DevSecOps Administrator Linux. Advanced Infrastructure as a code in Ansible Супер - интенсив по паттернам проектирования Супер - интенсив по Kubernetes Экспресс-курс «IaC Ansible»
Специализации Курсы в разработке Подготовительные курсы
+7 499 938-92-02

Программирование на языке Go: полезные советы. Часть 2

Go_deep_10.9-5020-12a219.png

Продолжаем публикацию полезных советов по мотивам статьи «Go Tips 101». При использовании данных рекомендаций учитывайте, что далеко не все из них можно применять в production. Первая часть советов находится здесь.

1. Как определить наличие метода у значения, не прибегая к использованию пакета reflect?

Решение показано в следующем примере (здесь мы проверяем наличие метода, соответствующего прототипу M(int) string).

package main

import "fmt"

type A int
type B int
func (b B) M(x int) string {
    return fmt.Sprint(b, ": ", x)
}

func check(v interface{}) bool {
    _, has := v.(interface{M(int) string})
    return has
}

func main() {
    var a A = 123
    var b B = 789
    fmt.Println(check(a)) // false
    fmt.Println(check(b)) // true
}

2. В некоторых случаях используйте срезы в 3-аргументной форме

Допустим, что некий пакет реализует функцию func NewX(...Option) *X, которая объединяет входные параметры с некоторыми внутренними параметрами, заданными по умолчанию. Приведенный ниже код для этого использовать не рекомендуется:

func NewX(opts ...Option) *X {
    options := append(opts, defaultOpts...)
    // Значение opts после слияния используется для формирования возвращаемого значения X.
    // ...
}

Потенциальная проблема данного кода заключается в том, что вызов функции append может изменить исходную последовательность opts типа Option, переданную в качестве аргумента. В большинстве случаев данная проблема не является критичной. Однако в некоторых особых сценариях это может привести к непредсказуемым результатам.

Ниже приведен код, который не вносит изменений в исходную последовательность элементов типа Option из входного аргумента:

func NewX(opts ...Option) *X {
    opts = append(opts[:len(opts):len(opts)], defaultOpts...)
    // Значение opts после слияния используется для формирования возвращаемого значения X.
    // ...
}

Однако если мы вызываем функцию NewX, то у нас нет никакой гарантии, что она не изменяет исходных элементов своего параметра-среза. Поэтому наилучшим решением является передача в функцию NewX входного параметра в виде трехаргументного среза.

Другой сценарий, в котором нужно использовать трехаргументный срез, описан в этой wiki-статье.

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

3. Как проверить и показать в коде реализацию пользовательским типом нужного интерфейса?

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

package myreader

import "io"

type MyReader uint16

func NewMyReader() *MyReader {
    var mr MyReader
    return &mr
}

func (mr *MyReader) Read(data []byte) (int, error) {
    switch len(data) {
    default:
        *mr = MyReader(data[0]) << 8 | MyReader(data[1])
        return 2, nil
    case 2:
        *mr = MyReader(data[0]) << 8 | MyReader(data[1])
    case 1:
        *mr = MyReader(data[0])
    case 0:
    }
    return len(data), io.EOF
}

// Каждая из трех строк ниже подтверждает,
// что тип *MyReader реализует интерфейс io.Reader.
var _ io.Reader = NewMyReader()
var _ io.Reader = (*MyReader)(nil)
func _() {_ = io.Reader(nil).(*MyReader)}

4. Некоторые полезные приемы проверки условий на этапе компиляции.

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

Вот несколько примеров, которые будут корректно компилироваться, только если значение константы N не меньше значения константы M:

// Успешная компиляция каждой из приведенных ниже строк кода гарантирует, что N >= M
func _(x []int) {_ = x[N-M]}
func _(){_ = []int{N-M: 0}}
func _([N-M]int){}
var _ [N-M]int
const _ uint = N-M
type _ [N-M]int

// При условии, что M и N целые положительные числа.
var _ uint = N/M - 1

Ещё один прием, заимствованный у Люка Шампена (Luke Champine, @lukechampine в Твиттере). Данный метод основан на правиле, гласящем, что составной литерал не может содержать дублирующиеся ключи-константы.

var _ = map[bool]struct{}{false: struct{}{}, N>=M: struct{}{}}

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

var _ = map[bool]int{false: 0, N>=M: 1}

Аналогично можно проверять на этапе компиляции равенство значений двух констант:

var _ [N-M]int; var _ [M-N]int
type _ [N-M]int; type _ [M-N]int
const _, _ uint = N-M, M-N
func _([N-M]int, [M-N]int) {}

var _ = map[bool]int{false: 0, M==N: 1}

var _ = [1]int{M-N: 0} // the only valid index is 0
var _ = [1]int{}[M-N]  // the only valid index is 0

var _ [N-M]int = [M-N]int{}

Последняя строка кода также навеяна публикациями Люка Шампена.

Как проверить, что строковая константа является непустой:

type _ [len(aStringConstant)-1]int
var _ = map[bool]int{false: 0, aStringConstant != "": 1}
var _ = aStringConstant[:1]
var _ = aStringConstant[0]
const _ = 1/len(aStringConstant)

Последняя строка кода написана по мотивам изящной идеи Яна Меркла (Jan Mercl).

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

Пример:

func _() {
    var _ = map[bool]int{false: 0, N>=M: 1}
    var _ [N-M]int
}

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться
🔥 Только до 28.02
Успейте приобрести курсы февраля на выгодных условиях! Подробности в чате.