5 найпоширеніших міфів про Golang: розвінчуємо на практиці

Go, або ж Golang — мова програмування, яка ввірвалася в IT-тусовку, як ковток свіжого повітря. І хоч «народилася» вона в стінах Google ще у далекому 2009 році (уявляєте, скільки води утекло з тих пір?), по-справжньому розквітла Go вже в наші дні. В Україні та всьому світі Go стає все популярнішим, і девелопери різного рівня поглядають на нього з цікавістю. Інколи навіть Си-шники :).

Від стартапів-вискочок до солідних корпорацій, Go обирають для проєктів різного калібру. Але, як то кажуть, «не все те золото, що блищить», і навколо Go вже встигли нарости міфи, як гриби після дощу. Дехто каже, що Go «занадто простий» для чогось серйознішого за «Hello, World!», інші плюються від обробки помилок, як від гіркої редьки. А ще є ті, хто впевнений, що Go — це «не-ООП», динозавр, і взагалі «що там ті дженерики, сльози одні!».

На DOU, де українські айтішники збираються на «поговорити за життя» (тобто, за технології!), треба розібратися з цими міфами по-чесному, без упереджень. Бо хто знає, може, Go це не такий вже й страшний звір, як його малюють? У цій статті ми беремося за сміливу справу — розвінчуємо «5 найпоширеніших страшилок» про Go, спираючись на досвід реальної розробки та суху (ну, майже) практику.

Але спочатку давайте познайомимось. Я Євгеній Бабіч, консультант GlobalLogic з 2020. Я той самий старий, сивий (не жарт) динозавр, який пам’ятає мейнфрейми. Думаю, мало хто навіть зустрічав назви таких мов як PL/2, Modula2, Clarion та інші. Звісно, сучасні мови мене не оминули (C, PHP, JavaScript, навіть маю трошки досвіду з Rust). За 28 років в IT довелося працювати як з «залізом» на рівні firmware, так і з дуже різноманітними мовами розробки, стеками та напрямками, від фінтех та блокчейн, адміністрування та оптимізації БД до «статичних» Web сторінок ;) Але останні 9 років я «закоханий» у Go, навіть інколи викладаю для команд GlobalLogic.

Тож, будь ласка, пристебніть ремені, починаємо!

Міф 1. Go занадто простий для складних проєктів

Є думка, що Go — це мова «лише для джунів», бо в ній немає складних конструкцій, а інколи код виглядає так, ніби його писав студент першого курсу після трьох чашок кави. Але чи справді простота — це погано?

Простота як суперсила

Go був створений з принципом KISS (Keep It Simple, Stupid), і це не означає «пиши код як попало», а навпаки — уникай зайвої складності. І ось чому це круто:

Найбільша технічна конфа ТУТ!🤌

  • Легкість у вивченні. Новачки можуть швидко увійти в курс справи й не витрачати роки на вивчення магії чорних дженериків.
  • Чистий та зрозумілий код. Ти відкриваєш код через пів року і не питаєш себе: «Хто це писав, і чому він ненавидить людей?»
  • Стабільність та масштабованість. Простий код означає менше багів, а менше багів означає більш спокійні нерви (принаймні теоретично).

А ще в спільноті Go немає жодних суперечок щодо форматування, бо є gofmt, який все зробить за тебе.

Реальні приклади крутих проєктів на Go

Якщо Go був би лише для «простих» задач, то чому його використовують для таких монстрів?

  • Kubernetes — «оркестр» контейнерів, що керує інфраструктурою крутіше за будь-якого сисадміна.
  • Docker — улюблений інструмент розробників, що рятує від «на моєму комп’ютері працює».
  • Prometheus — бог моніторингу, що знає про всі проблеми ще до тебе.
  • etcd — розподілена key-value база даних для тих, хто хоче швидко та надійно.
  • Terraform — магічний скрипт для керування інфраструктурою без сліз.
  • CockroachDB — SQL-база, яка виживе навіть після ядерної війни (майже).

І це приклади лише того, що «на слуху».

З «величезних» та дуже відомих: Google, Uber, Netflix, Twitch, Dropbox, Paypal, Cloudflare, SoundCloud використовують сервіси, розроблені на Go.

Але Go не лише для гіків у Кремнієвій долині. В Україні його теж полюбили. Його активно юзають у фінтеху, SaaS-рішеннях і в усьому, що має працювати швидко та надійно. Кажуть, що навіть кілька банківських сервісів на Go працюють (і це не жарт). З реального досвіду скажу, що багато іноземних компаній замовляє міграцію на Go сервісів, які були розроблені на NodeJS та Java. Тут дуже проста аргументація — гроші. Володіння пулом мікросервісів на Go значно дешевше за той же пул на NodeJS або Java. Як по використанню пам’яті, CPU, так і по підтримці. Uptime сервісів на Go може складати роки, бо немає витоків по пам’яті.

Але повернемось до теми. Go — це не просто. Go — це розумно. Його мінімалізм не обмежує, а допомагає. Kubernetes, Docker, CockroachDB та інші доводять, що на ньому можна будувати масштабні сервіси, і вони не впадуть, якщо тільки ти сам не видалиш продакшн-базу випадково.

Тож наступного разу, коли хтось скаже, що Go «занадто простий», просто покажіть йому код Kubernetes і запитайте: «Ну і що, ще досі думаєш, що це для початківців?»

Міф 2. Обробка помилок в Go надмірно багатослівна та дратує

Якщо ти писав на Go понад п’ять хвилин, то вже напевно зустрічав класичний if err != nil. І, можливо, твоє перше враження було щось типу: «Що це за ритуал повторення? Чому я маю писати це знову і знову?»

Дехто каже, що обробка помилок у Go схожа на дежавю: відкриваєш код і думаєш, що вже бачив це раніше... і ще раніше... і ще тисячу разів. Але чи справді це так погано? Так і ні одночасно. В пропозиціях до змін у мові (а їх було багато, бо це «жива» мова, з обіцянкою зворотної сумісності) була пропозиція заміни конструкції на функцію check(error) з автоматичним return, якщо error != nil. Поки що цього немає, можливо, радикально це зміниться у версії 2.

