company_banner

Go: Хороший, плохой, злой

https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  • Перевод

У Go есть некоторые замечательные свойства, которым посвящён раздел «Хороший». Но когда речь заходит о применении этого языка не для создания API или сетевых серверов (для чего он и был разработан), а для реализации бизнес-логики, то я считаю Gо слишком неуклюжим и неудобным. Хотя даже в рамках сетевого программирования найдётся немало подводных камней как в архитектуре языка, так и в реализации, что делает Go опасным, несмотря на его кажущуюся простоту.


Я решил написать эту статью после применения Go в одном из второстепенных проектов. Я активно использовал этот язык в предыдущем проекте при написании прокси (HTTP и TCP) для SaaS-сервиса. Работа над сетевой частью мне понравилась (я попутно изучал язык), но бухгалтерская и биллинговые части дались мне тяжело. Мой второстепенный проект представлял собой простой API, и мне казалось, что с помощью Go я смогу быстро его написать. Но, как вы знаете, многие проекты в результате оказываются сложнее, чем предполагалось. Мне пришлось реализовать обработку данных для обсчёта статистики, и я снова столкнулся с недостатками Go. Эта статья — рассказ об испытанных мной неприятностях.


Немного о себе: мне нравятся статически типизированные языки. Мои первые значимые программы были написаны на Pascal. В начале 1990-х использовал Ada и C/C++, затем перешёл на Java, потом на Scala (между ними было немного Go), и недавно начал изучать Rust. Также я написал большое количество кода на JavaScript, потому что до недавнего времени только этот язык был доступен в браузерах. Я чувствую себя неуютно при работе с динамически типизированными языками и стараюсь ограничить их использование простыми скриптами. Мне нравятся императивный, функциональный и объектно-ориентированный подходы.


Статья длинная, так что можете ориентироваться по содержанию:



Хороший


Go прост в изучении


Это факт: если вам знакомы все виды языков программирования, вы можете с помощью "Tour of Go" изучить синтаксис Go за пару часов, а через пару дней начать писать настоящие программы. Почитайте Effective Go, изучите стандартную библиотеку, поиграйтесь с веб-инструментами Gorilla или Go kit, и станете весьма приличным разработчиком на Go.


Всё дело во всеобъемлющей простоте языка. Когда я начал изучать Go, это напомнило мне времена моего знакомства с Java: тоже простой и богатый язык, стандартная библиотека без излишеств. Изучение Go стало приятным опытом на фоне современной тяжёлой среды Java. Благодаря простоте языка, код на Go очень легко читается, даже если блоки обработки ошибок несколько усложняют листинг (об этом ниже).


Но эта простота может оказаться ложной. Как сказал Роб Пайк: «простота сложна», и ниже мы увидим, что вас ожидает большое количество подводных камней, и что простота и минимализм препятствуют написанию DRY-кода.


Простое многопоточное программирование с помощью горутин и каналов


Пожалуй, горутины — лучшая особенность Go. Это небольшие потоки вычисления, отделённые от потоков вычисления ОС.


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


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


Однажды я столкнулся с утечкой горутин в приложении: прежде чем завершиться, они ожидали закрытия канала, а тот не закрывался (стандартная проблема дедлока). Процесс безо всяких причин потреблял 90 % ресурсов процессора, а при изучении expvars выяснилось, что сейчас простаивает 600 тысяч горутин! Полагаю, процессор занимал их диспетчер.


Конечно, система акторов наподобие Akka может безо всяких усилий обрабатывать миллионы акторов, отчасти потому, что у них нет стека. Но зато с помощью горутин гораздо легче создавать сильно распараллеленные приложения, действующие по схеме запрос-ответ (например, HTTP API).


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


Однако использовать каналы нужно обдуманно, поскольку при неправильном выборе размера (каналы по определению не буферизуются) могут возникать дедлоки. Ниже мы увидим, что из-за отсутствия неизменяемости в Go использование каналов не предотвращает состояния гонки.


Прекрасная стандартная библиотека


Стандартная библиотека Go действительно великолепна, особенно применительно к разработке сетевых протоколов или API: в ней есть HTTP-клиент и сервер, шифрование, форматы архивирования, сжатие, отправка писем и так далее. Есть даже парсер HTML и довольно мощный движок шаблонов, что позволяет создавать текст и HTML с автоматическим экранированием (automatic escaping) для защиты от XSS (к примеру, используется в Hugo).


Различные API в целом просты и легки для понимания. Хотя иногда они могут выглядеть чрезмерно упрощёнными: отчасти из-за модели горутин, то есть нам нужно заботиться об операциях, «кажущихся синхронными», а отчасти потому, что несколько универсальных функций могут заменить много специализированных, как я недавно обнаружил при вычислениях времени.


Производительность


Go компилируется в нативные исполняемые файлы. Многие программисты приходят в Go из Python, Ruby или Node.js. Им просто сносит крышу от такой возможности, поскольку сервер способен обрабатывать огромное количество одновременных запросов. То же самое можно сказать про тех, кто переходит с интерпретируемых языков без распараллеливания (Node.js) или с глобальной блокировкой интерпретатора. В сочетании с простотой языка это способствует популярности Go.


Но по сравнению с Java ситуация в бенчмарках производительности не столь однозначна. Зато Go лучше Java по использованию памяти и сборке мусора.


Сборщик мусора в Go спроектирован с учётом приоритетности задержки и избегания больших пауз, что особенно важно для серверов. Он может потреблять больше ресурсов процессора, но в горизонтально масштабируемой архитектуре это легко решается добавлением машин. Не забывайте, что Go создавался в Google, которой едва хватает ресурсов!


По сравнению с Java, сборщик мусора в Go выполняет меньше работы: слайсы структур представляют собой смежные массивы структур, а не массивы указателей, как в Java. Также maps в Go используют маленькие массивы в качестве блоков памяти (bucket). В результате сборщику мусора приходится выполнять меньше работы, что улучшает локальность кэша процессора.


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


Формат исходного кода определяется языком


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


Нравится вам это или нет, gofmt решает, как должен быть отформатирован код на Go, и эта проблема решена для всех раз и навсегда!


Стандартизированный тестовый фреймворк


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


Программы на Go очень удобны в эксплуатации


По сравнению с Python, Ruby или Node.js, установка единственного исполняемого файла — мечта инженеров по эксплуатации. Конечно, это вовсе не такая большая проблема с учётом всё более широкого использования Docker, но отдельные исполняемые файлы ещё и уменьшают размер контейнеров.


Также в Go есть некоторые встроенные возможности по наблюдению с помощью пакета expvar, позволяющего публиковать внутренние статусы и метрики, и облегчающего их добавление. Но будьте внимательны, потому что статусы и метрики автоматически отображаются — незащищённые — в обработчике HTTP-запросов по умолчанию. В Java для тех же целей есть JMX, но они гораздо сложнее в использовании.


Выражение defer помогает не забыть об очистке


Выражение defer играет ту же роль, что и finally в Java: в конце текущей функции исполняет код очистки, вне зависимости от того, как эта функция вышла. Любопытно, что defer не связано с блоком кода и может появляться в любое время. Это позволяет писать код очистки как можно ближе к коду, который создаёт то, что нужно вычистить:


file, err := os.Open(fileName)
if err != nil {
    return
}
defer file.Close()

// use file, we don't have to think about closing it anymore

Конечно, try-with-resource в Java получается менее многословно, а в Rust ресурсы автоматически забираются, когда их владелец дропается, но поскольку Go требует явной очистки ресурсов, то и наличие соответствующего кода рядом с выделением ресурсов идёт на пользу.


Новые типы


Мне нравятся типы, и меня раздражает и пугает, когда, к примеру, мы где угодно передаём идентификаторы сохранённых объектов (persisted object identifiers) в виде string или long. Обычно мы кодируем тип идентификатора в имени параметра, но когда в функции в качестве параметров есть несколько идентификаторов, это становится причиной мелких багов, а некоторые вызовы путают порядок параметров.


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


type UserId string // <-- new type
type ProductId string

func AddProduct(userId UserId, productId ProductId) {}

func main() {
    userId := UserId("some-user-id")
    productId := ProductId("some-product-id")

    // Right order: all fine
    AddProduct(userId, productId)

    // Wrong order: would compile with raw strings
    AddProduct(productId, userId)
    // Compilation errors:
    // cannot use productId (type ProductId) as type UserId in argument to AddProduct
    // cannot use userId (type UserId) as type ProductId in argument to AddProduct
}

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


Плохой


Go игнорирует достижения современного проектирования языков


В статье Less is exponentially more Роб Пайк объясняет, что Google создавал Go в качестве замены для С и С++, и его предшественником был язык Newsqueak, написанный в 1980-х. Также в Go есть много отсылок к Plan9, распределённой ОС, которую авторы Go создали в Bell Labs в 1980-х.


Даже ассемблер Go создавался под впечатлением от Plan9. Почему нельзя было использовать LLVM, который из коробки предоставляет большое количество целевых архитектур? Возможно, я что-то упускаю, но зачем нужно было так делать? Если тебе нужно написать ассемблер, чтобы воспользоваться всеми возможностями процессора, то разве ты не будешь напрямую использовать процессорный ассемблер?


Создатели Go заслуживают уважения, но выглядит так, словно архитектура языка создавалась в параллельной вселенной (или в лаборатории Plan9?), где не было ничего из того, что реализовали в компиляторах и архитектурах языков в 1990-х и 2000-х. Или словно Go создавался системными программистами, которые ещё и компилятор смогли написать.


Функциональное программирование? Даже не вспоминайте. Обобщённые типы? Они вам не нужны, посмотрите, какой из-за них бардак в С++! И это несмотря на то, что слайсы, map и каналы являются обобщёнными типами, как мы увидим ниже.


Go создавался как замена для С и С++, и очевидно, что его авторы не слишком смотрели по сторонам. Их надежды не сбылись. Думаю, что главная причина заключается в сборщике мусора. Низкоуровневые С-разработчики яростно отвергают управляемую память, поскольку не контролируют, что и когда в ней происходит. Им нравится контролировать ситуацию, даже если это всё усложняет и открывает возможность утечек памяти и переполнений буфера. Любопытно, что в Rust применён совершенно иной подход с автоматическим управлением памяти без сборщика мусора.


С точки зрения эксплуатационных инструментов, Go нравится пользователям скриптовых языков вроде Python и Ruby. Они получили высокую производительность и небольшое потребление ресурсов памяти/процессора/диска. И заодно больше статичной типизации, что для них было в новинку. Убойным приложением для Go стал Docker, обеспечивший широкое распространение этого языка в мире DevOps. А расцвет Kubernetes усилил эту тенденцию.


Интерфейсы и структурные типы


Интерфейсы Go похожи на интерфейсы Java или трейты Scala и Rust: они определяют поведение, которое позднее реализуется типом (не буду называть здесь это «классом»).


Но, в отличие от интерфейсов Java и трейтов Scala и Rust, типу не нужно явно определять, что он реализует интерфейс: он просто обязан реализовывать все функции, определённые в интерфейсе. Так что интерфейсы Go фактически относятся к структурной типизации.


Вы можете подумать, что это нужно для реализации интерфейсов в других пакетах, а не в типе, к которому они относятся, по аналогии с расширениями классов в Scala и Kotlin, или трейтами в Rust. Но это не так: все методы, относящиеся к типу, должны определяться в пакете этого типа.


Go — не единственный язык, использующий структурную типизацию, но я нашёл у него несколько недостатков:


  • Трудно понять, какие типы реализуют конкретный интерфейс, поскольку это зависит от соответствия определения функции (function definition matching). В Java и Scala я часто встречаю интересные реализации, когда ищу классы, реализующие интерфейс.
  • Добавляя метод в интерфейс, находишь типы, которые нужно обновить, только когда они используются в качестве значения этого интерфейсного типа. И довольно долго о них просто не вспоминаешь. Чтобы избежать такой ситуации, рекомендуется использовать маленькие интерфейсы с очень небольшим количеством методов.
  • Тип может случайно реализовать интерфейс из-за соответствующих методов. Однако случайность этого события может привести к тому, что семантика реализации будет отличаться от того, что вы ожидаете от контракта интерфейса.

Дополнение: что касается недостатков интерфейсов, почитайте главу «Интерфейсные nil-значения».


Отсутствие перечислений


В Go нет перечислений, и я считаю это упущенной возможностью.


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


Это также означает, что компилятор не может проверить, является ли выражение switch исчерпывающим, и нет возможности описать разрешённые в типе значения.


Дилемма := / var


В Go есть два способа объявления переменной и присвоения ей значения: var x = "foo" и x := "foo". Зачем?


