Десять причин не любить Golang - PullRequest
       5080

Десять причин не любить Golang

Лоуренс Кестелут, программист с солидным опытом за плечами (получивший Оскара за одну из первых систем рендеринга для компаний Pacific Data Images и DreamWorks) перечислил недостатки языка Go в своем блоге. Список показался нам любопытным и мы перевели его на русский язык.

Купить гитару в Москве
0 голосов
/ 16 июля

Когда я начал программировать на Go, то решил для себя: «Плюсы языка впечатляют, а с некоторыми странными вещами я вполне могу смириться». Спустя три года и несколько завершенных крупных проектов на Go - признаюсь, что больше не люблю этот язык и не буду использовать его для новых проектов. Десять причин в защиту моих слов:

1] Golang использует заглавные буквы для определения видимости идентификатора. Те, чтоначинаются со строчной буквы, видны только в данном пакете, а те, что начинаются с заглавной буквы - являются публичными. Суть в том, чтобы сократить использование ключевых слов public и private, но капитализация уже использовалась для обозначения других вещей: классы пишутся с заглавной буквы, а константы и вовсе полностью в верхнем регистре. Это постоянный источник дискомфорта для меня.

Ситуация ухудшается, если вам нужна приватная структура, так как она должна быть объявлена в нижнем регистре. Например, у вас может быть структура user. Как вы назовете переменную? Обычно я использовал обозначение user , но это не только сбивает с толку и может вызвать ошибки при компиляции:

type user struct { 
name string 

func main() { 
var user *user 
user = &user{} // Compile error 
}

Идиоматический Go использует более короткие имена, такие как u, но я прекратил использовать однобуквенные переменные 35 лет назад, когда забросил TRS-80 Basic.

Есть и практические соображения. Часто я начинаю с приватного поля или имени структуры и позже решаю сделать его публичным - и мне приходится исправлять все варианты использования идентификатора. И даже если вы хотите сохранить свои поля закрытыми, вы должны сделать их общедоступными, если хотите использовать пакет json . Фактически у меня была структура с 74 закрытыми полями, которую я хотел сериализовать с помощью json и я был вынужден сделать все поля общедоступными и обновить все виды использования в большом приложении.

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

2] Структуры явно не объявляют, какие интерфейсы они реализуют. Это делается неявным образом путем сопоставления сигнатур методов. Эта конструкция допускает фундаментальную ошибку: предполагается, что если два метода имеют одинаковую сигнатуру, то они имеют одинаковый контракт. В Java, когда класс реализует интерфейс, он делает больше, чем просто сообщает компилятору, что сигнатуры его методов будут совпадать. Также обещают, что контракты методов были реализованы. Если метод возвращает булево значение, комментарии в интерфейсе будут указывать, что означает это значение. (Например, true в случае успешного завершения, false при неудаче.)

Структура Go может реализовывать ту же сигнатуру метода, но полностью менять значение возвращаемого значения. Никто никому ничего не обещал, верно же? Конечно, Java-класс тоже может это делать, но тогда это явно ошибка в классе. В Go ошибка была введена тем, кто приводил объект к интерфейсу без предварительной проверки контрактной совместимости каждого метода. Но это бремя не должно ложиться на каждого пользователя API. Это должно быть сделано один раз разработчиком структуры и объявлено в коде.

3] В Go нет исключений. Он использует множественные значения для возврата кода ошибок. Слишком легко забыть проверить такой код:

db.Exec("DELTE FROM item WHERE id = 2") 

Здесь мы допустили грамматическую ошибку в слове DELETE, но мы даже не узнаем, что что-то пошло не так. Если это часть большой транзакции - вся транзакция ничего не сделает. Желаю удачи в попытках выясненить - почему так. Коды ошибок хороши, но программист вынужден проверять их (или сбросить оператором _ ).

Кроме того, идиома возврата либо значения, либо ошибки:

user, err := getUserById(userId) 

может в свою очередь приводить к ошибкам, потому что нет ничего, что могло бы обязывать, что только user или err содержит допустимые значения. При использовании исключений переменная user никогда не инициализируется (поэтому чтение из нее вызовет предупреждение).

4] Слишком много магии. Например, если я назову свой исходный файл i_love_linux.go - он не будет скомпилирован на моем Mac. Если я случайно назову функцию init() - она запустится автоматически. Это все часть соглашения «конвенция важнее конфигурации». Все это хорошо для небольших проектов, но скажется негативно на больших, а Go как раз и должен был решить проблемы таковых.