Чому ж так багато if err != nil?

Go проповідує підхід явного контролю помилок. Інші мови намагаються приховати їх десь у глибинах винятків або магічних try-catch, а Go каже: «Ні, друже, будь чесним — твій код може зламатися, і ти маєш це врахувати». Ось кілька причин, чому це не така вже й погана ідея:

  • Явна обробка помилок. Ти одразу бачиш, де може щось піти не так.
  • Менше несподіваних винятків. Немає раптових Exception чи інших неприємних сюрпризів.
  • Прогнозованість коду. Легко простежити, як працює обробка помилок, без зайвої магії.

Але ж це виглядає занадто громіздким!

Так, на перший погляд, код може виглядати надмірно багатослівним. Але є рішення:

  • Функції-помічники. Якщо в тебе багато однакових перевірок, варто винести їх у окрему функцію.
  • Обгортання помилок. Використання fmt.Errorf або errors.Join допомагає робити помилки більш інформативними.
  • Патерн early return. Замість вкладених if краще повертати помилку одразу, зменшуючи рівень відступів.

Приклади читабельної обробки помилок

func loadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("не вдалося прочитати файл %s: %w", path, err)
    }
    
    var config Config
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("невірний формат JSON у %s: %w", path, err)
    }
    
    return &config, nil
}

Цей підхід робить код зрозумілим: кожен if — це конкретна перевірка, без зайвого «магічного» приховування.

Ох вже ці панічні атаки!!!

Я вже бачу в комментах «а як же паніка?!»...

Так, мова Go все ж має виключення (exception) у вигляді panic. Бо інколи, як рантайм, так і пакети, не можуть аж ніяк повернути помилку. Для «сторонніх» пакетів це моветон. Але і тут все можна вирішити за допомогою «відкладання / defer» (яке дуже рятує, зокрема при обробці помилок).

Якщо є «небезпечний» код, обгорніть його в функцію-хелпер, та використайте схожий патерн:

package main

import (
    "errors"
    "fmt"
    "log"
)

func logMyError(err ...any) {
    log.Println(err...)
}
// Функція "де може" виникнути паніка
func myDangerousCall(in any) error {
    panic(fmt.Sprintf("I'm panic :%v", in))
}
// Якась функія, потенційно небезпечна
func callMyNextUnsafeCode() error {
    return nil
}
// Хелпер для небезпечного коду
func mySafetyHelper(mySomeDangerousParams any) (retErr error) {
    defer func() {
        if err := recover(); err != nil {
            // пишемо паніку в лог для подальшого "розслідування"
            logMyError("Panic recovered:", err)
            // повертаємо паніку що перехопили
            // як помилку з нашого "хелпера"
            retErr = errors.Join(retErr, fmt.Errorf("%v", err))
        }
    }()

    // робимо щось вкрай небезпечне
    if err := myDangerousCall(mySomeDangerousParams); err != nil {
        // якщо немає паніки, але є помилка то повертаємо помилку,
        // але якщо вона (паніка) виникла,
        // то буде перехоплена в defer ... recover()
        return err
    }
    // якщо все OK продовжуємо працювати,
    // але в разі паніки ми сюди вже не дойдемо...
    retErr = callMyNextUnsafeCode()
    return retErr
}

func main() {
    if err := mySafetyHelper("some dangerous params"); err != nil {
        log.Println("Error:", err)
    }
    // Отримуємо щось типу:
    // 2025/03/04 13:02:58 Panic recovered: I'm panic :some dangerous params
    // 2025/03/04 13:02:58 Error: I'm panic :some dangerous params
}

І так, я знаю що наведений приклад має named return, який використовувати не рекомендовано. Пропоную знайти вирішення дилеми зі зміною значень, що повертаються, безпосередньо у defer.

