Go — простой и забавный язык. Но в нём, как и в любых других языках, есть свои подводные камни. И во многих из них сам Go не виноват. Одни — это естественное следствие прихода программистов из других языков, другие возникают из-за ложных представлений и нехватки подробностей. Если вы найдёте время и почитаете официальные спецификации, вики, почтовые рассылки, публикации в блогах и исходный код, то многие из подводных камней станут для вас очевидны. Но далеко не каждый так начинает, и это нормально. Если вы новичок в Go, статья поможет сэкономить немало часов, которые вы бы потратили на отладку кода. Мы будем рассматривать версии Go 1.5 и ниже.

Содержание


Уровень: абсолютный новичок

1. Открывающую фигурную скобку нельзя размещать в отдельной строке
2. Неиспользуемые переменные
3. Неиспользуемые импорты
4. Короткие объявления переменных можно использовать только внутри функций
5. Переобъявления переменных с помощью коротких объявлений
6. Нельзя использовать короткие объявления переменных для настройки значений полей
7. Случайное сокрытие переменных
8. Нельзя использовать nil для инициализации переменной без явного указания типа
9. Использование nil-слайсов (slice) и хеш-таблиц (map)
10. Ёмкость хеш-таблиц
11. Строки не могут быть nil
12. Передача массивов в функции
13. Неожиданные значения в выражениях range в слайсах и массивах
14. Одномерность слайсов и массивов
15. Обращение к несуществующим ключам в map
16. Неизменяемость строк
17. Преобразование строк в байт-слайсы (Byte Slices), и наоборот
18. Строки и оператор индекса
19. Строки — не всегда текст в кодировке UTF-8
20. Длина строк
21. Отсутствующая запятая в многострочных литералах slice/array/map
22. log.Fatal и log.Panic не только журналируют
23. Несинхронизированные операции встроенных структур данных
24. Итерационные значения для строк в выражениях range
25. Итерирование хеш-таблиц (map) с помощью выражения for range
26. Сбойное поведение в выражениях switch
27. Инкременты и декременты
28. Побитовый NOT-оператор
29. Различия приоритетов операторов
30. Неэкспортированные поля структур не кодируются
31. Выход из приложений с помощью активных горутин
32. При отправке в небуферизованный канал данные возвращаются по мере готовности получателя
33. Отправка в закрытый канал приводит к panic
34. Использование «nil»-каналов
35. Методы, принимающие параметры по значению, не меняют исходных значений

Уровень: более опытный новичок

36. Закрытие тела HTTP-ответа
37. Закрытие HTTP-соединений
38. Десериализация (unmarshalling) JSON-чисел в интерфейсные значения
39. Сравнение struct, array, slice и map
40. Восстановление после panic
41. Обновление и привязка значений полей в slice, array и map в выражениях for range
42. «Скрытые данные» в слайсах
43. «Повреждение» данных в слайсах
44. «Устаревшие» слайсы
45. Методы и объявления типов
46. Как выбраться из кодовых блоков for switch и for select
47. Итерационные переменные и замыкания в выражениях for
48. Вычисление аргумента блока defer (Deferred Function Call Argument Evaluation)
49. Вызов блока defer
50. Ошибки при приведении типов
51. Блокированные горутины и утечки ресурсов

Уровень: продвинутый новичок

52. Применение методов, принимающих значение по ссылке (pointer receiver), к экземплярам значений
53. Обновление полей значений в хеш-таблице
54. nil-интерфейсы и nil-интерфейсные значения
55. Переменные стека и кучи
56. GOMAXPROCS, согласованность (concurrency) и параллелизм
57. Изменение порядка операций чтения и записи
58. Диспетчеризация по приоритетам (Preemptive Scheduling)

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


В большинстве других языков, использующих фигурные скобки, вам нужно выбирать, где их размещать. Go выбивается из правила. За это вы можете благодарить автоматическую вставку точки с запятой (точка с запятой предполагается в конце каждой строки, без анализа следующей). Да, в Go есть точка с запятой!

Неправильно:

package main

import "fmt"

func main()  
{ // ошибка, нельзя выносить открывающую фигурную скобку в отдельную строку
    fmt.Println("hello there!")
}

Ошибка компилирования:

/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {

Правильно:

package main

import "fmt"

func main() {  
    fmt.Println("works!")
}

2. Неиспользуемые переменные


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

Если вы присвоили неиспользуемой переменной новое значение, то ваш код всё равно не будет компилироваться. Придётся её где-то использовать, чтобы угодить компилятору.

Неправильно:

package main

var gvar int // not an error

func main() {  
    var one int   // ошибка, неиспользуемая переменная
    two := 2      // ошибка, неиспользуемая переменная
    var three int // ошибка, даже несмотря на присваивание значения 3 в следующей строке
    three = 3

    func(unused string) {
        fmt.Println("Unused arg. No compile error")
    }("what?")
}

Ошибки компилирования:

/tmp/sandbox473116179/main.go:6: one declared and not used /tmp/sandbox473116179/main.go:7: two declared and not used /tmp/sandbox473116179/main.go:8: three declared and not used

Правильно:

package main

import "fmt"

func main() {  
    var one int
    _ = one

    two := 2
    fmt.Println(two)

    var three int
    three = 3
    one = three

    var four int
    four = four
}

Другое решение: комментировать или удалять неиспользуемые переменные.

3. Неиспользуемые импорты


Если вы импортируете пакет и потом не используете какие-либо из его функций, интерфейсов, структур или переменных, то код не скомпилируется. Если нужно импортировать пакет, идентификатор «_» в качестве его имени поможет избежать ошибок компилирования. Идентификатор «_» чаще всего применяется для использования сайд-эффектов импортированных библиотек.

Неправильно:

package main

import (  
    "fmt"
    "log"
    "time"
)

func main() {  
}

Ошибки компилирования:

/tmp/sandbox627475386/main.go:4: imported and not used: "fmt" /tmp/sandbox627475386/main.go:5: imported and not used: "log" /tmp/sandbox627475386/main.go:6: imported and not used: "time"

Правильно:

package main

import (  
    _ "fmt"
    "log"
    "time"
)

var _ = log.Println

func main() {  
    _ = time.Now
}

Другое решение: удалить или закомментировать неиспользуемые импорты. В этом поможет инструмент goimports.

4. Короткие объявления переменных можно использовать только внутри функций


Неправильно:

package main

myvar := 1 // ошибка

func main() {  
}

Ошибка компилирования:

/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body

Правильно:

package main

var myvar = 1

func main() {  
}

5. Переобъявления переменных с помощью коротких объявлений


В одной области видимости выражения нельзя переобъявлять переменные, но это можно делать в объявлении нескольких переменных (multi-variable declarations), среди которых хотя бы одна — новая. Переобъявляемые переменные должны располагаться в том же блоке, иначе получится скрытая переменная (shadowed variable).
Неправильно:

package main

func main() {  
    one := 0
    one := 1 // ошибка
}

Ошибка компилирования:

/tmp/sandbox706333626/main.go:5: no new variables on left side of :=

Правильно:

package main

func main() {  
    one := 0
    one, two := 1,2

    one,two = two,one
}

6. Нельзя использовать короткие объявления переменных для настройки значений полей


Неправильно:

package main

import (  
  "fmt"
)

type info struct {  
  result int
}

func work() (int,error) {  
    return 13,nil  
  }

func main() {  
  var data info

  data.result, err := work() // ошибка
  fmt.Printf("info: %+v\n",data)
}

Ошибка компилирования:

prog.go:18: non-name data.result on left side of :=

Хотя разработчикам Go уже предлагали это исправить, не стоит надеяться на перемены: Робу Пайку нравится всё «как есть». Вам помогут временные переменные. Или предварительно объявляйте все свои переменные и используйте стандартный оператор присваивания.

Правильно:

package main

import (  
  "fmt"
)

type info struct {  
  result int
}

func work() (int,error) {  
    return 13,nil  
  }

func main() {  
  var data info

  var err error
  data.result, err = work() // ok
  if err != nil {
    fmt.Println(err)
    return
  }

  fmt.Printf("info: %+v\n",data) // выводит: info: {result:13}
}

7. Случайное сокрытие переменных


Синтаксис короткого объявления переменных так удобен (особенно для тех, кто пришёл в Go из динамических языков), что его легко принять за регулярную операцию присваивания. Если вы сделаете эту ошибку в новом блоке кода, то компилятор не выдаст ошибку, но приложение будет работать некорректно.

package main

import "fmt"

func main() {  
    x := 1
    fmt.Println(x)     // выводит 1
    {
        fmt.Println(x) // выводит 1
        x := 2
        fmt.Println(x) // выводит 2
    }
    fmt.Println(x)     // выводит 1 (плохо, если нужно было 2)
}

Это очень распространённая ошибка даже среди опытных Go-разработчиков. Её легко совершить и трудно заметить. Для выявления подобных ситуаций можно использовать команду vet. По умолчанию она не выполняет проверку переменных на скрытость. Поэтому используйте флаг -shadow: go tool vet -shadow your_file.go

8. Нельзя использовать nil для инициализации переменной без явного указания типа


Идентификатор nil можно использовать как «нулевое значение» (zero value) для интерфейсов, функций, указателей, хеш-таблиц (map), слайсов (slices) и каналов. Если не задать тип переменной, то компилятор не сможет завершить работу, потому что не сумеет угадать тип.

Неправильно:

package main

func main() {  
    var x = nil // ошибка

    _ = x
}

Ошибка компилирования:

/tmp/sandbox188239583/main.go:4: use of untyped nil

Правильно:

package main

func main() {  
    var x interface{} = nil

    _ = x
}

9. Использование nil-слайсов (slice) и хеш-таблиц (map)


Можно добавлять элементы в nil-слайс, но если то же самое сделать с хеш-таблицей, то это приведёт к runtime panic.

Правильно:

package main

func main() {  
    var s []int
    s = append(s,1)
}

Неправильно:

package main

func main() {  
    var m map[string]int
    m["one"] = 1 // ошибка

}

10. Ёмкость хеш-таблиц


Можно устанавливать ёмкость при создании хеш-таблиц, но нельзя применять к ним функцию cap().

Неправильно:

package main

func main() {  
    m := make(map[string]int,99)
    cap(m) // ошибка
}

Ошибка компилирования:

/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap

11. Строки не могут быть nil


Это подводный камень для начинающих, которые присваивают строковым переменным nil-идентификаторы.

Неправильно:

package main

func main() {  
    var x string = nil // ошибка

    if x == nil { // ошибка
        x = "default"
    }
}

Ошибки компилирования:

/tmp/sandbox630560459/main.go:4: cannot use nil as type string in assignment /tmp/sandbox630560459/main.go:6: invalid operation: x == nil (mismatched types string and nil)

Правильно:

package main

func main() {  
    var x string // возвращает значение по умолчанию "" (нулевое значение)

    if x == "" {
        x = "default"
    }
}

12. Передача массивов в функции


Если вы разрабатываете на С/С++, то массивы для вас — указатели. Когда вы передаёте массивы функциям, функции ссылаются на ту же область памяти и поэтому могут обновлять исходные данные. В Go массивы являются значениями, так что, когда мы передаём их функциям, те получают копию исходного массива. Это может стать проблемой, если вы пытаетесь обновлять данные в массиве.

package main

import "fmt"

func main() {  
    x := [3]int{1,2,3}

    func(arr [3]int) {
        arr[0] = 7
        fmt.Println(arr) // выводит [7 2 3]
    }(x)

    fmt.Println(x) // выводит [1 2 3] (плохо, если вам нужно было [7 2 3])
}

Если нужно обновить исходные данные в массиве, используйте типы указателей массива.

package main

import "fmt"

func main() {  
    x := [3]int{1,2,3}

    func(arr *[3]int) {
        (*arr)[0] = 7
        fmt.Println(arr) // выводит &[7 2 3]
    }(&x)

    fmt.Println(x) // выводит [7 2 3]
}

Другое решение: слайсы. Хотя ваша функция получает копию переменной слайса, та всё ещё является ссылкой на исходные данные.

package main

import "fmt"

func main() {  
    x := []int{1,2,3}

    func(arr []int) {
        arr[0] = 7
        fmt.Println(arr) // выводит [7 2 3]
    }(x)

    fmt.Println(x) // выводит [7 2 3]
}

13. Неожиданные значения в выражениях range в слайсах и массивах


Это может произойти, если вы привыкли к выражениям for-in или foreach в других языках. Но в Go выражение range отличается тем, что оно генерирует два значения: первое — это индекс элемента (item index), а второе — данные элемента (item data).

Неправильно:

package main

import "fmt"

func main() {  
    x := []string{"a","b","c"}

    for v := range x {
        fmt.Println(v) // выводит 0, 1, 2
    }
}

Правильно:

package main

import "fmt"

func main() {  
    x := []string{"a","b","c"}

    for _, v := range x {
        fmt.Println(v) // выводит a, b, c
    }
}

14. Одномерность слайсов и массивов


Кажется, что Go поддерживает многомерные массивы и слайсы? Нет, это не так. Хотя можно создавать массивы из массивов и слайсы из слайсов. С точки зрения производительности и сложности — далеко не идеальное решение для приложений, которые выполняют числовые вычисления и основаны на динамических многомерных массивах.

Можно создавать динамические многомерные массивы с помощью обычных одномерных массивов, слайсов из «независимых» слайсов, а также слайсов из слайсов «с совместно используемыми данными».

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

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

package main

func main() {  
    x := 2
    y := 4

    table := make([][]int,x)
    for i:= range table {
        table[i] = make([]int,y)
    }
}

Создание динамического многомерного массива с помощью слайсов из слайсов «с совместно используемыми данными» состоит из трёх шагов. Сначала нужно создать слайс, выполняющий роль «контейнера» данных, он содержит исходные данные (raw data). Затем — внешний слайс. В конце мы инициализируем каждый из внутренних слайсов, перенарезая слайс с исходными данными.

package main

import "fmt"

func main() {  
    h, w := 2, 4

    raw := make([]int,h*w)
    for i := range raw {
        raw[i] = i
    }
    fmt.Println(raw,&raw[4])
    // выводит: [0 1 2 3 4 5 6 7] <ptr_addr_x>

    table := make([][]int,h)
    for i:= range table {
        table[i] = raw[i*w:i*w + w]
    }

    fmt.Println(table,&table[1][0])
    // выводит: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>
}

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

15. Обращение к несуществующим ключам в map


Эту ошибку совершают разработчики, которые при обращении к несуществующему ключу ожидают получить nil-значение (как это происходит в некоторых языках). Возвращаемое значение будет nil, если «нулевое значение» для соответствующего типа данных — nil. Но для других типов возвращаемое значение окажется другим. Определять, существует ли запись в хеш-таблице (map record), можно с помощью проверки на правильное «нулевое значение». Но это не всегда надёжно (например, что вы будете делать, если у вас есть таблица булевых значений, где «нулевое значение» — false). Самый надёжный способ узнать, существует ли запись, — проверить второе значение, возвращаемое операцией доступа к таблице.

Плохо:

package main

import "fmt"

func main() {  
    x := map[string]string{"one":"a","two":"","three":"c"}

    if v := x["two"]; v == "" { // некорректно
        fmt.Println("no entry")
    }
}

Хорошо:

package main

import "fmt"

func main() {  
    x := map[string]string{"one":"a","two":"","three":"c"}

    if _,ok := x["two"]; !ok {
        fmt.Println("no entry")
    }
}

16. Неизменяемость строк


Если вы попытаетесь обновить отдельные символы строковой переменной с помощь оператора индекса, то это не сработает. Строки — это байт-слайсы (byte slices), доступные только для чтения. Если вам все-таки нужно обновить строку, то стоит использовать байт-слайс и преобразовывать его в строку по необходимости.

Неправильно:

package main

import "fmt"

func main() {  
    x := "text"
    x[0] = 'T'

    fmt.Println(x)
}

Ошибка компилирования:

/tmp/sandbox305565531/main.go:7: cannot assign to x[0]

Правильно:

package main

import "fmt"

func main() {  
    x := "text"
    xbytes := []byte(x)
    xbytes[0] = 'T'

    fmt.Println(string(xbytes)) // выводит Text
}

Стоит заметить, что это неправильный способ обновления символов в текстовой строке, потому что символ может состоять из нескольких байтов. В этом случае лучше конвертировать строку в слайс из «рун» (rune). Но даже внутри слайсов из «рун» одиночный символ может быть разбит на несколько рун, например если есть символ апострофа (grave accent). Такая непростая и запутанная природа «символов» является причиной того, что в Go строковые значения представляют собой последовательностей байтов.

17. Преобразование строк в байт-слайсы (Byte Slices), и наоборот


Когда вы преобразуете строку в байт-слайс (и наоборот), вы получаете полную копию исходных данных. Это не операция приведения (cast operation), как в других языках, и не перенарезка (reslicing), когда переменная нового слайса указывает на один и тот же массив, занятый исходным байт-слайсом.

В Go есть несколько оптимизаций для преобразований из []byte в string и из string в []byte, позволяющих избегать дополнительных выделений памяти (ещё больше оптимизаций в списке todo).

Первая оптимизация позволяет избежать дополнительного выделения памяти, когда ключи []byte используются для поиска записей в коллекциях map[string]: m[string(key)].

Вторая оптимизация позволяет избегать дополнительного выделения в выражениях for range, когда строки преобразуются в []byte: for i,v := range []byte(str) {...}.

18. Строки и оператор индекса


Оператор индекса, применяемый к строке, возвращает байтовое значение (byte value), а не символ (как в других языках).

package main

import "fmt"

func main() {  
    x := "text"
    fmt.Println(x[0]) // выводит 116
    fmt.Printf("%T",x[0]) // выводит uint8
}

Если нужно обратиться к конкретным «символам» (кодовым точкам/рунам Unicode), то используйте выражение for range. Также вам будут полезны официальный пакет unicode/utf8 и экспериментальный utf8string (golang.org/x/exp/utf8string). utf8string включает в себя удобный метод At(). Можно также преобразовать строку в слайс рун (slice of runes).

19. Строки — не всегда текст в кодировке UTF-8


Строковые значения необязательно должны быть представлены в виде текста в кодировке UTF-8. Здесь возможен произвольный набор байтов. Единственный случай, когда строки должны быть в кодировке UTF-8, — когда они используются как строковые литералы. Но даже они могут включать в себя данные с экранированными последовательностями.

Чтобы узнать кодировку строки, используйте функцию ValidString() из пакета unicode/utf8.

package main

import (  
    "fmt"
    "unicode/utf8"
)

func main() {  
    data1 := "ABC"
    fmt.Println(utf8.ValidString(data1)) // выводит: true

    data2 := "A\xfeC"
    fmt.Println(utf8.ValidString(data2)) // выводит: false
}

20. Длина строк


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

data = u'♥'  
print(len(data)) # выводит: 1  

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

package main

import "fmt"

func main() {  
    data := "♥"
    fmt.Println(len(data)) // выводит: 3
}

Встроенная функция len() возвращает не символ, а количество байт, как это происходит с Unicode-строками в Python.

Чтобы получить такой же результат в Go, используйте функцию RuneCountInString() из пакета unicode/utf8.

package main

import (  
    "fmt"
    "unicode/utf8"
)

func main() {  
    data := "♥"
    fmt.Println(utf8.RuneCountInString(data)) // выводит: 1

Технически функция RuneCountInString() не возвращает количество символов, потому что один символ может занимать несколько рун.

package main

import (  
    "fmt"
    "unicode/utf8"
)

func main() {  
    data := "e?"
    fmt.Println(len(data))                    // выводит: 3
    fmt.Println(utf8.RuneCountInString(data)) // выводит: 2
}

21. Отсутствующая запятая в многострочных литералах slice/array/map


Неправильно:

package main

func main() {  
    x := []int{
    1,
    2 // error
    }
    _ = x
}

Ошибки компилирования:

/tmp/sandbox367520156/main.go:6: syntax error: need trailing comma before newline in composite literal /tmp/sandbox367520156/main.go:8: non-declaration statement outside function body /tmp/sandbox367520156/main.go:9: syntax error: unexpected }

Правильно:

package main

func main() {  
    x := []int{
    1,
    2,
    }
    x = x

    y := []int{3,4,} // ошибки нет
    y = y
}

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

22. log.Fatal и log.Panic не только журналируют


Библиотеки для логирования часто обеспечивают различные уровни для сообщений. В отличие от других языков, пакет логирования в Go делает больше. Если вызвать его функции Fatal*() и Panic*(), то приложение будет закрыто.

package main

import "log"

func main() {  
    log.Fatalln("Fatal Level: log entry") // здесь выполняется выход из приложения
    log.Println("Normal Level: log entry")
}

23. Несинхронизированные операции встроенных структур данных


Некоторые возможности Go нативно поддерживают многозадачность (concurrency), но в их число не входят потокобезопасные коллекции (concurrency safe). Вы сами отвечаете за атомарность обновления коллекций. Для реализации атомарных операций рекомендуется использовать горутины и каналы, но можно задействовать и пакет sync, если это целесообразно для вашего приложения.

24. Итерационные значения для строк в выражениях range


Значение индекса (первое значение, возвращаемое операцией range) — это индекс первого байта текущего «символа» (кодовая точка/руна Unicode), возвращённый во втором значении. Это не индекс текущего «символа», как в других языках. Обратите внимание, что настоящий символ может быть представлен несколькими рунами. Если вам нужно работать именно с символами, то стоит использовать пакет norm (golang.org/x/text/unicode/norm).

Выражения for range со строковыми переменными пытаются интерпретировать данные как текст в кодировке UTF-8. Если они не распознают какую-либо последовательность байтов, то возвращают руны 0xfffd (символы замены Unicode), а не реальные данные. Если в вашей строке хранятся произвольные данные (не UTF-8), то для сохранения преобразуйте их в байт-слайсы.

package main

import "fmt"

func main() {  
    data := "A\xfe\x02\xff\x04"
    for _,v := range data {
        fmt.Printf("%#x ",v)
    }
    // выводит: 0x41 0xfffd 0x2 0xfffd 0x4 (нехорошо)

    fmt.Println()
    for _,v := range []byte(data) {
        fmt.Printf("%#x ",v)
    }
    // выводит: 0x41 0xfe 0x2 0xff 0x4 (хорошо)
}

25. Итерирование хеш-таблиц (map) с помощью выражения for range


На этот подводный камень натыкаются те, кто ожидают, что элементы будут располагаться в определённом порядке (например, отсортированные по значению ключа). Каждая итерация хеш-таблицы приводит к разным результатам. Среда исполнения (runtime) Go пытается сделать всё возможное, рандомизируя порядок итерирования, но ей это не всегда удаётся, поэтому вы можете получить несколько одинаковых итераций (например, пять) подряд.

package main

import "fmt"

func main() {  
    m := map[string]int{"one":1,"two":2,"three":3,"four":4}
    for k,v := range m {
        fmt.Println(k,v)
    }
}

А если вы используете Go Playground (https://play.golang.org/), то всегда будете получать одинаковые результаты, потому что код не перекомпилируется, пока вы его не измените.

26. Сбойное поведение в выражениях switch


Блоки case в выражениях switch по умолчанию прерываются (break). В других языках поведение по умолчанию другое: переход (fall through) к следующему блоку case.

package main

import "fmt"

func main() {  
    isSpace := func(ch byte) bool {
        switch(ch) {
        case ' ': // ошибка
        case '\t':
            return true
        }
        return false
    }

    fmt.Println(isSpace('\t')) // выводит true (хорошо)
    fmt.Println(isSpace(' '))  // выводит false (плохо)
}

Можно заставить блоки case переходить принудительно с помощью выражения fallthrough в конце каждого блока. Можно также переписать ваше выражение switch, чтобы в блоках использовались списки выражений.

package main

import "fmt"

func main() {  
    isSpace := func(ch byte) bool {
        switch(ch) {
        case ' ', '\t':
            return true
        }
        return false
    }

    fmt.Println(isSpace('\t')) // выводит true (хорошо)
    fmt.Println(isSpace(' '))  // выводит true (хорошо)
}

27. Инкременты и декременты


Во многих языках есть операторы инкрементирования и декрементирования. Но в Go не поддерживаются их префиксные версии. Также нельзя в одном выражении использовать оба этих выражения.

Неправильно:

package main

import "fmt"

func main() {  
    data := []int{1,2,3}
    i := 0
    ++i // error
    fmt.Println(data[i++]) // ошибка
}

Ошибки компилирования:

/tmp/sandbox101231828/main.go:8: syntax error: unexpected ++ /tmp/sandbox101231828/main.go:9: syntax error: unexpected ++, expecting :

Правильно:

package main

import "fmt"

func main() {  
    data := []int{1,2,3}
    i := 0
    i++
    fmt.Println(data[i])
}

28. Побитовый NOT-оператор


Во многих языках символ ~ используется в качестве унарной NOT-операции (aka побитовое дополнение, bitwise complement), однако в Go для этого применяется XOR-оператор (^).

Неправильно:

package main

import "fmt"

func main() {  
    fmt.Println(~2) // ошибка
}

Ошибка компилирования:

/tmp/sandbox965529189/main.go:6: the bitwise complement operator is ^

Правильно:

package main

import "fmt"

func main() {  
    var d uint8 = 2
    fmt.Printf("%08b\n",^d)
}

Кого-то может запутать, что ^ в Go — это XOR-оператор. Если хотите, выражайте унарную NOT-операцию (например, NOT 0x02) с помощью бинарной XOR-операции (например, 0x02 XOR 0xff). Это объясняет, почему ^ используется для выражения унарной NOT-операции.

Также в Go есть специальный побитовый оператор AND NOT (&^), который легко принять за оператор NOT. AND NOT выглядит как специальная функция/хак ради поддержки A AND (NOT B) без обязательного использования фигурных скобок.

package main

import "fmt"

func main() {  
    var a uint8 = 0x82
    var b uint8 = 0x02
    fmt.Printf("%08b [A]\n",a)
    fmt.Printf("%08b [B]\n",b)

    fmt.Printf("%08b (NOT B)\n",^b)
    fmt.Printf("%08b ^ %08b = %08b [B XOR 0xff]\n",b,0xff,b ^ 0xff)

    fmt.Printf("%08b ^ %08b = %08b [A XOR B]\n",a,b,a ^ b)
    fmt.Printf("%08b & %08b = %08b [A AND B]\n",a,b,a & b)
    fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n",a,b,a &^ b)
    fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n",a,b,a & (^b))
}

29. Различия приоритетов операторов


Помимо «довольно понятных» (bit clear) операторов (&^), в Go есть набор стандартных операторов, используемых многими другими языками. Но их приоритеты в данном случае не всегда такие же.

package main

import "fmt"

func main() {  
    fmt.Printf("0x2 & 0x2 + 0x4 -> %#x\n",0x2 & 0x2 + 0x4)
    //prints: 0x2 & 0x2 + 0x4 -> 0x6
    //Go:    (0x2 & 0x2) + 0x4
    //C++:    0x2 & (0x2 + 0x4) -> 0x2

    fmt.Printf("0x2 + 0x2 << 0x1 -> %#x\n",0x2 + 0x2 << 0x1)
    //prints: 0x2 + 0x2 << 0x1 -> 0x6
    //Go:     0x2 + (0x2 << 0x1)
    //C++:   (0x2 + 0x2) << 0x1 -> 0x8

    fmt.Printf("0xf | 0x2 ^ 0x2 -> %#x\n",0xf | 0x2 ^ 0x2)
    //prints: 0xf | 0x2 ^ 0x2 -> 0xd
    //Go:    (0xf | 0x2) ^ 0x2
    //C++:    0xf | (0x2 ^ 0x2) -> 0xf
}

30. Неэкспортированные поля структур не кодируются


Поля структур (struct fields), начинающиеся со строчных букв, не будут кодироваться (JSON, XML, GON и т. д.), так что при декодировании структуры вы получите в этих неэкспортированных полях нулевые значения.

package main

import (  
    "fmt"
    "encoding/json"
)

type MyData struct {  
    One int
    two string
}

func main() {  
    in := MyData{1,"two"}
    fmt.Printf("%#v\n",in) // выводит main.MyData{One:1, two:"two"}

    encoded,_ := json.Marshal(in)
    fmt.Println(string(encoded)) // выводит {"One":1}

    var out MyData
    json.Unmarshal(encoded,&out)

    fmt.Printf("%#v\n",out) // выводит main.MyData{One:1, two:""}
}

31. Выход из приложений с помощью активных горутин


Приложение не будет ждать завершения ваших горутин. Новички часто об этом забывают. Все когда-то начинают — в таких ошибках нет ничего стыдного.

package main

import (  
    "fmt"
    "time"
)

func main() {  
    workerCount := 2

    for i := 0; i < workerCount; i++ {
        go doit(i)
    }
    time.Sleep(1 * time.Second)
    fmt.Println("all done!")
}

func doit(workerId int) {  
    fmt.Printf("[%v] is running\n",workerId)
    time.Sleep(3 * time.Second)
    fmt.Printf("[%v] is done\n",workerId)
}

Вы увидите:

[0] is running 
[1] is running 
all done!

Одно из самых популярных решений — переменная WaitGroup. Это позволит главной горутине ожидать завершения работы всех рабочих горутин. Если ваше приложение использует долго выполняемые рабочие горутины с циклами обработки сообщений, то вам понадобится как-то сигнализировать им о том, что пора выходить. Можно отправлять каждой такой горутине сообщение kill. Или закрывать каналы, из которых рабочие горутины получают данные: это простой способ сигнализировать оптом.

package main

import (  
    "fmt"
    "sync"
)

func main() {  
    var wg sync.WaitGroup
    done := make(chan struct{})
    workerCount := 2

    for i := 0; i < workerCount; i++ {
        wg.Add(1)
        go doit(i,done,wg)
    }

    close(done)
    wg.Wait()
    fmt.Println("all done!")
}

func doit(workerId int,done <-chan struct{},wg sync.WaitGroup) {  
    fmt.Printf("[%v] is running\n",workerId)
    defer wg.Done()
    <- done
    fmt.Printf("[%v] is done\n",workerId)
}

Если запустить это приложение, вы увидите:

[0] is running 
[0] is done 
[1] is running 
[1] is done

Похоже, все горутины закончили работать до выхода главной горутины. Замечательно! Однако вы увидите и это:

fatal error: all goroutines are asleep - deadlock!

Нехорошо! Что происходит? Откуда взялась взаимоблокировка? Ведь все вышли и выполнили wg.Done(). Приложение должно работать.

Блокировка возникает, потому что каждый рабочий получает копию исходной переменной WaitGroup. И когда все они выполняют wg.Done(), это никак не влияет на переменную WaitGroup в главной горутине.

package main

import (  
    "fmt"
    "sync"
)

func main() {  
    var wg sync.WaitGroup
    done := make(chan struct{})
    wq := make(chan interface{})
    workerCount := 2

    for i := 0; i < workerCount; i++ {
        wg.Add(1)
        go doit(i,wq,done,&wg)
    }

    for i := 0; i < workerCount; i++ {
        wq <- i
    }

    close(done)
    wg.Wait()
    fmt.Println("all done!")
}

func doit(workerId int, wq <-chan interface{},done <-chan struct{},wg *sync.WaitGroup) {  
    fmt.Printf("[%v] is running\n",workerId)
    defer wg.Done()
    for {
        select {
        case m := <- wq:
            fmt.Printf("[%v] m => %v\n",workerId,m)
        case <- done:
            fmt.Printf("[%v] is done\n",workerId)
            return
        }
    }
}

Теперь всё работает правильно.

32. При отправке в небуферизованный канал данные возвращаются по мере готовности получателя


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

package main

import "fmt"

func main() {  
    ch := make(chan string)

    go func() {
        for m := range ch {
            fmt.Println("processed:",m)
        }
    }()

    ch <- "cmd.1"
    ch <- "cmd.2" // не будет обработано
}

33. Отправка в закрытый канал приводит к panic


Получение из закрытого канала безопасно. Возвращаемое значение ok в получаемом выражении (receive statement) станет false, что говорит о том, что никакие данные не были получены. Если вы получаете из буферизованного канала, то получите сначала буферизованные данные, а когда они закончатся, выражение ok станет false.

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

package main

import (  
    "fmt"
    "time"
)

func main() {  
    ch := make(chan int)
    for i := 0; i < 3; i++ {
        go func(idx int) {
            ch <- (idx + 1) * 2
        }(i)
    }

    // get the first result
    fmt.Println(<-ch)
    close(ch) //нехорошо (у вас всё ещё есть другие отправители)
    // do other work
    time.Sleep(2 * time.Second)
}

Решение зависит от вашего приложения. Это может быть небольшое изменение кода — или архитектуры, если потребуется. В любом случае удостоверьтесь, что приложение не пытается отправлять данные в закрытый канал.

Пример с багом можно исправить, сигнализируя через специальный канал отмены (special cancellation channel) остальным рабочим горутинам, что их результаты больше не нужны.

package main

import (  
    "fmt"
    "time"
)

func main() {  
    ch := make(chan int)
    done := make(chan struct{})
    for i := 0; i < 3; i++ {
        go func(idx int) {
            select {
            case ch <- (idx + 1) * 2: fmt.Println(idx,"sent result")
            case <- done: fmt.Println(idx,"exiting")
            }
        }(i)
    }

    // get first result
    fmt.Println("result:",<-ch)
    close(done)
    // do other work
    time.Sleep(3 * time.Second)
}

34. Использование «nil»-каналов


В канале nil операции отправки и приёма блокируются навсегда. Это хорошо задокументированное поведение, но оно может стать сюрпризом для новичков.

package main

import (  
    "fmt"
    "time"
)

func main() {  
    var ch chan int
    for i := 0; i < 3; i++ {
        go func(idx int) {
            ch <- (idx + 1) * 2
        }(i)
    }

    // get first result
    fmt.Println("result:",<-ch)
    // do other work
    time.Sleep(2 * time.Second)
}

При выполнении этого кода вы увидите ошибку runtime наподобие fatal error: all goroutines are asleep - deadlock!

Это поведение можно использовать для динамического включения и отключения блоков case в выражении select.

package main

import "fmt"  
import "time"

func main() {  
    inch := make(chan int)
    outch := make(chan int)

    go func() {
        var in <- chan int = inch
        var out chan <- int
        var val int
        for {
            select {
            case out <- val:
                out = nil
                in = inch
            case val = <- in:
                out = outch
                in = nil
            }
        }
    }()

    go func() {
        for r := range outch {
            fmt.Println("result:",r)
        }
    }()

    time.Sleep(0)
    inch <- 1
    inch <- 2
    time.Sleep(3 * time.Second)
}

35. Методы, принимающие параметры по значению, не меняют исходных значений


Параметры методов — это как обычные аргументы функций. Если они объявляются значением, то функция/метод получает копию вашего аргумента (receiver argument). Изменения в принятом значении не повлияют на исходное значение, если значение — переменная хеш-таблицы (map) или слайса и вы обновляете элементы коллекции или если обновляемые поля в значении — это указатели.

package main

import "fmt"

type data struct {  
    num int
    key *string
    items map[string]bool
}

func (this *data) pmethod() {  
    this.num = 7
}

func (this data) vmethod() {  
    this.num = 8
    *this.key = "v.key"
    this.items["vmethod"] = true
}

func main() {  
    key := "key.1"
    d := data{1,&key,make(map[string]bool)}

    fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
    // prints num=1 key=key.1 items=map[]

    d.pmethod()
    fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
    // prints num=7 key=key.1 items=map[]

    d.vmethod()
    fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
    // prints num=7 key=v.key items=map[vmethod:true]
}

36. Закрытие тела HTTP-ответа


Делая запрос с помощью стандартной HTTP-библиотеки, вы получаете переменную HTTP-ответа. Даже если вы не читаете тело ответа, всё равно нужно его закрыть. Обратите внимание: это относится и к пустым ответам. О них очень легко забыть, особенно новичкам.

Некоторые новички пытаются закрывать тело ответа, но в неправильном месте.

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    defer resp.Body.Close()// неправильно
    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

Этот код будет работать с успешными HTTP-запросами, но в случае сбоя переменная resp может быть nil, что приведёт к runtime panic.

Самый распространённый способ закрыть тело ответа — с помощью вызова defer после проверки ошибочности HTTP-ответа.

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    if err != nil {
        fmt.Println(err)
        return
    }

    defer resp.Body.Close()// допустимо, в большинстве случаев :-)
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

В большинстве случаев, когда возникают сбои HTTP-запросов, переменная resp будет nil, а переменная err — non-nil. Но при сбое переадресации обе переменные будут non-nil. Это означает возникновение утечки.

Её можно предотвратить, добавив вызов для закрытия тел ответов non-nil в блоке обработки ошибок HTTP-запросов. Другое решение: использовать один вызов defer для закрытия тел ответов для всех сбойных и успешных запросов.

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

Исходная реализация resp.Body.Close() также считывает и отклоняет данные оставшихся тел ответов. Благодаря этому HTTP-соединение может быть повторно использовано для другого запроса, если включено поведение keep alive. Поведение самого последнего HTTP-клиента отличается. Теперь вы ответственны за чтение и отклонение оставшихся данных ответов. Если этого не сделать, то HTTP-соединение вместо повторного использования может быть закрыто. Надеюсь, этот маленький подводный камень будет задокументирован в Go 1.5.

Если для вашего приложения важно повторно использовать HTTP-соединения, то в конце логики обработки ответа может понадобиться добавить что-то вроде этого:

_, err = io.Copy(ioutil.Discard, resp.Body)

Это будет необходимо, если вы не считываете всё тело ответа немедленно, например при обработке ответов JSON API с помощью подобного кода:

json.NewDecoder(resp.Body).Decode(&data)

37. Закрытие HTTP-соединений


Некоторые HTTP-серверы какое-то время держат сетевые соединения открытыми (согласно спецификации HTTP 1.1 и серверной конфигурации keep alive). По умолчанию стандартная HTTP-библиотека закрывает соединения, только когда об этом просит целевой HTTP-сервер. Тогда при определённых условиях в вашем приложении могут закончиться сокеты / файловые дескрипторы.

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

Другое решение: добавить заголовок запроса Connection и задать ему значение close. Целевой HTTP-сервер тоже должен ответить заголовком Connection: close. Когда библиотека его увидит, она закроет соединение.

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    req, err := http.NewRequest("GET","http://golang.org",nil)
    if err != nil {
        fmt.Println(err)
        return
    }

    req.Close = true
    //or do this:
    //req.Header.Add("Connection", "close")

    resp, err := http.DefaultClient.Do(req)
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(len(string(body)))
}

Можно ещё глобально отключить повторное использование HTTP-соединений. Для этого создайте кастомную конфигурацию HTTP-транспорта.

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    tr := &http.Transport{DisableKeepAlives: true}
    client := &http.Client{Transport: tr}

    resp, err := client.Get("http://golang.org")
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(resp.StatusCode)

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(len(string(body)))
}

Если вы отправляете на один сервер много запросов, то этого достаточно для сохранения соединения открытым. Но если приложение за короткое время шлёт один-два запроса на много разных серверов, то лучше закрывать соединения сразу после получения ответов. Также можно увеличить лимит на количество открытых файлов. Что лучше — зависит от вашего приложения.

38. Десериализация (unmarshalling) JSON-чисел в интерфейсные значения


Когда вы декодируете/десериализуете JSON-данные в интерфейс, Go по умолчанию обращается с числовыми значениями в JSON как с числами float64. Значит, вот такой код вызовет panic:

package main

import (  
  "encoding/json"
  "fmt"
)

func main() {  
  var data = []byte(`{"status": 200}`)

  var result map[string]interface{}
  if err := json.Unmarshal(data, &result); err != nil {
    fmt.Println("error:", err)
    return
  }

  var status = result["status"].(int) // ошибка
  fmt.Println("status value:",status)
}

Runtime Panic:

panic: interface conversion: interface is float64, not int