5] Частично из-за проблемы с капитализацией легко получить несколько идентификаторов с одинаковыми именами. На самом деле довольно легко получить на выходе пакет, структуру и переменную с одинаковым названием item. В Java имя пакета будет полностью квалифицированным, а класс обозначен заглавными буквами. Иногда мне трудно читать Go, потому что я не всегда могу сразу определить, к какой области относится идентификатор.

6] Трудно генерировать код Go автоматически. Компилятор строг в отношении предупреждений, в итоге неиспользуемые при импорте пакеты и переменные вызывают сбой сборки. Но при создании большого файла изначально может быть не ясно, какие пакеты нужно импортировать. Кроме того, у вас может быть два пакета, имена которых конфликтуют, и это нелегко разрешить автоматически, потому что вы не можете даже знать внутреннее имя, если знаете только имя пакета. (Пакет, импортированный как github.com/lkesteloot/foo на самом деле может быть виден как пакет bar внутри проекта, и некоторые библиотеки с открытым исходным кодом делают это.) Даже если вы поймете это, генератор будет вынуждена заменить псевдонимом имя пакета, чтобы избежать конфликты. В Java все эти проблемы решаются путем пустой секции импорты и полной квалификации всех ссылок на классы - что не разрешено в Go.

7] Здесь нет тернарного ( ? : ) оператора. Каждый C-подобный язык имеет его, и я скучаю по нему каждый день, когда программирую на Golang. Язык забивает на функциональные идиомы в тот самый момент, когда все согласились, что они полезны. Вместо функционального и элегантного:

var serializeType = showArchived ? model.SerializeAll : model.SerializeNonArchivedOnly 

тебя принуждают к этому императивному многословию:

var serializeType model.SerializeType 

if showArchived { 
serializeType = model.SerializeAll 
} else { 
serializeType = model.SerializeNonArchivedOnly 

Я не вижу веских аргументов в пользу отсутствия этого оператора.

8] Подход sort.Interface неуклюж. Если у вас есть 10 различных структур, которые вы хотите отсортировать (в массивах), вы должны написать 30 функций, и 20 из них тривиально схожи (length & swap). Кроме того, их сложно составить: вы можете делегировать другому Less() , но вы должны верить, что его Len() и Swap() совместимы. И, наконец, это выглядит странно, потому что приведение выглядит как вызов функции:

sort.Sort(sort.Reverse(UsersByLastSignedInAt(users))) 

Испытанный и верный подход предоставления метода сравнения compare прекрасно работает и не имеет ни одного из этих недостатков.

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

10] Отсутствие дженериков. Об этом также хорошо сказано в других местах, но опять же - я не могу по-настоящему использовать язык, который не позволяет мне реализовывать универсальный класс Stack. Обычно решение состоит в том, чтобы запилить код стека, используя функции слайса, такие как append(), но опять же - на дворе 2016 год и я хочу писать push() и pop() , а не:

stack = append(stack, object) 

а также:

object = stack[len(stack) - 1] 
stack = stack[:len(stack) - 1] 

Я был удивлен, как много сторонних библиотек используют interface{}. Это признак плохо спроектированной системы типов.

11] Еще одна причина в качестве бонуса! Она незначительна, но указывает на неспособность дизайнеров языка понять, как работают программисты. Функция append() расширяет массив, возвращая новый массив:

users = append(users, newUser) 

Проблема в том, что следующий код будет работать почти всегда:

append(users, newUser) 

Функция append() изменяет тот же самый массив, если возможно, и возвращает другой массив, если в исходном закончилось место. Трудно представить образчик худшего дизайна API. Сколько ошибок вызвано забывчивостью при присвоении результата? Множество, потому что первоначальное тестирование не может вызвать изменение размера. Либо конструкция должна работать по-другому (модифицируя первый аргумент на месте), либо должна заставить программиста использовать возвращаемое значение.

UPDATE: теперь компилятор работает иначе и генерирует ошибку, если результат функции append() не назначен.

Вот краткое изложение моих рекомендаций по использованию Go: 

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

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

1 Ответ

1 голос
/ 29 июля
Golang очень популярен в e-commerce проектах, по крайней мере в России. Авито, Озон, Ламода, Wildberries базируют архитектуру на концепции микросервисов, TDD и гексагональной архитектуры - гошечка и облачная обвязка вокруг нее вписывается в этот контекст идеально.

Язык имеет свои минусы, бесспорно - но плюсы для enterprise среды перевешивают. Быстрый, конкурентный, строгие типы, встроенные тесты - все это решает.
...