А можливо хтось навіть запитає: «А чому не спростити до if retErr = recover(); retErr != nil {», нащо нам зайва змінна та retErr = errors.Join(retErr, fmt.Errorf("%v«, err)) ?! Якщо це питання виникло, пропоную подумати.

Обробка помилок у Go — це не баг, а фіча. Вона змушує тебе бути відповідальним за свій код, а не сподіватися, що «якось воно буде». Так, if err != nil — це свого роду мантра, але саме вона допомагає писати стабільний код, який не вибухне у продакшені через неочікуваний try-catch з нульовою інформацією про те, що ж пішло не так.

Міф 3. Дженерики в Go відсутні

Цей міф давно спростовано. Не буду забирати ваш час.

Міф 3. Дженерики в Go не потрібні або надто складні для використання

Раніше багато хто критикував Go за відсутність дженериків, бо це призводило до дублювання коду та меншої продуктивності розробки. Але тепер, коли дженерики з’явилися, починаючи з версії Go 1.18, новий міф звучить так: «Дженерики — це або зайве ускладнення, або взагалі непотрібні».

Чи справді Go не потребував дженериків?

Go успішно розвивався понад десять років без них, і це було можливим завдяки:

  • Інтерфейсам — вони дозволяють створювати узагальнені рішення без дженериків.
  • Композиції — замість спадкування Go використовує складання функцій і структур.

Приклад складання:

package main

import "fmt"
// Людина (громадянин з ІПН)
type Person struct {
    name string
    inn  string // Податковий код (ІПН)
}
// Даємо повну інформацію про громадянина (або громадянку)
func (p *Person) Info() string {
    return p.name + ",ІНН: " + p.inn
}
// Маємо структуру даних про працевлаштування
type Job struct {
    position   string
    department string
}
// Даємо повну інформацію про позицію та департамент
func (p *Job) Info() string {
    return p.position + " в " + p.department
}
// Комбінуємо дані
type Employee struct {
    Person
    Job
}
// "Підмінюємо" фунцію нащадка спадків двох предків
func (p *Employee) Info() string {
    // Отримуємо інформацію з предків та комбінуємо
    return p.Job.Info() + " " + p.Person.Info()
}

func main() {
    emp := Employee{
        Person: Person{name: "Остап Бендер", inn: "1234567890"},
        Job:    Job{position: "Розробник", department: "IT"},
    }
    fmt.Println(emp.Info()) 
    // Маємо вивід:
    // "Розробник в IT Остап Бендер,ІНН: 1234567890"
    // Все ще маємо доступ до предків:
    fmt.Println(emp.Person.Info()) //Остап Бендер,ІНН: 1234567890
    fmt.Println(emp.Job.Info()) //Розробник в IT
}

Мені здається, чи це нагадує трохи ООП ?

Коли дженерики справді корисні?

Є випадки, коли дженерики значно покращують код:

  • Алгоритми та структури даних — наприклад, реалізація стеків, списків або карт.
  • Робота з колекціями — уникнення повторюваного коду для масивів чи мап.

Приклад корисного використання:

package memcache

import "time"
// Приклад інтерфейсу з використанням дженеріків
type Cache[T any] interface {
    Set(key string, value T)
    Get(key string) (T, bool)
}
// Структура, яка має дженерік в якості поля
type data[T any] struct {
    value      T
    expiration int64
}
// Структура, яка має всередині іншу структуру з дженериком в якості значення для мапи.
type CacheImpl[T any] struct {
    defaultExpiration time.Duration
    cache             map[string]data[T]
}
// Приклад ініціалізації
func NewCache[T any](defaultExpiration time.Duration) *CacheImpl[T] {
    return &CacheImpl[T]{
        defaultExpiration: defaultExpiration,
        cache:             make(map[string]data[T]),
    }
}
// Приклад вхідного дженерік параметра
func (c *CacheImpl[T]) Set(key string, value T) {
    c.cache[key] = data[T]{
        value: value, 
        expiration: time.Now().Add(c.defaultExpiration).Unix(),
        }
}
// Приклад повертання дженеріка
func (c *CacheImpl[T]) Get(key string) (T, bool) {
    var dummy T
    if data, ok := c.cache[key]; ok {
        if data.expiration <= time.Now().Unix() {
            delete(c.cache, key)
            return dummy, false
        }
        return data.value, ok
    }
    return dummy, false
}

Звісно, використовувати цей код «як є» я не рекомендую, тому що тут не вистачає «запобіжників» гонки даних і багатьох корисних речей. Але тут маємо приклади:

  • Об’явлення інтерфейсу з дженериком.
  • Використання структури з дженериком всередині іншої.
  • Як ініціювати та повертати дженерики.

Тож якщо розробник тільки починає свій шлях GO, та використання дженериків може здатися «складним». Цей приклад можна використати як патерн, щоб розібратися, як це працює.

Як на мене, все виглядає не складно та досить прозоро. Але якщо ви дійсно бажаєте отримати досвід з дженериками, зазирніть «під капот» стандартних бібліотек, наприклад, пакету slices.

Чи варто використовувати їх всюди?

Ні! Багато кодових баз чудово обходяться без дженериків, і їхнє використання має бути виправданим. Дженерики корисні там, де вони спрощують код, а не ускладнюють його без потреби.

Дженерики в Go — це потужний, але не обов’язковий інструмент. Якщо твій код стає простішим завдяки їм — використовуй. Якщо ні — старий добрий Go без них усе ще працює чудово.

Міф 4. Go не підходить для об’єктноорієнтованого програмування (ООП)

Коли в далекому 2016 я тільки почав знайомитися з Go, мене здивували 2 речі:

  1. можливість повернути з функції багато результатів виконання;
  2. майже повна відсутність згадувань про ООП в сучасній мові програмування.

Але коли вже я ближче познайомився з цією чудовою мовою, я зрозумів багато неочевидних з першого погляду речей. Go реалізує базову ООП-подібну функціональність через композицію та інтерфейси. Композиція та інтерфейси дають більше гнучкості та спрощують код (а це головна мета мови Go).

Інтерфейси та їхня композиція

  • Відповідність інтерфейсу. В мові Go немає потреби явним чином вказувати інтерфейс, який ви імплементуєте. Достатньо відповідати патерну інтерфейсу.
  • Композиція структур. В Go можна вкладати структури одна в одну. Методи, імплементовані у структурі, що вкладена, будуть «успадковані» новою структурою.
  • Композиція інтерфейсів. В Go можна вкладати інтерфейси. Методи, задекларовані в інтерфейсах, будуть складатися, розширюючи функціонал нового інтерфейса.
  • Композиція структур та інтерфейсів. Якщо вам не подобається, що достатньо лише того, що метод відповідає інтерфейсу, щоб використати цю структуру як інтерфейс, то це можна зробити явно. Просто «вклавши» інтерфейс в структуру. Але важливо в даному випадку імплементувати відповідні до інтерфейсу методи. Інакше не імплементовані методи будуть «віртуальними» та їх виклик призведе по паніки.

Дуже простий приклад:

package main

import "fmt"

type Speaker interface {
    Speak()
}

type Walker interface {
    Walk()
}

type Animal struct {
    Name string
}

func (a Animal) Speak() {
    fmt.Println(a.Name, "робить звук")
}

type Dog struct {
    Animal
}

// заміщення функції "нащадком"
func (d Dog) Speak() {
    fmt.Println(d.Name, "гавкає")
}

// Імплементація інтерфейсу Walker
func (d Dog) Walk() {
    fmt.Println(d.Name, "гуляє")
}

// Має "віртуальний" метод Walk() завдяки вкладеному інтерфейсу
type Cat struct {
    Animal
    Walker // Вкладений інтерфейс
}
// Приклад використання вхідним параметром інтерфейсу
func doWalk(in Walker) {
    in.Walk()
}

func doSpeak(in Speaker) {
    in.Speak()
}

func main() {
    dog := Dog{Animal: Animal{"Барсик"}}
    var spk Speaker = dog
    // Викликаємо метод інтерфейсу Speaker
    spk.Speak() // Барсик гавкає
    // Передаємо Dog як інтерфейс, бо він відповідає Walker
    doWalk(dog) // Барсик гуляє
    // Виклик parent метода
    dog.Animal.Speak() // Барсик робить звук
    
    // Cat не має імплементації жодного метода
    сat := Cat{Animal: Animal{"Мурка"}}
    //(буде викликано метод parent Animal)
    doSpeak(сat) // Мурка робить звук 
    
    // Виклик віртуального метода призведе до паніки. Не робіть так :)
    // doWalk(сat)  

    // Але ми можемо підмінити не реалізовану функцію
    сat.Walker = dog
    // Виклик буде не сat, а dog
    doWalk(сat) //Барсик гуляє
}
  • Композиція інтерфейсів. Можна комбінувати інтерфейси, що дозволяє будувати гнучкі рішення.
package somthing
type (
    StringReader interface {
        Read() string
    }
    StringWriter interface {
        Write(string) error
    }
    StringReaderWriter interface {
        StringReader
        StringWriter
    }
)
  • Віртуальні методи без реалізації. Якщо інтерфейс вкладено у структуру, можна створювати моки без реалізації всіх методів — це корисно для тестування, коли ви використовуєте лише частину методів. Особливо для імпортованих сторонніх пакетів.

Go підтримує базовий ООП, просто робить це зовсім інакше. Чого зовсім немає з «повноцінного» ООП — це overload. Чого нема, того нема. Але це зроблено лише для спрощення мови та сприйняття коду (про що я вже казав).

Але якщо вам дійсно потрібна дуже універсальна реалізація (логування, вивід даних тощо), то є патерн: func (...any). Як приклад:

type Logger interface {
    Log(...any) // будь-яка кількість вхідних параметрів будь-якого типу даних
}

Міф 5: В Go поганий менеджмент залежностей

Колись керування залежностями в Go було схоже на дикий захід: все зберігалося в GOPATH, а оновлення бібліотек могло легко зламати проєкт. Багато розробників пам’ятають той хаос, коли потрібно було вручну копіювати пакети або користуватися сторонніми інструментами. Але ті часи минули.

Сучасне рішення: Go Modules

Go отримав сучасну систему керування залежностями — Go Modules, яка розвʼязала більшість проблем, пов’язаних із версіонуванням та ізоляцією пакунків. Тепер керування залежностями стало простим, передбачуваним і ефективним.

Переваги Go Modules:

  • Версіонування залежностей — можна точно контролювати, яка версія кожної бібліотеки використовується.
  • Відтворення збірок — легко отримати ту саму робочу конфігурацію через go.mod та go.sum.
  • Інтеграція з екосистемою Go — більше не потрібно турбуватися про GOPATH або сторонні менеджери залежностей.

Як легко працювати з Go Modules

Створення нового проєкту

Раніше потрібно було створювати новий проєкт виключно в ~/go/src/, але з Go Modules це більше необов’язково! Тепер проєкт можна розміщувати в будь-якому каталозі:

mkdir myproject && cd myproject

Ініціалізація Go Modules:

go mod init github.com/username/myproject

Додавання та очищення залежностей

Go дозволяє легко додавати нові залежності. Достатньо імпортувати бібліотеку в код і виконати go mod tidy, щоб вона автоматично додалася до go.mod:

go get github.com/gin-gonic/gin
go mod tidy

Команда go mod tidy також видаляє невикористані залежності, що допомагає підтримувати чистоту проєкту.

Оновлення залежностей

Оновити всі залежності до останніх патч-версій дуже просто:

go get -u ./...

Використання вендорингу

Щоб закріпити всі залежності у папці vendor, достатньо виконати:

go mod vendor

Якщо існує думка, що керування залежностями в Go досі складне, варто переглянути її. GOPATH залишився в минулому, а Go Modules зробили роботу з залежностями простою і зручною. Go — це про мінімалізм та ефективність, і тепер це стосується й менеджменту залежностей!

Епілог

Ми розглянули п’ять поширених міфів про Go та розвінчали їх, доводячи, що ця мова — не просто експериментальний інструмент, а зріла, потужна та універсальна технологія. Вона ефективно справляється з реальними викликами розробки, забезпечуючи високу продуктивність, зручний менеджмент залежностей, просту та надійну обробку помилок, а також гнучку власну модель ООП (або власне бачення, як це зробити просто).

Go може стати чудовим вибором для широкого спектра задач — від веброзробки та мікросервісів до хмарних технологій і високонавантажених систем. Однак найкращий спосіб сформувати власну думку — це випробувати Go на практиці.

Приєднуйтесь до дискусії!

Маєте досвід роботи з Go? Можливо, ваші враження відрізняються, або ви стикалися з іншими міфами? Поділіться своїми думками в коментарях — обговорення допомагають розвивати спільноту та розширювати кругозір!

Що робити далі?

Якщо Go вас зацікавила, ось кілька корисних ресурсів для подальшого вивчення:

Спробуйте написати невеликий проєкт на Go, наприклад, простий HTTP-сервер або CLI-утиліту. Це допоможе особисто переконатися, наскільки зручна ця мова!

Go — це не просто ще одна технологія, а ефективний інструмент, який варто додати до свого арсеналу. Відкиньте міфи та відкрийте для себе нові можливості! 🚀

👍ПодобаєтьсяСподобалось22
До обраногоВ обраному9
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Дуже гарний список речей, які дуже бісять у цій мові, але попри зусилля автора, їх «розвінчування» навряд чи перекреслить болючий досвід.

А можете розписати детальніше про болючий досвід з Go? Хоча б три-чотири пункти.

Чомусь менша кількість фіч у Go одразу переходить в

Чистий та зрозумілий код

На практиці ж, це швидше вивчення мови, і довший код. Код буде скоріш «знайомим», а не простим. (Посилання на Simple made easy)

Завдяки шановному @Oleksandr Redko виправив трохи «очепятки». Прошу вибачення за помилки. Як Харків’янин я все життя розмовляв російскою... аж до лютого 2022.

PL/2??? Чи може PL/1? напевно з OS/2 сплутали. А Rexx зачепили?

Ні, був такий різновид як PL/2 Disp — розширення мови для роботи зі специфічними графічними станціями та специфічних операцій типу motocopy (копіювання з котушки на котушку магнітної стрічки).
Rexx на жаль пройшов повз, але ось від чого я дійсно був у захваті так це від JPI Modula-2. Її 9-ти прохідний компілятор забезпечував такий рівень оптимізації, що можна було використовувати отримані бінарики у стародавніх ПЗУ без додаткової оптимізації/обробки. І чудова можливість компіляції в асемблер для дуже великої кількості контролерів та процесорів.
Там навіть був свій модуль планувальника, що дозволяв виконувати завдання конкурентно, дуже схоже на те, як це реалізовано в Go.

Дякую, почитав про TopSpeed JPI — вражає. Але я якось пропустив цей продукт того часу.

Міф 6: Go подобається тим, хто не осилив на достатньому рівні нормальні мови. Change my mind.

Сподіваюсь це сарказм? Бо вмене купа знайомих що з однаковим задоволенням працюють використовуючи Erlang, Rust та Go. Залежно від сподобань замовника та загальної архетектури проектів.

Рочків 5 тому тут пророчили що всі мови будуть непотрібні, тіки го і залишиться. Щось із того часу популярність сильно не підросла. Навіть женеріки не допомогли.

Дженерики испортили рост популярности Go

Я не можу сказати дженерики, що зупинили або значно сповільнили ріст популярності мови Go. Але в деяких випадках трохи зросла складність читання чужого коду. А як на мене легкість читання та спийняття не власного коду є найпершою перевагою цієї мови.
Це звісно моє суб’єктивне враження.

Чисто суб’єктивні враження.
Коли бачу цей синтаксис то кров очами йде.

Зразу починаються флешбеки з КПІшних часів, коли робив лаби на чистому С.

Але таке враження що всі навколо прям фанатіють від нього.
Якось так....

Якщо тільки синтакс брати, то аля сішний напевно один з найбільш розповсюджених в тій чи іншій формі серед інших мов. Але golang чомусь іноді тригерить саме pascal аналогії (var, :=, типи з іншого боку, модулі, ...).

Раніше потрібно було створювати новий проєкт виключно в ~/go/src/, але з Go Modules це більше необов’язково! Тепер проєкт можна розміщувати в будь-якому каталозі:

Ось це мені дико не сподобалося, коли я вперше побачив Go на проєкті. Це крінж. Дуже добре, якщо такого більше немає.

Насправді, мені не цікавий Go, я просто не виключаю, що доведеться колись його вивчити заради проєкту, який буде цікавим та корисним для мене.

зашел почитать срач в комментах и не был разочарован ))

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

Використовую дженеріки в бібліотечних пакетах. По типу github.com/...​oogle/go-github/pull/3355

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

Є (наче) два відносно сучасні підходи щодо помилок — через exceptions, і через сум-типи. Мастодонти індустрії, які дизайнили го, вирішили проігнорувати здобутки останньої половини століття, і використати бест практіс прямо з 70х.

Вийшла комбінація недоліків обох підходів:
1. Мова ніяк не енфорсить обробку помилок. Ніхто не забороняє витягти значення, проігнорувавши або необробивши err, і піти далі по Ok-сценарію, коли все зовсім не Ok. З сум-типами, не обробивши помилку, ок-варіант не отримаєш. Якщо ексепшн кинувся, далі код теж не піде.
2. При цьому, потрібно руками писати if err != nil на кожен чих. Це не ергономічно, як не крути — забруднює код, замилює око, створює когнітивне навантаження, відволікає від того що функція має робити в першу чергу. І ексепшени, і сум-типи тут виграють всуху.

Аргумент про «явність контролю помилок» і «треба пам’ятати що тут може бути помилка» все одно нівелюється тим, що в 95% випадків помилки просто прокидуються нагору, іноді з додатковим контекстом.

Ну і це вже не кажучи, про те, навіщо повертати з функції N+M байтів, коли можна легко повертати max(N,M) байтів. Не стверджую, що це якось критично, але в око завжди кидається.

Тобто, якщо ввести в мову сум-тип Result, пару методів до нього і оператор для error propagation, стане тільки краще. Задекларовані цілі виконуються — явна обробка, немає несподіваних викликів, control flow прогнозований — при цьому обробка помилок енфорситься компілятором, код чистіше, очам приємніше, байти зекономлені.

Створи PR на той Result, а раптом і приймуть, раз це така кілер-фіча.

Ваш сарказм недоречний. В гошному репозиторії пропозалів на сум-типи/резалт/кращий_еррорхендлінг за останні 15 років хоч греблю гати. Всі closed.

Ну тому і closed, бо не реально зробити кращим те, шо ідеальне.

error якщо що інтерфейс. Не тип, інтерфейс. І це вже не кажучі про те що навіть із «стандартним» можна дуже багато що, прописуючи як «константи помилок» так роблячи цілі группи по типам.
Вся філософія Го як у Lego — зроби все що хочеш сам.

А за

помилки просто прокидуються нагору, іноді з додатковим контекстом

ви знов таки підходите до Го як до іншої мови яку ви вивчили раніше. Потрібно try-catch так зробіть, panic-recover вже давно відносно е.
Взагалі такий підхід заставляе розробника ДУМАТИ так як в 80% випадків помилку можна і потрібно обробляти «на місці» а не тягнути кудись. Навіть візуально якщо бачиш змію із десятків err != nil в одному методі значить ти щось робиш не так. Додаючи до помилки «контекст» ви тільки гірше робите, так как тепер цю помилку можна розпізнати лише контекстно руками, а не на рівні коду, оброблюючи як конкретну помилку чи перехоплюючи по группі

Це не python де «простота» зроблена для тупих розробників, можна робити складні речі но за замовчуванням очікується мінімальне розуміння.
В Го ти повинен знати що ти робиш і робити це просто, якщо починаються динозаври то явний знак зупинитись і подумати. І робити це оптимально — все що ви описали з «чистим кодом» кладеться потім на кліента із-за чого ми зараз маемо калькулятори по 200Мб які ще й лагають і невірно можуть порахувати прості арифметичні операціі (сінус, корінь) де задіюеться плаваюча точка (або взагалі проста арифметика с цілими числами) якраз тому що все так і починалось

Потрібно try-catch так зробіть, panic-recover вже давно відносно е.

Макросів, щоб робити власні синтаксичні конструкції теж не завезли.

в 80% випадків помилку можна і потрібно обробляти «на місці» а не тягнути кудись.

Я не знаю, що тут сказати, крім того, що це не так. По хорошому, можна було б залізти в сорці згаданих гошних продуктів і пораховувати, в якому відсотку випадків помилка просто прокидується, але мені лінь.

все що ви описали з «чистим кодом» кладеться потім на кліента із-за чого ми зараз маемо калькулятори по 200Мб які ще й лагають і невірно можуть порахувати прості арифметичні операціі (сінус, корінь) де задіюеться плаваюча точка (або взагалі проста арифметика с цілими числами) якраз тому що все так і починалось

Ви сперечаєтесь з солом’яним опудалом у вигляді уявного джавіста. Я не джавіст, не співробітник Oracle, не адепт дядьки Боба. Я також усвідомлюю, що у світі існує більше мов і підходів, ніж джава і го.

якщо починаються динозаври

Динозаври динозаврам рознь, Zero Cost Abstractions існують. І дизайн мови впливає, наскільки легко і вигідно їх писати.

писав коментар, вийшло багато, тому оформив в якості топіку з приводу помилок dou.ua/forums/topic/53360
по тому що ви писали вище, таке враження що ви взагалі незнайомі з помилками в Go, або просто пробіглись і не розбирались далі прикладів з інтернету. Тому як дуже дивні речі пишете, особливо стосовно сум-типів тому як «обов’язковість» обробки робиться через panic і не обробивши її програма завершиться видав помилку.

а за макроси — багато добра вони дозволяють робити в С, але ще більше відстріляних колін породжують. Так що нехай синтаксис буде лише і лише справою компілятора. А якщо так хочеться то можна зібрати свою версію компілятора, всеж таки Го пишеться на самому собі і додати «що потрібно» не проблема.

Дякую Вам за більш розгорнуту статтю щодо обробки помилок. Я не став описувати обробку настільки детально, щоб не робити матеріал надто великим. Якщо Ви не проти, я хотів би додати посилання на Вашу статтю до цього матеріалу.

У тому ж rust помилки дуже схоже обробляються, але значно зручніше через використання типу контейнера, а також присутній цукор який через знак питання дозволяє уникнути перевірки помилок руками одразу ж повертаючи помилку результат функції. Це говорить про те що rust створювався з розумом, а в го нажаль окрім планувальника ніяких прикольних рішень не бачив...

Мастодонти індустрії, які дизайнили го, вирішили проігнорувати здобутки останньої половини століття, і використати бест практіс прямо з 70х.

В Гуглі працює купа амбітних молодих спеціалістів, які замість того, щоб вивчати досвід інших розробників, як справжні піддослідні панів Данінга та Крюґєра сказали, що вони настільки круті, що можуть перевершити все, що тільки можна було вигадати до них. Це прямо якийсь тренд. Спостерігаю таке не вперше.

Є (наче) два відносно сучасні підходи щодо помилок — через exceptions, і через сум-типи. Мастодонти індустрії, які дизайнили го, вирішили проігнорувати здобутки останньої половини століття, і використати бест практіс прямо з 70х.

Як завжди оці голословні наброси абсолютно не розібравшись в мат часті самого Го та дизайнерських рішень, які приймалися Го командою.

Го дизайнився в Гуглі для заміни Сі. Конкретно для роботи з великими об’ємами тексту, бо більшість такого коду в Гуглі була на Сі. Більше ні для чого. Основний фокус мови був memory safety та вбудований GС, ефективні структури даних для роботи зі стрічками, interop з СІ, простота та швидкість роботи. Сішний код надалі просто транспайлився в Го код, а потім руками поправляли складні місця типу використання юніонів, які категорично не захотіли добавляти в Го.

І тут наступає момент прозріння. В Сі також немає ексепшинів. В Сі прийнято або встановлювати код помилки в глобальну змінну, а потім кожного разу після виклику функції перевіряти цю глобальну змінну, або, УВАГА, повертати код помилки з функції. Саме по цьому самому принципу і працює обробка помилок в Го.

Те, що го почали використовувати для куберів, докерів та, о святі небеса, написання всяких апі серверів, і народ страждає з обробкою помилок — ЦЕ НЕ ПРОБЛЕМА ДИЗАЙНУ МОВИ. Це проблема тільки того, хто вирішив використовувати таку мову там, де код обробки помилок займає більше ніж інша логіка.

З власного досвіду скажу, що Го дуже крутий для якихось супер простих, або супер складних задач, в особливості інфраструктурних. Щось посередині, типу зробити апішку чи робота з базою, і го перетворюється в кусок гівна.

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

вроді як з перших слів пан гастрафет (боже, тільки щойно вчитався, що він не гефест) сказв, що дивують саме захисники, які вигадують переваги такого дизайну, замість сказати «ну так треба було для задачі».

це якби гвідо сказав «відступи в пітоні будуть тільки пробілами, бо мені лівий мізинець в дитинстві собака відкусив», а за десять років євангєлісти пітона пояснювали, що «пробіли гнучкіші, і зручніші, і примушують глибше зануритись в код і взагалі дрібна моторика корисна і там насправді глибокий смисл»

В Го світі все приблизно так само. Що Робу Пайку збрело в голову, те і вважається каноном. І замість реальних дизайн гайдів мови, люди тиражують посилання на коменти Пайка під issues на гітхабі, де він пояснює, наприклад, чого i++ йому здається складними і того в мову таку фічу не завезли.

Як стейтмент, а не як експрешн.

го перетворюється в кусок гівна

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

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

Це щось типу культа. Он дженеріків не було, й вони казали що дженерики не потрібні, це великий плюс Go. Зʼявились дженерики, у когось внутрішній конфлікт й вони продовжили казати що це погане рішення, а багатьом пофігу що вони там казали раніше й тепер дженерики це круто. Просто мова Go крута. Методом дедукції, якщо щось зроблено в Go — це круто, якщо цього немає в Go — це лайно. Щоб щось стало крутим — його мають додати в Go. Твоя думка лайно, тому що її немає в документації Go.

Я буду дуже вдячний, якщо шановний автор прочитає текст, а не тільки коментарі. :)
Деякі речі не є зручними в мові Go і я з цим згоден, але те що через рік код залишається зрозумілим, на мій погляд і є головним.
За дуже багато років я мав досвід з великою кількістю мов. Інколи зараз я переглядаю код і мені доводиться дуже напружувати пам’ять, щоб згадати що малося на увазі саме в цьому фрагменті, до якого я не додав коментарі, бо «це ж очевидно! ».

