Golang глазами PHP-программиста
Hello World
Как-то появилась причина попробовать пописать на Go. На тот момент я знал, что это язык от Google, язык молодой, язык компилируемый, вроде как активноразвивающийся и с зарплатами выше средних. Неплохой набор.
В первой попавшейся статье узнаем, что Go к тому же легкий в изучении. Интересно, сколько PHP-программистов стало PHP-программистами, потому что PHP легкий в изучении? И действительно, за пару вечеров можно уже неплохо ориентироваться в языке.
Итак, ищем какой-нибудь golang roadmap, небольшое количество времени, и вот он, helloworld на Golang. Теперь надо его запустить. Сама установка Go - быстрая и простая, занимает пару минут(скачать, нажать далее несколько раз), так что смотрим пример:
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
Первый взгляд
Вместо пространств имен из PHP, здесь пакеты. Смысл немного похожий, но Golang-компоузер идет сразу из коробки. Что еще сразу же бросится в глаза, стоит написать пару строчек кода, так это автоформатирование. PSR, который про оформление кода, также получаем из коробки. И это здорово.
В ознакомительных статьях по Golang можно встретить такое заявление разработчиков: "Если что-то можно сделать на Go, то это можно сделать единственным способом". Громкое утверждение, которое, конечно, не надо воспринимать буквально, но по мере знакомства с языком, можно подмечать такие моменты. Автоформатирование как раз один из них.
Итак у нас есть простая установка, менеджер зависимостей и автоформатирование. Что дальше?
Ошибки
Например работа с ошибками. В Golang нет исключений, зато функции и методы могут возвращать несколько значений. Одно из возвращаемых значений принято использовать для передачи информации об ошибках.
package main
import (
"errors"
"fmt"
)
// f1 returns two values
func f1(arg int) (int, error) {
if arg == 42 {
return -1, errors.New("can't work with 42")
}
return arg + 2, nil
}
func main() {
num, err := f1(42)
if err != nil {
panic(err)
}
fmt.Println(num)
}
Да, как-то так, сюда надо добавить, что если объявить переменную в Go, то ее необходимо использовать, иначе код просто не скомпилируется. Язык подталкивает обрабатывать ошибки, хотя и оставляет возможность этого не делать.
Выше я написал, что в Golang нет исключений, это не совсем так, в Go есть стандартная функция panic, которая при вызове прервет текущее выполнение и начнет "всплывать" наверх по аналогии с исключениями. По аналогии с исключениями panic можно перехватить, обработать и продолжить выполнение программы.
И еще немного про ошибки, вроде язык и подталкивает к их обработке, но также Go предлагает конструкцию пустого идентификатора(Blank identifier), используя его, можно не создавать новую переменную под возвращаемые функцией значения, следовательно, не нужно будет ошибку обрабатывать.
func main() {
num, _ := f1(42)
fmt.Println(num)
}
Получается, если захотеть, то можно и не обрабатывать, но вряд ли это останется незамеченным на каком-нибудь код ревью.
Рутина
Go - язык строготипизированный, плюсы понятны, но это же придется возиться с типами? Да. К счастью, кое-где разработчики Go насыпали сахарку. Например, при создании новой переменной, можно использовать оператор := и тогда указывать её тип не придется.
Второй момент - это работа с массивами. Да, всепоглощающего array из PHP здесь нет, нужно будет вспомнить конструкции вида [][]string, но вот с выделением памяти уже подсобили, в Go есть тип Slice, который сильно упрощает эту задачу.
func main() {
//create slice with len 1
myslice := make([]int, 1)
for i := 1; i < 10; i++ {
// add necessary amount of elements
myslice = append(myslice, i)
}
//print slice with 11 elements
fmt.Println(myslice)
}
А вот удалять неиспользуемые данные будет сборщик мусора. Из рантайма. Из рантайма еще будет безопасность для указателей. Но, собственно, в Go есть рантайм, без него не поедем.
ООП
В Go нет наследования классов, привычного PHP-программисту. Вместо наследования - нечто вроде композиции. При этом мы не можем использовать "наследника" вместо "родителя". Но можем реализовывать интерфейс, и использовать уже его. Получается полиморфизм только на интерфейсах.
Стоит добавить, что реализация интерфейса в Go неявная, не нужно использовать аналог implements, достаточно чтобы "класс" содержал методы из интерфейса. Здесь класс написан в кавычках, потому что в Go нет привычных нам классов. Зато есть структуры и возможность привязывать к ним функции. Вот как это может выглядеть:
// our new type
type Type1 float64
// Golang way for method's definition
func (t1 Type1) Show() {
fmt.Println(t1)
}
// methods and properties of Type1 accessible over Type2
type Type2 struct {
Type1
}
// redefine Show method
func (t2 Type2) Show() {
fmt.Println(t2.Type1 + 7)
}
func myF1(t1 Type1) {
t1.Show()
}
// our new types have implemented this interface just because
// they contain Show method
type Shower interface {
Show()
}
func myF2(s Shower) {
s.Show()
}
func main() {
t1 := Type1(54)
t1.Show() //54
t2 := Type2{Type1: 45}
t2.Show() //52
//we can access method of "parent"
t2.Type1.Show() //45
myF1(t1) //54
// will not be compiled, we cant pass Type2 instead Type1
//Show(t2)
myF2(t1) //54
myF2(t2) //52
}
И про инкапсуляцию, в рамках одного пакета (это не прямая аналогия с пакетами composer'a, пакет в Go - это один или несколько файлов, объединенных по какому-то смыслу. Из пакетов в Golang получается модуль. Модуль - это уже что-то похожее на пакет из composer'а) у нас есть доступ ко всем свойствам и методам наших типов. А при использовании одного пакета в другом пакете будут доступны только свойства и методы начинающиеся с большой буквы. С маленькой буквы - это аналог private. А protected доступа нет, т.к нет наследования.
Многопоточность
Цирковой номер - PHP-программист рассказывает про многопоточность. И все же, многопоточность является одной из выделяемых особенностей Golang. Легко можно создать несколько новых потоков, наладить между ними взаимодействие, установить контроль над данными, чтобы избежать гонки.
Но как я понял из пары-двойки статей про многопоточность - это не палочка-выручалочка по увеличению производительности. Бездумно нарастить количество потоков не получится, даже если твоя задача хорошо распараллеливается, то все может упереться в специфичность окружения, и выигрыш в производительности окажется не таким уж впечатляющим.
И продолжая тему производительности. Хоть язык и компилируемый, но у него есть рантайм со сборщиком мусора и безопасностью. И судя по паре-двойке статей, где сравнивалась производительность Go с другими языками, получается что-то такое: в большинстве приведенных в статьях тестов, Go обгонял интерпретируемые языки, в большинстве тестов был быстрее Java, но ни в одном приведенном тесте не был быстрeе C, C++, Rust.
Сообщество
Язык молодой, людей в нем не то, чтобы много, соответственно готовых решений под твои потребности может и не найтись. Готовых ответов на твой вопрос может и не найтись и т.д. Если в рамках микросервисных задач материалов достаточно, то если отойти в сторону какого-нибудь AI, будет куда сложнее. В последние несколько лет рост Go замедляется.
Выводы
У Golang есть целый ряд плюсов. Один из главнейших, на мой взгляд, это простота в изучении. Много вещей идут сразу из коробки. Язык от Google. Писать на Go просто приятно. Концепции, заложенные авторами, позволяют под новым углом взглянуть на привычные вещи. Но хватит ли этого, чтобы попасть и закрепиться в "топах" - вопрос открытый.
P.S. Подозреваю, что в статье есть неточности или даже откровенные ошибки. Если ты их заметил, помоги тому, кто мог не заметить - оставь комментарий.