IT
November 29, 2023

Go (Golang) - просто и со вкусом смузи

Новомодный язык Go уже лет как десять, но до сих пор не утихают споры – хороший это язык или не очень. Это что-то новое или хорошо забытое старое? Мой интерес был вызван отсутствием исключений в языке Go. Как так? А так можно было?

Есть онлайн книга http://golang-book.ru/, где можно основы языка изучить и выполнить стартовые задачи для закрепления знаний. Рекомендую.

Go — компилируемый многопоточный язык программирования, разработанный внутри компании Google. Разработка Go началась в сентябре 2007 года, его непосредственным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон, занимавшиеся до этого проектом разработки операционной системы Inferno.

Особенности Go:

  • компилируемый и многопоточный язык программирования.
  • строгая статическая типизация.
  • сборщик мусора.
  • go рутины и параллелизм.
  • ключевое слово "defer".
  • нет исключений "try-catch".
  • почти отсутствие ООП, но он есть.

Язык Go считается языком программирования общего назначения. При знакомстве с Go заметно преобладание облачного, веб- и системного программирования. Но благодаря активности сообщества, Go движется во все сферы IT.

По быстродействию Go проигрывает Rust, C++ и C. Но, отчаиваться не стоит, зато в Go есть свой сборщик мусора для управления памятью. Встроенный и легкий механизм распараллеливания. Отсутствие исключений дополнительно ускоряет программы на Go. Комфорт, за маленькую толику производительности.

Ставим Go и VS Code. VS Code надо настроить и можно погружаться.

"Комфорт", как первое впечатление.

Первое впечатление – комфорт. Разметкой цветом и подсказкой функций и переменных никого уже не удивишь. В Go выполняется принудительное форматирование исходного кода при сохранении файла. В начале меня это немного бесило, а потом я вжился. А как удобно то... И действительно, зачем программисту оставлять возможность форматирования кода? Он же там всё не так сделает.

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

Задействованные сторонние модули будут автоматически добавлены в секцию import. И ненужный импорт будет удален автоматически.

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

Огромный плюс, что Go учит сразу правильно писать программы. Чуть что не так – получай ошибку.

Первая программа. Создаем файл main.go и пишем:

package main

import "fmt"

func main() {
    fmt.Println("Привет мир!")
}

Запуск:
go run main.go
Привет мир!

Для запуска выполняем команду "go run main.go" в консоли для запуска.

Для компиляции "go build main.go". Что позволит получить двоичный файл для компьютера.

Команда "go build -ldflags="-s" main.go" позволит сделать двоичный файл минимального размера (около мегабайта).

Каждая программа на Go состоит из пакетов. Пакет main главный, с него начинается запуск программы.

В примере импортируется пакет fmt, который содержит функцию вывода строки в консоль.

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

Остальная информация есть в книге http://golang-book.ru/. Я не ставил перед собой задачи создавать учебник.

Особенности, чуть подробнее.

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

Строгая статическая типизация. Переменные с определенным типом.

package main

import "fmt"

func main() {
    var i int
    var s string    
    var f float64 = 3.141529
    
    tmp := 18.4    // так тоже можно определять переменные
    i = 3    
    s = "четыре"
    
    fmt.Println(f, i, s, tmp)
}

Запуск:
go run main.go
3.141529 3 четыре 18.4 

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

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

package main

import (    
    "fmt"
    "time")
    
func main() {

    for i := 0; i < 10; i++ {        
        go prn(i)    
    }    
    
    time.Sleep(1 * time.Second)
}
    
func prn(i int) {
    fmt.Println(i)
}

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

Запуск: 
go run main.go
8
1
0
9
4
5
6
7
3
2

Ключевое слово "defer". Функция отложенного запуска. Любая инструкция, вызванная после ключевого слова "defer" будет выполнена перед выходом из функции.

package main

import (    
    "fmt")
    
func main() {

    defer fmt.Println("обработка")    
    fmt.Println("старт")    
    fmt.Println("финиш")
}
Запуск:
go run main.go
старт
финиш
обработка

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

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

Нет исключений "try-catch". Именно из-за этого я обратил внимание на язык Go. Как так? Разве можно без исключений? Неужели язык C вернулся? Оказывается можно и даже нужно. Ошибочные статусы возвращаются дополнительным параметром из функции. И требуют обработки. Go не любит не использованных переменных, поэтому или принудительно игнорируй ошибку или обрабатывай. Игнорировать можно, но совсем не нужно.

package main

import (    
    "fmt"    
    "os")
    
func main() {

    file, err := os.Open("file.txt")    
    
    if err != nil {
        fmt.Println(fmt.Errorf("%w", err))        
        os.Exit(1)    
    }    
    
    defer file.Close()    
    // to do
}

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

Закрытие файла осуществляется через команду defer, что позволяет не беспокоиться, что забудем это сделать.

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

Паника panic(), непредвиденное поведение программы. При возникновении такой ошибки работа программы заканчивается. Панику можно обработать и "починить", используя инструкции defer и recover.

Почти отсутствие ООП. В языке существуют сложные составные типы данных – структуры. Такие конструкции нужно определять самому. Есть ли в Go ООП? Да, но другое. Язык Go больше про функциональность, а не соответствие нормам, правилам и академическим определениям.

У объектов есть свойства и поведение, объекты могут взаимодействовать – чем это не ООП?

package main

import (    
    "fmt"
    )
type addMetod struct {    
    a int
    b int
}
func (add *addMetod) twoMetod() int {
    return add.a + add.b
}

type multMetod struct {
    a int    
    b int
}
func (mult *multMetod) twoMetod() int {
    return mult.a * mult.b
}

type twoable interface {
    twoMetod() int
}

func sameFunc(obj twoable) {
    fmt.Println(obj.twoMetod())
}

func main() {

    var first addMetod = addMetod{3, 3}    
    var second multMetod = multMetod{3, 3}
    sameFunc(&first)    
    sameFunc(&second)
}

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

Функция sameFunc принимает параметр, реализующий интерфейс, и выводит результат работы. В функции main выполнена инициализация переменных и вызов sameFunc для каждой переменной.

Запуск:
go run main.go
6
9

Полезен ли Go начинающим?

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

Читаемость – любой код на Go будет выглядеть одинаково, об этом сразу позаботились.

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

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

И еще немного важного о языке Go

Errors are values. Don’t just check errors, handle them gracefully. Don’t panic. Make the zero value useful. The bigger the interface, the weaker the abstraction. Interface{} says nothing. Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite. Documentation is for users. A little copying is better than a little dependency. Clear is better than clever. Concurrency is not parallelism. Don’t communicate by sharing memory, share memory by communicating. Channels orchestrate; mutexes serialize.

Rob Pike, Go Proverbs

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

Всем удачи!