Дякую, легко, корисно і цікаво читати. Пишу на го з якого перейшов з perl :) Забрав на канал.

Після Перлу все буде в радість)

як людина яка пєдаліла і на джаві і на гошкє можу сказати наступне

Go занадто простий для складних проєктів

для технічних проектів так, для складного бізнес домену/ентепрайзу, так, простий

Обробка помилок в Go надмірно багатослівна та дратує

пфффф... це не міф, це правда, це гівно з помилками зїдає дохєра часу інженеру, по факту шльопанні непотрібної хєрні

Дженерики в Go відсутні

вони є, їх додали бо не було вибору, і в них купа обмежень, того функціоналу шо є в джаві нереально досягнути

Дженерики в Go не потрібні або надто складні для використання

потрібні звичайно, але із за обмежень, так використання іхнє слкадне

Go не підходить для об’єктноорієнтованого програмування (ООП)

підходить, але знову ж, зі своєму обмеженнями, і до Джави в цьому сенсі дуже далеко

В Go поганий менеджмент залежностей

непоганий, але й не так шоб офігєнний, іноді гємор на сраку можна отримати знатний

Тож

Kubernetes

звісно не

ентепрайз

і не використвується Google та Microsoft як рішення для бізнесу. Ок.

Kubernetes звісно не ентрепрайз. Це очевидно для тих хто розробляв ентрепрайз системи.