Главное отличие в том, что var позволяет объявлять без инициализации (потом приходится объявлять тип), как в случае с var x string, а := требует присваивания и позволяет смешивать существующие и новые переменные. Думаю, что := изобрели для существенного упрощения обработки ошибок:


С var:
var x, err1 = SomeFunction()
if (err1 != nil) {
  return nil
}

var y, err2 = SomeOtherFunction()
if (err2 != nil) {
  return nil
}
C:=:
x, err := SomeFunction()
if (err != nil) {
  return nil
}

y, err := SomeOtherFunction()
if (err != nil) {
  return nil
}

Синтаксис := позволяет случайно «затенить» переменную. Я несколько раз попадался на этом, поскольку := (объявить и присвоить) слишком похоже на = (присвоить):


foo := "bar"
if someCondition {
  foo := "baz"
  doSomething(foo)
}
// foo == "bar" even if "someCondition" is true

Нулевые значения приводят к панике


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


На практике, многие типы не могут быть полезны без соответствующей инициализации. Давайте рассмотрим объект io.File, который взят из Effective Go:


type File struct {
    *file // os specific
}

func (f *File) Name() string {
    return f.name
}

func (f *File) Read(b []byte) (n int, err error) {
    if err := f.checkValid("read"); err != nil {
        return 0, err
    }
    n, e := f.read(b)
    return n, f.wrapErr("read", e)
}

func (f *File) checkValid(op string) error {
    if f == nil {
        return ErrInvalid
    }
    return nil
}

Что мы видим?


  • Вызов Name() применительно к нулевому значению File приведёт к панике, поскольку поле file содержит nil.
  • Функция Read, как и почти все остальные методы File, начинается с проверки инициализации файла.

Так что, по сути, File с нулевым значением не только бесполезен, но и может привести к панике. Вам придётся использовать одну из функций-конструкторов вроде Open или Create. А проверка правильной инициализации — это дополнительные расходы, на которые придётся идти при каждом вызове функции.


В стандартной библиотеке есть множество типов, подобных этому, и некоторые даже пытаются делать что-то полезное со своими нулевыми значениями. Вызовите любой метод применительно к нулевому значению html.Template: все будут паниковать.


Также есть серьёзная проблема с нулевым значением map: вы можете его запросить, но если попытаетесь в нём что-то сохранить, возникнет паника:


var m1 = map[string]string{} // empty map
var m0 map[string]string     // zero map (nil)

println(len(m1))   // outputs '0'
println(len(m0))   // outputs '0'
println(m1["foo"]) // outputs ''
println(m0["foo"]) // outputs ''
m1["foo"] = "bar"  // ok
m0["foo"] = "bar"  // panics!

Это требует осторожности при работе со структурой, в которой есть поле map, потому что его нужно инициализировать прежде, чем добавлять в него какие-то записи.


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


В Go нет исключений. Хотя погодите… они есть!


В статье "Why Go gets exceptions right" подробно рассказано, чем плохи исключения и в чём преимущество подхода Go, который требует возврата error. Я могу с этим согласиться, трудно работать с исключениями в условиях асинхронного программирования или функционального стиля, наподобие потоков Java (не будем уточнять, что в Go первое не нужно благодаря горутинам, а последнее просто невозможно). В статье верно говорится, что panic «всегда фатальна для вашей программы, это конец».


В статье "Defer, panic and recover" объясняется, что делать в случае паники (нужно её ловить), и говорится: «реальный пример паники и работы с ней можно посмотреть в JSON-пакете из стандартной библиотеки Go».


Действительно, в JSON-декодере есть стандартная функция обработки ошибок, которая просто паникует. Возникшая паника нейтрализуется с помощью верхнеуровневой функции unmarshal, которая проверяет тип паники и возвращает её как ошибку, если это «локальная паника», либо повторяет панику в случае ошибки иного рода (попутно теряя трассировку стека исходной паники).


Для любого Java-разработчика это выглядит как try / catch (DecodingException ex). Так что исключения в Go есть, он использует их внутри себя, но вам не разрешает.


Любопытный факт: недавно сторонний разработчик исправил JSON-декодер, чтобы тот использовал обычное информирование об ошибках.


Злой


Кошмар управления зависимостями


Сначала процитирую Джаану Доган (Jaana Dogan, aka JBD), известную гофершу из Google, которая недавно вылила своё разочарование в Twitter:


Если через год ситуация с управлением зависимостями не разрешится, я откажусь от Go и никогда к нему не вернусь. Проблемы с управлением зависимостями регулярно портят всё удовольствие от использования этого языка.


— JBD (@rakyll) March 21, 2018


Скажу просто: в Go нет управления зависимостями. Все текущие решения — это хаки и ухищрения.


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


В Go добавление зависимости подразумевает клонирование репозитория исходного кода этой зависимости в ваш GOPATH. Какая ещё версия? Всё делается в текущую на момент клонирования мастер-ветку, вне зависимости от её содержимого. А если разным проектам нужные разные версии зависимости? Ничего не поделаешь. Отсутствует даже само понятие «версии».


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


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


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


Но всё же ситуация улучшается: недавно был представлен dep, официальный инструмент управления зависимостями для вендоринга. Он поддерживает версии (git-теги) и содержит средство разрешения версий (version solver), соблюдающее соглашения о семантическом версионировании. Работает пока нестабильно, но направление выбрано верное. Да, и проекты всё ещё должны находиться в GOPATH.


Однако dep может прожить недолго, поскольку инструмент vgo, тоже разработанный в Google, хочет самостоятельно привнести версионирование в Go и уже привлёк к себе внимание.


Управление зависимостями в Go кошмарное. Его трудно настраивать, и о нём не вспоминаешь, пока ситуация не взорвётся при новом импортировании или когда просто захочешь запулить ветку коллеги в свой GOPATH...


Но вернёмся к коду.


Изменяемость жёстко прописана в языке


В Go нельзя определить неизменяемые структуры: поля struct являются изменяемыми, а ключевое слово const к ним не применяется. Однако в Go можно легко скопировать всю структуру с простым присваиванием, так что можно подумать, что для реализации неизменяемости достаточно передать аргументы по значениям, лишь потратив ресурсы на копирование.


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


Чтобы было понятнее:


type S struct {
    A string
    B []string
}

func main() {
    x := S{"x-A", []string{"x-B"}}
    y := x // copy the struct
    y.A = "y-A"
    y.B[0] = "y-B"

    fmt.Println(x, y)
    // Outputs "{x-A [y-B]} {y-A [y-B]}" -- x was modified!
}

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


Есть библиотеки глубокого копирования, которые пытаются решить эту проблему с помощью (медленной) рефлексии (reflection), но толку от этого не так много, поскольку обращаться к приватным полям с помощью рефлексии нельзя. Так что трудно будет организовать защитное копирование в надежде избежать состояния гонки, поскольку это потребует большого количества шаблонного кода. В Go даже нет интерфейса Clone, который позволил бы это стандартизировать.


Подвохи слайсов


Со слайсами вас поджидает несколько подводных камней. Как объясняется в "Go slices: usage and internals", если слайс перенарезать, то ради сохранения производительности массив скопирован не будет. Причина достойная, но это означает, что подслайсы какого-то слайса будут являться всего лишь представлениями (view), повторяющими изменения исходного слайса. Так что не забудьте применить к слайсу copy(), если хотите отделить его от оригинала.


Если вы забудете применить copy(), то ситуация станет опаснее в связи с функцией append: добавление значений к слайсу приведёт к изменению массива, если у того не хватит ёмкости для хранения новых значений. То есть в зависимости от исходной ёмкости результат append может указывать на исходный массив, а может и не указывать. В результате возможно появление трудно выявляемых, недетерминированных багов.


В этом коде показано, как влияние функции, добавляющей значения в подслайс, зависит от ёмкости исходного слайса:


func doStuff(value []string) {
    fmt.Printf("value=%v\n", value)

    value2 := value[:]
    value2 = append(value2, "b")
    fmt.Printf("value=%v, value2=%v\n", value, value2)

    value2[0] = "z"
    fmt.Printf("value=%v, value2=%v\n", value, value2)
}

func main() {
    slice1 := []string{"a"} // length 1, capacity 1

    doStuff(slice1)
    // Output:
    // value=[a] -- ok
    // value=[a], value2=[a b] -- ok: value unchanged, value2 updated
    // value=[a], value2=[z b] -- ok: value unchanged, value2 updated

    slice10 := make([]string, 1, 10) // length 1, capacity 10
    slice10[0] = "a"

    doStuff(slice10)
    // Output:
    // value=[a] -- ok
    // value=[a], value2=[a b] -- ok: value unchanged, value2 updated
    // value=[z], value2=[z b] -- WTF?!? value changed???
}

Изменяемость и каналы: легко придти к состоянию гонки


Согласованность в Go основана на CSP, использующих каналы, что делает координирование горутин гораздо проще и безопаснее по сравнению с синхронизацией общих данных. Здесь применяется мантра «Не взаимодействую с помощью общей памяти, делай память общей с помощью взаимодействия». Это желаемый подход, но в реальности его невозможно применять безопасно.


Как мы уже видели, в Go невозможно создать неизменяемые структуры данных. Поэтому когда мы отправляем указатель в канал, для него всё кончено: мы поделились изменяемыми данными между параллельными процессами. Конечно, канал структур (а не указателей) копирует отправленные в него значения, но, как мы видели, не выполняется глубокое копирование ссылок, включая слайсы и map, которые изменяемы по своей сути. То же самое касается полей struct интерфейсного типа: это указатели, и любой метод изменения, определённый интерфейсом, является приглашением к состоянию гонки.


Так что, хотя каналы и облегчают согласованное программирование, они не предотвращают состояние гонки применительно к общим данным. И её вероятность возрастает из-за принципиальной изменяемости слайсов и map.


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


Неудобное управление ошибками


В Go вы быстро столкнётесь с ошибкой шаблона обработки ошибок, которая повторяется до умопомрачения:


someData, err := SomeFunction()
if err != nil {
    return err;
}

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


Вы быстро привыкнете не замечать этот шаблон и называть его «ага, обработка ошибок», но всё же он создаёт неудобства, да и к тому же иногда трудно найти нужный код среди обработок ошибок.


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


len, err := reader.Read(bytes)
if err != nil {
    if err == io.EOF {
        // All good, end of file
    } else {
        return err
    }
}

В статье "Error has values" Роб Пайк предлагает несколько подходов к уменьшению многословности обработки ошибок. Я считаю их довольно опасными:


type errWriter struct {
    w   io.Writer
    err error
}

func (ew *errWriter) write(buf []byte) {
    if ew.err != nil {
        return // Write nothing if we already errored-out
    }
    _, ew.err = ew.w.Write(buf)
}

func doIt(fd io.Writer) {
    ew := &errWriter{w: fd}
    ew.write(p0[a:b])
    ew.write(p1[c:d])
    ew.write(p2[e:f])
    // and so on
    if ew.err != nil {
        return ew.err
    }
}

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


В Rust такая же проблема: поскольку в нём нет исключений (действительно нет, в отличие от Go), функции, которые могут сбоить, возвращают Result<T, Error> и требуют шаблонного сопоставления результата. Поэтому в Rust 1.0 внедрили макрос try!, и учитывая его востребованность, макрос стал одним из главных свойств языка. В результате получается лаконичный код с корректной обработкой ошибок.


Перенести этот подход из Rust в Go, к сожалению, невозможно, потому что в Go нет ни обобщённых типов, ни макросов.


Интерфейсные nil-значения


Один пользователь Reddit jmickeyd заметил странное поведение nil и интерфейсов, которое определённо можно считать недостатком языка. Поясню:


type Explodes interface {
    Bang()
    Boom()
}

// Type Bomb implements Explodes
type Bomb struct {}
func (*Bomb) Bang() {}
func (Bomb) Boom() {}

func main() {
    var bomb *Bomb = nil
    var explodes Explodes = bomb
    println(bomb, explodes) // '0x0 (0x10a7060,0x0)'
    if explodes != nil {
        explodes.Bang() // works fine
        explodes.Boom() // panic: value method main.Bomb.Boom called using nil *Bomb pointer
    }
}

Код проверяет, чтобы explodes не был nil, но паники возникают в Boom, а не в Bang. Почему? Всё дело в строке println: указатель bomb ссылается на 0x0, по сути — nil, однако explodes не является nil (0x10a7060,0x0).


Первый элементы этой пары — указатель на таблицу назначения методов (method dispatch table) для реализации интерфейса Bomb типом Explodes, второй элемент — адрес реального объекта Explodes, который является nil.


