0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Продолжаем публикацию полезных советов по мотивам статьи "<a>Go Tips 101</a>". При использовании данных рекомендаций учитывайте, что далеко не все из них можно применять в production. Первая часть советов находится<a>здесь</a>.</p>
1
<p>Продолжаем публикацию полезных советов по мотивам статьи "<a>Go Tips 101</a>". При использовании данных рекомендаций учитывайте, что далеко не все из них можно применять в production. Первая часть советов находится<a>здесь</a>.</p>
2
<h2>1. Как определить наличие метода у значения, не прибегая к использованию пакета reflect?</h2>
2
<h2>1. Как определить наличие метода у значения, не прибегая к использованию пакета reflect?</h2>
3
<p>Решение показано в следующем примере (здесь мы проверяем наличие метода, соответствующего прототипу M(int) string).</p>
3
<p>Решение показано в следующем примере (здесь мы проверяем наличие метода, соответствующего прототипу M(int) string).</p>
4
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 }<h2>2. В некоторых случаях используйте срезы в 3-аргументной форме</h2>
4
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 }<h2>2. В некоторых случаях используйте срезы в 3-аргументной форме</h2>
5
<p>Допустим, что некий пакет реализует функцию func NewX(...Option) *X, которая объединяет входные параметры с некоторыми внутренними параметрами, заданными по умолчанию. Приведенный ниже код для этого использовать не рекомендуется:</p>
5
<p>Допустим, что некий пакет реализует функцию func NewX(...Option) *X, которая объединяет входные параметры с некоторыми внутренними параметрами, заданными по умолчанию. Приведенный ниже код для этого использовать не рекомендуется:</p>
6
func NewX(opts ...Option) *X { options := append(opts, defaultOpts...) // Значение opts после слияния используется для формирования возвращаемого значения X. // ... }<p>Потенциальная проблема данного кода заключается в том, что вызов функции<strong>append</strong>может изменить исходную последовательность opts типа Option, переданную в качестве аргумента. В большинстве случаев данная проблема не является критичной. Однако в некоторых особых сценариях это может привести к непредсказуемым результатам.</p>
6
func NewX(opts ...Option) *X { options := append(opts, defaultOpts...) // Значение opts после слияния используется для формирования возвращаемого значения X. // ... }<p>Потенциальная проблема данного кода заключается в том, что вызов функции<strong>append</strong>может изменить исходную последовательность opts типа Option, переданную в качестве аргумента. В большинстве случаев данная проблема не является критичной. Однако в некоторых особых сценариях это может привести к непредсказуемым результатам.</p>
7
<p>Ниже приведен код, который не вносит изменений в исходную последовательность элементов типа Option из входного аргумента:</p>
7
<p>Ниже приведен код, который не вносит изменений в исходную последовательность элементов типа Option из входного аргумента:</p>
8
func NewX(opts ...Option) *X { opts = append(opts[:len(opts):len(opts)], defaultOpts...) // Значение opts после слияния используется для формирования возвращаемого значения X. // ... }<p>Однако если мы вызываем функцию NewX, то у нас нет никакой гарантии, что она не изменяет исходных элементов своего параметра-среза. Поэтому наилучшим решением является передача в функцию NewX входного параметра в виде трехаргументного среза.</p>
8
func NewX(opts ...Option) *X { opts = append(opts[:len(opts):len(opts)], defaultOpts...) // Значение opts после слияния используется для формирования возвращаемого значения X. // ... }<p>Однако если мы вызываем функцию NewX, то у нас нет никакой гарантии, что она не изменяет исходных элементов своего параметра-среза. Поэтому наилучшим решением является передача в функцию NewX входного параметра в виде трехаргументного среза.</p>
9
<p>Другой сценарий, в котором нужно использовать трехаргументный срез, описан в<a>этой wiki-статье</a>.</p>
9
<p>Другой сценарий, в котором нужно использовать трехаргументный срез, описан в<a>этой wiki-статье</a>.</p>
10
<p>Одним из недостатков трехаргументной формы срезов является необходимость указывать значения, которые могли бы подразумеваться по умолчанию.</p>
10
<p>Одним из недостатков трехаргументной формы срезов является необходимость указывать значения, которые могли бы подразумеваться по умолчанию.</p>
11
<h2>3. Как проверить и показать в коде реализацию пользовательским типом нужного интерфейса?</h2>
11
<h2>3. Как проверить и показать в коде реализацию пользовательским типом нужного интерфейса?</h2>
12
<p>Чтобы проверить реализацию пользовательским типом нужного интерфейса, достаточно присвоить значение данного пользовательского типа переменной этого типа интерфейса. Таким образом мы также наглядно показываем, реализация каких интерфейсов требуется от пользовательского типа. В некоторых случаях самодокументированный код может быть предпочтительнее комментариев.</p>
12
<p>Чтобы проверить реализацию пользовательским типом нужного интерфейса, достаточно присвоить значение данного пользовательского типа переменной этого типа интерфейса. Таким образом мы также наглядно показываем, реализация каких интерфейсов требуется от пользовательского типа. В некоторых случаях самодокументированный код может быть предпочтительнее комментариев.</p>
13
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)}<h2>4. Некоторые полезные приемы проверки условий на этапе компиляции.</h2>
13
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)}<h2>4. Некоторые полезные приемы проверки условий на этапе компиляции.</h2>
14
<p>Помимо примера выше, существуют и другие способы проверки тех или иных условий на этапе компиляции.</p>
14
<p>Помимо примера выше, существуют и другие способы проверки тех или иных условий на этапе компиляции.</p>
15
<p>Вот несколько примеров, которые будут корректно компилироваться, только если значение константы N не меньше значения константы M:</p>
15
<p>Вот несколько примеров, которые будут корректно компилироваться, только если значение константы N не меньше значения константы M:</p>
16
// Успешная компиляция каждой из приведенных ниже строк кода гарантирует, что 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<p>Ещё один прием, заимствованный у Люка Шампена (Luke Champine, @lukechampine в Твиттере). Данный метод основан на правиле, гласящем, что<a>составной литерал не может содержать дублирующиеся ключи-константы</a>.</p>
16
// Успешная компиляция каждой из приведенных ниже строк кода гарантирует, что 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<p>Ещё один прием, заимствованный у Люка Шампена (Luke Champine, @lukechampine в Твиттере). Данный метод основан на правиле, гласящем, что<a>составной литерал не может содержать дублирующиеся ключи-константы</a>.</p>
17
var _ = map[bool]struct{}{false: struct{}{}, N>=M: struct{}{}}<p>Пример кажется довольно громоздким, однако он позволяет решать более общую задачу для проверки любых условий на этапе компиляции. Его также можно существенно упростить ценой незначительного увеличения расхода памяти:</p>
17
var _ = map[bool]struct{}{false: struct{}{}, N>=M: struct{}{}}<p>Пример кажется довольно громоздким, однако он позволяет решать более общую задачу для проверки любых условий на этапе компиляции. Его также можно существенно упростить ценой незначительного увеличения расхода памяти:</p>
18
var _ = map[bool]int{false: 0, N>=M: 1}<p>Аналогично можно проверять на этапе компиляции равенство значений двух констант:</p>
18
var _ = map[bool]int{false: 0, N>=M: 1}<p>Аналогично можно проверять на этапе компиляции равенство значений двух констант:</p>
19
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{}<p>Последняя строка кода также навеяна публикациями Люка Шампена.</p>
19
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{}<p>Последняя строка кода также навеяна публикациями Люка Шампена.</p>
20
<p>Как проверить, что строковая константа является непустой:</p>
20
<p>Как проверить, что строковая константа является непустой:</p>
21
type _ [len(aStringConstant)-1]int var _ = map[bool]int{false: 0, aStringConstant != "": 1} var _ = aStringConstant[:1] var _ = aStringConstant[0] const _ = 1/len(aStringConstant)<p>Последняя строка кода написана по мотивам<a>изящной идеи</a>Яна Меркла (Jan Mercl).</p>
21
type _ [len(aStringConstant)-1]int var _ = map[bool]int{false: 0, aStringConstant != "": 1} var _ = aStringConstant[:1] var _ = aStringConstant[0] const _ = 1/len(aStringConstant)<p>Последняя строка кода написана по мотивам<a>изящной идеи</a>Яна Меркла (Jan Mercl).</p>
22
<p>Иногда, чтобы не тратить лишнюю память на переменные, объявляемые на уровне пакета, можно поместить проверочный код в функцию с пустым идентификатором.</p>
22
<p>Иногда, чтобы не тратить лишнюю память на переменные, объявляемые на уровне пакета, можно поместить проверочный код в функцию с пустым идентификатором.</p>
23
<p>Пример:</p>
23
<p>Пример:</p>
24
func _() { var _ = map[bool]int{false: 0, N>=M: 1} var _ [N-M]int }
24
func _() { var _ = map[bool]int{false: 0, N>=M: 1} var _ [N-M]int }