Firwat Go ass schlecht fir onsmart Programméierer

Статья написана, как ответ на опубликованную ранее статью-антипод.

Firwat Go ass schlecht fir onsmart Programméierer

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

Entworf fir schwaach Programméierer?

Слабые говорят о проблемах. Сильные говорят об идеях и мечтах…

Go очень просто научиться, настолько просто, что читать код можно практически без подготовки вообще. Эту особенность языка используют во многих мировых компаниях, когда код читают вместе с непрофильными специалистами (менеджерами, заказчиками и т. д.). Это очень удобно для методологий типа Design Driven Development.
Даже начинающие программисты начинают выдавать вполне приличный код спустя неделю-другую. Книга, по которой я изучал “Go называется Программирование на языке Go” (автор Марк Саммерфилд). Книга весьма хороша, в ней затрагиваются многие нюансы языка. После неоправданно усложненных языков таких, как Java, PHP, отсутствие магии действует освежающе. Но рано или поздно у многих ограниченных программистов возникает использовать старые методы на новом поприще. Действительно ли это так необходимо?

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

Так для чего же он был создан таким простым? Вот пара цитат Роба Пайка:

Ключевой момент здесь, что наши программисты не исследователи. Они, как правило, весьма молоды, идут к нам после учебы, возможно изучали Java, или C/C++, или Python. Они не в состоянии понять выдающийся язык, но в то же время мы хотим, чтобы они создавали хорошее ПО. Именно поэтому язык должен прост для понимания и изучения.

Hie sollt vertraut sinn, ongeféier ähnlech wéi C. Programméierer, déi bei Google schaffen, fänken hir Carrière fréi un a si meeschtens mat prozedurale Sprooche vertraut, besonnesch d'C Famill. D'Ufuerderung fir séier Produktivitéit an enger neier Programméierungssprooch bedeit datt d'Sprooch net ze radikal sollt sinn.

Мудрые слова, не правда ли?

Артефакты простоты

Простота — необходимое условие прекрасного. Лев Толстой.

Быть простым — это одно из важнейших стремлений в любом дизайне. Как известно, совершенный проект это не тот проект, куда нечего добавить, а тот – в из которого нечего удалить. Многие считают, что для того, чтобы решить (или даже выразить) сложные задачи, необходим сложный инструмент. Однако, это не так. Возьмем к примеру язык PERL. Идеологи языка считали, что программист должен иметь как минимум три разных пути для решения одной задачи. Идеологи языка Go пошли другим путем, они решили, что для достижения цели достаточно одного пути, но действительно хорошего. Такой подход имеет под собой серьезный фундамент: единственный путь легче учится и тяжелей забывается.

Многие мигранты жалуются, что язык не содержит элегантных абстракций. Да, это так, однако это и есть одно из главных достоинств языка. Язык содержит в своем составе минимум магии – поэтому не требуется глубоких знаний для чтения программы. Что же касается многословности кода, то это и вовсе не проблема. Хорошо написанная программа на языке Golang читается по вертикали, практически без структурирования. Кроме того, скорость чтения программы как минимум на порядок превосходит скорость ее написания. Если учесть, что весь код имеет единообразное форматирование (выполненное при помощи встроенной команды gofmt), то прочитать несколько лишних строк вообще не является проблемой.

Net ganz expressiv

Искусство не терпит, когда стесняют его свободу. Точность не входит в его обязанности.

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

Zum Beispill, e Konsol-Utility deen stdin liest oder eng Datei aus Kommandozeilargumenter géif esou ausgesinn:

package main

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
)

func main() {

    flag.Parse()

    scanner := newScanner(flag.Args())

    var text string
    for scanner.Scan() {
        text += scanner.Text()
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }

    fmt.Println(text)
}

func newScanner(flags []string) *bufio.Scanner {
    if len(flags) == 0 {
        return bufio.NewScanner(os.Stdin)
    }

    file, err := os.Open(flags[0])

    if err != nil {
        log.Fatal(err)
    }

    return bufio.NewScanner(file)
}