ентрепрайзом зазвичай називають такє ПЗ яке безпосередньо реалізує бізнес логіку НЕ IT бізнеса. Додають ще слово — кривавий, бо створення і розвиток такого ПЗ дуже далекий від ідеалів інженерів.

що такє — кривавий ентрепрайз?
Чатгпт:
«Кривавий ентрепрайз» — це саркастичний або іронічний вираз, який часто використовують у технічному або бізнес-контексті для опису дуже складної, важко підтримуваної, застарілої або занадто бюрократизованої корпоративної (enterprise) системи. ...

Якщо Kubernetes — це бюрократизована корпоративна система — то так, він ентрепрайз.

... Зазвичай мається на увазі:
Неповоротка архітектура — монолітні додатки, важко масштабовані системи.
Старі технології — застаріле ПЗ, яке ніхто не хоче чіпати, бо «воно ще якось працює».
Костилі на костилях — вирішення проблем не через рефакторинг, а через хаотичні латки.
Великий технічний борг — усе зліплено роками без загального плану.
Низька якість коду — підтримувати такий код — це біль.
Культура страху та бюрократії — «не чіпай, бо зламається», «пропиши 20 заявок, щоб внести зміни»
(кінець цитати)

Хрестоматійним прикладом ентерпрайзу є
ERP системи.
en.wikipedia.org/...​erprise_resource_planning

