Slide 1

Slide 1 text

Впечатления Python разработчика от Go Python нельзя Go

Slide 2

Slide 2 text

Сурен Хоренян Руководитель команды бэкенд интерфейсов в ВК Рекламе 2

Slide 3

Slide 3 text

• почему я не ”заценил” Go • с какими утверждениями я не согласен • как быть • что делать 3

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Сразу обозначу, что я: ничего не понял ретроград / консерватор ни в чём не разобрался вообще не программист 6

Slide 7

Slide 7 text

О чем доклад Будучи Python разработчиком я познакомился с Go и не нашёл причин переходить на Go для веб-разработки и прикладных задач. Возможно, я чего-то не понял. В докладе поделюсь наблюдениями и расскажу, почему Go меня не очаровал. 7

Slide 8

Slide 8 text

Как я познакомился с Go 8

Slide 9

Slide 9 text

Классный набор занятий: ✦Позволяет ознакомиться с особенностями языка; ✦Объясняет все шаги и возможности; ✦Плавно вводит в курс дела; ✦Предоставляет редактор и компилятор на одной странице с уроками; ✦Ёмкий. И этот курс я, конечно же, не прошёл. A Tour of Go 9

Slide 10

Slide 10 text

Надо починить баг в гошном сервисе 10

Slide 11

Slide 11 text

Что за гошный сервис? Как получился новый микросервис: • Нужен был новый микросервис для аутентификации и авторизации; • Коллеге очень хотелось сделать быстрый микросервис. 11

Slide 12

Slide 12 text

Что получилось: • Самостоятельный Go сервис (с прогретыми кэшами и без обращений к БД) отвечал за 80-90 мс. (это быстро или медленно?) • Самостоятельно работать этот Go сервис не был задуман, это же auth центр. Работает только как прослойка для общения Python приложений. Что за гошный сервис? 12

Slide 13

Slide 13 text

Почему его нужно чинить мне, питонисту? 1. Автор микросервиса ушёл в долгий отпуск; 2. Моя ответственность за комплекс сервисов, куда вошёл новый микросервис; 3. В команде и в соседних командах больше нет компетенций в Go. 13

Slide 14

Slide 14 text

Что нужно было сделать Обновить view (в терминах Python): 1. Принимать новые данные; 2. Обрабатывать новые данные по-новому; 3. Изменить пару запросов в БД. Фото с места событий: 14

Slide 15

Slide 15 text

Всё было успешно релизнуто в рамках одного дня 15

Slide 16

Slide 16 text

Желание учить других Чтобы начать учить других нужно разобраться самому. Познакомился с горутинами. 16

Slide 17

Slide 17 text

Мои впечатления и вопросы без ответов 17

Slide 18

Slide 18 text

Почему отступы табами, а не пробелами? В Goland и на GitHub / GitLab код выглядит по-разному Отступы 18

Slide 19

Slide 19 text

Отступы для форматирования приводят к гит-вандализму +type Config struct { + Foo int `json:"foo"` + Bar int `json:"bar"` +} type Config struct { - Foo int `json:"foo"` - Bar int `json:"bar"` + Foo int `json:"foo"` + Bar int `json:"bar"` + FizzBuzz int `json:"FizzBuzz"` } -Нарушает историю; -Усложняет код-ревью. 19

Slide 20

Slide 20 text

Отступы для форматирования приводят к гит-вандализму +var ( + ToBe bool = false +) var ( - ToBe bool = false + ToBe bool = false + MaxInt uint64 = 1<<64 - 1 ) -Нарушает историю; -Усложняет код-ревью. Код из официального урока https://go.dev/tour/basics/11 «variable declarations may be "factored" into blocks» var ( - ToBe bool = false - MaxInt uint64 = 1<<64 - 1 + ToBe bool = false + MaxInt uint64 = 1<<64 - 1 + z complex128 = cmplx.Sqrt(-5 + 12i) ) _________________________________________ _________________________________________ 20

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Давайте сразу писать без багов Так мы сократим время разработки ещё сильнее. И очень порадуем наших продактов. 22

Slide 23

Slide 23 text

Почему гит-вандализм — это плохо ★ Нарушение гит-истории усложняет поиск ответственного за код; ★ На ревью нужно уделять внимание строчкам, которые не относятся к фиче совсем. 23

Slide 24

Slide 24 text

import ( "fmt"; "math/cmplx" ) 1. ок 2. тоже ок import ( "fmt" "math/cmplx" ) Отступы нужны для стиля, следить за переносами необходимо для соблюдения синтаксиса. и такой стиль приветствуется Перенос — это часть синтаксиса. 24