Решение этой же задачи на языке D хотя и выглядит несколько короче, однако, читается ничуть не проще

import std.stdio, std.array, std.conv;

void main(string[] args)
{
    try
    {
        auto source = args.length > 1 ? File(args[1], "r") : stdin;
        auto text   = source.byLine.join.to!(string);

        writeln(text);
    }
    catch (Exception ex)
    {
        writeln(ex.msg);
    }
}

Hell vun Kopie

Человек носит ад в самом себе. Мартин Лютер.

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

package main

import "fmt"

func int64Sum(list []int64) (uint64) {
    var result int64 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func int32Sum(list []int32) (uint64) {
    var result int32 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func main() {

    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(int32Sum(list32))
    fmt.Println(int64Sum(list64))
}

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

package main

import "fmt"

func Eval32(list []int32, fn func(a, b int32)int32) int32 {
    var res int32
    for _, val := range list {
        res = fn(res, val)
    }
    return res
}

func int32Add(a, b int32) int32 {
    return a + b
}

func int32Sub(a, b int32) int32 {
    return a + b
}

func Eval64(list []int64, fn func(a, b int64)int64) int64 {
    var res int64
    for _, val := range list {
        res = fn(res, val)
    }
    return res
}

func int64Add(a, b int64) int64 {
    return a + b
}

func int64Sub(a, b int64) int64 {
    return a - b
}

func main() {

    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(Eval32(list32, int32Add))
    fmt.Println(Eval64(list64, int64Add))
    fmt.Println(Eval64(list64, int64Sub))
}

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

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

import std.stdio;
import std.algorithm;

void main(string[] args)
{
    [1, 2, 3, 4, 5].reduce!((a, b) => a + b).writeln;
}

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

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

С точки зрения поддерживаемости, расширяемости, читаемости, по-моему выигрывает язык Go, хотя и проигрывает по многословности.

Обобщенное программирование в ряде случаев дает нам неоспоримую выгоду. Это наглядно иллюстрирует нам пакет sort. Так, для сортировки любого списка нам достаточно реализовать интерфейс sort.Interface.

import "sort"

type Names []string

func (ns Names) Len() int {
    return len(ns)
}

func (ns Names) Less(i, j int) bool {
    return ns[i] < ns[j]
}

func (ns Names) Swap(i, j int) {
    ns[i], ns[j] = ns[j], ns[i]
}

func main() {
    names := Names{"London", "Berlin", "Rim"}
    sort.Sort(names)
}

Если Вы возьмете любой open source проект и выполните команду grep «interface{}» -R, то увидите, как часто используются путые интерфейсы. Недалекие товарищи сразу же скажут, что все это из-за отсутствия дженериков. Однако, это далеко не всегда так. Возьмем к примеру язык DELPHI. Несмотря на наличие у него этих самых дженериков, он содержит специальный тип VARIANT для операций с произвольными типами данных. Аналогично поступает и язык Go.

Из пушки по воробьям

И смирительная рубашка должна соответствовать размеру безумия. Станислав Лец.

Многие любители экстрима могут заявить, что в Go есть еще один механизм для создания дженериков — рефлексия. И они будут правы,… но только в редких случаях.

Роб Пайк предупреждает нас:

Dëst ass e mächtegt Tool dat sollt mat Vorsicht benotzt ginn. Et soll vermeit ginn, ausser streng néideg.

Википедия говорит нам следующее:

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

Однако, как известно, за все необходимо платить. В данном случае это:

  • сложность написания программ
  • скорость исполнения программ

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

Культурный багаж из Си? Нет, из ряда языков!

Вместе с состоянием наследникам оставляют и долги.

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

Siwebiergen

Прежде всего, синтаксис грамматических конструкций основывается на синтаксисе языка Си. Однако, существенное влияние оказал и язык DELPHI. Так, мы видим, что полностью убраны избыточные скобки, так сильно снижающие читаемость программы. Также язык содержит оператор «:=», присущий языку DELPHI. Понятие пакетов заимствовано из языков, подобных ADA. Декларация неиспользуемых сущностей заимствована из языка PROLOG.

Семантика

За основу пакетов была взята семантика языка DELPHI. Каждый пакет инкапсулирует данные и код и содержит приватные и публичные сущности. Это позволяет сокращать интерфейс пакета до минимума.

Операция реализации методом делегирования была заимствована из языка DELPHI.

Kompiléierung

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

Высокоуровневые конструкции

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

  • Linnen
  • Хэш таблицы
  • Слайсы
  • Утиная типизация позаимствована из языков, подобных RUBY (которую, к сожалению, многие не понимают и не используют на полную мощь).

Erënnerung Gestioun

Управление памятью вообще заслуживает отдельной статьи. Если в языках типа C++, управление полностью отдано на откуп разработчика, то в более поздних языках типа DELPHI, была использована модель подсчета ссылок. При таком подходе не допускалось циклических ссылок, поскольку образовывались потерянные кластера, то в Go встроено детектирование таких кластеров (как в C#). Кроме того, по эффектвности garbage collector превосходит большинство известных на текущий момент реализаций и уже может быть использован для многих real time задач. Язык сам распознает ситуации, когда значение для хранения переменной может быть выделено в стеке. Это уменьшает нагрузку на менеджер памяти и повышает скорость работы программы.

Параллельность и конкурентность

Параллельность и конкурентность языка выше всяких похвал. Ни один низкоуровневый язык не может даже отдаленно конкурировать с языком Go. Справедливости ради, стоит отметить, что модель не была изобретена авторами языка, а просто заимствована из старого доброго языка ADA. Язык способен обрабатывать миллионы параллельных соединений задействуя все CPU, имея при этом на порядок реже типичные для многопоточного кода сложные проблемы с дедлоками и race conditions.

Дополнительные выгоды

Если это будет выгодно — бескорыстными станут все.

Язык также предоставляет нам также ряд несомненных выгод:

  • Единственный исполнимый файл после сборки проекта существенно упрощает deploy приложения.
  • Статическая типизация и вывод типов позволяют существенно сократить число ошибок в коде даже без написания тестов. Я знаю некоторых программистов, которые вообще обходятся без написания тестов и при этом качество их кода существенно не страдает.
  • Очень простая кросс-компиляция и отличная портабельность стандартной библиотеки, что сильно упрощает разработку кросс-платформенных приложений.
  • Регулярные выражения RE2 потокобезопасные и с предсказуемым временем выполнения.
  • Мощная стандартная библиотека, что позволяет в большинстве проектов обходиться без сторонних фреймворков.
  • Язык достаточно мощный, чтобы концентрироваться на задаче, а не на методах ее решения и в то же время достаточно низкоуровневый, чтобы задачу можно было решить эффективно.
  • Эко система Go содержит уже из коробки развитый инструментарий на все случаи жизни: тесты, документация, управление пакетами, мощные линтеры, кодогенерация, детектор race conditions и т. д.
  • У Go версии 1.11 появилась встроенное семантическое управления зависимостями, построенное поверх популярных хостингов VCS. Все инструменты, входящие в состав экосистемы Go используют эти сервисы, чтобы скачивать, собирать и устанавливать из них код одним махом. И это здорово. С приходом версии 1.11 также полностью разрешилась проблема с версированием пакетов.
  • Поскольку основной идеей языка является уменьшение магии, язык стимулирует разработчиков выполнять обработку ошибок явно. И это правильно, поскольку в противном случае, он просто будет забывать вообще про обработку ошибок. Другое дело, что большинство разработчиков сознательно игнорирую обработку ошибок, предпочитая вместо их обработки, просто пробрасывать ошибку вверх.
  • Язык не реализует классической методологии ООП, поскольку в чистом виде в Go нет виртуальности. Однако, это не является проблемой при использовании интерфейсов. Отсутствие ООП существенно снижает входной барьер для новичков.

Простота для выгоды сообщества

Усложнять просто, упрощать сложно.

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

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

Konklusioun

Большой размер мозга еще не сделал ни одного слона лауреатом Нобелевской премии.

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

Source: will.com

Setzt e Commentaire