Ніхто з
Kubernetes, Docker, Prometheus, etcd, Terraform, CockroachDB
і близько до ентерпрайз ПЗ не має відношення.

того функціоналу шо є в джаві нереально досягнути

Кожна мова програмування має свої плюси та мінуси. Є уніваерсальні, як Джава, Си. Є дуже високо спеціалізовані, як Pl/SQL або Cobol (в яких теж мав досвід). Go мабудь десь посередині, бо точно не для UI/UX, але кросс-платформена та орієнтована на мережевий стек.
Свічнутись з Джави на Go займе до тижня. Але навпаки займе місяці вивчення мови.
Саме для того ії розробляли, щоб була простою як 2 копійки. :)

Свічнутись з Джави на Go займе до тижня. Але навпаки займе місяці вивчення мови.

В мене дуже довго був «акцент» пiсля джави))

Це зрозуміло. Навики та звички залишаються з тобою на досить довгий час.

Сі по рівню універсальності мабуть навіть нижче Го, бо в останньому хоч мапи та дженерики є. В Сі навіть цього немає. Авжеж, можна обмазуватися (void *) та самописними імплементаціями мапи, але чи на довго тебе тоді хватить — хз.

таке враження що ви классичний «вкатун» який намагався використовувати іншу мову «як звик» і із-за цього щось погано пішло