Slide 25

Slide 25 text

func main() { fmt.Println("Welcome!") } 1. ок 2. тоже ок func main() { fmt.Println( "Welcome!") } func main() { fmt.Println( "Welcome!" ) } 3. совсем не ок Это ограничение усложняет форматирование длинных кусков кода. ./prog.go:17:41: syntax error:
 unexpected newline in argument list;
 possibly missing comma or ) Перенос — это часть синтаксиса. 25

Slide 26

Slide 26 text

это ок func main() { fmt.Println( "Welcome!", ) } Стандартный форматер поможет вам. Перенос — это часть синтаксиса. 26

Slide 27

Slide 27 text

Возвращаемые значения 27

Slide 28

Slide 28 text

Функции умеют возвращать несколько значений package main import "fmt" func getValues() (int, string) { return 42, "Hello, Go!" } func main() { num, str := getValues() fmt.Println("Number:", num) // Output: Number: 42 fmt.Println("String:", str) // Output: String: Hello, Go! } Необходимо сразу принять указанное количество значений. 28

Slide 29

Slide 29 text

Функции умеют возвращать несколько значений package main func getValues() (int, string) { return 42, "Hello, Go!" } func main() { ./main.go:10:9: assignment mismatch:
 1 variable but getValues returns 2 values res := getValues() } Нельзя ”собрать всё в одну кучку”. 29

Slide 30

Slide 30 text

так происходит не всегда! Но 30

Slide 31

Slide 31 text

Итерирование по массиву 31

Slide 32

Slide 32 text

Количество получателей влияет на результат функции. func main() { names := []string{"Bob", "Sam"} for idx := range names { fmt.Println(idx, names[idx]) } // Output: // 0 Bob // 1 Sam } Итерирование по массиву. Получаем индекс, работаем с индексом: 32

Slide 33

Slide 33 text

Количество получателей влияет на результат функции. func main() { names := []string{"Bob", "Sam"} for idx, name := range names { fmt.Println(idx, name) } // Output: // 0 Bob // 1 Sam } Итерирование по массиву. Получаем индекс и имя: 33

Slide 34

Slide 34 text

Получение из словаря по ключу 34

Slide 35

Slide 35 text

Количество получателей влияет на результат функции. Получение по ключу всегда возвращает успешный результат: func main() { jobTitles := map[string]string{ "Bob": "Accountant", "Alice": "Engineer", } fmt.Printf("Bob's Job Title: %q\n", jobTitles["Bob"]) // Output: Bob's Job Title: "Accountant" fmt.Printf("Alice's Job Title: %q\n", jobTitles["Alice"]) // Output: Alice's Job Title: "Engineer" fmt.Printf("John's Job Title: %q\n", jobTitles["John"]) // Output: John's Job Title: "" } 35

Slide 36

Slide 36 text

Количество получателей влияет на результат функции. Получение по ключу всегда возвращает успешный результат: func main() { jobTitles := map[string]string{ "Bob": "Accountant", "Alice": "Engineer", } bTitle := jobTitles["Bob"] fmt.Printf("Bob's Job Title: %q\n", bTitle) // Output: Bob's Job Title: "Accountant" jTitle := jobTitles["John"] fmt.Printf("John's Job Title: %q\n", jTitle) // Output: John's Job Title: "" } 36

Slide 37

Slide 37 text

Количество получателей влияет на результат функции. Чтобы проверить наличие ключа, нужно получить два результата. Вторым будет true, если ключ есть. Иначе вторым придёт false. func main() { jobTitles := map[string]string{ "Bob": "Accountant", "Alice": "Engineer", } bTitle, ok1 := jobTitles["Bob"] fmt.Printf( "Bob's Job Title: %q (exists: %t)\n", bTitle, ok1) // Out: Bob's Job Title: "Accountant" (exists: true) jTitle, ok2 := jobTitles["John"] fmt.Printf( "John's Job Title: %q (exists: %t)\n", jTitle, ok2) // Out: John's Job Title: "" (exists: false) } 37

Slide 38

Slide 38 text

Количество получателей влияет на результат функции. Лишнее необходимо игнорировать: func main() { jobTitles := map[string]string{ "Bob": "Accountant", "Alice": "Engineer", } names := []string{"Bob", "Sam"} for _, name := range names { if jobTitle, exists := jobTitles[name]; exists { fmt.Printf( "%s's Job title: %q\n", name, jobTitle) } else { fmt.Printf( "Missing job title: for %q\n", name) } } // Bob's Job title: "Accountant" // Missing job title: for "Sam" } 38

Slide 39

Slide 39 text

Количество получателей влияет на результат функции. Что в Go так себя ведет: - range - map - chan (канал) 39

Slide 40

Slide 40 text

Экспорт переменных и их обнаружение 40

Slide 41

Slide 41 text

Регистр первого символа имени имеет значение. package main import ( "fmt" "math" ) func main() {
 ./prog.go:9:19:
 undefined: math.pi fmt.Println(math.pi) } В Go имя экспортируется, если оно начинается с заглавной буквы. 41

Slide 42

Slide 42 text

package main import ( "fmt" "math" ) func main() { fmt.Println(math.Pi) // 3.141592653589793 } В Go имя экспортируется, если оно начинается с заглавной буквы. Регистр первого символа имени имеет значение. 42

Slide 43

Slide 43 text

В Go нет ООП 43

Slide 44

Slide 44 text

Классов нет, методы есть. 44

Slide 45

Slide 45 text

Функция, куда можно передать структуру type Person struct { Name string Age int } func IncreaseAge(p *Person, years int) { p.Age += years } func main() { person := Person{"Alice", 30} IncreaseAge(&person, 5) fmt.Printf("%s is %d years old\n", person.Name, person.Age) // Output: Alice is 35 years old } Обычная функция. Передаём различные аргументы, изменяем структуру. 45

Slide 46

Slide 46 text

Метод, который доступен для вызова со структуры type Person struct { Name string Age int } func (p *Person) IncreaseAge(years int) { p.Age += years } func main() { person := Person{"Alice", 30} person.IncreaseAge(5) fmt.Printf("%s is %d years old\n", person.Name, person.Age) // Output: Alice is 35 years old } Схожий синтаксис. Функция становится методом. 46

Slide 47

Slide 47 text

Классов нет, наследования нет, встраивание есть. https://go.dev/doc/effective_go#embedding 47

Slide 48

Slide 48 text

Объявляем структуры // Person struct type Person struct { Name string Age int } // Employee struct type Employee struct { // Embedding the Person struct Person Title string Salary int } И встраиваем одну структуру в другую: 48

Slide 49

Slide 49 text

Инициализируем func main() { // Create a new Employee e := Employee{ Person: Person{ Name: "John Doe", Age: 30, }, Title: ”Golang Software Engineer", Salary: 300_000, } } Создаём новый экземпляр Employee со встроенным экземпляром Person: 49

Slide 50

Slide 50 text

Обращаемся func main() { // Create a new Employee e := Employee{ Person: Person{ Name: "John Doe", Age: 30, }, Title: ”Golang Software Engineer", Salary: 300_000, } fmt.Printf("Employee name: %s, age: %d\n", e.Name, e.Age) // Output: Employee name: John Doe, age: 30 } Мы можем обращаться к свойствам Person через структуру Employee, так как Person встроен в Employee 50

Slide 51

Slide 51 text

Объявляем методы // Speak method for Person func (p Person) Speak() { fmt.Printf( "Hello, my name is %s, and I am %d years old.\n", p.Name, p.Age) } // Work method for Employee func (e Employee) Work() { fmt.Printf( "I am working as a %s and earning a salary of %d per year.\n", e.Title, e.Salary) } 51

Slide 52

Slide 52 text

Обращаемся func main() { // Create a new Employee e := Employee{ Person: Person{ Name: "John Doe", Age: 30, }, Title: "Golang Software Engineer", Salary: 300_000, } fmt.Printf("Employee name: %s, age: %d\n", e.Name, e.Age) // Output: Employee name: John Doe, age: 30 e.Speak() // Output: // Hello, my name is John Doe, // and I am 30 years old. e.Work() // Output: // I am working as a Golang Software Engineer // and earning a salary of 300000 per year. } Мы можем обращаться к свойствам и методам Person через структуру Employee, так как Person встроен в Employee 52

Slide 53

Slide 53 text

Множественное встраивание type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } // ReadWriter is the interface that combines // the Reader and Writer interfaces. type ReadWriter interface { Reader Writer } Наследования нет. Твердо и чётко. 53

Slide 54

Slide 54 text

Что мне, питонисту, понравилось в Go Что понравилось 54

Slide 55

Slide 55 text

1. Классный набор занятий 2. Всё подробно описано 3. Лёгкое знакомство A Tour of Go 55

Slide 56

Slide 56 text

Интересное решение с defer При помощи defer мы откладываем выполнение переданной ”задачи” на момент завершения функции. func main() { f := createFile("/py.txt") defer closeFile(f) writeFile(f, "Hello!") } Полный пример: https://gobyexample.com/defer 56

Slide 57

Slide 57 text

package main import ( "fmt" "os" ) func main() { f := createFile("./py.txt") defer closeFile(f) writeFile(f, "Hello!") } func createFile(p string) *os.File { fmt.Println("creating") f, err := os.Create(p) if err != nil { panic(err) } return f } func writeFile(f *os.File, data string) { fmt.Println("writing") _, err := fmt.Fprintln(f, data) if err != nil { panic(err) } } func closeFile(f *os.File) { fmt.Println("closing") err := f.Close() if err != nil { _, err = fmt.Fprintf(os.Stderr, "error: %v\n", err) if err != nil { panic(err) } os.Exit(1) } } Каждое действие требует дополнительной обработки всех возможных исходов. Полный листинг 57

Slide 58

Slide 58 text

Контекстный менеджер в Python легче Это полный код, готовый к выполнению ”as is”. with open("go.txt") as f: f.write("Hello there") 58

Slide 59

Slide 59 text

Удобные горутины Легчайший запуск потоков (threading). Может напомнить asyncio в Python. func f(line string) { for i := 0; i < 2; i++ { fmt.Println(line, i) } } func main() { go f("goroutine") f("direct") time.Sleep(time.Second) fmt.Println("done") // direct 0 // direct 1 // goroutine 0 // goroutine 1 // done } Пример: https://gobyexample.com/goroutines 59

Slide 60

Slide 60 text

Классные встроенные пакеты, взять тот же прекрасный net/http Но: 1. Батарейки идут в комплекте не только в Go; 2. Для разработки в большинстве случаев будут использованы сторонние обертки, упрощающие работу. 60

Slide 61

Slide 61 text

if err != nil 1. Интересное решение для явности обработки всех ошибок; 2. Все ошибки так не обработать, в некоторых случаях всё равно летит паника; 3. Утомляет на каждый чих писать эту конструкцию. Функции разрастаются молниеносно. 61

Slide 62

Slide 62 text

Обнаружение протокола Удобно реализовано автоматическое обнаружение протокола. Указывать протокол явно не требуется. type Person struct { Name string Age int } func (p Person) GetFormatted() string { return fmt.Sprintf( "%s is %d years old", p.Name, p.Age) } type Formattable interface { GetFormatted() string } func printFormatted(d Formattable) { fmt.Println(d.GetFormatted()) } func main() { p := Person{ Name: "John Doe", Age: 42, } printFormatted(p) // John Doe is 42 years old } 62

Slide 63

Slide 63 text

Политика одной мажорной версии Go с 2012 года поддерживается на версии 1.x, мажорную версию обновлять не планируют. Обещают совместимость. Что-то напоминает… 63

Slide 64

Slide 64 text

Share Memory By Communicating Go продвигает идею обмена данными через коммуникацию через каналы вместо использования одной области памяти для общения. 64

Slide 65

Slide 65 text

Быстрый пример работы с каналами Increment отправляет в канал значение каждые 10 мс. Print постоянно читает значения из канала. func Increment(ch chan<- int) { counter := 0 for { ch <- counter fmt.Println( "Sent count", counter) counter++ time.Sleep( 10 * time.Millisecond) } } func Print(ch <-chan int) { for { counter := <-ch fmt.Println( "Received count", counter) } } 65

Slide 66

Slide 66 text

Запускаем работу с каналами в потоках - Создаём канал для общения - Запускаем потоки - Ждём и смотрим func main() { // Create a channel to share memory ch := make(chan int) // Start the goroutines go Increment(ch) go Print(ch) // Let the program run for a while time.Sleep(50 * time.Millisecond) // Received count 0 // Sent count 0 // Sent count 1 // Received count 1 // Sent count 2 // Received count 2 // Received count 3 // Sent count 3 // Sent count 4 // Received count 4 } 66

Slide 67

Slide 67 text

Каналы можно использовать только в потоках Отправку в канал можно воспринимать как фоновую задачу, которая может выполниться только когда запущены треды. Если не запущена ни одна горутина, программа упадёт с ошибкой. fatal error:
 all goroutines are asleep - deadlock! Это неочевидное поведение. 67

Slide 68

Slide 68 text

68

Slide 69

Slide 69 text

Глобально не понимаю, зачем переходить с Python на Go Но зачем? 69

Slide 70

Slide 70 text

Дело не в Go И дело даже не в Python. Я вижу смысл учить новый язык для перехода между сферами: мобилки - фронт - бек - embedded. Переход с Python / Java на Go / C# не повлияет на вашу работу настолько, чтобы это стоило даунгрейда по должности. 70

Slide 71

Slide 71 text

Что пишут в пользу перехода на Go? Разберем тезисы 71

Slide 72

Slide 72 text

Классные инструменты • Какой классный gofmt; • Насколько легко запускать горутины; • Как круто придумали defer; • Очень удобные каналы; • Компактная сборка в один бинарь; • и т. д.; • и т. п.. Это тезисы про развлечения разработчика, но нет речи о пользе для бизнеса. 72

Slide 73

Slide 73 text

Один из основных тезисов в пользу Go СКОРОСТЬ 73

Slide 74

Slide 74 text

Производительност ь программы 1. Математика; 2. Работа с изображениями и видео; 3. Конкурентность (горутины); 4. Доступ к низкому уровню; 5. Быстрая сборка. Есть не только в Go. 74

Slide 75

Slide 75 text

Новая библиотека.. ‣ new video processing lib ‣ look inside ‣ ffmpeg 75

Slide 76

Slide 76 text

Более того 76

Slide 77

Slide 77 text

Нет задачи писать быструю программу. Обычно нужно писать новые фичи быстро. 77

Slide 78

Slide 78 text

Вспоминаем недавнее разбирательство по Clean Code Также есть задача писать поддерживаемый и понятный код 78

Slide 79

Slide 79 text

Вычислительные ресурсы — не основная трата бизнеса 1. Зарплата сотрудникам; 2. Реклама; 3. Рабочие ноутбуки и мониторы; 4. Уборка в офисе; 5. Печенье + кофе и чай; 6. …; 7. Серверы. Недавно мы ”просто” докидывали одному сервису ещё 300 CPU и 600 Gb RAM. 79

Slide 80

Slide 80 text

Технологии! 80

Slide 81

Slide 81 text

Надежно и быстро! gRPC + Protobuf 81

Slide 82

Slide 82 text

Аргумент за Go из-за gRPC уровня ”JSON для JavaScript и только”. •Реализация протокола, и не менее удобная, есть в других языках. Как-то же они общаются с Go сервисами. •К сожалению, эта технология и не близко к притеснению HTTP + REST. gRPC и Protobuf есть не только в Go 82

Slide 83

Slide 83 text

• Python слишком гибкий, а Go ограничивает разработчика, тем самым упрощая код. А все эти ваши декораторы, переопределения, наследования, метаклассы - это плохо; • Go быстро компилируется. А Python вообще компилировать не надо, как вам такое? • Возможность собрать команду. (??) И тут же: Прежде всего, начнем с очевидного: разработчиков Go не так много по сравнению со старыми языками, такими как C++ и Java. А собрать команду питонистов возможности нет? • Сильная экосистема: инструменты для работы с СУБД, планировщиками задач, шаблонизаторы, и тд. Конечно, не такая сильная, как у Java, Python или Node, но он вполне крепкая. Вот у Rust и Elixir похуже. Попытки найти ответы в статьях не приводят к пониманию В статьях Go vs Python я встречал тезисы, которые не смог переварить: Why We Switched from Python to Go By Thierry Schellenbach 83

Slide 84

Slide 84 text

Важно: ”простой” не значит ”лёгкий”. easy vs simple 84

Slide 85

Slide 85 text

Go требует от разработчика самостоятельную реализацию многих вещей. Go простой 85

Slide 86

Slide 86 text

Python позволяет быстро и легко начать, а с внутрянкой разобраться потом. Python лёгкий 86

Slide 87

Slide 87 text

Порог входа Для входа в Go разработку нужно быть знакомым с программированием: • Следовать строгим правилам; • понимать указатели и не только; На старте в Python можно городить всё, что угодно, и учиться на этом. 87

Slide 88

Slide 88 text

88

Slide 89

Slide 89 text

Если вы ещё ”зелёный” 1. Python комьюнити уже огромное, есть куда вливаться; 2. Невероятный объем готовых библиотек и ответов на StackOverflow; 3. Простой синтаксис; 4. Ветвистый путь — множество направлений (не только веб). 89

Slide 90

Slide 90 text

Что по вакансиям 90

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

Go действительно нужен 92

Slide 93

Slide 93 text

Когда Go реально нужен в продукте Потребности разработчика. У разработчика есть потребности. Хочется пощупать что-то новое, кто-то просто хочет ”написать что-нибудь на гошке”. Если дать сотруднику такое развлечение, ты особо не потеряешь в скорости реализации фичи, зато сможешь закрыть хотелки ребят. 93

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

Всё! Почему мне не нравится Go Краткий экскурс в Go 95

Slide 96

Slide 96 text

Сурен Хоренян Руководитель команды разработки, преподаватель, open-source contributor. Все-все мои ссылки: https://taplink.cc/mahenzon Телеграм канал: https://t.me/Khorenyan