Если JSON-значение, которое вы пытаетесь декодировать, целочисленное, есть несколько вариантов.

  • Использовать значение с плавающей запятой как есть :-)
  • Преобразовать значение с плавающей запятой в целочисленный тип, который вам нужен.

    package main
    
    import (  
      "encoding/json"
      "fmt"
    )
    
    func main() {  
      var data = []byte(`{"status": 200}`)
    
      var result map[string]interface{}
      if err := json.Unmarshal(data, &result); err != nil {
        fmt.Println("error:", err)
        return
      }
    
      var status = uint64(result["status"].(float64)) // хорошо
      fmt.Println("status value:",status)
    }
    

  • Использовать тип Decoder для десериализации JSON и представления JSON-чисел с помощью интерфейсного типа Number.

    package main
    
    import (  
      "encoding/json"
      "bytes"
      "fmt"
    )
    
    func main() {  
      var data = []byte(`{"status": 200}`)
    
      var result map[string]interface{}
      var decoder = json.NewDecoder(bytes.NewReader(data))
      decoder.UseNumber()
    
      if err := decoder.Decode(&result); err != nil {
        fmt.Println("error:", err)
        return
      }
    
      var status,_ = result["status"].(json.Number).Int64() // хорошо
      fmt.Println("status value:",status)
    }
    

    Можно использовать строковое представление вашего значения Number, чтобы десериализовать его в другой числовой тип:

    package main
    
    import (  
      "encoding/json"
      "bytes"
      "fmt"
    )
    
    func main() {  
      var data = []byte(`{"status": 200}`)
    
      var result map[string]interface{}
      var decoder = json.NewDecoder(bytes.NewReader(data))
      decoder.UseNumber()
    
      if err := decoder.Decode(&result); err != nil {
        fmt.Println("error:", err)
        return
      }
    
      var status uint64
      if err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status); err != nil {
        fmt.Println("error:", err)
        return
      }
    
      fmt.Println("status value:",status)
    }
    

  • Использовать тип struct, который преобразует (maps) числовое значение в нужный вам числовой тип.

    package main
    
    import (  
      "encoding/json"
      "bytes"
      "fmt"
    )
    
    func main() {  
      var data = []byte(`{"status": 200}`)
    
      var result struct {
        Status uint64 `json:"status"`
      }
    
      if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result); err != nil {
        fmt.Println("error:", err)
        return
      }
    
      fmt.Printf("result => %+v",result)
      //prints: result => {Status:200}
    }
    

  • Использовать struct для преобразования числового значения в тип json.RawMessage, если требуется отложить декодирование значения.

    Это полезно, если вы должны выполнить декодирование условного JSON-поля (conditional field) в условиях возможности изменения структуры или типа поля.

    package main
    
    import (  
      "encoding/json"
      "bytes"
      "fmt"
    )
    
    func main() {  
      records := [][]byte{
        []byte(`{"status": 200, "tag":"one"}`),
        []byte(`{"status":"ok", "tag":"two"}`),
      }
    
      for idx, record := range records {
        var result struct {
          StatusCode uint64
          StatusName string
          Status json.RawMessage `json:"status"`
          Tag string             `json:"tag"`
        }
    
        if err := json.NewDecoder(bytes.NewReader(record)).Decode(&result); err != nil {
          fmt.Println("error:", err)
          return
        }
    
        var sstatus string
        if err := json.Unmarshal(result.Status, &sstatus); err == nil {
          result.StatusName = sstatus
        }
    
        var nstatus uint64
        if err := json.Unmarshal(result.Status, &nstatus); err == nil {
          result.StatusCode = nstatus
        }
    
        fmt.Printf("[%v] result => %+v\n",idx,result)
      }
    }
    


39. Сравнение struct, array, slice и map


Можно использовать оператор эквивалентности == для сравнения переменных структур, если каждое поле структуры можно сравнить с помощью этого оператора.

package main

import "fmt"

type data struct {  
    num int
    fp float32
    complex complex64
    str string
    char rune
    yes bool
    events <-chan string
    handler interface{}
    ref *byte
    raw [10]byte
}

func main() {  
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:",v1 == v2) // выводит: v1 == v2: true
}

Если хоть одно из полей несравниваемо, то применение оператора эквивалентности приведёт к ошибке компилирования. Обратите внимание, что сравнивать массивы можно только тогда, когда сравниваемы их данные.

package main

import "fmt"

type data struct {  
    num int                // ok
    checks [10]func() bool // несравниваемо
    doit func() bool       // несравниваемо
    m map[string] string   // несравниваемо
    bytes []byte           // несравниваемо
}

func main() {  
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:",v1 == v2)
}

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

Самое популярное решение: использовать функцию DeepEqual() из пакета reflect.

package main

import (  
    "fmt"
    "reflect"
)

type data struct {  
    num int                // ok
    checks [10]func() bool // несравниваемо
    doit func() bool       // несравниваемо
    m map[string] string   // несравниваемо
    bytes []byte           // несравниваемо
}

func main() {  
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2)) // prints: v1 == v2: true

    m1 := map[string]string{"one": "a","two": "b"}
    m2 := map[string]string{"two": "b", "one": "a"}
    fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2)) // prints: m1 == m2: true

    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2)) // prints: s1 == s2: true
}

Помимо невысокой скорости (что может быть критично для вашего приложения), DeepEqual() имеет свои собственные подводные камни.

package main

import (  
    "fmt"
    "reflect"
)

func main() {  
    var b1 []byte = nil
    b2 := []byte{}
    fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) // prints: b1 == b2: false
}

DeepEqual() не считает пустой слайс эквивалентным nil-слайсу. Это поведение отличается от того, что вы получите при использовании функции bytes.Equal(): она считает эквивалентными nil и пустые слайсы.

package main

import (  
    "fmt"
    "bytes"
)

func main() {  
    var b1 []byte = nil
    b2 := []byte{}
    fmt.Println("b1 == b2:",bytes.Equal(b1, b2)) // prints: b1 == b2: true
}

DeepEqual() не всегда идеальна при сравнении слайсов.

package main

import (  
    "fmt"
    "reflect"
    "encoding/json"
)

func main() {  
    var str string = "one"
    var in interface{} = "one"
    fmt.Println("str == in:",str == in,reflect.DeepEqual(str, in))
    //prints: str == in: true true

    v1 := []string{"one","two"}
    v2 := []interface{}{"one","two"}
    fmt.Println("v1 == v2:",reflect.DeepEqual(v1, v2))
    //prints: v1 == v2: false (not ok)

    data := map[string]interface{}{
        "code": 200,
        "value": []string{"one","two"},
    }
    encoded, _ := json.Marshal(data)
    var decoded map[string]interface{}
    json.Unmarshal(encoded, &decoded)
    fmt.Println("data == decoded:",reflect.DeepEqual(data, decoded))
    //prints: data == decoded: false (not ok)
}

Если ваши байт-слайсы (или строки) содержат текстовые данные, то, когда понадобится сравнить значения без учёта регистра, вы можете использовать ToUpper() или ToLower() из пакетов bytes и strings (прежде чем прибегнуть к ==, bytes.Equal() или bytes.Compare()). Это сработает для англоязычных текстов, но не для многих других языков. Так что лучше выбрать strings.EqualFold() и bytes.EqualFold().

Если ваши байт-слайсы содержат секретные данные (криптографические хеши, токены и т. д.), которые нужно сравнивать с предоставленной пользователями информацией, обойдитесь без reflect.DeepEqual(), bytes.Equal() или bytes.Compare(). Эти функции сделают приложение уязвимым к атакам по времени. Чтобы избежать утечки информации о времени, используйте функции из пакета crypto/subtle (например, subtle.ConstantTimeCompare()).

40. Восстановление после panic


Функцию recover() можно использовать для поимки/перехвата panic. Это получится, только если вызвать её в блоке defer.

Некорректно:

package main

import "fmt"

func main() {  
    recover() // ничего не делает
    panic("not good")
    recover() // не будет выполнено :)
    fmt.Println("ok")
}

Правильно:

package main

import "fmt"

func main() {  
    defer func() {
        fmt.Println("recovered:",recover())
    }()

    panic("not good")
}

Вызов recover() сработает, только если будет выполнен в блоке defer.

Неправильно:

package main

import "fmt"

func doRecover() {  
    fmt.Println("recovered =>",recover()) // prints: recovered => <nil>
}

func main() {  
    defer func() {
        doRecover() // восстановление panic не произошло
    }()

    panic("not good")
}

41. Обновление и привязка значений полей в slice, array и map в выражениях for range


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

package main

import "fmt"

func main() {  
    data := []int{1,2,3}
    for _,v := range data {
        v *= 10 // оригинал не изменился
    }

    fmt.Println("data:",data) // выводит: [1 2 3]
}

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

package main

import "fmt"

func main() {  
    data := []int{1,2,3}
    for i,_ := range data {
        data[i] *= 10
    }

    fmt.Println("data:",data) // выводит: [10 20 30]
}

Если коллекция содержит значения указателей, то правила немного меняются. Вам всё ещё нужно использовать индексный оператор, чтобы исходная запись указывала на другое значение. Но вы можете обновлять данные, хранящиеся в целевом месте, с помощью второго значения в выражении for range.

package main

import "fmt"

func main() {  
    data := []*struct{num int} {{1},{2},{3}}

    for _,v := range data {
        v.num *= 10
    }

    fmt.Println(data[0],data[1],data[2]) // prints &{10} &{20} &{30}
}

42. «Скрытые данные» в слайсах


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

package main

import "fmt"

func get() []byte {  
    raw := make([]byte,10000)
    fmt.Println(len(raw),cap(raw),&raw[0]) // выводит: 10000 10000 <byte_addr_x>
    return raw[:3]
}

func main() {  
    data := get()
    fmt.Println(len(data),cap(data),&data[0]) // выводит: 3 10000 <byte_addr_x>
}

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

package main

import "fmt"

func get() []byte {  
    raw := make([]byte,10000)
    fmt.Println(len(raw),cap(raw),&raw[0]) // выводит: 10000 10000 <byte_addr_x>
    res := make([]byte,3)
    copy(res,raw[:3])
    return res
}

func main() {  
    data := get()
    fmt.Println(len(data),cap(data),&data[0]) // выводит: 3 3 <byte_addr_y>
}

43. «Повреждение» данных в слайсах


Допустим, вам нужно переписать путь (хранящийся в слайсе). Чтобы ссылаться на каждую папку, вы его перенарезаете, изменяя имя первой папки, а затем комбинируете имена в новый путь.

package main

import (  
    "fmt"
    "bytes"
)

func main() {  
    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')
    dir1 := path[:sepIndex]
    dir2 := path[sepIndex+1:]
    fmt.Println("dir1 =>",string(dir1)) // выводит: dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2)) // выводит: dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)
    path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})

    fmt.Println("dir1 =>",string(dir1)) // выводит: dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2)) // выводит: dir2 => uffixBBBB (not ok)

    fmt.Println("new path =>",string(path))
}

Так не сработает. Вместо AAAAsuffix/BBBBBBBBB вы получите AAAAsuffix/uffixBBBB. Причина в том, что слайсы обеих папок ссылаются на один и тот же массив данных из исходного слайса пути. То есть исходный путь тоже изменился. Это может быть проблемой для вашего приложения.

Ее можно решить, разместив в памяти новые слайсы и скопировав туда нужные данные. Другой выход: использовать полное выражение слайса (full slice expression).

package main

import (  
    "fmt"
    "bytes"
)

func main() {  
    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')
    dir1 := path[:sepIndex:sepIndex] // полное выражение слайса
    dir2 := path[sepIndex+1:]
    fmt.Println("dir1 =>",string(dir1)) // выводит: dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2)) // выводит: dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)
    path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})

    fmt.Println("dir1 =>",string(dir1)) // выводит: dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2)) // выводит: dir2 => BBBBBBBBB (ok now)

    fmt.Println("new path =>",string(path))
}

Дополнительный параметр в полном выражении управляет ёмкостью нового слайса. Добавление этого параметра к новому слайсу запустит размещение в памяти нового буфера вместо перезаписи данных во второй слайс.

44. «Устаревшие» слайсы


На одни и те же данные могут ссылаться несколько слайсов. Например, когда вы создаёте новый слайс на основе имеющегося. Если такое поведение важно для вашего приложения, позаботьтесь об «устаревших» слайсах.

В какой-то момент добавление данных в один из слайсов приведёт к размещению в памяти нового массива, потому что в старом не хватит места для новых данных. Теперь на старый массив (со старыми данными) ссылаются несколько слайсов.

import "fmt"

func main() {  
    s1 := []int{1,2,3}
    fmt.Println(len(s1),cap(s1),s1) // выводит 3 3 [1 2 3]

    s2 := s1[1:]
    fmt.Println(len(s2),cap(s2),s2) // выводит 2 2 [2 3]

    for i := range s2 { s2[i] += 20 }

    // всё ещё ссылается на тот же массив
    fmt.Println(s1) // выводит [1 22 23]
    fmt.Println(s2) // выводит [22 23]

    s2 = append(s2,4)

    for i := range s2 { s2[i] += 10 }

    //s1 is now "stale"
    fmt.Println(s1) // выводит [1 22 23]
    fmt.Println(s2) // выводит [32 33 14]
}

45. Методы и объявления типов


Когда вы определяете новый тип на основе существующего (не интерфейсного), тем самым вы создаёте объявление типа и не наследуете методы, объявленные в существующем типе.

Неправильно:

package main

import "sync"

type myMutex sync.Mutex

func main() {  
    var mtx myMutex
    mtx.Lock() // ошибка
    mtx.Unlock() // ошибка
}

Ошибки компилирования:

/tmp/sandbox106401185/main.go:9: mtx.Lock undefined (type myMutex has no field or method Lock) /tmp/sandbox106401185/main.go:10: mtx.Unlock undefined (type myMutex has no field or method Unlock)

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

Правильно:

package main

import "sync"

type myLocker struct {  
    sync.Mutex
}

func main() {  
    var lock myLocker
    lock.Lock() // ok
    lock.Unlock() // ok
}

Объявления интерфейсных типов также сохраняют свои наборы методов.

Правильно:

package main

import "sync"

type myLocker sync.Locker

func main() {  
    var lock myLocker = new(sync.Mutex)
    lock.Lock() // ok
    lock.Unlock() // ok
}

46. Как выбраться из кодовых блоков for switch и for select


  • уровень: более опытный

Выражение break без метки (label) выводит вас только из внутреннего блока switch/select. Если использовать выражение return — не вариант, тогда лучший выход — задать метку для внешнего цикла.

package main

import "fmt"

func main() {  
    loop:
        for {
            switch {
            case true:
                fmt.Println("breaking out...")
                break loop
            }
        }

    fmt.Println("out!")
}

То же самое и с выражением goto

47. Итерационные переменные и замыкания в выражениях for


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

Некорректно:

package main

import (  
    "fmt"
    "time"
)

func main() {  
    data := []string{"one","two","three"}

    for _,v := range data {
        go func() {
            fmt.Println(v)
        }()
    }

    time.Sleep(3 * time.Second)
    // горутины выводят: three, three, three
}

Простейшее решение (не требующее менять горутины): сохранить текущее значение итерационной переменной в локальной переменной внутри блока цикла for.

Правильно:

package main

import (  
    "fmt"
    "time"
)

func main() {  
    data := []string{"one","two","three"}

    for _,v := range data {
        vcopy := v //
        go func() {
            fmt.Println(vcopy)
        }()
    }

    time.Sleep(3 * time.Second)
    // горутины выводят: one, two, three
}

Другое решение: передать текущую итерационную переменную анонимной горутине в виде параметра.

Правильно:

package main

import (  
    "fmt"
    "time"
)

func main() {  
    data := []string{"one","two","three"}

    for _,v := range data {
        go func(in string) {
            fmt.Println(in)
        }(v)
    }

    time.Sleep(3 * time.Second)
    // горутины выводят: one, two, three
}

Здесь чуть более сложная версия ловушки.

Некорректно:

package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

func (p *field) print() {  
    fmt.Println(p.name)
}

func main() {  
    data := []field{{"one"},{"two"},{"three"}}

    for _,v := range data {
        go v.print()
    }

    time.Sleep(3 * time.Second)
    // горутины выводят: three, three, three
}

Правильно:

package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

func (p *field) print() {  
    fmt.Println(p.name)
}

func main() {  
    data := []field{{"one"},{"two"},{"three"}}

    for _,v := range data {
        v := v
        go v.print()
    }

    time.Sleep(3 * time.Second)
    // горутины выводят: one, two, three
}

Как вы думаете, что вы увидите (и почему), запустив этот код?

package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

func (p *field) print() {  
    fmt.Println(p.name)
}

func main() {  
    data := []*field{{"one"},{"two"},{"three"}}

    for _,v := range data {
        go v.print()
    }

    time.Sleep(3 * time.Second)
}

48. Вычисление аргумента блока defer (Deferred Function Call Argument Evaluation)


Аргументы для вызовов отложенных функций вычисляются тогда же, когда и выражение defer (а не когда на самом деле выполняется функция).

package main

import "fmt"

func main() {  
    var i int = 1

    defer fmt.Println("result =>",func() int { return i * 2 }())
    i++
    //выводит: result => 2 (not ok if you expected 4)
}

49. Вызов блока defer


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

package main

import (  
    "fmt"
    "os"
    "path/filepath"
)

func main() {  
    if len(os.Args) != 2 {
        os.Exit(-1)
    }

    start, err := os.Stat(os.Args[1])
    if err != nil || !start.IsDir(){
        os.Exit(-1)
    }

    var targets []string
    filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        if !fi.Mode().IsRegular() {
            return nil
        }

        targets = append(targets,fpath)
        return nil
    })

    for _,target := range targets {
        f, err := os.Open(target)
        if err != nil {
            fmt.Println("bad target:",target,"error:",err) //выводит ошибку: too many open files
            break
        }
        defer f.Close() // не будет закрыто в конце этого блока
        // сделай что-нибудь с файлом...
    }
}

Один из способов решения проблемы — обернуть кодовый блок в функцию.

package main

import (  
    "fmt"
    "os"
    "path/filepath"
)

func main() {  
    if len(os.Args) != 2 {
        os.Exit(-1)
    }

    start, err := os.Stat(os.Args[1])
    if err != nil || !start.IsDir(){
        os.Exit(-1)
    }

    var targets []string
    filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        if !fi.Mode().IsRegular() {
            return nil
        }

        targets = append(targets,fpath)
        return nil
    })

    for _,target := range targets {
        func() {
            f, err := os.Open(target)
            if err != nil {
                fmt.Println("bad target:",target,"error:",err)
                return
            }
            defer f.Close() // ok
            // сделай что-нибудь с файлом...
        }()
    }
}

Другое решение: избавиться от выражения defer :-)

50. Ошибки при приведении типов


Сбойные утверждения типов возвращают «нулевое значение» для целевых типов, использованных в операторе утверждения. Наложившись на сокрытие переменных, это может привести к непредсказуемому поведению.

Некорректно:

package main

import "fmt"

func main() {  
    var data interface{} = "great"

    if data, ok := data.(int); ok {
        fmt.Println("[is an int] value =>",data)
    } else {
        fmt.Println("[not an int] value =>",data)
        //выводит: [not an int] value => 0 (not "great")
    }
}

Правильно:

package main

import "fmt"

func main() {  
    var data interface{} = "great"

    if res, ok := data.(int); ok {
        fmt.Println("[is an int] value =>",res)
    } else {
        fmt.Println("[not an int] value =>",data)
        // выводит: [not an int] value => great (as expected)
    }
}

51. Блокированные горутины и утечки ресурсов


В выступлении «Go Concurrency Patterns» на конференции Google I/O в 2012-м Роб Пайк рассказал о нескольких фундаментальных concurrency-шаблонах. Один из них — извлечение первого результата.

func First(query string, replicas ...Search) Result {  
    c := make(chan Result)
    searchReplica := func(i int) { c <- replicas[i](query) }
    for i := range replicas {
        go searchReplica(i)
    }
    return <-c
}

Для каждой копии (replica) поиска функция запускает отдельную горутину. Каждая из горутин отправляет свои поисковые результаты в канал результатов. Возвращается первое значение из канала.

А что с результатами от других горутин? И что насчёт них самих?

В функции First() канал результатов не буферизован. Это значит, что возвращается только первая горутина. Все остальные застревают в попытке отправить свои результаты. Получается, что если у вас более одной копии (replica), то при каждом вызове происходит утечка ресурсов.

Чтобы этого избежать, все горутины должны завершиться (exit). Одно из возможных решений: использовать достаточно большой буферизованный канал результатов, способный вместить все результаты.