для технічних проектів так, для складного бізнес домену/ентепрайзу, так, простий

Для бізнесу він ще кращій. купа готових бізнес-модулів для інтеграцій, прості тести з коробки та збірки під все. І головне він «простий» тому менше навантаження на тімліда для контролю, хуйню видно одразу

пфффф... це не міф, це правда, це гівно з помилками зїдає дохєра часу інженеру, по факту шльопанні непотрібної хєрні

якщо обробка помилок для вас «непотрібна херня» то питання явно не в оброці помилок. Якщо хочется «як звик» ловити «ексепшени» то можна дуже просто наговнокодити «перехват» і виходити через panic. В цілому ж така система навпаки дозволяе явно контролювати де що і як. І едине що можу сказати за помилки — не вистачае структурності як з контекстом, то що помилка це просто строка не завжди доречно і іноді вимагае робити обгортки (хоча порівняння по ерорам е з коробки)

вони є, їх додали бо не було вибору, і в них купа обмежень, того функціоналу шо є в джаві нереально досягнути

їх додали бо дуже сильно просили, хоча це і суперечило ідеології мови. Взагалі в Го просте ти пишеш руками, для складного пишеш генератор. Стандартний template+go:generate дозволяе будувати складні варіативні системи дуже просто і підтримувано.
З дженериками же ти по суті пишеш зайвий код, який ще при змінах потрібно пам’ятати і міняти (а зазвичай ті хто юзають дженерики не заморочуються типами і пхають any) із-за чого потім можуть виникнути проблеми, хоча «тести пройшло»