Вызов Bang успешен, потому что он применяется к указателям на Bomb: для вызова метода нет нужды разыменовывать указатель. Метод Boom применяется к значению, и поэтому вызов приводит к разыменованию указателей, что вызывает панику.


Обратите внимание, что если написать var explodes Explodes = nil, тогда != nil не будет успешно выполнено.


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


if explodes != nil && !reflect.ValueOf(explodes).IsNil() {
    explodes.Bang() // works fine
    explodes.Boom() // works fine
}

Баг или фича? В Tour of Go целая страница посвящена объяснению этого поведения, и там ясно сказано: «Обратите внимание, что интерфейсное значение, содержащее конкретное nil-значение, само по себе не является nil».


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


Теги полей struct: runtime DSL в строковых


Если вы использовали JSON в Go, то наверняка сталкивались с чем-то подобным:


type User struct {
    Id string    `json:"id"`
    Email string `json:"email"`
    Name string  `json:"name,omitempty"`
}

Это теги struct, которые спецификация называет строковыми. Они «видимы через рефлексивный интерфейс (reflection interface) и участвуют в идентификации struct’ов, но в остальном игнорируются». Так что помещайте в эти строковые что угодно, и во время runtime парсите с помощью рефлексии. И паникуйте во время runtime, если синтаксис ошибочный.


Эта строковая представляет собой метаданные поля, которые в других языках десятилетиями известны в качестве «аннотаций» или «атрибутов». Благодаря поддержке языка, их синтаксис формально определён и проверяется при компилировании, при этом оставаясь расширяемым.


Почему в Go решили использовать обычную строковую, которую любая библиотека может использовать с любым DSL, парсящимся во время runtime?


Всё становится ещё сложнее, когда вы используете несколько библиотек. Вот пример из буфера протокола из документации Go:


type Test struct {
    Label         *string             `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
    Type          *int32              `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
    Reps          []int64             `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
    Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
}

Примечание: почему эти теги столь часто применяются с JSON? Потому что в публичных полях в Go нужно использовать UpperCamelCase, или хотя бы начинать с заглавной буквы, в то время как соглашение по именованию полей в JSON подразумевает использование lowerCamelCase или snake_case. В результате приходится применять утомительное тегирование.


Стандартный кодировщик/декодер JSON не разрешает использовать стратегию именования для автоматизации преобразования, как это делает Jackson в Java. Вероятно, этим объясняется, почему все поля в Docker API именованы с помощью UpperCamelCase: его разработчикам не приходится писать громоздкие теги для своих больших API.


Обобщённых типов нет… по крайней мере, для вас


Трудно представить себе современный, статически типизированный язык без обобщённых типов, но именно таким и является Go: в нём нет обобщённых типов… или, точнее, почти нет. И как мы увидим, это ещё хуже, чем если бы их не было вовсе.


Встроенные слайсы, map, массивы и каналы являются обобщёнными типами. Объявление map [string]MyStruct ясно свидетельствует об использовании обобщённого типа с двумя параметрами. И это хорошо, потому что допускает типобезопасное программирование с поимкой ошибок всех видов.


Однако в Go отсутствуют определяемые пользователями обобщённые структуры данных. Это означает, что вы не можете типобезопасным способом определить многократно используемые абстракции, способные работать с любыми типами. Придётся использовать нетипизированный interface{} и приводить значения к соответствующему типу. Любая ошибка будет поймана только в runtime и приведёт к панике. Для Java-разработчиков эта ситуация аналогична JSE 5.0 2004 года.


В статье "Less is exponentially more" Роб Пайк почему-то относит обобщённые типы и наследование к «типизированному программированию» и говорит, что предпочитает композицию, а не наследование. Прекрасно, ты можешь не любить наследование (я пишу много кода на Scala и стараюсь избегать наследования), но обобщённые типы помогают решать другую задачу: многократное использование с сохранением типобезопасности.


Как мы увидим дальше, разделение на встроенные типы с обобщёнными и пользовательские без обобщённых влияет не только на «комфорт» разработчиков и типобезопасность при компилировании — это влияет на всю экосистему Go.


В Go мало структур данных помимо slice и map


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


Разберём пример с sync.Map — это согласованная map с более низкой конкуренцией за поток исполнения (thread contention) по сравнению с защитой обычной map с помощью мьютекса:


type MetricValue struct {
    Value float64
    Time time.Time
}

func main() {
    metric := MetricValue{
        Value: 1.0,
        Time: time.Now(),
    }

    // Store a value

    m0 := map[string]MetricValue{}
    m0["foo"] = metric

    m1 := sync.Map{}
    m1.Store("foo", metric) // not type-checked

    // Load a value and print its square

    foo0 := m0["foo"].Value // rely on zero-value hack if not present
    fmt.Printf("Foo square = %f\n", math.Pow(foo0, 2))

    foo1 := 0.0
    if x, ok := m1.Load("foo"); ok { // have to make sure it's present (not bad, actually)
        foo1 = x.(MetricValue).Value // cast interface{} value
    }
    fmt.Printf("Foo square = %f\n", math.Pow(foo1, 2))

    // Sum all elements

    sum0 := 0.0
    for _, v := range m0 { // built-in range iteration on map
        sum0 += v.Value
    }
    fmt.Printf("Sum = %f\n", sum0)

    sum1 := 0.0
    m1.Range(func(key, value interface{}) bool { // no 'range' for you! Provide a function
        sum1 += value.(MetricValue).Value        // with untyped interface{} parameters
        return true // continue iteration
    })
    fmt.Printf("Sum = %f\n", sum1)
}

Прекрасная иллюстрация, почему в экосистеме Go так мало структур данных: их трудно использовать по сравнению со встроенными слайсами и map. И ещё одна причина — в Go есть две категории структур данных:


  • аристократия, встроенные слайсы, map, массивы и каналы: типобезопасные и обобщённые, удобные в использовании с range,
  • и весь остальной код на Go: нет типобезопасности, неудобно использовать из-за необходимости приведения значений (casts).

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


Дуализм встроенных структур и остального кода вредит и тогда, когда мы хотим писать многократно используемые алгоритмы. Вот пример сортировки слайса из пакета sort стандартной библиотеки:


import "sort"

type Person struct {
    Name string
    Age  int
}

// ByAge implements sort.Interface for []Person based on the Age field.
type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

func SortPeople(people []Person) {
    sort.Sort(ByAge(people))
}

Погодите… Серьёзно? Мы вынуждены определять новый тип ByAge, который должен реализовывать три метода, чтобы соединить обобщённый (в смысле «многократно используемый») алгоритм сортировки и типизированный слайс.


Единственное, что должно заботить нас, разработчиков, — функция Less, которая сравнивает два объекта и предметно-зависима (domain-dependent). Всё остальное — шум и шаблонный код, необходимые лишь потому, что в Go нет обобщённых типов. И всё это приходится повторять для каждого типа, который мы хотим сортировать. И для каждого компаратора.


Обновление: мне указали на упущенный мной sort.Slice. Выглядит лучше, хотя под капотом использует рефлексию (ой!) и для сортировки требует наличия завершения слайса в виде функции-компаратора, что выглядит уродливо.


Когда утверждают, что Go не нуждается в обобщённых типах, это всегда объясняют «путём Go», который позволяет иметь многократно используемые алгоритмы, избегая приведения к дочернему типу (downcasting) interface{}...


Ну ладно. Тогда для облегчения ситуации было бы неплохо иметь макросы, способные генерировать этот нелепый шаблонный код, верно?


go generate: неплохо, но...


В Go 1.4 появилась команда go:generate для запуска генерирования кода из аннотаций исходного листинга. Ну, под «аннотациями» подразумеваются волшебные комментарии //go:generate со строгими правилами: «комментарий должен начинаться в начале строки и не иметь пробелов между // и go:generate». Если вставите пробел, ни один инструмент вас об этом не предупредит.


Таким образом решаются две задачи:


  • Генерирование Go-кода из других источников: схем ProtoBuf / Thrift / Swagger, языковых грамматик (language grammars) и так далее.
  • Генерирование Go-кода, дополняющего существующий код, вроде stringer, который генерирует метод String() для ряда типизированных констант.

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


Что касается второго случая, то многие языки, в том числе Scala и Rust, поддерживают макросы (упомянутые в документации по архитектуре), которые обращаются к AST исходного кода во время компилирования. Stringer импортирует парсер компилятора Go для прохождения AST. В Java такого макроса нет, но ту же роль играют обработчики аннотаций.


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


Кстати, вы знали, что в компиляторе Go есть аннотации/прагмы и условное компилирование, использующие этот синтаксис комментариев?


Заключение


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


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


До недавнего времени у нас не было реальных альтернатив там, где царит Go: в сфере разработки эффективных, нативных исполняемых файлов без мучений C или C++. Rust быстро развивается, и чем больше я с ним работаю, тем больше он мне кажется крайне интересным и тщательно продуманным. Я считаю, что Rust — один из тех друзей, с которыми сначала не так просто поладить, но потом хочется долго с ним общаться.


Что касается технических аспектов, то в сети есть статьи, утверждающие, что Rust и Go не конкурируют друг с другом, что Rust — это системный язык, поскольку в нём нет сборщика мусора, и тому подобное. Думаю, эти утверждения становятся всё менее верными. Rust поднимается всё выше в списке замечательных веб-фреймворков и хороших ORM’ов. Он наделяет приятной уверенностью, что «если код скомпилировался, то ошибки связаны с написанной мной логикой, а не особенностями языка, про которые я забыл».


В сфере контейнеров/service mesh сегодня наблюдаются интересные изменения, связанные с прокси Sozu, написанным на Rust. Компания Buoyant (разработчик Linkerd) создаёт новый Kubernetes-service mesh Conduit, в котором Go используется на уровне управления (вероятно, благодаря доступным Kubernetes-библиотекам), а Rust, благодаря своей эффективности и надёжности, — на уровне работы с данными.


Swift тоже начинает рассматриваться как альтернатива C и C++. Хотя его экосистема всё ещё слишком Apple-центрична, несмотря на доступность языка под Linux и на развитие серверных API и фреймворка Netty.


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


Несколько дней спустя...


Через три дня после публикации: реакция на статью оказалась невероятной. Она попала на главные страницы Hackernews (доходила до третьего места) и /r/programming (доходила до пятого места), а также получила поддержку в Twitter.


Комментарии, в целом, положительные (даже на /r/golang/), или хотя бы отмечают сбалансированность статьи и стремление к честности. Конечно, людям на /r/rust понравился мой интерес к Rust. Мне даже написал какой-то незнакомец: «Хочу лишь сказать, что ваш текст — самый лучший. Спасибо за все ваши усилия».


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


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


Я писал статью по вечерам две недели, но это было интересно. Делая серьёзную и честную работу, ты получаешь много хороших отзывов в интернете (если игнорировать несколько троллей и вечно недовольных). Очень мотивирует писать более глубокие статьи!

Mail.Ru Group 673,87
Строим Интернет
Поделиться публикацией
Похожие публикации
Комментарии 189
  • –5
    Люди зачем-то пытаются писать вне Гугла на языке, который Гугл придумал исключительно сам для себя. И удивляются, что получается плохо.
    • –5
      Ангулар был создан гуглом для гугла. Как по мне, когда он вышел, избавил многих от головной боли в написании одностраничных приложений и получалось хорошо.
      А Го хорош там, для чего он создавался. А те, кто его пытаются засунуть туда, где ему не место — потом хейтят.
      • +1
        за что заминусовали интересно? за ангулар?
        • 0
          Тут сидят молодые разработчики на Реакте, которым сказали на гурсах гикбрейнса, что Реакт — совершенство, а Ангулар — зло. Теперь они ходят по комментариям и минусуют все записи, где фигурирует Ангулар. Ну-ка товарищи, которые минусуют, скажите альтернативу первому Ангулар, когда он только вышел?
          • +1
            Knockout.js, Backbone, SproutCore как минимум, да вполне себе были альтернативы.
      • –2
        Потому что неплохо так получается. Да и разве в языке дело, а не в инженерных задачах и подходах к их решению?
        • +1
          Неплохо так получается в случаях, когда Ваши задачи и подходы в их решении случайно совпадают с гугловскими. Это делает Go похожим на какую-то библиотеку, а не на язык программирования общего назначения.
          • 0
            Написано: сервис массового импорта товаров, обработки изображений под highload, мобильный back для мессенджера, протокол обмена сообщениями в Ethereum, куски самого Ethereum, всякие мониторинги до кучи.
            Где что-то из этого совпадает с Гуглом, кроме мониторингов?
            • 0
              Думаете у гугла нет сервисов массового импорта, обработки изображений или бекендов мессенджеров? Именно для такого го и делался. Мне вот было бы интересно посмотреть на его применение в чём-то десктопном (с UI), в системном программировании, в мобильной разработке. Но таких кейсов нет, всё сводится к «принять 2 байта по сети, отправить 2 байта по сети».
              • –3
                Повторюсь, ну вот я сейчас в мобильной разработке пишу back. То есть гошка крутится к клиента на телефоне и подключается к фронту.
      • +1
        не для создания API <...>, а для реализации бизнес-логики

        странное противопоставление: API же не существует само по себе
        • +1
          Не совсем. Есть узкий спектр задач, когда необходимо реализовать внешнее API как обертку над существующим сервисом. С развязкой через tcp/rest/etc. При этом логики в таких обвязках довольно таки мало. Конвертация параметров, конвертация представления данных в запросе, etc.
        • 0
          > Главное отличие в том, что var позволяет объявлять без инициализации (потом приходится объявлять тип)
          Можно пример без инициализации? Т.к. инициализая все равно происходит.
          Считаю, что отличие в «non-declaration statement outside function body».
          Нельзя написать a := «test» вне функции.
          • +1

            Всё несколько глубже. Вот более-менее полный список нюансов про := и var.


            Факты:


            • явно указать тип можно только в var
            • наглядно сгруппировать (в скобках, с выравниванием) несколько переменных можно только в var
            • создать глобальные переменные может только var
            • создать переменные локальные для if, for, switch может только :=
            • задать смесь новых и существующих можно только в :=
              • использование := в этом случае избавляет от лишних строк объявляющих
                переменные да ещё и с обязательным указанием типа (что не всегда
                возможно — в переменной может хранится значение не экспортируемого
                типа возвращаемое некоторыми конструкторами)

            Выводы:


            • var более функционален для объявления новых переменных
            • var более нагляден для объявления новых переменных
            • var защитит от случайного изменения существующих переменных вместо объявления новых (go vet -shadow, go-nyet и т.п. могут детектить shadowing, что может снять претензию к := — а плюс в том, что не нужно заранее объявлять и указывать тип)

            И пара цитат из книжки Кернигана:


            • var name type или var := value в зависимости от важности инициализации начальным значением.
            • if err := ...; err != nil {} для уменьшения области видимости err.
          • +6
            странно, когда в языке, вроде как с строгой типизацией, дают возможность делать много неявных вещей…

            и казалось бы, язык прост, но в ходе написания реального кода для production, простым вещам требуется много уделять внимания, что позволяет выстрелить в ногу, теми же интерфейсами, а ресурсоемкую рефлексию использовать не хочется… а про надстройки работы с типами в go мне ничего не известно…

            добавили бы уже опцию компилятора\макрос к функции: если string пытаешься свести к int и panic — возвращать 0, один фиг явность в go условная…

            • +1

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

            • –1
              Да давно уже понятно, что Go это тупо хайп, который уже сейчас начал спадать. Практически все кто писал на серьезных языках типа С#/Java от Go просто чертыхаются. Ибо он реально ущербный какой-то.
              • –1
                Ну я, например, долгое вермя писал(и пишу) на C#/Java и мне Go нравится.
              • –1
                Спасибо, качественная статья, по совокупности пока не вижу ничего лучше Go а различные подводные камни можно тем или иным образом обходить.
                • 0

                  Имейте уважение к автору, который утверждает обратное:


                  До недавнего времени у нас не было реальных альтернатив там, где царит Go: в сфере разработки эффективных, нативных исполняемых файлов без мучений C или C++. Rust быстро развивается, и чем больше я с ним работаю, тем больше он мне кажется крайне интересным и тщательно продуманным. Я считаю, что Rust — один из тех друзей, с которыми сначала не так просто поладить, но потом хочется долго с ним общаться.
                • –1
                  Прямо видно как комьюнити сильно разделилось на тех кто пишет на Go, но жалуется на его недостатки и тех кто хвалит Rust за его эффективность, но фактически язык не востребован на рынке. С моей точки зрения Rust конечно эффективнее и лучше задуман, но читать код на нем мне просто больно. И это как борьба асемблера с Си, Си менее эффективен, но он удобнее. п.с. может Go 2.0 спасет нас всех? ))
                  • +1

                    Я за 3 дня нашел работу на Rust. Рынок бурлит!

                  • +1
                    Они получили высокую производительность и небольшое потребление ресурсов памяти/процессора/диска.


                    Прекрасная статья. Go решает на мой взгляд очень узкую проблему как метко заметил автор статьи. У меня была задача запустить тысячу нодов кластер и опа-на! я обнаружил, что тот же ТомКет просит под сервер с апп 380М памяти, а Го скромно укладывается и на 60М. Пришлось ударными темпами переписывать под Го.

                    • +2
                      TomCat просит 380, но сколько просил бы, скажем, Netty?
                    • +2
                      В Rust такая же проблема: поскольку в нём нет исключений (действительно нет, в отличие от Go)

                      Но ведь в Rust есть точно такой же panic и его так же можно поймать и отменить…
                      • 0

                        Подскажи как отменить панику, если в настройках стоит panic = abort?

                        • +3
                          Никак, но это и не поведение по-умолчанию.
                          Я-то в курсе, что panic в rust это совсем не то, чем кажется, просто в статье слишком безаппеляционно сказано, что исключения в Go есть, а в Rust нет.
                      • +8
                        Автор так старался поддерживаться нейтралитета, что записал два недостатка в преимущества.

                        Прекрасная стандартная библиотека
                        Это скорее в недостатки. Библиотека как для нового языка — непоследовательная и непродуманная. Чего только стоит совершенно разные подходы к парсингу в flag (через ссылочную муть с тонной копипасты и никакой декларативностью) и json/xml (через теги). При это flag — совершенно не критична к производительности, ведь парсинг запускается лишь однажды!

                        Стандартизированный тестовый фреймворк
                        А это в «Ужасный». Серьезно, фреймворк настолько отвратительный, что лучше его бы не было. В нем просто никаких преимуществ, даже банального Assert нету, а из-за отсутствия Generic написать свой, адекватный — крайне сложно. То есть банальный Assert из C# в Go выглядит так (в синтаксисе мог ошибиться, т.к. давно не писал):
                        a := fn()
                        if a != 10 {
                          t.Fail(fmt.Sprintf("Expect a to be (%v), actual: (%v)", 10, a);
                        }


                        • –4

                          Библиотека в целом отличная. Да, там есть проблемы, как упомянутые Вами так и другие, и да, если бы вся библиотека была написана исключительно гениями из альфа-центавра, не допустившими ни одной ошибки проектирования в её коде — было бы лучше. К несчастью для нас — её писали люди. Тем не менее, в среднем код стандартной библиотеки заметно лучше среднего, намного лучше поддерживается (исключая не очень удачные пакеты, от которых решили отказаться — вроде net/rpc), и очень помогает то, что весь этот функционал в принципе есть из коробки.


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


                          Тестовый фреймворк отличный. И свою задачу он выполнил — отсутствие assert-ов стимулировало попробовать писать тесты в табличном стиле, и помогло оценить этот подход. А потом assert-ы элементарно добавляются поверх, например: https://godoc.org/github.com/powerman/check

                          • +4
                            К несчастью для нас — её писали люди
                            Можете привести похожие примеры в стандартной библиотеке, к примеру, C#?

                            тесты в табличном стиле
                            Которые, кстати, тоже вручную пишутся. Класс. Так зачем такая надобиблиотека? И вы называете её классной просто потому что из-за неудобства вам приходится искать способ занять позу поудобнее? Очень похоже на Стокгольмский Синдром: «насильник был хороший, потому что я сам мог выбрать позу»
                            • 0
                              Плюс help для каждого флага занимает много места, и внутри тегов это бы смотрелось не очень
                              Ну а это вы уже совершенно уродский дизайн тегов в языке осуждаете. Какой наркоман до такого додумался — я не знаю. Во всех адекватных языках аналоги позволяют писать текст любой длины в анотациях без всяких проблем:
                              [ObsoleteAttribute("This property is obsolete. Use NewProperty instead.", false)]
                              public static string OldProperty

                              Я просто уже не стал добавлять в Ugly от себя и только прокоментировал неудачные «плюсы» из топика
                          • +5
                            Вот зря вы так про стандартную библиотеку. Она очень мощная.

                            Сравниим, к примеру, банальную сортировку.
                            Для Python всего то есть sorted, да у списков метод list.sort имеется. Весьма и весьма куцо. А вот у Go есть целый отдельный модуль sort. Тут и сортировка строк, и сортировка целых, и сортировка флоатов!

                            Или, например, в Go есть полезнейший пакет errors с инструментами для упрощения работы с ошибками. Он даже в отдельной директории находится, настолько он важен. В Python банально нету аналога для такого модуля.
                            • +8
                              А вот у Go есть целый отдельный модуль sort. Тут и сортировка строк, и сортировка целых, и сортировка флоатов!

                              Я же правильно понял, что это такая издевательская ирония?

                              • +4
                                Т.е. «не имеющий аналогов» «полезнейший пакет errors» не смутило? :)
                                • +7
                                  Кстати, «из коробки» начиная с 1.8 можно ведь сортировать и slice-ы! А чтобы это было быстро (рефлексия ведь), добавили специальную функцию reflect.Swapper. Так что библиотека не только мощная, но еще и гибкая!
                            • 0
                              Простите, но:
                              func Assert(cond_ bool, str_ *string){
                                  if !cond{
                                      panic(fmt.Sprintf("ERROR: ", str_))
                                  }
                              }

                              Вставляете в любое нужно место в качестве пре-, ин- и пост условия (а можно и просто panic), а если ещё внимательно почитаете про recover и гарантированный defer — жизнь станет на много легче.
                              Также вы, похоже, всё же не понимаете методику проведения проверок на допустимость значений в golang и как обрабатывать ошибки.

                              Да и обработка ошибок делается не так:
                              
                              if a, err := f(); !err{
                                  ...bla-bla-bla...
                              }
                               if a==0{
                                      ...bla-bla-bla..
                               }else{
                                      ...bla-bla-bla...
                                  }
                              }
                              
                              • 0
                                Всякие Assert нужны вовсе не для того, чтобы тупо кинуть панику, а чтобы напечатать детальное сообщение при фейле теста (мол ожидали то и то, получили это). И не просто fmt.Sprintf("expected %v, actual %v", expected, actual), а удобочинаемый дифф, чтобы потом не ломать себе глаза, высматривая «ну и где же тут, !»№%, отличие...". Хорошие сообщения о фейлах очень и очень помогают при анализе ошибок на всяких CI.

                                Для сравнения посмотрите (я даже не прошу внимательно читать, просто глянуть одним глазком), как это сделали, например, в Python.
                                type-specific equality function will be called in order to generate a more useful default error message
                                А если у вас свои структуры данных, то в фреймворк можно добавить хук для своего типа.

                                если ещё внимательно почитаете про recover и гарантированный defer…
                                Также вы, похоже, всё же не понимаете методику проведения проверок на допустимость значений в golang и как обрабатывать ошибки.
                                Ну может быть вы нас просветите. А то мы люди темные, читали невнимательно.
                                • 0
                                  Вы так написали, как-будто всю эту информацию нельзя сделать в голанге?)))
                                  Хотите своих хуков? Ну напишите декоратор, никто вам не мешает. Не нравится декоратор — реализуйте интерфейс, что в используется в структуре данных. Это всё делается и вполне доступно описано.

                                  Откройте книжку Роба Пайка, и посмотрите пример по выводу тайминга работы функции, реализованную через defer _внезапно_ с отложенной анонимной функцией (ленивые вычисления, привет Скале). И десятом строк ниже, как пробрасывать тип error сквозь любое количество уровней с навешиванием информации на каждом уровне.
                                  Например, стр. 180, 186-187 (полный и подробный вывод стека), стр. 187 и дальше — отличное описание recover. Если это всё вы внимательно читали, то должны были усвоить в том числе — почему в golang нет try/except/finnaly.
                                  • 0
                                    Вы так написали, как-будто всю эту информацию нельзя сделать в голанге?)))
                                    Ну так мы то говорим в контектсе «богатство стандатной библиотеки». Из коробки таких функций нету (вообще почти ничего нету).

                                    Откройте книжку Роба Пайка, и посмотрите пример по выводу тайминга работы функции, реализованную через defer _внезапно_ с отложенной анонимной функцией (ленивые вычисления, привет Скале). И десятом строк ниже, как пробрасывать тип error сквозь любое количество уровней с навешиванием информации на каждом уровне.
                                    Не могли бы вы снабдить ссылкими? Или цитатами. А то, знаете ли, в 21 веке я меньше всего хочу вручную отсчитывать номера страниц и строк.

                                    Если это всё вы внимательно читали, то должны были усвоить в том числе — почему в golang нет try/except/finnaly.
                                    Моя ваша не понимать. Речь была про «testing» и его убогость, а вы переводите в сторону холивара «экепшны в Go»… Тема, безусловно, интересная, но ее обсуждение совершенно бесперспективно.

                                    А про все эти трюки с навешиванием инфы через defer, пробросы ошибок через панику, оборачивания, дофичагачивание стектрейса (зачастую сделанного в месте логирования ошибки, а не ее генерации, лол) и прочее я в курсах… Правда, как показывает практика, зачастую обработка ошибок в Go выглядит так:
                                    if err != nil {
                                    	return nil, fmt.Errorf("some error: %v", err)
                                    }
                                    


                                    PS: "(ленивые вычисления, привет Скале)" — вы это так написали, будто бы это единственный известный вам язык, где есть ленивые вычисления…
                                    • –1
                                      Ну так мы то говорим в контектсе «богатство стандатной библиотеки». Из коробки таких функций нету (вообще почти ничего нету).

                                      Богатство?
                                      1. Я где-то употребил это слово?
                                      2. Мы говорим про то, что это вполне реализуемо, вообще без напряга, если это вам действительно надо.
                                      Не могли бы вы снабдить ссылкими? Или цитатами. А то, знаете ли, в 21 веке я меньше всего хочу вручную отсчитывать номера страниц и строк.

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

                                      Моя ваша не понимать. Речь была про «testing» и его убогость, а вы переводите в сторону холивара «экепшны в Go»… Тема, безусловно, интересная, но ее обсуждение совершенно бесперспективно.

                                      Думаю, Вы искренне не понимаете. Речь шла не про testing. Речь шла про отладку и посмертный дамп. И как раз в этой теме golang сделан несколько разумней чем try/except/finnaly чем в python. Хотя, лично моё мнение — golang не особенно далеко ушёл вперёд. Ещё раз: речь не про try\execpt\finally. Речь про отладку.

                                      (зачастую сделанного в месте логирования ошибки, а не ее генерации, лол

                                      Ну, не все руки одинаково прямые. Тут от языка не зависит.
                                      Правда, как показывает практика, зачастую обработка ошибок в Go выглядит так:

                                      Ну, частенько так и есть, и это в рамках логики обработки ошибок. (всё те же страницы, что я вам привёл).
                                      PS: "(ленивые вычисления, привет Скале)" — вы это так написали, будто бы это единственный известный вам язык, где есть ленивые вычисления…

                                      Я так написал, потому что именно Скала наиболее часто упоминается как язык с функциями не имеющими побочных эффектов, что позволяет ленивые вычисления и жестокую параллельность.
                                      • +3
                                        Изначально вы отвечали на коммент TheShock, где он писал дескать «это ложь, что библиотека в Go хорошая; например вот модуль testing просто ужасен».

                                        Речь шла не про testing. Речь шла про отладку и посмертный дамп.
                                        Как раз про testing речь и шла. Перечитайте внимательнее.

                                        Я так написал, потому что именно Скала наиболее часто упоминается как язык с функциями не имеющими побочных эффектов, что позволяет ленивые вычисления и жестокую параллельность.
                                        Вы, наверное, живете в параллельной вселенной. Ну или поисковики вам выдают статьи по скале, подстраиваясь под ваши интересы. А вообще, например, в статье на вики Scala даже не упоминается, зато есть и Haskell, и OCaml, и Scheme…
                                        • 0
                                          Речь шла не про testing

                                          Прочитайте внимательно мое сообщение, я процитировал статью. Речь была о «Стандартизированный тестовый фреймворк»
                                      • +2
                                        Если это всё вы внимательно читали, то должны были усвоить в том числе — почему в golang нет try/except/finnaly.

                                        try/except/finally — это действительно плохо. Но плохо оно тогда, когда в вашем языке есть альтернатива в виде монадической обработки ошибок, и это не случай Go.

                                        реализованную через defer _внезапно_ с отложенной анонимной функцией (ленивые вычисления, привет Скале)

                                        А можно для незнающих Go настолько детально, чем это аналогично ленивым вычислениям?
                                        • 0
                                          try/except/finally — это действительно плохо. Но плохо оно тогда, когда в вашем языке есть альтернатива в виде монадической обработки ошибок, и это не случай Go.

                                          Вне всяких сомнений Go, как достаточный и непротиворечивый язык сделан… так себе. Этот язык такой же мой, как и ваш. Но как по мне — он безопаснее питона.

                                          А можно для незнающих Go настолько детально, чем это аналогично ленивым вычислениям?

                                          Я сам сильно не знаю. Если я правильно понимаю:
                                          значение переменных-функций вычисляется тогда, когда к им происходит обращение. Т.е. не было обращения — не за чем считать.
                                          С помощью уже достаточно пропиаренной команды go — переменная функция может запустить анонимную функцию внутри себя с параллельным исполнением. Как приятный бонус — вложенная функция имеет доступ к обрамляющему пространству имён, и таким образом происходит реализация аналога статических переменных в функциях. Немного внезапно, но работает)
                                          • 0
                                            Этот язык такой же мой, как и ваш. Но как по мне — он безопаснее питона.

                                            Питон — так себе образец для подражания с точки зрения типобезопасности.

                                            С помощью уже достаточно пропиаренной команды go — переменная функция может запустить анонимную функцию внутри себя с параллельным исполнением.

                                            Но ведь она выполняется всегда, разве нет? То есть, это не ленивые вычисления, а всего лишь параллельные.
                                            • –1
                                              Питон — так себе образец для подражания с точки зрения типобезопасности.

                                              Давайте не будем скромничать. Питон в этом отношении — просто печален. И если принять за отправную точку, что голанг — замена питону, имхо — прогресс на лицо.
                                              Но ведь она выполняется всегда, разве нет? То есть, это не ленивые вычисления, а всего лишь параллельные.

                                              Если вызывать напрямую — то это произойдёт сразу. Если вызывать, как анонимную функцию внутри переменной-функции — тогда вовсе нет.
                                              • 0
                                                И если принять за отправную точку, что голанг — замена питону, имхо — прогресс на лицо.

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

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

                                                Если вызывать, как анонимную функцию внутри переменной-функции — тогда вовсе нет.

                                                В смысле, она не вызовется, что ли?
                                                • 0
                                                  Прогресс на самом деле тоже так себе, ...

                                                  Вы мне не покажете, в каком месте я написал «вот это прогресс!»?))

                                                  … и Go пользуется нулём из тамошних достижений.

                                                  А вы не подскажете, сколько среди программистов высоколобых математиков? Особенно, когда сделать надо было «ещё вчера»? И что же это высоколобые математики до сих пор не сделали идеальный язык программирования ещё 30 лет назад?))

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

                                                  Когда припрёт к стенке — на брайнфаке писать будете и говорить «спасибо, что даёте мне работать на самом лучшем в мире языке программирования и не даёте сдохнуть от голода».))

                                                  В смысле, она не вызовется, что ли?

                                                  В смысле, при получении ссылки на анонимную функцию внутри вызываемой функции — нет, в тот момент не вызывается.
                                                  • 0
                                                    Вы мне не покажете, в каком месте я написал «вот это прогресс!»?))

                                                    После того, как вы покажете, в каком месте я написал, что вы написали, что вот это прогресс.

                                                    Если серьёзно, «прогресс налицо» предполагает некоторую степень новизны. Можно, конечно, действительно ограничиться [статической] типобезопасностью одного лишь питона, но зачем? Ассемблер возьмите, ещё эффектнее будет.

                                                    А вы не подскажете, сколько среди программистов высоколобых математиков? Особенно, когда сделать надо было «ещё вчера»?

                                                    А это совсем другой вопрос.

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

                                                    И что же это высоколобые математики до сих пор не сделали идеальный язык программирования ещё 30 лет назад?))

                                                    Примерно потому же, почему ещё не доказали все теоремы.

                                                    Когда припрёт к стенке — на брайнфаке писать будете и говорить «спасибо, что даёте мне работать на самом лучшем в мире языке программирования и не даёте сдохнуть от голода».))

                                                    Не представляю, как должно так к стенке припереть. Под дулом пистолета, разве что.

                                                    В смысле, при получении ссылки на анонимную функцию внутри вызываемой функции — нет, в тот момент не вызывается.

                                                    А когда вызовется-то?
                                                    • –1
                                                      Можно, конечно, действительно ограничиться [статической] типобезопасностью одного лишь питона, но зачем? Ассемблер возьмите, ещё эффектнее будет.

                                                      А в ассемблере есть типизация из коробки? Управление памятью? Контроль границ массива? Ээээ… Вы зачем это написали?

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

                                                      Правда? Я правильно понимаю, что в голанге типизацию пользовать не принято?))

                                                      Не представляю, как должно так к стенке припереть. Под дулом пистолета, разве что.

                                                      Не дай Бог, конечно. Но, знаете, дерьмо случается.
                                                      Это возможно с каждым.

                                                      Примерно потому же, почему ещё не доказали все теоремы.

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

                                                      А когда вызовется-то?

                                                      В момент обращения к переменной-функции, полученной по ссылке при обращении к функции (которая обрамляет и возвращает ссылку на анонимную функцию)
                                                      • 0
                                                        А в ассемблере есть типизация из коробки?

                                                        Статической примерно столько же, сколько в питоне (до тайпхинтов, по крайней мере).

                                                        Я правильно понимаю, что в голанге типизацию пользовать не принято?))

                                                        Нет, неправда. Более того, я не понимаю, как вы сделали такой вывод.

                                                        Правда, я ещё и не понимаю, какое отношение Go имеет к формальной теории типов, ну да ладно.

                                                        Не дай Бог, конечно. Но, знаете, дерьмо случается. Это возможно с каждым.

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

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

                                                        Я не думаю, что он существует, более того.

                                                        Однако, это не повод не сравнивать и не оценивать имеющися языки.

                                                        В момент обращения к переменной-функции, полученной по ссылке при обращении к функции (которая обрамляет и возвращает ссылку на анонимную функцию)

                                                        Я могу из неё вытащить результат и передать в какую-нибудь другую функцию, но чтобы этот результат вычислился только тогда, когда эта самая другая функция этого потребует? Нужно ли мне писать эту другую функцию каким-то особым образом для этого?
                                    • +3
                                      Простите, но:
                                      func Assert(cond_ bool, str_ *string){
                                          if !cond{
                                              panic(fmt.Sprintf("ERROR: ", str_))
                                          }
                                      }

                                      То есть стандартная библиотека Гоу настолько отвратительная, что даже такая функция ее украсит?
                                      Кстати, функцию вы написали тоже отвратительную, но, видимо, евангелисты Гоу даже такое говно приводят в пример. В C# ценность AssertEquals в тестах в том, что я могу посмотреть, какое значение получилось, а не просто «получилось неправильное значение». А еще у вас на какую строку укажет эта ошибка? Там где случилась ошибка, или на библиотечную? И как на счет того, чтобы выполнить все тесты, а не упасть только на первом?

                                      Или я не понял — это был очередной сарказм, где в виде положительного отзыва вы приводите негативный пример языка, как anjensan?
                                      • –2
                                        Простите, но
                                        1. Я не считаю эту функцию отвратительной и пример прекрасной функции вы не удосужились привести. Считаем, предмет обсуждения исчерпан. Ну вкус и цвет все фломастеры квадратные. Если вам в консоли нужны цвета и бики — ну так не поленитесь: либо сами напишите, либо возьмите готовое. Стандартная библиотека в Го достаточная чтобы в пять строк поднять сервер и ещё в десять навесить пару обработчиков с шифрованием и параллельной обработкой.
                                        2. Вы меня с кем-то путаете. Я никоим образом не отношусь ни к евангелистам Го, ни вообще к профессиональным программистам. Чукча не писатель, чукча читатель. Если у чукчи дети в холодильнике греются — ему всё-равно какой формы холодильник. Если уж на то дело пошло, я предпочитаю Паскали и Обероны.
                                        3. Вам никто не запрещает посмотреть что получилось. Делается дописыванием параметров.
                                        4. Вы можете не падать в ассерте, а только вывести лог. Я думал, вы знаете, что есть хард ассерт(интересный паник), и софт ассерт (красная лампочка для ленивых).
                                        5. Это не был сарказм, это исключительно ваше желание увидеть то, что вы хотите увидеть. А не решить проблему.
                                        • +1
                                          пример прекрасной функции вы не удосужились привести
                                          Я же привел. Читаете через слово? Или вообще половину не читаете.

                                          Ну вкус и цвет все фломастеры квадратные
                                          При чем тут на вкус и цвет? Я описал, чем именно она плоха.

                                          ну так не поленитесь: либо сами напишите, либо возьмите готовое
                                          Сперва добейся? Но зачем мне это? В мире есть множество не таких отвратительных языков, как Гоу, языков с отличной стандартной библиотекой, в том числе библиотекой тестирования. Они уже добились и убрали в этом необходимость для меня.
                                          • –1
                                            Хорошо, хорошо. Я не читаю, «сперва добейся» на собеседованиях не спрашивают, и вам это не надо. Названий хороших языков, впрочем я так и не вижу.
                                            Ну, так если всё хорошо, что вы делаете в этой ветке?))
                                            • +4

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


                                              Это мне напоминает реакцию специалистов по PHP. Когда им указываешь, что в PHP переменные начинаются с доллара и это как-то странно, они, вместо того, чтобы сказать — да, есть косяк — начинают объяснять, что это и не проблема вовсе и вообще они сайты на раз-два делают.

                                              • 0
                                                Вы правда считаете, что вариант, который я предложил за 10 секунд — это итоговое решение? Или если в стандартную библиотеку не входит какая-то плюшечка, которую вы так любите (при этом задать вопрос Яндексу лень) — это сразу плохой язык?))

                                                И всё-таки вынужден повторить свой вопрос:
                                                Ну, так если всё хорошо, что вы делаете в этой ветке?))

                                                Я так полагаю, что обсуждение всегда начинается, чтобы найти решение проблемы (если она есть), а не заявить «язык гавно, потому что я хоть библиотеку не смотрел, ничего не искал, но вот в том языке, которому уже 35 лет (с использованием библиотек языка, которому уже 46 лет) в стандартной библиотеке это есть!»
                                                • 0
                                                  Вы правда считаете, что вариант, который я предложил за 10 секунд — это итоговое решение?

                                                  Не знаю. Обсуждаем то, что вы представили.


                                                  Или если в стандартную библиотеку не входит какая-то плюшечка, которую вы так любите (при этом задать вопрос Яндексу лень) — это сразу плохой язык?))

                                                  Нет, но если в библиотеку не входит какая-то плюшечка, которая мне нужна, то библиотека хуже, чем библиотека, в которой эта плюшечка есть.


                                                  Я так полагаю, что обсуждение всегда начинается, чтобы найти решение проблемы (если она есть)

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


                                                  Ну и чтобы найти решение проблемы, надо для начала признать её существование.

                                                  • 0
                                                    Не знаю. Обсуждаем то, что вы представили.

                                                    Понятно. Не знаю, но обсуждаем.

                                                    Нет, но если в библиотеку не входит какая-то плюшечка, которая мне нужна, то библиотека хуже, чем библиотека, в которой эта плюшечка есть.

                                                    Ещё раз повторю свой вопрос:
                                                    Ну, так если всё хорошо, что вы делаете в этой ветке?))

                                                    Если бы автор не говорил, что она превосходна, никто бы ему ничего не сказал.

                                                    Итак, делаем поиск по статье и всему обсуждению с шаблоном «превосх». Есть вот это:
                                                    Стандартная библиотека Go действительно великолепна, особенно применительно к разработке сетевых протоколов или API: в ней есть HTTP-клиент и сервер, шифрование, форматы архивирования, сжатие, отправка писем и так далее. Есть даже парсер HTML и довольно мощный движок шаблонов, что позволяет создавать текст и HTML с автоматическим экранированием (automatic escaping) для защиты от XSS (к примеру, используется в Hugo).

                                                    В контексте написанного явно следует «хорошая библиотека», а не «библиотека на все случаю жизни». Думаю, тут может надо задать вопросы переводчику? Great — прекрасный, красивый, сильный. Но не как rich — богатый или big — большой.
                                                    • 0
                                                      Понятно. Не знаю, но обсуждаем.

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


                                                      Ну, так если всё хорошо, что вы делаете в этой ветке? ))

                                                      Ну вообще я хотел сказать вам, что вы игнорируете что вам пишет TheShock.


                                                      Вы там что, курите? В каком месте было написано «превосходная библиотека» в статье?

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

                                                      • 0
                                                        Вообще логично, что я никак не могу знать, считаете ли вы решение, которое вы предложили за 10 секунд итоговым.

                                                        Т.е. то, что с самого начала условия, что должен делать код описаны не были (потом, задним числом было «а посмотрите вот тут»).
                                                        Как вопрос был задан — так я и ответил. Что-то ещё хотите? Ну так опишите, что вам надо.
                                                        Ну вообще я хотел сказать вам, что вы игнорируете что вам пишет TheShock.

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

                                                        Это меняет. Фраза безотносительно к языку «вот этого нет в библиотеке, поэтому язык — гавно» — это то, из-за чего программисту, претендующему на трудоустройство в мою контору — я, как директор и владелец, сразу говорю «спасибо, до свидания».
                                                        • +2
                                                          Почему я должен брать в расчёт то, что мне не интересно или ошибочно сформулировано изначально?

                                                          Потому, что вы участвуете в диалоге, и, если вас не интересует, что говорит собеседник — что вы вообще делаете в этой ветке? :)

                                                          • –2
                                                            Потому, что вы участвуете в диалоге, и, если вас не интересует, что говорит собеседник — что вы вообще делаете в этой ветке? :)

                                                            Объясняю. Я — говорю про go. Говорю про его косяки и сильные стороны. Товарищ ввалился со скудной аргументацией из области C# и на том основании, что go — не C#, делает выводы которые я должен принять к сведению. При этом, он больше не появляется, и если я даже лично сделаю ему хорошо — он этим пользоваться не будет. Зачем он написал то, что написал?
                                                            • 0
                                                              Зачем он написал то, что написал?

                                                              В статье была ложь о, цитирую: «Go поставляется с прекрасным тестовым фреймворком в стандартной библиотеке». Мне не нравится маркетологическая лодь, потому я ее опроверг, в т.ч. на примере.
                                                  • 0
                                                    потому что я хоть библиотеку не смотрел, ничего не искал
                                                    Почему не смотрел? Я вполне себе писал на Go, так что имею мнение прям с передовой, в отличии от вас:
                                                    1. Я не гофер. И даже не программист.

                                                    Тестовая либа в языке — отвратительная, приходится искать сторонние решения. Процитирую вам статью, которую вы, видимо, читали через строчку, раз пропустили эту фразу:
                                                    Go поставляется с прекрасным тестовым фреймворком в стандартной библиотеке
                                                    Я спорил именно с этим утверждением. Тестовая либа/фреймворк в Гоу — отвратительная, а автор утверждает, что она прекрасная. Нету в ней ничего прекрасного, приходится пользоваться или сторонними решениями, или писать глючные костыли типа вашего.
                                            • +2
                                              Простите, но:
                                              func Assert(cond_ bool, str_ *string){
                                                  if !cond{
                                                      panic(fmt.Sprintf("ERROR: ", str_))
                                                  }
                                              }
                                              1. Я не считаю эту функцию отвратительной и пример прекрасной функции вы не удосужились привести.
                                              Ну это уже на цирк какой то смахивает. Вот вам по пунктикам, в порядке моего офигевания:
                                              1. code-style — что за 'cond_', зачем 'str_'; не надо так;
                                              2. code formatting — куда делись пробелы между ) и {; go fix такой код не пропустит;
                                              3. а нафига, простите, тут '*string', вместо 'string'; вопрос риторический, можете не отвечать…
                                              4. придирочка — гораздо лучше сделать 'Assert(cond bool, fmt strging, a ...interface{}) (надеюсь не надо пояснять зачем); но я допуская что вы, как истинный сусликгофер, хотите сделать две функции Assert и Assertf, допустим;
                                              5. в коде тупейшая ошибка! вместо «ERROR: » нужно писать «ERROR: %s».

                                              Как по мне, так это вполне себе «отвратительный код».
                                              • –2
                                                1. code-style — что за 'cond_', зачем 'str_'; не надо так;

                                                Я вас не заставляю пользоваться таким оформлением. Не нравится — сделайте себе как нравится. Мне вот так удобно. На вкус и цвет — все фломастеры квадратные.
                                                code formatting — куда делись пробелы между ) и {; go fix такой код не пропустит;

                                                При сохранении или компиляции все пробелы будут вставлены. Это правда проблема?
                                                3. а нафига, простите, тут '*string', вместо 'string'; вопрос риторический, можете не отвечать…

                                                Не отвечаю. Таки зачем ви спрашивали?
                                                4. придирочка — гораздо лучше сделать 'Assert(cond bool, fmt strging, a ...interface{}) (надеюсь не надо пояснять зачем); но я допуская что вы, как истинный сусликгофер, хотите сделать две функции Assert и Assertf, допустим;

                                                1. Я не гофер. И даже не программист.
                                                2. Хотите сделать универсальную функцию — сделайте. Я слабал пример за 10 сек, по поставленным условиям (которые вы теперь дописываете задним числом). Что не так?
                                                5. в коде тупейшая ошибка! вместо «ERROR: » нужно писать «ERROR: %s».

                                                Вот видите, какой вы молодец?! Вы же всё знаете, всё умеете. Ну так и напишите себе функцию без лишних слов.

                                                В чём проблема то? Я так и не понял))
                                                • 0
                                                  В чём проблема то? Я так и не понял))
                                                  1. Я не гофер. И даже не программист.
                                                  При этом активно спорите, что-то доказываете (частенько дичь всякую).
                                                  В этом, собственно, и проблема :)
                                                  • –2
                                                    Вы задали вопрос. Я на него ответил: можно, например, сделать вот так.
                                                    Я не спорю, и не доказываю. Мне вообще наплевать на golang.
                                                    Я показываю возможный путь решения — с вашей точки зрения — проблемы, с моей точки зрения — задачи.
                                                    Возможно решение задачи показал? Показал.
                                                    В чём проблема?
                                                • 0
                                                  в коде тупейшая ошибка! вместо «ERROR: » нужно писать «ERROR: %s».

                                                  И компилятор за это по рукам не даст? А что случится, рантайм-ошибка, креш или что-то ещё?

                                                  Круто, уже даже сишные компиляторы несколько лет как умеют на это ругаться.
                                                  • +1
                                                    И компилятор за это по рукам не даст? А что случится, рантайм-ошибка, креш или что-то ещё?

                                                    Не даст, будет очень странная строка. Вот плейграунд:
                                                    fmt.Println("Hello, playground") // Hello, playground
                                                    	
                                                    str_ := "123";
                                                    fmt.Println(fmt.Sprintf("ERROR: ", str_)); // ERROR: %!(EXTRA string=123)
                                                    fmt.Println(fmt.Sprintf("ERROR: %s", str_)); // ERROR: 123
                                                    	
                                                    result := fmt.Sprintf("ERROR: ", str_)
                                                    fmt.Println(result) // ERROR: %!(EXTRA string=123)
                                                    fmt.Println(len(result)) // 27


                                                    На всякий случай я показал, что это действительно формируется такая строка, а не просто ошибка в консоль пишется и как результат — может улететь в базу в виде запроса или еще что.
                                                    • +1
                                                      Ну, то есть, стек не портится, буферы не переполняются. И то хорошо.
                                                • +1
                                                  3. Вам никто не запрещает посмотреть что получилось. Делается дописыванием параметров.

                                                  Кстати, это интересно было бы посмотреть. Можно пример?
                                          • 0
                                            Здесь применяется мантра «Не взаимодействую с помощью общей памяти, делай память общей с помощью взаимодействия».

                                            Этот принцип не подходит для одной из самой важной части бэкенда — базы данных или разного рода кешей. Когда есть большая структура-граф объектов в оперативной памяти то нет никакого смысла хранить копию этой структуры в разных горутинах или потоках не говоря уже о необходимости синхронизации частичных обновлений этой структуры между ними и опасности race condition. Для таких задач нужна именно общая память и параллельный доступ к памяти из разных потоков
                                            • 0

                                              Похоже, Вы просто не понимаете суть подхода. Структура хранится в одной горутине — выделенном "менеджере" этой структуры. А все остальные горутины получают доступ к ней общаясь по каналам с горутиной-менеджером. Ничего копировать и синхронизировать не нужно. Единственная ситуация, в которой этот подход не очень хорошо работает — когда жёстко не хватает производительности, и замена каналов на один (или группу) мьютексов позволяет заметно всё ускорить.

                                              • +3
                                                Структура хранится в одной горутине — выделенном «менеджере» этой структуры. А все остальные горутины получают доступ к ней общаясь по каналам с горутиной-менеджером

                                                В этом случае решение превратится в однопоточное — только одна горутина (менеджер структуры) будет работать а все остальные будут только посылать инструкции. Понятно что при обработке запросов и формирования инструкций будут задействованы все ядра, но дальше все они будут выстраиваться в очередь и ждать пока один единственный поток их не обработает. Если на обновление структуры объектов глобальный лок еще оправдан для избежания race-condition, то инструкции на чтения структуры (базы данных или кеша) могут безопасно выполняться параллельно но не могут из-за такого вот подхода когда только одна горутина будет работать со структурой
                                                • 0

                                                  Если эта структура — god object, который хранит все данные приложения — то да. Не надо так делать. А если таких структур куча (и у каждой своя горутина-менеджер) — то нет, не превратится в однопоточное.


                                                  Насчёт RWMutex Вы правы, он позволит распараллелить доступ. Но обычный Mutex точно так же выстроит всех в одну очередь, как и горутина-менеджер.

                                            • –1
                                              В статье «Less is exponentially more» Роб Пайк почему-то относит обобщённые типы и наследование к «типизированному программированию» и говорит, что предпочитает композицию, а не наследование.

                                              Наследование нужно для того чтобы оптимизировать логику декораторов которые в случае композиции будут создавать отдельные объекты в рантайме. Допустим у нас есть класс DBConnection объект которого представляет собой соединение с базой данных. Применяя композицию обычно создают отдельный класс Repository который представляет собой crud-операции с таблицами базы данных, который в конструкторе создает new DBConnection(...) и использует его для взаимодействия c базой данных. А вот применяя наследование вместо композиции класс Repository отнаследуется от DBConnection и добавит нужный код работы с crud. И здесь принципиальное отличие — в случае композиции при создании объекта Repository будет создано два объекта в рантайме (сам Repository и объект DBConnection) а применяя наследование — только один объект. А в случае если у на будет цепочка из 10 различных сущностей которые что-то добавляют и переопределяют то с композицией это уже 10 рантайм-объектов а с наследованием только один вне зависимости от длины этой цепочки (да хоть тысячу сущностей). В это и суть наследования — оно позволяет вынести в compile-time много работы экономя cpu-циклы и память
                                              • –13

                                                Все Ваши проблемы синтетические. Как меня задолбал Хабр подобными постами.

                                                • 0

                                                  В общем и целом статья отличная! Немного комментариев:


                                                  так что без проблем можно иметь сотни, и даже тысячи горутин

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


                                                  Go игнорирует достижения современного проектирования языков

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


                                                  Трудно понять, какие типы реализуют конкретный интерфейс

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


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

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


                                                  Синтаксис := позволяет случайно «затенить» переменную.

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


                                                  Так что исключения в Go есть, он использует их внутри себя, но вам не разрешает.

                                                  Чушь (может просто перевод не очень удачный?). Исключения для обработки ошибок рекомендуется использовать исключительно внутри своего пакета, как в вышеупомянутом json — чтобы на тех, кто просто использует ваш пакет, это никак не отражалось и в их код случайные паники не вылетали.


                                                  Кошмар управления зависимостями

                                                  С появлением vgo есть веские основания надеяться, что через год (когда vgo станет официальным go) этой проблемы уже не будет. Причём не просто не будет, а всё будет работать лучше, чем сейчас в других языках!

                                                  • 0
                                                    Очень много полярных комментариев про Go, и меня даже сейчас смущает эта политизированность.
                                                    Но, всё же, я решил взять Go как рабочий язык.
                                                    1. Будем считать, что я слез с python (Go по духу близок, типизация это ооочень сильное послабление мне — невнимательному погроммисту)
                                                    2. Go (на мой исключительно субъективный взгляд) — это испорченный Oberon. А Oberon я действительно люблю. Имхо, очень продуманная вещь.
                                                    3. Стандартная библиотека почти во всём заточена под веб. Шифрование, шаблоны, обработка строк, изображения. Всё это работает с сетью, отображает результат в браузере (что тоже кросплатформенно, как и сам бинарник на выходе Go).
                                                    4. Производительность, всё-таки, не последнее, на что смотрит владелец ресурса. 20 серверов на python или один на Go — разница есть. Конкурентность вносит свою сложность (и коллеги из лагеря Erlanga убедительно показали, что в Go она сделана совсем не лучшим образом), но всё же, эффективность исполнения кода заставляет принять Go к сведению.
                                                    5. У Go таки есть странные решения. Типа, интерфейсов. Имхо, опасная штука с их автоматическим удовлетворением. Но стандартные типы никто не запрещал. Я пока профан в Go, так что посмотрим. Крики про отсутствие ООП мягко говоря — сильно преувеличены. Сделано своеобразно, но оно там есть.
                                                    6. В плане разработки у Go есть явные преимущества по причине наличия интересных IDE. LiteIDE, к слову. Качество контроля кода на Go я бы оценил где-то на 50-70% выше, чем в python на этапе разработки.

                                                    Ничего не скажу в духе книги Саммерфильда «ах, какая прелесть, ну просто паром писаю», но по сравнению с python — да, это безусловно шаг вперёд.
                                                    • 0
                                                      Если честно, я тоже считал это проблемой
                                                      А как быть с рефакторингом? Как IDE узнает нодо изменить имя метода или нет если в интерфейсе мы поменяли?
                                                      • 0
                                                        Если поменяли в интерфейсе, то менять надо однозначно. Интерфейс — это аналог абстрактного класса для методов.
                                                        • 0
                                                          Если поменяли в интерфейсе, то менять надо однозначно
                                                          А если Я нигде не передаю эту структуру там где ожидают этот интерфейс?
                                                          • 0
                                                            Если Вы не передаёте эту структуру, но этот интерфейс ожидается, значит тот интерфейс, который Вы меняете — должен наследоваться от интерфейса, который лежит уровнем ниже, и именно он должен быть ожидаемым интерфейсом. Поэтому, у гоферов есть правило: интерфейс должен быть как можно уже, интерфейсов должно быть много. А тот, кто создаёт тип от interface{} — таких бьют линейкой по рукам, пока не посинеют (руки).
                                                      • 0
                                                        не та ветка
                                                        • 0
                                                          hype driven development как он есть :)
                                                          • 0

                                                            Аргументация? :)

                                                            • +5
                                                              Хреновенький язык-то. Да и сам Пайк не такая светлая голова, как некоторые иные разработчики языков программирования.
                                                              Прошли мимо многих, придуманных до них, классных находок. Что приобрели — не понятно.
                                                              Аргументация, зачем так сделано, хромает.
                                                              И вся популярность языка обусловлена не высокой согласованностью фичей, не их разнообразием, не решением наболевших проблем других языков при сохранении того, что не было поломано, не прагматичностью при новаторском подходе, а PR-отделом гугла с их бабками, кучей статей, конференций и тп.
                                                              • 0
                                                                Главная фича этого языка — простота, и он отлично конкурирует скажем с питоном. Вообще язык отличный. А про PR от гугла как-то надуманно, зачем? Это же не коммерческий проект типа Java. Вам гугл не предъявит за использование языка в вашем проекте, как оракл.
                                                                • +5
                                                                  Почему простота — это фича, а не баг? Им простота понятно зачем, им проще компилятор ваять, а мне?
                                                                  Был простой бейсик, и где он нынче?
                                                                  Причем в го простота граничит с примитивностью.
                                                                  Может быть, хотели компенсировать тулингом, как в яве в итоге получилось. Но при это замутили дактайпинг, который с тулингом не особо хорошо дружит. Странно.
                                                                  Почему вы с питоном сравниваете статически типированный язык? Любой скрипт при таком сравнении всосёт.
                                                                  И у вас неверные сведения о яве, есть OpenJDK, который почти идентичен оракловскому хотспоту, javaFX только нет.
                                                                  • 0
                                                                    Потому что вы сейчас пишете не на асемблере, и причина сложность, вы предпочитаете что-то более простое и удобное в работе. Go такой же простой в использовании как питон, но производительнее, вот почему он де факто на рынке конкурирует с питоном, там где раньше использовали бы питон (тот же веб, сервисы) сейчас пишут на Go. При сопоставимой простоте/сложности вы получаете бонусом производительность.
                                                                    • +3
                                                                      Потому что вы сейчас пишете не на асемблере, и причина сложность, вы предпочитаете что-то более простое и удобное в работе.
                                                                      Ну так ведь наоборот, высокоуровневые языки сложнее. Не путайте пожалуйста сложность самого языка (количество фич в нем, абстракций и т.п.) и сложность написания кода на этом языке. Так-то Brainfuck вообще архипростой, всего 8 символов, 1 тип данных… Красота, а не язык!

                                                                      Go такой же простой в использовании как питон

                                                                      Ох… ну раз уж я тут в другой ветке поднял тему сортировок… давайте сравним еще раз. Пример из статьи:
                                                                      оimport "sort"
                                                                      
                                                                      type Person struct {
                                                                          Name string
                                                                          Age  int
                                                                      }
                                                                      
                                                                      // ByAge implements sort.Interface for []Person based on the Age field.
                                                                      type ByAge []Person
                                                                      
                                                                      func (a ByAge) Len() int           { return len(a) }
                                                                      func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
                                                                      func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
                                                                      
                                                                      func SortPeople(people []Person) {
                                                                          sort.Sort(ByAge(people))
                                                                      }

                                                                      И вот аналогичный код на Python (даже с тайпхинтами!, без них еще проще):
                                                                      import typing
                                                                      
                                                                      class Person(typing.NamedTuple):
                                                                          name: str
                                                                          age: int
                                                                      
                                                                      def SortPeople(people: typing.List[Person]):
                                                                          people.sort(key=lambda p: p.age)
                                                                      

                                                                      А теперь представим, что понадобилось сортировать еще и по Name (в зависимости от выбора пользователя)… А потом добавилось еще 5 новых полей, по которым тоже надо сортировать…
                                                                      • 0
                                                                        А с удобной compile-time рефлекшном можно и такое забацать, а потом писать
                                                                        AssertEx.AllPropertiesAreEquals(expected, actual)

                                                                        Даже если авторы компилятора забыли это написать. Ах да, пользователи языка — смерды, и не имеют права на написание обобщений…
                                                                        • 0
                                                                          Ну справедливости ради такое можно и в Go замутить. Сигнатурка будет не ахти func (a, b interface{}), но сделать приличный репортинг дифа технически вполне можно. Без дифа даже из-коробки имеется reflect.DeepEqual.
                                                                          • 0

                                                                            Я Go плохо знаю. interface{} в данном случае должен быть интерфейсом, поля которых мы сравниваем? Если да, то проверит ли он в compile time что a и b реализуют этот интерфейс, причем для a он является наиболее специфичным?

                                                                            • 0
                                                                              Нет. Это аналог object.
                                                                              • +1
                                                                                Ну, тогда это совсем не то. В том-то и дело, что пример который я дал один раз строит по типам лямбду, а потом просто её вызывает с нулевым оверхедом. А Go вариант больше похож на старый-добрый рефлекшн.
                                                                                • 0
                                                                                  В том-то и дело, что пример который я дал один раз строит по типам лямбду, а потом просто её вызывает с нулевым оверхедом
                                                                                  А в чем профит если не секрет? Дабы юнит тесты выполнялись на пару ms быстрее? :)

                                                                                  Go вариант больше похож на старый-добрый рефлекшн.
                                                                                  Это и есть старый добрый рефлекшн.
                                                                                  • +2
                                                                                    А в чем профит если не секрет? Дабы юнит тесты выполнялись на пару ms быстрее? :)

                                                                                    Ну, отчасти для образовательных целей, отчасти да, чтобы было быстрее. Т.к. эта функция вызывалась по сути в нескольких сотнях тестов для проверки тысяч объектов в каждом. Экономия даже пары десятков секунд на выполнение тестов на ровном месте это уже неплохо, как мне кажется.
                                                                                    • 0

                                                                                      а вы сравнивали тайминги? Я в тестах reflect.DeepEqual, точнее уже готовой обертки типа testify.assert, чем это отличается от вашего?

                                                                                      • 0
                                                                                        Сравнивал. Например получить свойство через рефлекшн в 1000 раз дольше, чем получить его напрямую (линк). Если у нас несколько сотен тестов (возьмем 250), в каждом 1000 объектов, у каждого из которых 5 свойств, которые мы сравниваем парами (еще х2), то это 2500000 вызовов. Используя данные по ссылке, это займет 0.53 секунды при прямом вызове, и 482 секунды (8 минут) через рефлекшн. Как по мне, разница существенная, даже если тесты гоняются параллельно.
                                                                                        • 0

                                                                                          вы сравниваете С# реализации, а не с Go reflect.DeepEqual. Код нужно скомпилировать, заранить тесты. Насколько существенная разница будет для Go?

                                                                                          • +3
                                                                                            Ну я не проверял, но думаю, что вряд ли Go каким-то магическим образом сильно отличается, поэтому можно принять данные по шарпу с точностью до порядка. Если вы считаете иначе, то прошу дать ссылку на сравнение, потому что у меня после беглого гугления информации не нашлось.
                                                                                            • +3
                                                                                              Можно подумать в Go рефлексия молниеносно быстрая.
                                                                                              Вот например баг. Там указывают вполне реалистичные оценки
                                                                                              reflect.Value.Call is ~65-80x slower than other invocation types


                                                                                              Как я понимаю PsyHaSTe не призывал так делать для Go (для и шарпа походу больше для just-for-fun сделал, что есть респект). В Go такое банально не сделаешь — ну нету в нем рантаймовой генерации байткода / компиляции на лету…
                                                                                              • 0

                                                                                                если это реально узкое место, то можно и код генерить. Мне интересно сравнить всю цепочку

                                                                                • –4
                                                                                  Я Go плохо знаю.

                                                                                  но достаточно, чтобы критиковать?

                                                                                  • +1
                                                                                    Я использовал информацию, предоставленную в статье. Если у вас есть обоснованное сомнение в этих выводах, то я с радостью почитаю статью-ответ. Я не трогал каких-то других особенностей языка, о которых не знал.
                                                                            • +1
                                                                              Справедливости ради, код выше переписывается так:

                                                                              import "sort"
                                                                              
                                                                              type Person struct {
                                                                              	Name string
                                                                              	Age  int
                                                                              }
                                                                              
                                                                              func SortPeople(people []Person) {
                                                                              	sort.Slice(people, func(i, j) bool {
                                                                              		return people[i].Age < people[j].Age
                                                                              	})
                                                                              }
                                                                              


                                                                              и разница внезапно становится не столь значительной.
                                                                          • +1
                                                                            Почему вы с питоном сравниваете статически типированный язык? Любой скрипт при таком сравнении всосёт.
                                                                            PHP с его type declarations будет построже, чем Go.
                                                                        • +1
                                                                          Хотелось бы чтобы место go занял d. Но без бабок и pr это сейчас или невозможно или практически невозможно.
                                                                          • +1
                                                                            Я думаю что хорошим пиаром для языка будут сильные проекты на нем. Сейчас очень хайповый и популярный проект Докер, он сам по себе и пиарит Go
                                                                            • 0
                                                                              Pr докера это отдельная тема. На d есть прекрасный проект vibe.d брал типы в разных бенчмарках. И тем не менее все ещё никому не нужен
                                                                              • 0
                                                                                Не понимаю, почему на выбор языка программирования вообще должен влиять хайп. Мы инженеры, а не модельеры какие-нибудь.
                                                                                Рациональный выбор нам, по идее, не чужд.
                                                                                Разбираться в дизайне языков программирования, уметь сравнивать их, и выбирать под задачу наиболее подходящий инструмент — не это ли части нашей работы?
                                                                                • +1
                                                                                  Потому что хайп влияет на то, сколько людей уже занимаются этим языком, а количество занимающихся этим языком людей влияет на количество библиотек и количество вопросов на SO.

                                                                                  Да и, в конце концов, больший хайп означает, что большее количество людей могут решать проблемы на этом языке. Это я могу и вы, судя по некоторым комментам, можете прочитать такой ответ на вопрос о том, почему totaliy checker иногда странно себя ведёт, как будто бы не согласуясь с тайпчекером, но требовать этого от каждого инженера — это перебор.
                                                                            • –1
                                                                              Вы правы. Аргументация необходимости фич — откровенно слабая. Но теперь представьте себе: golang — замена python. Разве это плохо? Приведённый пример с типизацией для python — не серьёзный. Никакой типизации в нём нет.
                                                                              В golang вполне себе настоящая.
                                                                        • –3
                                                                          Go подкупает простотой и минимализмом, он просто вынуждает всех писать простой, понятный, линейный код без всяких извратов в стиле «смотри как я могу!». После ентерпрайзных проектов на C#, с огромной complexity и купой абстракций, Go стал прям глотком свежего воздуха. Но потом приходит осознание того что разруха в головах, и на том же привычном C# можно писать простой и понятный код. Потом попробовал в деле .NET Core (кроссплатформенный, сильно похудевший, переработанный .NET для тех кто не знает), и знаете, разработка очень похожа на Go, только с мощностью C# без компромиссов и нормальным тулингом. Единственная проблема это командная разработка — если ты проникся простым и понятным кодом и можешь себя контролировать не использовать фичи C# не к месту, коллеги часто такое воротят что глаза на лоб лезут. На GO им делать это было бы сложнее.
                                                                          • –1
                                                                            Go заметно проще других языков, но он далеко не прост и не минимален.
                                                                            С# лично мне не нравится своим синтаксисом, но и в Go можно написать так, что глаза сломаешь. Тут немного легче при принудительном использовании gofmt. Стиль везде один, да.
                                                                            Хотите глоток свежего воздуха? Вот прям настоящего свежего воздуха? Поковыряйте на досуге BlackBox/Component Pascal и Oberon-07 — особенно последний покажет вам минимализм и простоту.

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

                                                                            У Go есть свой путь, безусловно. Но пИсать паром — ну нет, пока ещё рановато. Вполне себе обычный, предсказуемый ответ на злобу дня.
                                                                            • +1
                                                                              Поковыряйте на досуге BlackBox/Component Pascal и Oberon-07 — особенно последний покажет вам минимализм и простоту.

                                                                              Brainfuck ещё минималистичнее и проще.
                                                                              • 0
                                                                                Брайнфак не выполняет главное указание, вынесенное в эпиграф сообщения об Обероне: «Сделай максимально просто, но не примитивно. А. Эйнштейн».
                                                                                Оба перечисленных языка ровно такие: просты, но не примитивны.
                                                                                • 0
                                                                                  Осталось разобраться с какими-нибудь более-менее формальными критериями простоты, примитивности и прочего.
                                                                                  • 0
                                                                                    Ну, например.
                                                                                    Форма объявления с присвоением в голанге — целых ТРИ!
                                                                                    В Обероне — одна. Не могу сказать, что ух какой контекст, но тем не менее.
                                                                                    В голанге есть форма присвоения значения константам iota. С возможность автоматических сложных выражений — ух. Но на сколько iota реально востребована?
                                                                                    В голанге форма привязки функций к типам спёрта из Оберона. Отличается множественными возвращаемыми значениями (при явном указании — именованными значениями), в Обероне — возвращаемое значение — одно. Но встретить просто return без возвращаемого значения в голанге… Немного вводит в ступор.
                                                                                    В голанге нужно явно получать адрес переменной при передаче по ссылке. Оберон это делает автоматически и голова не болит.
                                                                                    В голанге понятие пакета сделано странно. Обероновское MODULE куда проще и понятней. Экспорт в голанге большой буквы, и сокрытие с маленькой — ну это какая-то фигня. В Обероне в конце имени ставь звёздочку — и вот тебе экспорт.
                                                                                    В голанге, не смотря на то, что ООП слизано с обероновского — интерфейс штука скользковатая. В Обероне просто нет интерфейсов. В Компонентном Паскале есть абстрактные классы, абстрактные методы, расширяемые методы, классы с ограниченным расширением, частично абстрактные классы, ненаследуемые классы, и… Вроде всё. Но при этом Компонентный Паскаль проще голанга раза в два. Там негде и нечем стрелять себе по ногам.
                                                                                    Короче, голанг, лучше питона. Но он не прост и не самый логичный язык.
                                                                                    • 0
                                                                                      Форма объявления с присвоением в голанге — целых ТРИ!

                                                                                      Кто третья?


                                                                                      Но на сколько iota реально востребована?

                                                                                      Вполне востребована и выполняет свою функцию: уменьшает количество ошибок, которые были бы при ручном объявлении перечислений.


                                                                                      Но встретить просто return без возвращаемого значения в голанге…

                                                                                      Да, на мой взгляд это лишняя фича.


                                                                                      В голанге нужно явно получать адрес переменной при передаче по ссылке.

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


                                                                                      В голанге понятие пакета сделано странно.
                                                                                      Экспорт в голанге большой буквы, и сокрытие с маленькой — ну это какая-то фигня.

                                                                                      Вкусовщина.

                                                                                      • 0
                                                                                        Кто третья?

                                                                                        1. var i int
                                                                                        2. for i int = 0
                                                                                        3.for i:=0
                                                                                        Вполне востребована и выполняет свою функцию: уменьшает количество ошибок, которые были бы при ручном объявлении перечислений.

                                                                                        И как часто она используется?
                                                                                        Т.е.
                                                                                        const(
                                                                                        a int = 0
                                                                                        b int = a +1
                                                                                        )

                                                                                        это будет много ошибок?))

                                                                                        Да, на мой взгляд это лишняя фича.

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

                                                                                        А в Обероне компилятор это делает во всех случаях, а если такое поведение не верно — значит компилятору это надо указать явно. Но такие случаи — большущая редкость.

                                                                                        Вкусовщина.

                                                                                        Это не вкусовщина. Это явный промах. Забылся, шифт не отжал и получи косяк.
                                                                                        Ну это ладно. Победить можно, а вот система пакетов (уже упомянутая) — вещь куда более спорная. Была заявка на модульность, а получилось как-то не очень. Для поддержки больших систем — это важно.
                                                                                        • 0

                                                                                          Их только две: var и :=. Вторая внутри for (а так же if и switch) ничем не отличается от использования вне этих конструкций.


                                                                                          это будет много ошибок?

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


                                                                                          Забылся, шифт не отжал и получи косяк.

                                                                                          Чушь. Если шифт не нажал где нужно — не будет доступа снаружи к нужному идентификатору, это невозможно не заметить поскольку это помешает реализовать функционал, ради которого идентификатор задумывался экспортируемым. Если шифт нажал где ненужно (гораздо более частый случай) — линтер потребует написать документацию, потому что это обязательно для публичного интерфейса, так что эта ошибка проживёт до первого запуска линтера (т.е. до ближайшего коммита). И, в конце концов, звёздочку в конце имени можно ровно так же забыть или добавить лишнюю.


                                                                                          Была заявка на модульность, а получилось как-то не очень.

                                                                                          Вообще-то нет. Заявку на модульность делают только сейчас, в vgo. До этого заявка была только на пакеты, т.е. по сути на namespace — это нужно для модульности, но это не она.

                                                                                          • 0
                                                                                            Их только две: var и :=. Вторая внутри for (а так же if и switch) ничем не отличается от использования вне этих конструкций.
                                                                                            А как вы относитесь к 'for ... := range ... {'. Это отдельная форма присвоения, это вообще не форма присвоения, это частный случай ":="?
                                                                                            • 0

                                                                                              Это не частный случай, а абсолютно обычный. Вы же не считаете a := f() частным случаем a := 5?

                                                                                              • –1
                                                                                                На самом деле отличия есть, вот смотрите.

                                                                                                a := "abc"
                                                                                                Тип abcstring, тип a — тоже string.

                                                                                                for a := range "abc"
                                                                                                Тут переменная a имеет тип int.

                                                                                                for _, a := range "abc"
                                                                                                А вот тут тип уже int32 (т.е. rune).
                                                                                                • 0

                                                                                                  А в чём отличия-то? range от строки возвращает не строку, а индекс символа и сам символ. Вы создаёте новую переменную с тем же именем и присваиваете туда возвращённое range значение.


                                                                                                  Оператор := работает здесь ровно так же, как и в любом другом месте:


                                                                                                  • требует чтобы слева была хоть одна новая переменная,
                                                                                                  • затеняет существующую переменную в новой области видимости при необходимости,
                                                                                                  • создаёт новые переменные,
                                                                                                  • присваивает значения всем заданным переменным.
                                                                                                  • 0
                                                                                                    Вы не можете использовать ":= range" вне цикла.
                                                                                                    Если присмотреться чуть внимательнее, то можно заметить, что это вообще не отдельная операция ":=", а часть синтаксиса for-each.

                                                                                                    В спеке он, кстати, описывается в отдельной секции синтаксиса циклов, а не в секции оператора :=.
                                                                                                    • 0

                                                                                                      Всё верно, но мне казалось мы тут обсуждаем количество разных вариантов объявления переменных — 2 их или 3. Их 2. А range это часть for и к этой теме вообще отношения не имеет.

                                                                                          • 0
                                                                                            И как часто она используется?

                                                                                            не часто, но удобная когда нужно сгенерить разные последовательности.
                                                                                            Можно же генерить с любым шагом или, например, n^2 последовательности