func First(query string, replicas ...Search) Result {  
    c := make(chan Result,len(replicas))
    searchReplica := func(i int) { c <- replicas[i](query) }
    for i := range replicas {
        go searchReplica(i)
    }
    return <-c
}

Другое решение: использовать выражение select со сценарием (case) default и буферизованный канал на одно значение. Сценарий default позволяет быть уверенным, что горутина не застряла, даже если канал результатов не может принимать сообщения.

func First(query string, replicas ...Search) Result {  
    c := make(chan Result,1)
    searchReplica := func(i int) {
        select {
        case c <- replicas[i](query):
        default:
        }
    }
    for i := range replicas {
        go searchReplica(i)
    }
    return <-c
}

Также можно использовать специальный канал отмены (special cancellation channel) для прерывания рабочих горутин.

func First(query string, replicas ...Search) Result {  
    c := make(chan Result)
    done := make(chan struct{})
    defer close(done)
    searchReplica := func(i int) {
        select {
        case c <- replicas[i](query):
        case <- done:
        }
    }
    for i := range replicas {
        go searchReplica(i)
    }

    return <-c
}

Почему в презентации есть такие баги? Роб Пайк просто не хотел усложнять слайды (slides) своей презентации. Такое объяснение имеет смысл, но это может быть проблемой для новичков, которые используют код, не думая о вероятных проблемах.

52. Применение методов, принимающих значение по ссылке (pointer receiver), к экземплярам значений


Пока значение адресуемо (addressable), к нему можно применять метод, принимающий значение по ссылке. Иными словами, в некоторых случаях вам не нужно иметь версию метода, принимающего параметр по значению.

Но не каждая переменная адресуема. Элементы хеш-таблицы (map) неадресуемы. Переменные, на которые ссылаются через интерфейсы, тоже неадресуемы.

package main

import "fmt"

type data struct {  
    name string
}

func (p *data) print() {  
    fmt.Println("name:",p.name)
}

type printer interface {  
    print()
}

func main() {  
    d1 := data{"one"}
    d1.print() //ok

    var in printer = data{"two"} // ошибка
    in.print()

    m := map[string]data {"x":data{"three"}}
    m["x"].print() //ошибка
}

Ошибки компилирования:

/tmp/sandbox017696142/main.go:21: cannot use data literal (type data) as type printer in assignment: data does not implement printer (print method has pointer receiver)
/tmp/sandbox017696142/main.go:25: cannot call pointer method on m["x"] /tmp/sandbox017696142/main.go:25: cannot take the address of m["x"]

53. Обновление полей значений в хеш-таблице


Если у вас есть таблица, состоящая из структур, то вы не можете обновлять отдельные структурные поля.

Неправильно:

package main

type data struct {  
    name string
}

func main() {  
    m := map[string]data {"x":{"one"}}
    m["x"].name = "two" // error
}

Ошибка компилирования:

/tmp/sandbox380452744/main.go:9: cannot assign to m["x"].name

Это не работает, потому что элементы таблицы не адресуемы.

Новичков может дополнительно путать то, что элементы слайсов — адресуемы.

package main

import "fmt"

type data struct {  
    name string
}

func main() {  
    s := []data {{"one"}}
    s[0].name = "two" // ok
    fmt.Println(s)    // prints: [{two}]
}

Обратите внимание, что когда-то в одном из компиляторов (gccgo) можно было обновлять поля элементов таблицы. Но это быстро пофиксили :-) Также считалось, что такая возможность появится в Go 1.3. Но в то время это было не так важно, так что фича всё ещё висит в списке todo.

Первое обходное решение: использовать временную переменную.

package main
import "fmt"

type data struct {  
    name string
}

func main() {  
    m := map[string]data {"x":{"one"}}
    r := m["x"]
    r.name = "two"
    m["x"] = r
    fmt.Printf("%v",m) //выводит: map[x:{two}]
}

Второе обходное решение: использовать хеш-таблицу с указателями.

package main

import "fmt"

type data struct {  
    name string
}

func main() {  
    m := map[string]*data {"x":{"one"}}
    m["x"].name = "two" //ok
    fmt.Println(m["x"]) //выводит: &{two}
}

Кстати, что будет, если выполнить этот код?

package main

type data struct {  
    name string
}

func main() {  
    m := map[string]*data {"x":{"one"}}
    m["z"].name = "what?" //???
}

54. nil-интерфейсы и nil-интерфейсные значения


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

Интерфейсный тип и поля значений заполняются на основе типа и значения переменной, использованной для создания соответствующей интерфейсной переменной. Если вы попытаетесь проверить, эквивалентна ли переменная nil, то это может привести к непредсказуемому поведению.

package main

import "fmt"

func main() {  
    var data *byte
    var in interface{}

    fmt.Println(data,data == nil) // выводит: <nil> true
    fmt.Println(in,in == nil)     // выводит: <nil> true

    in = data
    fmt.Println(in,in == nil)     // выводит: <nil> false
    //'data' является 'nil', но 'in' — не 'nil'
}

Остерегайтесь этой ловушки, когда у вас есть функция, возвращающая интерфейсы.

Некорректно:

package main

import "fmt"

func main() {  
    doit := func(arg int) interface{} {
        var result *struct{} = nil

        if(arg > 0) {
            result = &struct{}{}
        }

        return result
    }

    if res := doit(-1); res != nil {
        fmt.Println("good result:",res) // выводит: good result: <nil>
        // 'res' не является 'nil', но его значение — 'nil'
    }
}

Правильно:

package main

import "fmt"

func main() {  
    doit := func(arg int) interface{} {
        var result *struct{} = nil

        if(arg > 0) {
            result = &struct{}{}
        } else {
            return nil // возвращает явный 'nil'
        }

        return result
    }

    if res := doit(-1); res != nil {
        fmt.Println("good result:",res)
    } else {
        fmt.Println("bad result (res is nil)") // здесь — как и ожидалось
    }
}

55. Переменные стека и кучи


Не всегда известно, находится ли переменная в стеке или в куче. Если в С++ создать переменную с помощью оператора new, то она всегда будет в куче. В Go место размещения переменной выбирает компилятор, даже если используются функции new() или make(). Компилятор делает выбор на основании размера и результата «анализа локальности» (escape analysis). Это также означает, что можно возвращать ссылки на локальные переменные, что недопустимо в других языках, например в С и С++.

Если вы хотите знать, где находятся переменные, то передайте -gcflags -m в go build или go run (например, go run -gcflags -m app.go).

56. GOMAXPROCS, согласованность (concurrency) и параллелизм


Go 1.4 и ниже используют только один тред контекста исполнения / ОС. Это значит, что в каждый момент времени может исполняться лишь одна горутина. Начиная с Go 1.5 количество контекстов исполнения стало равно количеству логических процессорных ядер, возвращаемому runtime.NumCPU(). Оно может не совпадать с общим количеством логических ядер в системе, в зависимости от настроек привязки CPU для процесса. Количество можно настроить, изменив переменную среды GOMAXPROCS или вызвав функцию runtime.GOMAXPROCS().