непоганий, але й не так шоб офігєнний, іноді гємор на сраку можна отримати знатний

Як? просто як можно отримати гемор? менеджмент залежностей в Го це найкраще що я зустрічав взагалі. І це я порінюю з C, C++, Ruby, Python, Java, JavaScript, Php.
Ні в якій іншій мові це не зроблено настільки просто, при цьому вариативно і на рівні компілятора.
Хочеш зовнішню — ось лінк, важливо лише щоб релізи лінкувались по стандартам гіт.
Хочеш внутрішню — вкажи шлях. Причому непідтримувану хуйню коли локальна ліба по за зоною бачення проекта неможливо зробити, компілятор не дозволить.
Хочеш зовнішню яка б лінкувалась локально — тримай хлопче.
Да і в цілому дуже круто.
— Приватність контролюется регістром, до цього звикаеш і це дуже зручно, відразу видно навіть візуально
— Неможливо наговнокодити запутанні зв’язки між пакетами, компілятор не дозволить

Взагалі в Го просте ти пишеш руками, для складного пишеш генератор.

Самый подходящий язык для генерации кода llm- ками и go:generate не нужен.
Минимум разнообразия конструкций делает код максимально однотипным и унифицированным.
А нейросетке пофиг на то сколько там if с err. Будущее: AI + go — 80 % человеков. А оставшиеся 20 % будут просто радоваться, что у них есть работа )).

Напоркуа LLM go, якщо вона може генерувати прямо байткод?

На следующем этапе так и будет. AI + компилятор — 99% человеков. 1% процент будет писать промпты и тестировать высокоуровнево.
p.s. Мы не лезем в машинный код, который генерирует компилятор, тестируем только результаты работы этого машинного кода. С AI будет также.
p.p.s. Весь холивар под этой статьей станет бесмысленным.

Так воно одразу було зрозуміло, що go не потрібний...

Це бо з Джави дуже важко на Го перейти через ідеологічну полярність:

1. В Джаві ти звик ляпнути ексепшн де попало і надіятися, шо він не трігернеться.
2. Наліпити якусь дивну ієрархію з абстракними класами напиханими дженеріками, яку крім тебе ніхто потім не прочитає, щоб «писати менше коду через 10 років».
3. Писати методи по 3 рядочки, бо якийсь дядько сказав, шо так треба і це чистий код і підходить під якусь букву в якійсь рандомній абревіатурі.
4. Давати потужні назви класам по типу «SimpleBeanFactoryAwareAspectInstanceFactory», а після цього використовувати їх без тайп інференса.
5. Чекати 30 секунд поки стартане спрінг бут...

Тут можна до безкінечності продовжувати.

1. Тому що я пишу загальний middleware/interceptor який викликається ще до API контролера, й кидаю в коді тільки ті помилки які підтримує цей інтерсептор. Де б не виникла помилка — я знаю що вона коректно обробиться цим інтерсептором й відобразиться в API.
2. Тут згоден.
3.а. На прктиці ви такого не зустрінете. Пишуть макарони по 1000 строк як й в будь які іншій мові програмування.
3.б. Такі рекомендації є для будь якої мови програмування, method extraction refactoring взгаалі не привʼязаний до мови. Але схоже не для го. У го є лише один патерн проектування if-else-elseif-if-else-else-if-if != nil.
4. Частково згоден, але по факту це продовження пункту 2.
5. Спрінг бут стартує на сучасному комьпютері < 1 секунди. Весь інший час — це запуск міграції, старт усяких підключень до редису, кафки й прочих. Я не знаю чи якось магічно це пофісили в Go, але інші фреймворки які заявляли що швидко стартують, так само довго стартують коли треба робити міграції, підключатись до бази й все таке. Але частково згоден, все одно бут стартує десь на 30% довше при схожих умовах.

о адепти Гошки понабігли, я ці всі «аргументи» вже чув, не знаю де так вас зомбують )) готові захищати по факту тулзу з піною ))

github.com/moby/moby ну да проект совсем не интерпрайз, установлен как у каждого формошлепа и бородатого сто
Когда джависты пересаживаются на го они начинают натягивать все оттуда парадигмы и концепции, детектится интерфейсайми, начинаются на IUser, тащат пакеты и фреймоворки, которые хоть как-то имитируют DI

Go був створений з принципом KISS (Keep It Simple, Stupid),

Але напевно забули інший принцип DRY тож вкрутили

if err :

Так ніхто тобі не заважає використати «_», якшо драй тобі важливіший за обробку ерорів.))

мені не потрібно кожен раз смикатися «а от сталася помилка чи ні??» а треба реагувати на неї у окремому блоку кода який тільки тим й займається. колись дууже давно писав на одній хаййомугрець мові з On Error Resume Next  більше не хочу такого

EngEneer — с вами все ясно.)))

У вас теж є одруківки в описі профілю. Перше, що побачив: “dis- tributed systems”.

res.cloudinary.com/...​/afqmy1zphto6b6ufudfc.png

Да ладно. А что там еще нашли в профиле. Может в моих pull request-ах еще комменты через gpt прогоните. И я свой профиль лет 10 не обновлял, а просто вставил из TEX формата в свое время.

Даже один коммент вставить не можете. Idempotent api ваш страдает.

Редька, docker через два кк

Може він діси пише, де віддає дань пацанам павшим в очікуваннях старту спрінг бут.

О! Дякую, не помітив. Мабудь занадто старий став :)))

На самом деле это приятно видеть, значит текст сами писали и не сильно пользовались ai хайповыми вещами.

С вами тоже все ясно — хамло обыкновенное

Підписатись на коментарі