Существует распространённое заблуждение, что GOMAXPROCS представляет собой количество процессоров, которые Go будет использовать для запуска горутин. Документация к функции runtime.GOMAXPROCS() только добавляет неразберихи. Но в описании к переменной GOMAXPROCS (https://golang.org/pkg/runtime/) говорится именно о тредах ОС.

Значение GOMAXPROCS может превышать количество ваших процессоров, верхний предел — 256.

package main

import (  
    "fmt"
    "runtime"
)

func main() {  
    fmt.Println(runtime.GOMAXPROCS(-1)) // выводит: X (1 on play.golang.org)
    fmt.Println(runtime.NumCPU())       // выводит: X (1 on play.golang.org)
    runtime.GOMAXPROCS(20)
    fmt.Println(runtime.GOMAXPROCS(-1)) // выводит: 20
    runtime.GOMAXPROCS(300)
    fmt.Println(runtime.GOMAXPROCS(-1)) // выводит: 256
}

57. Изменение порядка операций чтения и записи


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

package main

import (  
    "runtime"
    "time"
)

var _ = runtime.GOMAXPROCS(3)

var a, b int

func u1() {  
    a = 1
    b = 2
}

func u2() {  
    a = 3
    b = 4
}

func p() {  
    println(a)
    println(b)
}

func main() {  
    go u1()
    go u2()
    go p()
    time.Sleep(1 * time.Second)
}

Если запустить этот код несколько раз, то можно увидеть такие комбинации переменных a и b:

1 
2

3 
4

0 
2

0 
0

1 
4

Самая интересная комбинация — 02 — говорит о том, что b была обновлена раньше a.

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

58. Диспетчеризация по приоритетам (Preemptive Scheduling)


Могут появляться разбойничьи (rogue) горутины, не дающие другим горутинам выполняться. Такое случается, если у вас есть цикл for, не позволяющий запустить диспетчер.

package main

import "fmt"

func main() {  
    done := false

    go func(){
        done = true
    }()

    for !done {
    }
    fmt.Println("done!")
}

Цикл for не должен быть пустым. Проблема не исчезнет, пока в цикле содержится код, не запускающий исполнение диспетчера.

Он запускается после сборки мусора, выражений go, операций блокирования каналов, блокирующих системных вызовов и операций блокирования. Также он может работать, когда вызвана невстроенная (non-inlined) функция.

package main

import "fmt"

func main() {  
    done := false

    go func(){
        done = true
    }()

    for !done {
        fmt.Println("not done!") // не встроена
    }
    fmt.Println("done!")
}

Чтобы узнать, встроена ли вызываемая вами в цикле функция, передайте -gcflags –m в go build или go run (например, go build -gcflags -m).

Другое решение: явно вызвать диспетчер. Это можно сделать с помощью функции Gosched() из пакета runtime.

package main

import (  
    "fmt"
    "runtime"
)

func main() {  
    done := false

    go func(){
        done = true
    }()

    for !done {
        runtime.Gosched()
    }
    fmt.Println("done!")
}

Если вы дочитали до конца и у вас есть комментарии или идеи, добро пожаловать в дискуссию на Reddit (и в комментарии здесь, на Хабре. — Примеч. пер.).
Поделиться с друзьями
-->

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


  1. wOvAN
    09.11.2016 15:38
    +7

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

    Н-р: нет функции типа IsFileExists () (ну серьезно нет)

    «Чего только нет стандартной библиотеке Go, практически ничего нет»

    Стандартными средствами для работы с SQL невозможно получить данные запроса полный состав которых и тип которых вам заранее неизвестен. Обработка элементарного запроса «select * from table» превратится для вас в ад по поиску костыля.

    Определение видимости элемента в зависимости от кейса первой буквы отдельный гемор, что будет если вы решите поменять область видимости? Да, да…

    Возвращаемых функцией кортежи нельзя на лету преобразовать тип.

    func get() (int64, err) {}

    i int
    err error
    i, err = get() // Ошибка, которую нельзя исправить в этой строке

    А ещё нет директив компилятора (Ну и так далее)

    Вообще, взглянув на те решения которые предлагает Go в тех или иных ситуациях (Синтаксис и прочее)

    Создается впечатление, что автор языка школьник который решил изобрести свой язык. Как ещё можно объяснить такое решение для условной компиляции под разные платформы:

    pkg_windows.go
    pkg_linux.go

    и многие, многие другие решения.

    Серьезно, строить современный язык, стоя на плечах гигантов и имея весь их опыт и сделать такое? Ну блин(

    пс За статью спасибо


    1. AnimaLocus
      09.11.2016 15:49
      -6

      Мне кажется в данной статье обсуждаются особенности, а не минусы. Причем скорее положительные.


      1. SirEdvin
        09.11.2016 15:52
        +9

        Тут именно особенности. Например, мне жутко не нравится штука с импортами. Закомментировал кусок кода, комментируй и импорт?


        Отключалось бы оно, вопросов бы не было, но…


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


        1. AnimaLocus
          09.11.2016 15:54

          Дак да, в этом и прелесть. В итоге можно читать чужой код без боли.


          1. SirEdvin
            09.11.2016 15:56
            +1

            Ну, а вот если вам приходится делать что-то, что не нравится компилятору (а такое часто бывает), то приходится страдать вам, а потом в войне приходится страдать тем, кто это читает.


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


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


            1. AnimaLocus
              09.11.2016 16:03

              Да, в рамках одной команды никто не мешает. Но учитывая что есть постоянная потребность использовать чужие пакеты, в идеале слать реквесты и вот это все (open source)… Гайдлайнов к сожалению не хватает, да, но сравнивать выходит все равно не с чем. :)


              1. SirEdvin
                09.11.2016 16:11
                +1

                Ну, могу сказать, что Python и Java в целом отлично обходятся и без такого жесткого забивания в open-source пакетах и в 90% случаев код читаем и прост.


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


                1. Alexeyco
                  09.11.2016 16:46

                  И часто вы Питон компилируете?


                  1. SirEdvin
                    09.11.2016 16:55

                    Тут я что-то затупил, спасибо за замечание :)
                    Можно заменить "компилятор" на "интерпретатор" для питона, суть то все равно не поменяется. Он так же мог бы ругатся на неиспользуемые импорты и прочее.


                    1. Alexeyco
                      09.11.2016 17:50

                      Ну а еще go мог бы, например, не ругаться на несоответствия типов, а вести себя как php.

                      Если бы у бабушки были яйца… (с)


                      1. SirEdvin
                        09.11.2016 17:54

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


                        Скажем, если вы увидете отличный язык программирования в котором каждая строка должна будет начинатся с ":", разве вы не скажите, что это какая-то дичь?


                        1. AnimaLocus
                          09.11.2016 17:57
                          -2

                          Почему каждая строка? Только лишь объявление переменной. Это какраз прекрасно ибо хорошо видно область видимости каждой переменной, да и вообще это статическая типизация — попахивает холиваром статическая vs динамическая.


                          1. SirEdvin
                            09.11.2016 17:58

                            Это как раз вопрос к тому, почему в Go нельзя отключить проверку на неиспользуемые импорты и переменные.


                            А вот причем тут статическая типизация, я к сожалению, не пойму.


                            1. AnimaLocus
                              09.11.2016 18:01

                              Тред просто ушел в что-то большее и границы размылись. По поводу импорта, да, не сильно радует. Но с другой стороны можно найти этому логичное объяснение: не нужно использовать «лишние» компоненты. Хотя конечно, напрягает во время тестирования новых пакетов. :)


                              1. Source
                                09.11.2016 20:11
                                +5

                                Но с другой стороны можно найти этому логичное объяснение

                                У многих языков есть несколько режимов компиляции, хотя бы Debug и Release. Что мешает Go при импорте лишних пакетов выдавать warning, а ошибку компиляции оставить только для Release-варианта?
                                Сделать это элементарно, зато Вы смогли бы во время разработки не отвлекаться на удаление импортов, а потом почистить их единоразово, когда текущая задача уже решена.


                                1. AnimaLocus
                                  09.11.2016 20:32
                                  -1

                                  Я уверен что технически ничего не мешает. Дело в подходе.


          1. Sixshaman
            10.11.2016 12:29
            +1

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


            1. polifill
              10.11.2016 12:39
              -3

              Отнюдь.

              1. Современные IDE поддерживают автоформатирование. Настройте под себя чтобы при открытии код становился таким как вам нужно. Поправьте под себя gofmt или вовсе не запускайте его — пока вы работаете один никому нет дела.

              2. Если вы работаете над чем-то серьезным, в коллективе — то у вас есть git (или нечто подобное).
              Простое переформатирование кода под потребности ОТДЕЛЬНОГО программиста превращает компактные ИНФОРМАТИВНЫЕ коммиты git, в котором ты ТОЧЕЧНО видишь что было изменено — в ужастные гигантские простыни, состоящие большей частью из переносов скобочек на иную строку да изменению отступов.

              Та же проблема возникает из за разности стилей написания — пока глаз подстроится, проходит время. Немного. Но часто.

              То есть по сути выбор только между:

              или
              ТЫ МЕШАЕШЬ КОЛЛЕГАМ.
              или
              ТЫ ПОДСТРАИВАЕШЬСЯ ПОД ЕДИНЫЙ СТИЛЬ и КОЛЛЕГАМ ОБЛЕГЧАЕШЬ ЖИЗНЬ.


            1. stokker
              10.11.2016 15:56
              -1

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

              Наоборот, в идеале синтаксис должен поддерживать только один стиль написания. Остальное сразу отлавливается компилятором (или IDE уже при написании).


              1. Alexeyco
                10.11.2016 16:29

                Справедливости ради стоит отметить, что в этом плане gofmt более демократичен. Он по дефолту имеет определенное видение того, как должен выглядеть код. Но в целом его возможно поднастроить. Правда я согласен, что разрабатывать нужно максимально «ванильно». То есть, в соответствии с тем, как это представляли себе создатели языка.


              1. Sirikid
                10.11.2016 16:32

                Размышляя таким образом можно сделать синтаксис частью языка и придти к Python, Haskell и различным ML.


                1. stokker
                  10.11.2016 18:51

                  Не можно, а нужно )) Но, естественно, это не единственный критерий хорошего ЯП.


        1. Laney1
          09.11.2016 15:59
          +1

          Закомментировал кусок кода, комментируй и импорт?

          в теории да, на практике же все (ну, может и не все, но я точно) пользуются goimports


        1. kamenevn
          09.11.2016 20:31
          -5

          Например, мне жутко не нравится штука с импортами. Закомментировал кусок кода, комментируй и импорт?

          Я использую редактор Atom с плагином для go и он так умеет делать.


        1. 3vilhamst3r
          09.11.2016 20:33

          в atom/vim-go это решается использованием утилиты goimports, которая сама проставляет или удаляет импорты. Пока не используется кастомная сборка (gb и прочие), работает очень сносно.


        1. polifill
          10.11.2016 15:58
          -2

          Странно, что вас напрягают такие мелочи.
          У меня вот vim при записи автоматически удаляет или добавляет нужные импорты.


      1. TargetSan
        09.11.2016 18:37
        +3

        Nil interfaces, одна из самых нетривиальных подлянок


    1. SirEdvin
      09.11.2016 15:50
      +1

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


      Единственное, что я никогда не смогу понять — упор на кодогенерацию и то, что в языке нет нормальных комментариев, а только метаданные.


      1. polifill
        10.11.2016 10:21
        -3

        Боюсь, вы просто никогда не работали над большими серьезными проектами — потому и не понимаете зачем в golang введены все эти ограничения.

        Отклоняться от идеологии, предлагаемой авторами, вовсе и не нужно.

        То, что в других языках пытаются исправить с помощью code style guide да специальных линтеров для проверки этих стилей — в golang просто жестко директивно внедрено создателями языка.

        Сие экономит кучу времени именно в больших проектах.

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

        Я согласен, что та же фигурная скобка после if { может напрягать.
        Но нужно себя пересилить.

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


        1. SirEdvin
          10.11.2016 10:39
          +2

          Если у вас большой сложный проект, то все решается довольно просто — у вас точно есть CI, если у вас есть CI, у вас есть немного автотестов, которые блочат выкатку, если падают. Добавляем к ним style-check и профит. Если проект небольшой — то практика Code Review, которая все равно должна использоватся вас спасет.


          Про интеграцию сторонних библиотек я уже говорил — 90% либ на Java или Python написаны хорошо и понятно, и им для этого не нужна жесткая проверка.


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


          1. Alexeyco
            10.11.2016 10:45
            -4

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


            1. SirEdvin
              10.11.2016 10:46

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

              А вот как с этим то быть? Все равно же и для Golang нужно настраивать синтаксический анализатор, что бы такие случаи отлавливать.


              1. Alexeyco
                10.11.2016 10:48

                Вы впадаете в какие-то крайности. То есть, Golang теперь плох потому, что не имеет из коробки статического анализатора? ))) Буквально вчера он был плох своей строгостью. Я за вами не успеваю.


                1. SirEdvin
                  10.11.2016 10:51
                  +1

                  Нет, я утверждаю следующее:
                  "У golang есть строгий, бесполезный и не отключаемый статический анализатор, который по факту не решает проблем".
                  И поэтому зачем он там нужен, мне не понятно. Качества кода в целом доверить ему нельзя, и в итоге он просто иногда вставляет палки в колеса.


                  1. Alexeyco
                    10.11.2016 10:56
                    -2

                    А я утверждаю, что проще создать строгий компилятор, который, конечно, руки не отрастит программеру. Но хотя бы заставит его задуматься, что же он творит. Вместо того, чтобы разбирать исключительные ситуации и проверять… «так, ага, вот этот импорт он не использует — игнорируем… ага, вот переменную объявил, но не использует… не буду-ка я выделять память для нее» и так далее. Люди странные. Дают им PHP. Делай что угодно. Только что фатальные ошибки отрубать (вроде) нельзя. Плохо. Дают им строгий во всех смыслах язык. Плохо.

                    У меня (не обижайтесь) складывается впечатление, что вы не пытались быть конструктивным первым своим комментом. Но потом все завертелось, и вы продолжаете из принципа. Как говорит моя мама, «усраться, но не поддаться».


                    1. SirEdvin
                      10.11.2016 11:02

                      Возможно, я не совсем понимаю, но чем именно этот компилятор заставил программистов задуматся? Так же, он все равно не убирает необходимостью Code Review и прочих проверок.


                      Более того, для того, что бы обойти этот компилятор, люди сделал goimport, который автоматически обновляет строку импортов и удаляет/добавляет туда строки.


                      Собственно, возникает вопрос — зачем?


                      P.S. Скорее всего, я просто плохо формулирую мои мысли, поэтому так и получается. + я таки немного люблю спросить, потому что, например, в спорах так же всплывают и позитивные стороны Go :)


                      1. Alexeyco
                        10.11.2016 11:10
                        -1

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

                        > Собственно, возникает вопрос — зачем?
                        Собственно, встречный вопрос — почему нет?


                        1. SirEdvin
                          10.11.2016 11:14

                          Собственно, встречный вопрос — почему нет?

                          Если я не ошибаюсь, то Go вроде хотел быть минималистичный. Зачем добавлять в минималистический язык бесполезные вещи?


                          1. Alexeyco
                            10.11.2016 11:14

                            Это не бесполезные вещи, а вещи, которые помогают делать его минималистичным.


                            1. SirEdvin
                              10.11.2016 11:17
                              +1

                              В целом, мой аргумент остается таким же: если занятся автоматизацией code style на проекте, то все равно нужно будет переделывать или значительно дополнять то, что делает компилятор.


                              А если не заниматся, то основные проблемы плохого кода все равно останутся.


                              А если взять гипотезу у том, что значительная часть этих проверок была сделана для ускорения сборки, то почему бы ее не включать по ключу --release или как-то так?


                              1. Alexeyco
                                10.11.2016 11:23

                                Мой аргумент тоже остается прежним. Это вредность и простое нежелание сдаваться. В вопросе личного отношения спорить невозможно, а все остальные доводы были развенчаны. Поэтому остается только комментить «я пишу и плююсь» без аргументов.


                              1. polifill
                                10.11.2016 11:24

                                Останутся проблемы плохого кода, конечно же.
                                Но часть проблем плохого кода УЖЕ РЕШЕНА за вас.
                                И способ решения ЕДИНООБРАЗЕН по всему программисткому сообществу golang.

                                Code style guide возникли не просто так.

                                Это как Вавилонское стопотворение — пока ты говоришь на одном языке, пока ты манипулирует одинаковыми категориями в коллективе — ты работаешь намного быстрее.

                                Поэтому чем раньше происходит унификация и чем невозможнее её избежать — тем лучше.

                                golang решает известную проблему: «есть 100 стандартов, как так можно работать, давайте придумаем общий стандарт… упс — и уже 101 стандарт». golang решает это навязыванием своего видения, подобно тому, как Python учил программистов правильно делать отступы.

                                У меня есть опытнейший коллега (не golang, другой язык) — но после него просто невозможно работать над кодом, пока отступы не выставлены как нужно.


                      1. polifill
                        10.11.2016 11:17
                        -1

                        Люди в большинстве своем — весьма ленивы, посредственны и инертны.

                        Если позволить людям решать все самим — мы получим скатывание 99% программного кода в полный шлак.

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

                        К примеру, когда-то именно для этого была в Java сделана обязательная обработка/прокидываение наверх exception вплоть по всей иерархии стека вызовов в ЯВНОМ виде.

                        Дизайн Java пытался решить ту же задачу что и Go. Принудительно поднять средний уровень разработки. Чуть раньше такую задачу пытались решить, проектируя язык Ada.

                        Просто Go пытается минималистично, облегчая работу программисту, поднять уровень кодирования. Java пытался сделать это же максималистично.


              1. polifill
                10.11.2016 11:05
                -1

                Да конечно можно все сделать вручную. Можно и свою ОС написать и свой компилятор — прежде чем приступать к основной задаче.

                Но ЗАЧЕМ лишний труд, если за вас уже решили часть задач?

                Авторы golang установили минимальную планку, ниже которой язык/компилятор запрещает опускаться.

                То есть просто принудительно подняли средний уровень code style.


                1. SirEdvin
                  10.11.2016 11:13

                  Но ЗАЧЕМ лишний труд, если за вас уже решили часть задач?

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


                  1. polifill
                    10.11.2016 11:31

                    После решения любых проблем — проблемы останутся. Это жизнь.
                    Там может, вообще, лучше лечь на диван и ничего не делать?


    1. VGrabko
      09.11.2016 16:05

      1.

      if _, err := os.Stat("./config.ini"); os.IsNotExist(err) {

      }

      2. тут строгая типизация, чего вы хотели?
      3. вы так говорите «такое решение для условной компиляции под разные платформы», будто это что-то плохое.


      1. wOvAN
        09.11.2016 16:12
        -1

        1. Напиши сложную конструкцию, вместо одной функции.
        2, Хотел бы например такого: int64(i), err = get()
        // для чего кортежи в языке со структурами, для меня тоже вопрос.
        3. Очень плохое, те возможности которые дает банальный IFDEF в коде, во истину удивительны.
        .// тут ещё вспомнил про include-ы (


        1. Sirikid
          09.11.2016 16:20
          +2

          2. Коллизия с синтаксисом вызова функции
          А кортежи что бы было удобнее возвращать ошибки, иключений то нет


          1. wOvAN
            09.11.2016 17:09

            Коллизия, ну ок, есл итакой слабый анализатор, придумайте другой синтаксис н-р так: (i)int64 или как то иначе
            Ну про отсутствие исключений я не упоминаю, про это и так написано не мало.


            1. Sirikid
              09.11.2016 18:13

              Вы же понимаете что так языки не дизайнят? Не самый худший вариант того что может получится с таким подходом представляет C++, только не надо говорить что все прекрасно читается и парсится.
              Проведите эксперимент, возьмите грамматику Go и измените её так, что бы реализовать пункт 2.


        1. Alexeyco
          09.11.2016 16:41
          +1

          А что мешает i, err := get()? Невозможность придраться?


          1. wOvAN
            09.11.2016 16:55
            -1

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


            1. AnimaLocus
              09.11.2016 16:58

              Так x.x, err := get()?


              1. AlexWinner
                09.11.2016 22:51

                Так нельзя: https://play.golang.org/p/vNXS9ufN7a


                1. AnimaLocus
                  09.11.2016 23:05

                  Действительно. Только так работает:

                  	var err error
                  	x.x, err = get()
                  

                  https://play.golang.org/p/UPcj383JTp


            1. Alexeyco
              09.11.2016 17:04
              -2

              Это же строго типизированный язык. Типизируйте строго. А то, что _вам_бы_хотелось_orm_из_коробки_ ник не делает язык плохим. С С++ в компекте как-то не наличествует ORM. И то, что go не C++ — я думаю, тоже не аргумент. Зато на go гораздо проще кодить ДАЖЕ при отсутствии некоторого синтаксического сахара, который присутствует в других языках.

              $i = 123;
              $i = "456";

              Если хочется такого, шествуйте в мир PHP. Честное слово… не хватает только программиста 1С, который бы заявился и сказал «go говнище потому, что нет библиотек на кириллице».


    1. 3vilhamst3r
      09.11.2016 16:29
      -3

      Попробую ответить на претензии.

      1.

      IsFileExists ()


      Почему это должна быть отдельная функция?
      if _, err := os.Stat("/path/to/whatever"); os.IsNotExist(err), одно условие, понятное вполне

      2.
      По поводу «select * from table»

      Язык строготипизированный, поэтому результат потребуется описать, через хаки можно разобрать результат как map[string]sql.RawBytes, это не очень удобно, но, возможно, решит вашу проблему.

      3.
      По областям видимости

      Можно подробнее описать проблесный кейс? Для каких то кейсов подойдет gorename, для других придется или ручками изменять, или надеяться на ide, но где это не понадобится?

      4.
      func get() (int64, err) {}

      i int
      err error
      i, err = get() // Ошибка, которую нельзя исправить в этой строке


      Тут да, Роб Пайк против сложных авто приведений, придется использовать дополнительную переменную.

      5.
      А ещё нет директив компилятора

      Вы смотрели на ключи сборки?
      Ну и вкусовщина
      pkg_windows.go
      pkg_linux.go
      для платформозависимого кода, очень неплохо


      1. SirEdvin
        09.11.2016 16:40
        +1

        для платформозависимого кода, очень неплохо

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


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


        Так получается, что решение в С++ как-то проще и логичнее.


        1. RPG18
          09.11.2016 16:54

          В C++ так же применяется такой подход.

          Например в Qt
          qfilesystemwatcher.cpp
          qfilesystemwatcher.h
          qfilesystemwatcher_fsevents.mm
          qfilesystemwatcher_fsevents_p.h
          qfilesystemwatcher_inotify.cpp
          qfilesystemwatcher_inotify_p.h
          qfilesystemwatcher_kqueue.cpp
          qfilesystemwatcher_kqueue_p.h
          qfilesystemwatcher_p.h
          qfilesystemwatcher_polling.cpp
          qfilesystemwatcher_polling_p.h
          qfilesystemwatcher_win.cpp
          qfilesystemwatcher_win_p.h


          1. SirEdvin
            09.11.2016 16:57
            +2

            В целом, правильнее будет сказать что в С++ может применятся такой подход.


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


            1. RPG18
              09.11.2016 17:04

              Никаких может. Он применяется. Чрезмерное использование #ifdef ухудшают чтение и модификацию кода.


              1. SirEdvin
                09.11.2016 17:10
                +1

                Наверное, вы меня не поняли. Я говорю про то, что существуют и эффективно используются другие подходы на ряду с этим.
                В случае QT такое разбитие кода походит из-за архитектуры.


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


                1. RPG18
                  09.11.2016 17:27

                  У разных библиотек обычно разный интерфейс. Например таким образом сделана поддержка различных UI библиотек в CoolReader и FBReader.


        1. TargetSan
          09.11.2016 18:43
          +1

          И это, знаете ли, хорошо. Рискну предположить, что вы не видели С/С++ библиотек, состоящих из #ifdef наполовину и более.


          1. SirEdvin
            09.11.2016 19:03
            -2

            Видел. Для непривычного глаза — дичь дикая, разобратся сложно, особенно, если не знаешь что означают все эти переменные.
            Рискну предположить, что люди, которые с этим работают как-то привыкли.


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


            1. creker
              09.11.2016 19:15

              И в этом то, почему Го так многим нравится. Чем меньше способов сделать одно и тоже, тем лучше. Была бы возможность, из него стоило бы еще немного отрезать.


            1. TargetSan
              09.11.2016 19:45

              "Урезанность" Го — вопрос отдельный.


      1. wOvAN
        09.11.2016 16:50
        +1

        1.
        if _, err := os.Stat("/path/to/whatever"); os.IsNotExist(err)
        vs
        if FileExists("/path/to/whatever") {}

        Правда не понятно почему? (И это я только один пример взял первый пришедший в голову)

        2. «Язык строготипизированный» это совсем не объяснение. Костыли и не достаточно проверенные решения типа sqlx — превращают Go в СтрогоВелосипедизированный язык

        4. Это непродуманность языка.

        5. x:={ifdef windows}5{elseif linux}1{endif} — вот это очень неплохо
        а если речь про кастомные определения, позволяющие управлять кодом, эх!

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


        1. Alexeyco
          09.11.2016 16:57
          -2

          > вот поэтому я называю автора языка школьником не имеющим достаточного опыта, для того, чтобы понять, где это понадобится
          Вот таких комментаторов я называю школьниками, не имеющими возможности _придумать_, где это может понадобиться. ))) Да, драйверы я не писал и во что оно там скомпилялось — мне в целом интересно не было. Писал микросервисы. Работало, работало ожидаемо, стабильно и быстрее, чем ожидалось. Написано было тоже быстро. На чем бы по-вашему, кроме go я должен был бы писать? На C++? Может на Java? Или, например, C#?


          1. SirEdvin
            09.11.2016 17:01
            -2

            Очевидно, в микросервисах практически не нужна платформозависимая работа. Немного примеров о том, где может приходится IFDEF:


            1. Работа с графическим окружением. Там, добавление иконки в трей, отрисовка на экране, вывод алертов и прочее.
            2. Работа с сложными вычислениями для разной архитектуры\
            3. Работа с графическими движками или просто поддержка нескольких графических оболочек (например, QT/GTK) при компиляции.
            4. Кроссплатформенные консольные утилиты, которые требуют работы с системой.


            1. Alexeyco
              09.11.2016 17:06

              Я ненавижу Фольксваген за то, что он не Тойота. (с)


              1. SirEdvin
                09.11.2016 17:11

                То есть, вы согласны с тем, что Go — язык не универсальный с ограниченной зоной применения ну или разрабатывался таким?


                1. Alexeyco
                  09.11.2016 17:15

                  Безусловно ))) и меня это вполне устраивает. Вы согласны, что вы — не универсальный, с ограниченной зоной применения?


                  1. SirEdvin
                    09.11.2016 17:17

                    Скорее всего именно так :)


              1. wOvAN
                09.11.2016 17:15

                В Go меня привлекает сама платформа, отсутствие необходимости таскать за собой рантаймы.
                Но в самом языке многое напрягает, о чем я пишу.
                Почему бы Фольксваген не сделать лучше, а не залазить каждый раз в салон через багажник?


                1. Alexeyco
                  09.11.2016 17:39

                  Великодушнейше простите меня. Но как вам мешает писать на go наличие pkg_windows.go и pkg_linux.go? Это, скорее, какая-то мания.

                  Да, лично мне тоже несколько… ну не мешает, а непривычно отсутствие исключений. Еще мне не нравится отсутствие дефолтных значений у входящих параметров функций и методов. Было бы совсем круто наличие, например, полноценной объектной модели. Но я же не кляну его за это. В С, например, тоже нету исключений. С плохой? Зато в других языках нет (или из коробки нет) многого, что есть здесь и что реально удобно пользовать. Да, гуй на go (особенно в виндах) — это путь боли и страданий. Да, но плохой ли именно язык от этого? Плоха ли платформа? Плох ли я из-за того, что я не космонавт или не дворник?


                  1. SirEdvin
                    09.11.2016 17:49

                    Думаю, что это последствия активного рекламного продвижения Golang во все места, в которые можно и нельзя.


                    1. AnimaLocus
                      09.11.2016 17:59
                      -2

                      Это закономерность, а не намеренное продвижение. Считаете что лучше бы везде был Rust, например? :) Хочу Вас огорчить: сообщество с этим не согласно.


                      1. SirEdvin
                        09.11.2016 18:01
                        +1

                        Я все еще помню эти шикарные success статьи про то, что ребята переписали микросервисы с tomcat+java на go и получили 10-кратный прирост в силе. А их тогда были десятки.


                        Это слишком было похоже на такое продвижение.


                        1. Alexeyco
                          10.11.2016 01:05
                          -4

                          Смотрите. Лично вас заставляет кто-то писать на Golang? Дайте мне его, я его публично оскоплю. Вытащу за космы на площадь, достану свой палаш и отсеку ему его… объектную модель. Почему вы считаете, что условные «масоны» проплатили каким-то софтверным компаниям, чтобы те пиарили Golang на пустом месте? Вы серьезно? Нет, честно ответьте, вы правда так считаете?


                          1. SirEdvin
                            10.11.2016 10:33

                            Вы считаете, что никто не занимается маркетингом Goland, как языка? Маркетологи есть у каждого популярного языка и он так и иначе продвигается в массы, потому что иначе умирает.


                            За чьи тогда деньги организовывают конференции? За чьи деньги маркетологи продвигают Goland как язык, который решает проблемы с поддержкой качества кода (которые уже были решены и до него)?


                            Глупо отрицать, что маркетинга у Goland нет.


                            P. S. Писать на Go никто не заставляет, но я же могу критиковать продутк, которым мне предлагают попользоватся?


                            1. polifill
                              10.11.2016 10:39
                              -4

                              Маркетинг?
                              КОМУ это надо?

                              В отличие от .NET — golang совершенно некоммерческий.

                              Есть добровольческий евангелизм тех, кому golang нравится,
                              не путайте с коммерческим маркетингом (market — это рынок по английски)


                              1. SirEdvin
                                10.11.2016 10:43

                                Сразу нагугли три конференции:
                                http://golanguk.com/
                                https://gophercon.com/
                                http://www.dotgo.eu/


                                Вот если зайти в список спонсоров — можно сразу понять кому. Язык может быть некоммерческим только в одном случае — если у него конференций или нет.


                                1. polifill
                                  10.11.2016 10:51

                                  Вы путаете причину и следствие.

                                  Спонсоры хотят ЗАСВЕТИТЬСЯ там, где куча профессионалов собирается.


                                  1. SirEdvin
                                    10.11.2016 10:52
                                    +2

                                    Особенно засветится хочет google :)


                                    В целом, цепочка работает так:


                                    1. Google разрабатывает что-то на golang.
                                    2. Ему нужно сообщество вокруг golang, что бы он развивался.
                                    3. Он спонсирует разработку и вкладывается в маркетинг, что бы больше людей начало его использовать.

                                    Современный мир IT работает именно так.


                              1. Azoh
                                10.11.2016 10:47
                                +2

                                Google вкладывает средства в разработку языка по доброте душевной?


                                1. polifill
                                  10.11.2016 10:59

                                  > Google вкладывает средства в разработку языка по доброте душевной?

                                  Google язык вовсю использует.
                                  Уже очень давно.
                                  Он первый пользователь языка.

                                  Он вкладывается в СВОЙ СОБСТВЕННЫЙ ИНСТРУМЕНТ.
                                  При чем здесь душевная доброта?

                                  Собственно и по некоторым замечаниям разработчиков понятно — они часто говорят «нам так надо, нас так устраивает». Например, про вендоринг — «нас устраивает, внутри Гугля есть своя система вендоринга, для массового потребления она не годится, кто хочет — пишите свою».


                                  1. Azoh
                                    10.11.2016 11:03

                                    Вы правда не замечаете некоторой противоречивости утверждений "golang совершенно некоммерческий" и "Он вкладывается в СВОЙ СОБСТВЕННЫЙ ИНСТРУМЕНТ"?


                                    1. polifill
                                      10.11.2016 11:10
                                      -3

                                      Нет никакого противоречия.

                                      Если у вас на рабочем месте стоит удобное кресло — то какое отношение это кресло имеет к сайтам на 1С-Битрикс, которые вы создаете с коммерческой точки зрения?

                                      Ваш коммерческий интерес — в создаваемых вами сайтах.

                                      А вовсе не в кресле.


                                      1. Azoh
                                        10.11.2016 12:17

                                        В огороде бузина, а в Киеве дядька.


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


                                        Во-вторых, ситуация такова, что golang контролируется Google, а его разработка ведется со вполне определенной целью — повысить эффективность сотрудников Google, использующих его в работе.


                                        Так что аналогия с креслом тут никак не применима.


                                        1. polifill
                                          10.11.2016 12:27
                                          -1

                                          Вполне применима.

                                          Кресло такой же инструмент повышения производительности труда. НЕ ПРИВЯЗАННЫЙ к технологиям Гугля, как и язык программирования.

                                          Вот пример Apple с его Swift или Objective C — это да.
                                          Тут если хочешь писать под OS X или iOS — намного проще это делать на языках, развиваемых Apple.

                                          С golang — нет жесткой завязки под сервисы Гугля. Нет требований сервисов использовать именно golang.

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

                                          Такие же кресла и тот же язык программирования — их могут использовать кто угодно. Ограничений при создании веб-сервисов нет никаких.

                                          Нет привязки к Гуглю, нет монопольного требования использовать только golang на сервисах Гугля — нет и коммерциализации.


                                          1. SirEdvin
                                            10.11.2016 12:38

                                            Golang + Google такая же связка как C#/.net Core + Microsoft. Тоже вроде как open-source в Github, но все то понимают, кому принадлежит основной актив и разработка языка/экосистемы.


                                            1. polifill
                                              10.11.2016 12:45

                                              Разумеется ВСЕГДА КТО-ТО КОНКРЕТНЫЙ принимает решения о развитии языка. Так и должно быть.

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

                                              Так что какая разница кто ему платит (если это не лично ты из своего кармана), если язык УЖЕ СУЩЕСТВУЕТ, если ОН УНИВЕРСАЛЕН.

                                              Касаемо golang 1.7, к примеру:

                                              в релизе приняли участие 170 человек,
                                              из которых 140 — НЕ ИЗ Google.


                                              1. SirEdvin
                                                10.11.2016 12:52

                                                1. Про релиз — это как релиз ядра Linux. Вроде и дофига разработчиков, но все знают, кто главный.
                                                2. Разница в том, что центр языка не какой-то фонд, а компания. Следовательно язык будет развиватся так, как выгодно компании.


                                                1. polifill
                                                  10.11.2016 13:04
                                                  -2

                                                  Беспомощная у вас аргументация.

                                                  Главный разработчик запрещает вам лично писать/использовать сторонние библиотеки?

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

                                                  А какой достаточно развитый язык развивался децентрализовано?

                                                  C# — Microsoft
                                                  Python — Google; кое-какая свобода дала две плохосовместимые версии, в результате полмира уже много лет как сидит на Python 2, хотя Python 3 уже зрелый.
                                                  Golang — Google
                                                  Delphi — Borland
                                                  Java — Sun, Oracle
                                                  Rust — Mozilla централизовано его разрабатывает, в своих целях.
                                                  Ruby — там там вообще один разработчик, в результате чего и скорость компилятора не успевает поднять, хотя знает как, нет времени.

                                                  С++ — получили зоопарк компиляторов и стандартов. Если на Golang или Python компилиться с полпинка, то для С++ умение написать так, чтобы компилилось везде или умение адаптировать, чтобы скомпилировалось под конкретную систему — это отдельное умение.


                                                  1. SirEdvin
                                                    10.11.2016 13:07

                                                    Давайте не будем уходить от темы разговора.
                                                    Мы говорим о том, что golang — коммерческий язык. А вы тут начали обсуждать пути и развитие языков.


                                                    1. polifill
                                                      10.11.2016 13:19

                                                      Коммерческим можно назвать Object Pascal — потому что иначе как в весьма платном Borland Delphi его и негде было применить.

                                                      Коммерческим можно было МНОГО ЛЕТ НАЗАД назвать C#.

                                                      Коммерческим можно назвать язык 1С.

                                                      Большая же часть языков — некоммерческая.

                                                      golang — не более чем кресло или удобная мышь сотрудника Google.

                                                      Да, он позволяет Google делать больше бабла за счет большей производительности труда и меньшей нагрузки на сервер.

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

                                                      Так что он — некоммерческий.

                                                      А всего лишь — внутренний продукт, предназначенный для повышения прибыли.

                                                      Внутренняя эффективность компании — это отнюдь не коммерция.


                                                      1. wOvAN
                                                        10.11.2016 13:45

                                                        «Коммерческим можно назвать Object Pascal»

                                                        http://www.lazarus-ide.org/


                                                        1. polifill
                                                          10.11.2016 15:17

                                                          Лазарус появился много позже Delphi

                                                          А баги в Лазарусе были основные устранены и можно стало без боязни использовать в продакшене ко времени, когда коммерческие возможности Delphi были исчерпаны.


                                    1. Alexeyco
                                      10.11.2016 11:14

                                      Например, я создаю инновационный молоток. Для себя. В целом, если вы будете делать такие же молотки и докладывать мне о своих ощущениях, чтобы я мог вносить в его конструкцию изменения, я буду не против. Однако продавать молоток я не планирую. Как и давать в аренду. Коммерческий ли мой молоток от этого? И если да, то… КАК?


                                      1. Azoh
                                        10.11.2016 12:08

                                        Это зависит от ваших намерений относительно молотка.


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


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


                                        Еще бывает благотворительность, там цель — передача некоторых ценностей третьим лицам (в случае, если лица оказались не третьи, этим всем могут заинтересоваться органы).


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


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


                                        Так что, молоток для чего нужен?


                                        Возвращаясь к golang, Google, очевидно, контролирует разработку языка, а цель разработки явно далека от чистого исследования или от благотворительности.


                                        1. polifill
                                          10.11.2016 12:22

                                          Ерунду пишете.

                                          1. Любое удобное кресло или цветы в горшочках в офисе — в конечном итоге ради увеличения производительности труда, то есть ради увеленичения прибыли. Но это не означает, что коммерция делается за счет кресла.

                                          Коммерция все также продолжает делаться за счет тех сайтов, которые вы делаете и продаете.

                                          2. И хорошо, что проект golang не исследовательский.

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

                                          Google сделал удобное кресло.
                                          Другие подсмотрели — и стали использовать такие же кресла.

                                          Для увеличения производительности труда и, следовательно, прибыли.

                                          Но коммерцию на кресле Гугль не делает.

                                          Пример коммерции на языке программирования — это Visual Studio, .NET. MS развивала долгие годы эту платформу как специализированную под свои Windows.

                                          golang — слишком малоспециализированная, мало заточенная на сервисы Google вещь.

                                          Даже для коммерческого хостинга Google AppEngine язык golang — всего лишь один из многих. И появился не самым первым в хостинге.


                        1. Azoh
                          10.11.2016 10:20
                          +2

                          Может дело в том, что переписав с tomcat+java на tomcat+java с 10-кратным приростом, получится покаянное письмо "какими же говнокодерами мы были"?


                          1. SirEdvin
                            10.11.2016 10:33
                            -3

                            Дело в том, что использование tomcat для микросервисов уже указывает на такое утверждение.


                            1. grossws
                              10.11.2016 18:15

                              Catalina не столь уж тяжеловесна. Можно подставить вместо томката jetty/vertx/jboss swarm/whatever, оно не противоречит идее микросервисов, которая в первую очередь про разделение ответственности, данных и возможность независимой эволюции.


        1. 3vilhamst3r
          09.11.2016 17:10

          1. Непонятно, почему утилитарные функции должны засорять общий namespace? Этот php-style, простыни функций, и это плохо.

          2. В каком кейсе вам нужно получить «получить данные запроса полный состав которых и тип которых вам заранее неизвестен»? Когда вам нужен велосипед, вы используете велосипед. Еще раз, то можно сделать, и да, после динамических языков, это будет очень неудобно, и это нормально.

          4. Это привычки из нетипизированных языков.

          5. Опять же вкусовщина, мне кажется, что проще иметь отдельный файл, который содержит все «платформозависимые» фичи. + если уж хочется что то такеого есть runtime

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

          Я попросил привести кейс, если вы используете инкапсуляцию, у вас не будет проблем


          1. sshikov
            09.11.2016 19:53

            Есть несколько штук SQL API, для строго типизированных языков, скажем для простоты JDBC и ODBC, где спокойно можно доставать метаданные и данные любого SQL-запроса. И никакого нарушения статической типизации при этом не происходит. И никаким велосипедом там не пахнет.

            Более того, это вполне удобно (хотя конечно, не так просто делается, как запрос с известным числом колонок результата).


            1. 3vilhamst3r
              09.11.2016 20:44

              согласен, database/sql не может конкурировать с odbc/jdbc по функционалу, просто для меня, отсутствие поддержки * не является краеугольной проблемой.


        1. Sirikid
          09.11.2016 18:28
          -6

          Если вам нужна эта функция напишите её и опубликуйте в виде библиотеки.
          Также, я прошу показать разработанный вами язык, прежде чем назвать автора(ов) Go школьником(ами) и говорить о его непродуманности.


          1. HaruAtari
            09.11.2016 18:33
            +1

            Ага.
            Сначала добейся (с).


          1. wOvAN
            09.11.2016 20:14
            +3

            Написать относительно не сложно, но этот пункт про велосипеды, о котором я писал.
            Чем плох велосипед?
            Он скорее всего хуже протестирован, чем стандартная библиотека
            У каждого разработчика будет свой велосипед и чужой код будет восприниматься хуже
            Лишняя работа по поиску или написанию велосипеда


            1. Sirikid
              10.11.2016 10:55
              -3

              А почему вы тестируете свои велосипеды хуже чем стандартную библиотеку? Приведите пример достаточной стандартной библиотеки, пожалуйста.
              Может быть Golang просто не подходит вам? Недавно я писал IPC на C, мягко говоря это было не очень приятно, думаю Go подошел бы для этой задачи гораздо лучше. А ещё в C проверка файла на существование выглядит точно так же, поэтому меня такая стандартная библиотека не смущает.


            1. SirEdvin
              10.11.2016 10:56

              Он скорее всего хуже протестирован, чем стандартная библиотека

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


              1. demoth
                12.11.2016 13:35

                Так что в своём велосипеде, скорее всего, всё будет гораздо хуже. :)


        1. Sirikid
          09.11.2016 19:25

          Возможно я был слишком резок в суждениях и выказываниях, в таком случае прошу у вас прощения.


        1. rzhikharevich
          10.11.2016 00:17

          1. А если произошла ошибка?


  1. sand14
    09.11.2016 16:23

    Где-то в (около)официальных источниках было рекомендовано использовать название Golang для устранения коллизий и возможности поиска.


  1. ertaquo
    09.11.2016 17:55

    У меня недавно случился нехилый факап из-за незнания особенностей работы go с базами данных (db/sql). А именно: по умолчанию количество открытых соединений к БД не ограничено, это надо делать вручную при помощи функций SetMaxOpenConns и SetMaxIdleConns. Ну а если это значение не указать, то при возросшей нагрузке забьется весь пул соединений к БД, и все остальные запросы будут просто отфутболиваться с ошибкой.


    1. AnimaLocus
      09.11.2016 18:05

      Да, таких моментов много в любой платформе. Но ведь… В настройках любой DB может быть любое кол-во соединений и они могут использоваться\переиспользоваться по разному.
      Вообще интересно стало: неужели где-то в языках на уровне драйвера DB есть встроенное ограничение на кол-во коннектов?


      1. ertaquo
        09.11.2016 18:19

        Тут скорее претензия к документации. Эти две функции лишь кратко упомянуты на этой странице документации, и нигде более. Про механизм connection pooling'а ничего не раскрыто. Да даже в большинстве примеров в интернете (в том числе в официальной вики на гитхабе) про них ничего нет. Хотя момент как бы довольно важный.


        1. creker
          09.11.2016 19:11
          -4

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


          1. ertaquo
            09.11.2016 20:00
            +1

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


            1. AnimaLocus
              09.11.2016 20:09
              -2

              На самом деле знание + тестирование обязательно. Не важно какой язык\платформа\библиотека. Т.е. такие вещи должны освещаться в каких-то базовых курсах\статьях чтоли…


            1. creker
              09.11.2016 22:26
              +1

              Все абсолютно библиотеки требуют тестирования перед применением, вы как в первый раз вообще. Детали реализации не должны быть в документации, именно так. Наружу выставлены методы и достаточно информации о том, что они делают. Это как бы везде так, по крайней мере в серьезных фреймворках и больших библиотеках. В закрытых библиотеках так вообще черный ящик. А тут вам дается исходный код, чтобы при крайней необходисти узнать все точно.


  1. gkraser
    09.11.2016 18:47
    +11

    Мда… Собирался на выходных поизучать этот язык… Прочитал про особенности и коментарии и понял, что это не тот язык, на котором мне хотелось бы писать…


    1. creker
      09.11.2016 19:09
      -6

      А в чем проблема? Половина особенностей тут только от незнания языка от слова вообще. Аж смешно от большинства пунктов


    1. Yury_Reshetov
      09.11.2016 20:23

      GoLang — это не для чукчей-писателей.

      В принципе, если писать на Java, то Eclipse почти всё из вышеперечисленного в статье списка, т.е. свойственного «индусскому коду» помечает жёлтым цветом и приходится все эти «желтки» править, хотя компилятор их переварит и никто не заставляет. Т.е. если бы под GoLang был аналогичный IDE, который бы помечал бы всякий «индусский код» до компиляции, а не после, то ИМХО процесс был бы гораздо дружественным и более приятным.


      1. darth_dolphi
        09.11.2016 21:20
        +1

        В Visual Studio Code есть отличный плагин для Golang-а, который делает вышеописанное, разве что индусокод не помечает желтым. Но вещи которые не дадут коду скомпилироваться указывает. Переехал на Code с LiteIDEб и доволен.


        1. Alexeyco
          09.11.2016 21:33

          А еще есть вполне себе замечательная Intelij IDEA с плагином к Go. И никаких проблем. Индусский код она не помечает. Хочется верить, что мне оно не нужно.


      1. Alexeyco
        09.11.2016 21:43
        -1

        Ну все же понимают, что import — не для красоты, а совершенно четкая инструкция, которая включает в бинарник какой-то пакет. Или, скорее всего, несколько. И если nodejs-ребятюням не позволяют говнокодить — это не повод сикать в штаники. А повод задаться вопросом «а может быть, если меня лупят рейсфедером по лбу, значит я что-то делаю не так?». Нет? Или компилятор должен содержать инструкции на случай «ооо… короче, чувак, говнокодер, давай-ка я проигнорирую его попытку говнокодить»?

        Ну или другой вариант. Человекам что ни дай, они все равно найдут повод для нытья. Проклятые эмо-кодеры.


        1. ibnteo
          09.11.2016 22:17

          Вместо ошибки можно было бы просто предупреждать, при отладке кода не очень удобно, лучше потерять миллисекунды на сборку лишнего кода, чем время программиста на переход в начало файла, а потом ещё и обратно, где код до этого писался. Ладно ещё прерывать компиляцию при go build, но при go run то зачем останавливать её из-за такой мелочи?

          В общем-то это наверное единственное, что меня не устраивает, да и то, проблему решил с помощью среды разработки, стал использовать VS Code, и вообще забыл про import, он сам автоматически формируется и при этом ещё разбивается на группы и сортируется.

          А вот остановка компиляции при неиспользуемых переменных это благо, позволяет выявить ошибки в коде, навроде определения переменной в блоке, вместо присваивания, например: a := -1; if a < 0 {a := 0} выдаст ошибку, и убережёт от долгого поиска лишнего двоеточия, и кроме того, заставляет писать законченные куски кода, раз объявил переменную, будь добр, доделай дело.


          1. creker
            09.11.2016 22:37

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


            1. ibnteo
              11.11.2016 17:54

              Так и говорю, достаточно быть строгим лишь при сборке проекта (go build), а при отладке (go run) можно и послабление дать, чтобы время разработчика не отнимать. Да и то, это касается лишь неиспользуемых import, с переменными это может иметь неприятный эффект, когда не заметили, что используется := вместо =


              1. stokker
                11.11.2016 18:24

                Чем раньше почищено — тем быстрее работать дальше.


          1. Alexeyco
            10.11.2016 00:55
            -1

            То есть, если компилятор Golang в себе сочетает еще и элементы статического анализатора, это великое зло? ))) Ну правда же смешно. Честно, меня лично не заденет, если кто-то прогает на чем угодно. Хоть на брейнфаке. Пожалуйста. Единственное, чего я с трудом выношу — это нытье. И отвечайте же как взрослые дяди, аргументируя.


          1. Alexeyco
            10.11.2016 01:01

            А самое интересное, что вот я поставил себе кое-что из node. Честно… сделал большую ошибку, т.к. ставил на винде. Так я потом не мог удалить засёранную директорию с установленным. Т.к. винда говорила «че-то оно как-то названо слишком длинно». Пришлось пилить утилитку, которая грохала все это добро рекурсивно. Это, конечно, великое благо. Да. Это прямо-таки рай для программиста. Нет, ну серьезно. Может уже в кои-то веки взглянуть объективно? Я, честно, параллельно еще изучаю Rust. Меня вообще не останавливает… да ничто. Я буду изучать языки столько, сколько они будут выходить и столько, сколько буду в здравом уме. И взять и захейтить лучик света — ну это надо быть отъявленным «одинэсником».


            1. DarkEld3r
              10.11.2016 12:50

              Т.к. винда говорила «че-то оно как-то названо слишком длинно».

              Go как-то защищает от таких проблем?


              1. Alexeyco
                10.11.2016 15:17

                Безусловно! В нем нет npm.


  1. vlreshet
    09.11.2016 19:34
    -6

    Этот пост нужно показывать тем кто кричит «JavaScript до ужаса кривой язык». Компилятор указывает где ставить фигурную скобку, от названия переменной зависит область видимости, нужно следить за импортами…


    1. AnimaLocus
      09.11.2016 19:41
      +10

      JavaScript до ужаса кривой язык.


      1. vlreshet
        10.11.2016 10:41
        -1

        Большинство людей которые ругаются на JS просто не читали к нему документацию, а потом ругаются что в нём что-то не как в Java или C++


        1. Alexeyco
          10.11.2016 10:47

          Только JS имеет право на свое собственное видение, которое, тем не менее, указано в доках. Golang такого права не имеет! Правда? Правда, товарищ?


          1. vlreshet
            10.11.2016 16:46
            +1

            Ага, то есть когда Golang имеет странности — так это видение, а как JS — так ужастно кривой язык, и нужно слить карму всем кто с этим несогласен. Правда? Правда, товарищ?


            1. Alexeyco
              10.11.2016 18:19

              Про странности Golang мы, по-моему, тут уже все выяснили. Итого за больше, чем сутки нашли одну странность.


        1. AnimaLocus
          10.11.2016 10:50
          -1

          Боюсь что читал. Но он впринципе не особо дизайнился. Сейчас ES6+ конечно намного лучше. Но суть все та же: callback hell, итд, итп. И это уже никогда не уйдет из языка.


          1. polifill
            10.11.2016 12:55
            +1

            Одни профи кругом со своим мнением
            ;)

            Callback hell — это прежде всего свойство общепринятой методологии «как писать», свойство конкретных библиотек.

            Сам язык не мешает вам использовать callback напрямую или использовать библиотеки реализующие обертку по типу await/async…


            1. AnimaLocus
              10.11.2016 17:40

              Да, в том то и дело что в Go идет пересечение дизайна языка вместе с идеологией (ограничениями). Поэтому не совсем корректно сравнивать лишь дизайн JS. Еще раз: ES6+ довольно красивый и не плохой, но проблем от этого в языке меньше не становится. Т.е. я рассматриваю язык в целом, а не только дизайн синтаксиса или какую-то другую часть.


    1. Alexeyco
      09.11.2016 21:26
      -1

      Это кому и где компилятор указывает, где ставить скобку?


      1. grossws
        10.11.2016 18:25

        func main()
        {
        }

        tmp/sandbox505802630/main.go:8: syntax error: unexpected semicolon or newline before {


  1. gkraser
    09.11.2016 19:55
    +5

    Проблема в обманутых ожиданиях. Я ожидал новый современный язык общего назначения, который с оглядкой на недостатки других языков, предлагает некий некий новый мир, в котором прошлое исправлено и можно теперь жить по другому, красиво и изящно. Но здесь я вижу какой-то странный, на мой взгляд, вариант низкоуровневого языка. Непривычное поведение во многих случаях (например строки, реакция на неиспользуемые переменные и импорт, nil и многие другие) создаст дискомфорт в задачах общего назначения (читай рутина).

    Так что нет, для работы мне не надо, а для души поищу (или подожду) чего нить другого.


    1. AnimaLocus
      09.11.2016 20:10
      -2

      Дело просто не в «фишках» а в идеалогии.


    1. Alexeyco
      09.11.2016 21:29

      Тут выше кричат, что Java хороша тем, что у ней IDE помечает индусский код. И тут же претензия «оооо, компилятор go ругается на неиспользуемые импорты и переменные, а еще на ситуацию, когда я не вместо указателя, а вместо значения пытаюсь тиснуть nil». Как уживаются вместе эти люди — одному Стауструпу известно.


      1. playermet
        10.11.2016 12:03

        Т.е. вы не видите разницы между «помечает» и «не компилирует, и выключить нельзя»?


        1. Alexeyco
          10.11.2016 12:05
          -2

          Т.е. я вижу, что люди просто сами себе противоречат… если IDE помечает, то зачем это оставлять. Если можно неиспользуемые импорты, то зачем их помечать. Знаков вопроса нет, т.к. вопросы риторические.


    1. creker
      09.11.2016 22:32

      предлагает некий некий новый мир, в котором прошлое исправлено и можно теперь жить по другому, красиво и изящно

      Именно это он и предложил для конкурентного кода, а это как раз серверный код. И именно там Go живет прекрасно, решая проблемы, которые создавали прошлые языки. И еще спецификация языка призвана решить костыли, которые традиционно качуют в С-подобные языки. И это тоже успешно получилось исправить. Из языка вырезали весь хлам, которые есть в «мощных» языках, чтобы разработчик просто сел и написал надежный быстрый код, который легко читать, а не сидел на форумах с вопросами, а какой же способ реализации ему выбрать. Я это тоже не понимал до тех пор, пока не написал на нем несколько серверных приложений. Сложно описать, какое удовольствие получаешь от серверного Go кода на горутинах, каналах и select'ах.


    1. Halt
      10.11.2016 07:53
      +2

      Не холивара ради, посмотрите в сторону Rust. Как человек давно занимающийся компиляторами могу сказать, что это именно «новый мир, в котором прошлое исправлено». Это язык, который ставит своей задачей решать фундаментальные проблемы, а не «быть модным и не таким как все».

      Если хочется получить быстрое введение в те проблемы, которые Rust позволяет решать, советую посмотреть статью Fearless Concurrency.


      1. polifill
        10.11.2016 10:44
        -2

        Ну вот почему люди смешивают языки программирования в одну кучу ТОЛЬКО ПОТОМУ, ЧТО ИХ СИНТАКСИС отдаленно НАПОМИНАЕТ синтаксис С?

        Вы бы еще на JavaScript предложили посмотреть. Или Swift. Или на уже умирающий D.

        Ниша у Rust совершенно другая.


        1. Halt
          10.11.2016 10:59

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


      1. Sirikid
        10.11.2016 11:08
        +1

        А вам не кажется что концепция borrowing «попахивает» зависимыми типами? (Rust ещё не знаю и что под капотом не читал.)


        1. Halt
          10.11.2016 16:04

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


    1. serge-phi
      10.11.2016 11:06
      +1

      Вот вполне развернутое объяснение целей Go: https://talks.golang.org/2012/splash.slide
      Вкратце: он разрабатывался для написания сложного ПО большими группами программистов. В общем, это скучный энтерпрейз-грейд язык, не особо веселый и дружественный.


  1. nexcode
    09.11.2016 20:26

    9. Использование nil-слайсов (slice) и хеш-таблиц (map):
    append() — инициализирует слайс, если необходимо.
    Утверждение о возможности добавить значения в переменную, которая указывает на не инициализированный слайс (nil) в корне неверно.
    Более того, нет такого понятия nil-слайс, есть, как я уже сказал выше, в котором не определен массив для хранения значений.


    1. 3vilhamst3r
      09.11.2016 20:28

      Вы правы, в процессе перевода тоже хотелось отметить недопонимание автора механизма append (если не хватает capacity в текущем слайсе, по сути создается новый), но, поскольку это перевод, оставил как есть.


  1. Freedate
    09.11.2016 21:21
    +1

    Для тех кто пишет на Go Си биндинги стоит учитывать:
    /*
    Си код
    */
    import «C»
    Только так, с одним переносом (иначе компилятор ругается)
    Запрещено использование pointer to pointer (с версии 1.6+)
    Например:
    var b *C.MyStruct = &C.MyStruct{}
    err := C.MyFunc((**C.MyStruct)(unsafe.Pointer(&b)))
    При переезде с 1.5 на 1.6 это стало довольно неприятным сюрпризом


  1. Alexeyco
    09.11.2016 21:33
    -2

    Уже зарекался не комментировать под постами про Android vs OSX. Теперь, видимо, не буду комментировать под постами про Go. Слишком уж много обиженок на его компилятор. Причем один и тот же человек может написать «у go нет приличных IDE, которые бы ругались на индусский код» и «компилятор go ублюдочный, т.к. не позволяет мне неиспользуемые импорты и переменные». Один и тот же человек. В 1С все можно, отправляйтесь туда, там райские кущи.


    1. grossws
      10.11.2016 18:50

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


      А про компилятор — в треде выше обозначили вполне понятный момент и даже пояснили, что проблема не в строгости, а в отсутствии возможности эту строгость временно уменьшить, тогда как inspections в IDE являются рекомендациями, enforce которых может происходить дальше (при релизной сборке, например), а не всегда при разработке. Это и плюс, и минус.


      В том же rust компилятор ещё больше бьёт по рукам, но не за скобку, оказавшуюся не на той строке, а за потенциально небезопасные вещи, что куда лучше, но тем не менее неприятно при экспериментах с языком/новыми библиотеками и т. п.


      1. stokker
        10.11.2016 18:54

        что проблема не в строгости, а в отсутствии возможности эту строгость временно уменьшить

        А зачем ВРЕМЕННО уменьшать? Вот разработчики не согласны с любым уменьшением строгости. И я с ними согласен.


      1. Alexeyco
        10.11.2016 21:34
        -3

        Там вон выше жалуются на то, что компилятор ругается на скобочки. Рядом же утверждают, что хороший ЯП — это язык со строгим синтаксисом. Ну и какое тут может быть человеколюбие?

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


        1. grossws
          10.11.2016 21:44
          +1

          Из строгого синтаксиса не следуют фашиствующий компилятор, равно как и наоборот.


          И, конечно, ещё вопрос что называть строгим синтаксисом. Например, можно ли сказать, что у whitespace строгий синтаксис? Если писать для него лексер и парсер, то они будут описываться тривиальной грамматикой в ABNF/EBNF, но писать при этом на нём можно очень по разному.


          1. Alexeyco
            11.11.2016 10:39

            Да господи, ну плюнулся компилятор на неиспользуемый импорт. Ну все теперь…


  1. xRay
    09.11.2016 22:18
    -1

    Неиспользуемые импорты и неиспользуемые переменные вот надо что бы все IDE и компиляторы на это ругались и не собирали приложения и библиотеки.
    За вам неиспользуемые импорты и неиспользуемые переменные? Про запас?


  1. stokker
    10.11.2016 02:40

    Думаю, что ошибки, пойманные компилятором, подводными камнями никогда не станут (или избавят от других подводных камней).


  1. Deosis
    10.11.2016 08:35
    +3

    Вопросы для тех, кто пишет на Го:
    5,7. При беглом взгляде на код можно отличить объявление и присваивание?
    6. Чем поле так отличается от переменной?
    8. Почему такая разница в поведении контейнеров?
    12. Чем отличается слайс от указателя на массив?
    13. Неужели индекс элемента при перечислении важнее его значения?
    22. Кто вообще дал такие привилегии логгеру?


    1. Alexeyco
      10.11.2016 10:37
      -2

      На самом деле, мое личное имхо. У Go много недостатков (ниже). Но не нужно путать недостатки с особенностями.

      5,7 Да… если писать просто и использовать gofmt… снова же — годная IDE сама в нужных местах краснеет в ситуации из п. 5
      6 Данный пример приведен от недалекости того, кто его писал. Чуть выше несколько раз это разобрали.
      8 Я лично даже не представляю, как и что ответить автору этого пункта. Однако если так сильно хочется, можно создавать свои типы. К примеру, переменная типа string не может быть nil, однако она может быть пустой строкой. Но если так сильно хочетсяИ то же самое с time.Time.
      12 Область видимости, дык… и вообще чем отличается значение от указателя?
      13, 22 Снова же, лично я теряюсь, что на это ответить. Автору того пункта советую читать доки. Однако же вам ничто и никто не мешает напилить свой логгер, это просто и удобно. И вот в нем будете делать как вашей душе будет угодно.

      Тем не менее, я не являюсь фанбоем этого ЯП. Просто я так уж устроен, что когда пишут глупости, мне очень тяжело сдержаться и не назвать человека идиотом.

      Golang — лично с моей позиции обладает достаточно большими недостатками. Но именно как платформа, а не как ЯП. Самое большее, чего лично мне не хватает и чего бы мне хотелось вот прям вот сильно. Это полноценный платформонезависимый гуй. Начали, было, да как-то не задалось. Да, я в курсе, что есть всякие там аналоги, но хотелось бы именно официальный. Это раз. А два — компиляция его под виндами — это путь боли и страдания.


    1. 3vilhamst3r
      10.11.2016 10:44
      +1

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

      7. частая проблема, пару раз очень долго разбирался с кодом, спасает metalinter, прикрутил в atom, теперь вижу все такие блоки, без подсказок может стать адом.

      6. Это особенность синтаксиса, грубо говоря, слева от := могут быть только имена переменных.

      8. nil это константа без типа, язык строготипизированный, чтоб не творить магии, «дефолтного» типа нет.

      12. Лучше почитать в офф доке, слайс, это ссылка на массив + пара доп полей, для обеспечения динамического поведения.

      13. Я думаю посыл в том, чтобы ходить по индексу, и изменять значение.

      22. Это и правда ужасно, когда первый раз наткнулся на exit(1), в Fatalf, я не ожидал такого поведения, с тех пор пользуюсь другими библиотеками.


    1. polifill
      11.11.2016 12:30

      > 5,7. При беглом взгляде на код можно отличить объявление и присваивание?

      Да. И такая локаничность объявления очень удобна.

      > 8. Почему такая разница в поведении контейнеров?

      Ради скорости.

      > 12. Чем отличается слайс от указателя на массив?

      https://habrahabr.ru/post/202948/

      > 13. Неужели индекс элемента при перечислении важнее его значения?

      key, value или value, key? Первый порядок — логичнее.

      > 22. Кто вообще дал такие привилегии логгеру?

      В сложных системах используют другие логгеры.
      С помощью интерфейсов они совместимы со стандартными логгерами.


  1. Hamper
    10.11.2016 10:19

    По поводу FileExists, как ни странно, в документации nodejs можно увидеть, к чему может привести его необдуманное использование: https://nodejs.org/dist/latest/docs/api/fs.html#fs_fs_exists_path_callback возможно, в го его не стали реализовывать по этой же причине.


    1. wOvAN
      10.11.2016 10:36

      Я привет FileExists только как пример, и уж разработчик разберется обоснованно ли он его использует.
      Но даже если вы правы, допустим, почему в Go нет например и функции Round делающей округление?


      1. Alexeyco
        10.11.2016 10:42
        -2

        Действительно нету, и вот это — первый и пока единственный хоть сколько-то разумный недостаток из тех, что были приведены здесь. Потребовались сутки, чтобы накопать хоть что-то. Однако ж на эту тему интернеты уже бурлили и продолжают бурлить.


        1. wOvAN
          10.11.2016 11:09
          +2

          Хотите найти недостатки начните писать на Го, что то полезное и они сразу полезут.
          Я на Го пробую писать всего 2 недели, написал пару серверных приложений, поэтому к экспертам себя не отнесу,
          Но куда не ткни, везде тихий ужас.


          1. Alexeyco
            10.11.2016 11:20

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


            1. wOvAN
              10.11.2016 11:31

              Интересно, какими вы ещё языками пользовались, на основании опыта общения с которыми, вы не видите очевидных недостатков Го. Наверное если бы это был первый язык в жизни и не с чем было бы сравнивать, но увы.


              1. Alexeyco
                10.11.2016 11:33
                -2

                Апелляция ad hominem верный признак того, что я-таки загнал вас в угол и аргументов кроме как «я знаю, но вам не скажу, т.к. вы все равно дебилы и не поймете» мы больше не увидим. )))


                1. wOvAN
                  10.11.2016 11:38
                  +1

                  Я не знаю куда вы меня загнали и зачем вы это делаете, я язык обсуждаю и конкретные его недостаки, которые выше приводил, а вы меня загнать пытаетесь, с вами все ок?
                  Гонения на неверных признак религиозного фанатизма?


                  1. Alexeyco
                    10.11.2016 11:42
                    -1

                    Я же говорил ))


              1. AnimaLocus
                10.11.2016 11:39
                -1

                Если виден недостаток нужно пойти и открыть Issue/Pull Request.
                Если это действительно bug/недостаток он будет исправлен.
                Если это стон от недостаточной квалификации, будет получено немного опыта и боли. :)


                1. wOvAN
                  10.11.2016 11:51
                  +2

                  Ок, про функцию Round есть issue, закрытое по причине «нам это не надо».
                  А так же есть религиозная догма в одной из вики Golangю Примерного содержания: Если чего то нет в Go, то это ненужно, довольствуйтесь тем, что есть ибо нам виднее как нужно.


                  1. Alexeyco
                    10.11.2016 11:56
                    -1

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


                  1. AnimaLocus
                    10.11.2016 12:01

                    Посмотрел.
                    Вот 2 варианта округления: https://play.golang.org/p/Xog_9wXSqj
                    Один из них, самый популярный выглядит так: math.Floor(f + .5)
                    Вопрос, зачем для самого популярного варианта плодить сущность если он элементарен, понятен и расширяем?
                    Т.е. Вы предлагаете сделать что-то типа math.Round(f, 0) вместо math.Floor(f + .5)?
                    Ну и чем же это надо обосновывать? Не забываем про общую архитектуру (нельзя сделать значение для функции по умолчанию).


                    1. playermet
                      10.11.2016 12:28
                      +3

                      > Один из них, самый популярный выглядит так: math.Floor(f + .5)
                      И абсолютно неправильный. Потому что по стандарту IEEE 754 для floating point при округлении -3.5 должно быть -4, а оба описанные по ссылке методы дают -3.


                      1. AnimaLocus
                        10.11.2016 12:38
                        -2

                        Т.е. Вы предлагаете сделать еще If/case на ровном месте чтобы поддерживать еще один вариант.
                        В этом то и суть, зачем? Сделайте math.Floor(f — .5) если округляете отрицательные значения.
                        Хотите универсальности? Сделайте навороченную функцию в 5 раз медленнее и на 20 строк кода.
                        Выбор за Вами.


                        1. playermet
                          10.11.2016 13:41
                          +3

                          > Вы предлагаете сделать еще If/case на ровном месте чтобы поддерживать еще один вариант.
                          Я предлагаю ввести функцию, которая делает это правильно. Если вы хотите правильно округлить число знакового типа, вам ПРИЙДЕТСЯ расширить приведенные формулы.

                          > В этом то и суть, зачем?
                          Как раз затем, что выше описаны два неправильных но очень часто используемых без знаний об потенциальной ошибке костыля.

                          > Сделайте навороченную функцию в 5 раз медленнее и на 20 строк кода.
                          А может не будем кидаться крайностями? Округление — это типичная операция.


                          1. AnimaLocus
                            10.11.2016 18:50

                            Уделил немного времени: вот корректный вариант https://play.golang.org/p/kIOGJMGafI и то, есть что доделать (см. комментарии).
                            Элементарный тест производительности представлен, вот результаты:
                            Round(3.5, 0): 10.0159ms
                            math.Floor(3.5+.5): 2.0011ms
                            Разница в скорости в 5 раз. Я прямо угадал. :)
                            Буду рад дальнейшему обсуждению по сути.

                            p.s. чтобы работало время, тестировать надо локально.


                            1. Source
                              10.11.2016 21:01
                              +1

                              Т.е. Вы мотивируете отсутствие корректной реализации тем, что она медленнее некорректной?
                              Давайте по такому же принципу вместо сортировки будем только первые 2 элемента сравнивать, так ведь быстрее.


                              1. AnimaLocus
                                10.11.2016 21:06

                                Ну а почему это не может являться мотивацией? Сравнение, кстати, абсолютно некорректно.


                                1. Source
                                  10.11.2016 22:32
                                  +2

                                  Ну а почему это не может являться мотивацией?

                                  Наверно потому, что быстро делать не то, что нужно, никому не надо…
                                  И почему сравнение некорректное? И то и то будет правильно работать в некоторых случаях.


                                  1. AnimaLocus
                                    10.11.2016 22:36
                                    -1

                                    В случае Round — в большинстве.
                                    В случае сортировка — почти никогда.


                            1. Azoh
                              10.11.2016 21:22
                              +2

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


                              Пример на C# http://ideone.com/KTbHGi


                              Может быть из-за этого и добавляют встроенные функции для таких операций?


                              1. AnimaLocus
                                10.11.2016 21:28
                                -1

                                Может быть.
                                Вообще Go написан на Go. Так что встроенность мало чем поможет. :)


                                1. Azoh
                                  10.11.2016 21:53
                                  +1

                                  К нашей великой радости, на Go написана только каноничная версия Go. Модуль math напичкан оптимизациями под разные процессоры https://go.googlesource.com/go/+/master/src/math/, которые должны быть эквивалентны каноничной версии


                                  1. AnimaLocus
                                    10.11.2016 21:55
                                    -1

                                    Спасибо. Не знал.
                                    Тогда выходить что надо требовать чтоб добавили нормальный Round на C! :)


                      1. Hamper
                        10.11.2016 22:35

                        А потом кто-то захочет вместо банковского округления — математическое, и придется дополнять функцию, вон в пхп например 4 способа поддерживаются http://php.net/round, в других языках тоже по несколько вариантов (которых вообще не мало https://ru.wikipedia.org/wiki/%D0%9E%D0%BA%D1%80%D1%83%D0%B3%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5 ), и часто это вызывает проблемы у людей, которые берут метод по умолчанию, не зная, как он точно работает.


                        1. playermet
                          11.11.2016 18:32
                          +1

                          Почти в каждом первом языке есть floor, ceil, чуть реже round, а иногда еще и trunc. То, что в php через параметры одной функции доступны они все — это конкретно его проблемы дизайна.


              1. polifill
                10.11.2016 11:41
                +2

                Опыт 20 лет: Java, C#, C++, C, Pascal, Object Pascal, 1C, PHP, JavaScript, TypeScript

                1. Почему в Go должны быть вещи, имеющиеся в других языках? Почему это само собой разумеющиеся? Зачем нам вторая Java или второй С++?

                Просто потому, что человечество придумало уже какие-то программные концепции — так давайте их все запихаем в очередной новый язык?

                Так ведь было уже — PL/I. Им невозможно пользоваться. Так и не было создано ни одного компилятора, который поддерживает на 100% синтаксис PL/I.

                Язык Go ФОКУСИРУЕТ программиста и заставляет самого брать ответственность за узкие места в программе (отсутствие remove, например, при наличии append)

                Многоопытные авторы языка фокусируют вас. Да, принудительно.
                Но другие многоопытные коллеги (Docker, Dropbox и пр.) проверили многоопытных авторов — и сказали — это хорошо.

                2. Язык — это всего лишь инструмент. Не считаете, что он подходит — выбирайте другой.


                1. SirEdvin
                  10.11.2016 11:45

                  А вот аппеляция к Docker — это зря.
                  Не знаю, как "многоопытные" коллеги могут каждый релиз ломать обратную совместимость и файловую систему, а потом предлагать хранить данные в контейнерах.


                  1. SirEdvin
                    10.11.2016 11:50
                    +1

                    Дополню свой ответ тем, что у них не было особо выбора, на чем писать помимо Go. Не на С++ же.


                    1. polifill
                      10.11.2016 12:04

                      А разве это не вы тут рядышком в комментариях утверждали, что выбор языка Go для серьезных продуктов — это галимый маркетинг?

                      Теперича утверждаете — что и альтернатив нет?

                      Юлите, меняете мнения по 100 раз на дню…


                      1. SirEdvin
                        10.11.2016 12:06

                        Я не помню такого, если было — ткните меня в это.


                        Я утверждал, что у Go есть маркетинг и методы, которым он ведется мне не нравится. Так же, я утверждал, что шквал критики в комментариях как раз из-за этого маркетинга, который пытается пропихнуть Go во все дыры. Ничего иного я не утверждал, если не ошибаюсь.


                        1. Alexeyco
                          10.11.2016 12:08

                          То есть, если бы маркетинг был другим, то и хрен с ним, уберем уж неиспользуемые импорты и переменные? )))


                          1. SirEdvin
                            10.11.2016 12:09

                            Маркетинг — это одно, а неиспользуемые импорты — это другое :)


                            Просто, если бы не было маркетинга — критики было бы меньше, так как всем было бы пофигу на язык. Например, как в случае с D.


                        1. polifill
                          11.11.2016 12:37
                          -1

                          Это не маркетинг. А евангелизм.
                          Он — бесплатный и добровольческий.

                          Люди освоили, испытали и — делятся опытом. Бесплатно. Это не маркетинг, это не за деньги.

                          Что вам не нравится?
                          Что люди УЖЕ освоили Golang, что он уже В МЕЙНСТРИМЕ у многих КРУПНЫХ проектов, а в ВЫ ВСЕ ЕЩЕ ДУМАЕТЕ?


                          1. SirEdvin
                            11.11.2016 13:12

                            Это не маркетинг. А евангелизм.

                            Название ничего не меняет.


                            Что люди УЖЕ освоили Golang, что он уже В МЕЙНСТРИМЕ у многих КРУПНЫХ проектов, а в ВЫ ВСЕ ЕЩЕ ДУМАЕТЕ?

                            У вас там стул впорядке? Я просто не знаю, как этот капс читать, но когда я читаю текст в голове, то получается, что вы прямо кричите.


                            Два года назад все осваивали javascript + nodejs. Как-то поутихло.


                            1. polifill
                              11.11.2016 13:58

                              Меняется — маркетинг это за деньги. Вас там убеждают, что черное это белое. Были бы бабки в очередной смене цвета.

                              Евангелизм — это из опыта, из практики и совершенно бесплатно. Вам ДОБРОВОЛЬНО рассказывают, о том как у них все ПОЛУЧИЛОСЬ.

                              Вы в каком времени живете?
                              В крупных проектах уже намного более чем 2 года назад с Ноды сползли в пользу Go.

                              Ну или вы занимаетесь мелкими поделками — там, да, Нода только только пришла из мира «больших систем».

                              Я на Go уже 4-й год сижу. И уже тогда golang был в мейнстриме. У вас точно нет машины времени? Вы нам пишете примерно из 2008 года…


                              1. SirEdvin
                                11.11.2016 13:59

                                Окей, четыре года назад. Суть то не меняется.
                                Появится новый язык, вот как Go, только лучше, и все начнут переписывать вещи на него.


                                1. polifill
                                  11.11.2016 14:12
                                  +1

                                  Это лично я 4-й год.
                                  До того, когда я начал — я тоже долго присматривался.

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

                                  Появится очередной язык, который делает ЦЕЛЕСООБРАЗНЫМ переписывание — начнут переписывать.

                                  Появился D — переписывать не бросились.

                                  Появился Golang — тогда да, переписывать нагруженные веб-сервисы стало целесообразным.

                                  Много лет назад (ну около 5-6) ваши слова о том, что golang — это всего лишь мода, были бы разумными.

                                  Сейчас — уже мейнстрим, уже несколько лет как мейнстрим.

                                  И проверенный в деле на серьезных задачах в том числе — масштабы Dropbox, Youtube, CloudFlare, Bitbucket и пр. и пр…


                  1. polifill
                    10.11.2016 11:53

                    Ребята выбрали себе путь совершенствования без оглядки.
                    Это вполне допустимый путь — ведь старые версии Docker остаются доступными. Всегда можешь использовать старые, проверенные уже в продакшене.


                    1. SirEdvin
                      10.11.2016 11:56
                      +1

                      Бекпортирование фиксов, обновление безопасности и утилит тоже обеспечили? Если нет (а оно так и есть), то и пользоватся старыми версиями тоже нельзя.


                      1. polifill
                        10.11.2016 11:59

                        Какое это имеет отношение к НЕДОСТАТКАМ ЯЗЫКА программирования?
                        На Go написан серьезный продукт, ежедневно запускающий миллиарды контейнеров…


                        1. SirEdvin
                          10.11.2016 12:01
                          +1

                          К тому, что называть разработчиков Docker — многоопытным, это как-то вот совсем неправильно.


                          То, что они написали обертку на lxc, который был не очень удобен и теперь ее допиливают — это здорово. Но проблемы Docker от этого никуда не пропадают и большинство этих проблем похожи на проблемы из-за неопытности, которая сильно сказалась на старте проекта.


                          А к недостаткам языка никакого. Но это же вы аппелируете к разработчикам Docker.


                          1. Alexeyco
                            10.11.2016 12:03
                            -1

                            Просто их заставляли убирать неиспользуемые импорты… (с)


                            1. SirEdvin
                              10.11.2016 12:04
                              +1

                              Шутку понял, но не понял, причем тут она :)


                          1. polifill
                            10.11.2016 12:15
                            -2

                            ПО Докер — открытое, любой может скопировать.

                            Тем не менее, используется прежде всего Докер, а не его клоны (вы их вообще назвать-то сможете, эти клоны? ), при том, что в инфраструктуре на базе Докера заинтересованы весьма богатые конторы — могли бы клонировать, если бы это было целесообразным.

                            Значит, ребята достаточно квалифицированы.

                            Докер — СТАНДАРТ ДЕ-ФАКТО.

                            А вы все пытаетесь его очернить только потому, что он написан на Go.


                            1. SirEdvin
                              10.11.2016 12:18
                              +1

                              А вы все пытаетесь его очернить только потому, что он написан на Go.

                              Мне плевать, на чем он написан. Мне не плевать на его недостатки, которых море. Например, вот тут можно прочитать (часть придирок тут правда просто бред) https://thehftguy.wordpress.com/2016/11/01/docker-in-production-an-history-of-failure/


                              По поводу клонок и альтернативных решений — mesos, rkt, atomic и это только первое, что приходит на ум. Существенная проблема только в том, что пока они разрабатывались, ребята уже закодили Docker и теперь развлекаются. + опять же у них довольно хороший маркетинг.


                              1. AnimaLocus
                                10.11.2016 12:21
                                -1

                                С таким подходом… Можете написать какие стеки часто используете и я напишу что из этого маркетинг (90%), и где что трууушно… Надо?)))


                                1. SirEdvin
                                  10.11.2016 12:23

                                  Нет, потому что так и будет (я про 90%).
                                  Но маркетинг же не делает ребят из Docker трушними программистами, которые правильно разрабатывают продукт?


                                  1. AnimaLocus
                                    10.11.2016 12:44

                                    Делает, если смотреть с прикладной стороны.
                                    1. Прикладная. Docker успешный продукт. Это уже показатель трушности, в том числе и программистов. :)
                                    2. Теоретическая. Трушность будет только в самой хардкорной математике. Программирование не тру.
                                    Оффтоп конечно но куда же без: немного троллинга богу троллинга и любителю троллинга. %)


                                    1. SirEdvin
                                      10.11.2016 12:49

                                      1. Мне кажется, тут нужно оценивать успех всей команды, а не программистов. В истории куча примеров написанных так себе продуктов, которые становились прибыльными, потому что решали важные и насущные задачи прямо сейчас, а не через 2 года.

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

                                      2. Тут сложный вопрос. У меня действительно важный претензий в Docker только три: обновления без поддержки предыдущих решений и предупреждений о том, что «все плохо», документация и то, что управление жестким диском все на тебе (особено жесткого это в случае регистра). Поправили бы все это и был бы отличный продукт.


                              1. polifill
                                10.11.2016 12:33

                                Простите, вы не в теме.

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

                                2. mesos — гораздо более грандиозная система, целая операционная система для датацентров. В то время как Докер — локальная система управления контейнерами.

                                Вы пишете о том, в чем не разбираетесь.


                                1. SirEdvin
                                  10.11.2016 12:36

                                  1. Это инструмент призванный исправит недостатки Docker. Вы просили меня назвать клоны, я назвал.
                                  2. Окей, если я скажу mesos-container, станет лучше? В целом, там так же есть изоляция и контейнеры. Да, оно большое альтернатива Docker-swarm + Docker, чем самому docker, но контейнеры то там другие.

                                  Или вам нужны именно технологии, которые были до Docker? Тогда есть lxc и chroot.


                              1. polifill
                                11.11.2016 12:43
                                -2

                                Недостатки есть у всех.

                                Тем не менее, Докер — мейнстримовый продукт.
                                ЗАСЛУЖЕНО.
                                Стандарт де-факто.

                                Речь вообще о другом.

                                Разработчики Докера выбрали инструмент — и не прогадали.

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

                                Огромное количество СЕРЬЕЗНЕЙШИХ продуктов реализовано на golang.

                                Люди поумнее вас и поумнее меня выбрали его — и НЕ ПРОГАДАЛИ.
                                А вы тут до сих пор ищете идеальный язык…

                                Кто-то перешел на Go с Node, кто-то c Python, кто-то с Java.
                                Об массовых ситуация ОБРАТНОГО перехода — не слуху не духу.


                                1. SirEdvin
                                  11.11.2016 13:08

                                  Тем не менее, Докер — мейнстримовый продукт.

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


                                  Огромное количество СЕРЬЕЗНЕЙШИХ продуктов реализовано на golang.

                                  Огромное количество "СЕРЬЕЗНЕЙШИХ" продуктов реализовано на java, python, c#, javascript. И что? Это как-то исправит недостатки языков?


                                  А вы тут до сих пор ищете идеальный язык…

                                  А Go значит вот идеальный?)


                                  1. polifill
                                    11.11.2016 14:06

                                    Я разве писал, что нельзя уже писать на Java, Python, C# — просто потому что появился Go?

                                    Вопрос в другом: почему вы ожидаете, что Go будет делать то же самое, что и Java? Зачем пихать в Go то, что уже есть в других языках? Тогда это и будет Java, Python, C#. А они УЖЕ есть.

                                    Идеал — недостижим.

                                    Go достаточно развит для серьезного практического использования. Ровно это я и хочу сказать.

                                    На нем УЖЕ и очень ДАВНО пишут серьезнейшие проекты. Крупные конторы. Опытные программисты, которые умнее и вас и меня.

                                    А вы все еще живете в прошлом.


                                    1. SirEdvin
                                      11.11.2016 14:08

                                      Go достаточно развит для серьезного практического использования. Ровно это я и хочу сказать.

                                      Ну так у него есть недостатки по моему мнению, что мне мешает на них указать?


                                      PHP вот тоже готов для сереьезного практического использования уже лет 10. И что?


                                      1. polifill
                                        11.11.2016 14:18
                                        -2

                                        Для того, чтобы ПОНИМАТЬ то, о чем вы пишете — неплохо бы иметь опыт. Не теоретизировать, а иметь СОБСТВЕННЫЙ опыт.

                                        Пока ваши претензии выглядят так:

                                        «Я привык иначе» — какой же это довод? Все языки разные.
                                        Что из того, что вы привыкли иначе — то же был другой язык.

                                        Вы не недостатки языка описывайте, а свою невозможность изменить вашу личную привычку — но ваша привычка только ваше личное дело.

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

                                        Это всего лишь ваша привычка. И отсутствие у вас опыта работы на больших проектах. Не более того.


                                        1. SirEdvin
                                          11.11.2016 15:42
                                          +2

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


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


                                          Я не спорю, Golang отличный язык, который как раз занял важную нишу, но решения в стиле "так надо, потому что так надо", хотя это как минимум сомнительно, все портят.


                                          Особенно смешно то, что никто из тех, кто любит Go не может просто пожать плечами и сказать "Ну хз, захотелось и вплили".


                                          1. stokker
                                            11.11.2016 18:25

                                            Нет, как раз сам подход «так надо, потому что так надо» хорош (потому Go и занял свою растущую нишу). Спорить можно только о конкретных решениях.


          1. Alexeyco
            10.11.2016 11:29

            Кстати, вот, похоже и ответ. Вам субъективно нравится какой-то язык, но вас заставляют писать что-то на Go. Признавайтесь, я же прав? ))) А вы теперь вымещаете все это на ни в чем неповинном ЯП.


            1. wOvAN
              10.11.2016 13:27
              +1

              Нет не правы, я сам предложил Golang и взял на тест. Хотя у меня есть языки которые мне субъективно нравятся, наверное есть у каждого.


              1. Alexeyco
                10.11.2016 15:13
                -1

                Поделитесь? )))


                1. Sirikid
                  10.11.2016 15:28

                  Присоединяюсь к вопросу


  1. TyVik
    10.11.2016 19:37
    +1

    Я не особо силён в Go, но количество WTF просто зашкаливает. Есть какая-нибудь документация почему было сделано именно так, а не иначе?


    1. AnimaLocus
      10.11.2016 19:43

      Логика + ключевые слова: code style, effective go. Обсуждения также можно погуглить по конкретным моментам.


      1. TyVik
        10.11.2016 19:55

        Вот в прошлый раз меня такими же словами обозвали. Погуглил, но все ответы сводились к «потому что Пайк так сказал». Взять ту же тему коды возврата vs исключения (статья здесь с id 269909) — так там в комментариях удивляются почему это реализовано в языке именно так и считается хорошей практикой. Собственно, ответа на эти вопросы я и не нашёл.


        1. AnimaLocus
          10.11.2016 20:16

          В любом языке есть идеолог/группа идеологов отвечающих за развитие языка. Что Вы еще хотите найти? Не смогли найти какой-то конкретный ответ — можно ведь спрашивать у разных людей, вплоть до Пайка.


        1. Sirikid
          10.11.2016 20:33

          Если хотите лучше понять Go (не как писать, а откуда пошло) можете посмотреть на Oberon, предыдущие языки Пайка и код на Си из Plan9.


        1. Alexeyco
          10.11.2016 21:36

          А мы вот тоже хотим, чтобы у нас был свой Монти ))) если он вам не нравится, не разжигайте, а отойдите и не мешайте нам на него молиться.


        1. SirEdvin
          11.11.2016 13:13

          В целом, все так — вам не нравится то, что нравится Пайку — Go не ваш язык :)


          1. stokker
            11.11.2016 14:43

            Я бы даже сказал не Ваша ниша в разработке… ((


        1. polifill
          11.11.2016 14:59

          Есть же замечательные статьи — почему плохи исключения в Java или почему от них отказались в Google для C++.
          http://neverworkintheory.org/2016/04/26/java-exception-handling.html
          https://google.github.io/styleguide/cppguide.html#Exceptions

          Если вкратце: не все так здорово с исключениями.

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

          Сейчас — люди поумнели и смотрят на мир шире.


        1. Nicklasos
          11.11.2016 15:50

          > потому что Пайк так сказал

          Ну так это же хорошо, все в одном стиле, даже если этот стиль не нравится, то к нему можно просто привыкнуть.
          Как противоположность можно привести php, где есть определенная неконсистентность языка.


          1. TyVik
            11.11.2016 16:06

            А можно привести в пример и python, где pep8 лишь рекомендация, но которую соблюдают все.


            1. stokker
              11.11.2016 18:27

              Почему Go должен копировать другие ЯП?


  1. AlexStav
    10.11.2016 23:31
    +2

    Вопрос от новичка, возможно наивный.
    Пункт 31, последний листинг. Допустим

    workerCount := 100
    

    и doit выглядит следующим обоазом
    func doit(workerId int, wq <-chan interface{},done <-chan struct{},wg *sync.WaitGroup) {  
        fmt.Printf("[%v] is running\n",workerId)
        defer wg.Done()
        for {
            select {
            case m := <- wq:
                time.Sleep(time.Second * 1) // чтобы наверняка задействовать больше одной горутины
                fmt.Printf("[%v] m => %v\n",workerId,m)
            case <- done:
                fmt.Printf("[%v] is done\n",workerId)
                return
            }
        }
    

    Допустим все воркеры запущены, в канал wq посланы все данные и наконец закрыт канал done, т.е. во
    всех запущенных горутинах доступны для получения оба канала wq и done. Возможна ли ситуация, когда
    все select во все горутинах случайным образом прочтут предсмертное послание из done и закончат свое выполнение? Или я не правильно понимаю логику работы select? Или вообще ничего не понимаю? :)


    1. polifill
      11.11.2016 15:04
      -1

      Пока wq не пуст — все go-routine (видимо doit завернут в go-routine? ) будут считывать данные из него.
      Как только wq пуст — будет предпринята попытка прочитать из done.
      Поскольку done закрыт — будет завершена процедура

      Если вы используете done как сигнал о завершении работы — то следует поместить done выше, сделав его самым первым в select

      И закрывать done не сразу, а только тогда, когда вам нужно завершить все go-routine с doit


      1. AlexStav
        11.11.2016 15:41

        Пока wq не пуст — все go-routine (видимо doit завернут в go-routine? ) будут считывать данные из него.

        то что завернут в го-рутину — это очевидно из примера в п.31 статьи. ПОЧЕМУ будут считывать из wq ??? Ведь если у select-у доступны для прима данных несколько каналов, то данные выбираются из одного из них СЛУЧАЙНЫМ образом. Или я все-таки что-то недопонимаю? :)


        1. polifill
          11.11.2016 15:58
          -1

          Данные выбираются из первого доступного среди веток в select.

          Так как нет ветки default — то select должен быть заблокирован до тех пор, пока какой нибудь из каналов (wq или done) не будет закрыт или в него не поступят данные.


      1. AlexStav
        11.11.2016 15:46

        Если вы используете done как сигнал о завершении работы — то следует поместить done выше, сделав его самым первым в select

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


        1. Sirikid
          11.11.2016 21:05

          Только вот в done мы ничего не посылаем, так что будет читать из wq


          1. polifill
            12.11.2016 11:31

            Речь шла о ЗАКРЫТИИ done. Закрытый канал прекрасно читается.

            Я постоянно использую подобную методу — создаю фиктивный канал chan struct{}, в который никто никогда не пишет, а использую этот канал для сигнализации, что объект/структура/сервис более не используется — просто вызвав close(done).
            Это разблокирует все select и корректно завершает go-routines


        1. polifill
          12.11.2016 11:53
          -1

          В пределах одного select чтение происходит не случайным образом, а сверху вниз.

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

          Если второй канал ничего не дает прочитать, а default нет, то select останется заблокированным — будут предприниматься попытки прочитать из двух каналов.

          После чего нет никакой гарантии какой канал будет прочитан первым.

          Но мне все же представляется логичным помещать сигнализирующий о прекращении процесса done — в самый верх. Это на случай начального входа в select, когда мы еще не знаем — сразу же мы из него выйдем или будем заблокированы.

          Ведь если процесс уже завершен ДО НАЧАЛА SELECT, то нам НЕ СЛЕДУЕТ ЧИТАТЬ НИЧЕГО ИЗ WQ, а правильным будет сразу выйти по ветке done.


          1. 3vilhamst3r
            13.11.2016 01:50
            +1

            Советую ознакомиться с документацией по select, особенно на пункт 2, дабы не наткнуться в какой то момент на потерянные данные. Для решения проблемы «приоритезации» опций внутри селекта, насколько я знаю, пока нет общего решения, можно погуглить golang select priority, решения есть, разной степени странности.


            1. polifill
              13.11.2016 11:17
              -1

              Не выдумывайте проблем на пустом месте.

              ОК, специально для вас РАЗЖУЮ:

              select {
              case < — каналСигналаВыхода:
              default:

              select {
              case<-каналСигналаВыхода:
              case d:= < — каналСДанными:
              }

              }


    1. FZambia
      11.11.2016 23:10
      +1

      В этом примере невозможна ситуация, когда close(done) выполнится перед тем как воркерами будут получены все данные из wq. Потому что канал wq небуферизованный, до close(done) управление дойдет только после того как последнее значение будет отправлено в канал wq и вычитано в воркере (потому что согласно go memory model: A receive from an unbuffered channel happens before the send on that channel completes).

      Если у wq будет буффер — тогда поведение недетерминировано и воркеры могут завершиться до выполнения всей работы. В этом случае как-то по-другому нужно написать код.


      1. polifill
        12.11.2016 11:48
        -2

        Это при условии, что когда дело доходит до чтения wq, туда УЖЕ записано.
        Если будут перерывы при записи wq, то select соскользнет в закрытый done и go-routine завершится.


      1. AlexStav
        12.11.2016 19:20

        Да, все логично. Спасибо.


  1. POPSuL
    14.11.2016 05:58

    Для меня самой удивительной особенностью оказалось то, что горутину нельзя взять и принудительно убить.
    Вызываю к примеру я в своей горутине какой-то из сторонней библиотеки, которая, например, выполняет бесконечный цикл. Как мне после такого убить свою горутину (к примеру, по таймауту)?