Содержание: Чтобы выучить Go, я портировал свой бекенд небольшого сайта с Python на Go и получил забавный и безболезненный опыт в процессе.

Я хотел начать учить Go какое-то время — мне нравилась его философия: маленький язык, приятная кривая обучения и очень быстрая компиляция (как для статически-типизированного языка). Что меня наконец заставило шагнуть дальше и таки начать его учить, так это то, что я стал видеть всё больше и больше быстрых и серьезных программ, написанных на Go — Docker и ngrok, в частности, из тех, которые я недавно использовал.


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


Портирование


Я начал с парочки 20-ти строчных скриптов на Go, но этого было как-бы мало, чтобы понять язык и экосистему. Поэтому я решил взять проект побольше и выбрал для портирования бекенд для моего сайта GiftyWeddings.com.


На Питоне это было около 1300 строк кода, используя Flask, WTForms, Peewee, SQLite и ещё несколько библиотек для S3, ресайзинга картинок и т.д.


Для Go-версии я хотел использовать как можно меньше внешних зависимостей, чтобы лучше освоить язык и как можно больше поработать со стандартной библиотекой. В частности, у Go есть отличные библиотеки для работы с HTTP, и я решил пока не смотреть на веб-фреймворки вообще. Но я всё же использовал несколько сторонних библиотек для S3, Stripe, SQLite, работы с паролями и ресайза картинок.


Из-за статической типизации Go и так как я использовал меньше библиотек, я ожидал, что код будет более, чем вдвое больше оригинального количества. Но в результате получилось 1900 строк (примерно на 50% больше, чем 1300 на Python).


Сам процесс портирования проходил очень гладко, и большая часть бизнес-логики портировалась практически механическим переводом кода строка в строку из Python в Go. Я был удивлён как хорошо многие концепции из Python транслируются в Go, вплоть до things[:20] синтаксиса слайсов.


Я также портировал часть библиотеки itsdangerous, которую использует Flask, так что я мог прозрачно декодировать сессионные cookies из Python-сервиса во время миграции на Go-версию. Весь код для криптографии, сжатия и сериализации был в стандартной библиотеке и это был простой процесс.


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


Также очень порадовал инструментарий языка: всё что нужно для сборки, запуска, форматирования и тестирования кода доступно через под-команды go. Во время разработки, я просто использовал go run *.go, чтобы скомпилировать на лету и запустить сервер. Компиляция и запуск занимала примерно секунду, что было глотком свежего воздухе после битвы на мечах от 20 секунд инкрементальной и 20 минут полной компиляции в Scala.


Тестирование


В стандартной библиотеке Go есть базовый пакет testing и готовый запускатель тестов (go test), который находит, компилирует и запускает ваши тесты. Этот пакет очень лёгкий и простой (возможно даже чересчур), но вы можете элементарно добавить свои хелперы, если необходимо.


В дополнение к unit-тестам, я написал тестовый скрипт (также с использование пакета testing), который запускает тесты HTTP натравленные на реальный сервер Gifty Weddings. Я сделал это на уровне HTTP, а не на уровне Go кода нарочно, чтобы иметь возможность натравить этот тест на старый сервер на Python и убедиться, что результаты идентичные. Это дало мне достаточную уверенность, что всё работает как нужно перед тем, как я подменил сервера.


Я также сделал немного white-box тестирования: скрипт проверяет ответы, но он также декодирует cookies и проверяет, что они содержат корректные данные.


Вот один из примеров такого теста, который создает registry и удаляет подарок:


func TestDeleteGift(t *testing.T) {
    client := NewClient(baseURL)
    response := client.PostJSONOK(t, "/api/create", nil)
    AssertMatches(t, response["slug"], `temp\d+`)

    slug := response["slug"].(string)
    html := client.GetOK(t, "/"+slug, "text/html")
    _, gifts := ParseRegistryAndGifts(t, html)
    AssertEqual(t, len(gifts), 3)

    gift := gifts[0].(map[string]interface{})
    giftID := int(gift["id"].(float64))
    response = client.PostJSONOK(t, fmt.Sprintf("/api/registries/%s/gifts/%d/delete", slug, giftID), nil)
    expected := map[string]interface{}{
        "id": nil,
    }
    AssertDeepEqual(t, response, expected)

    html = client.GetOK(t, "/"+slug, "text/html")
    _, gifts = ParseRegistryAndGifts(t, html)
    AssertEqual(t, len(gifts), 2)
}

Кросс-компиляция


Я считаю, что это просто нереально круто — вы можете на macOS сказать:


$ GOOS=linux GOARCH=amd64 go build

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


Кросс-компиляция cgo-модулей (вроде SQLite) была немного сложнее, так как требовала установки корректной версии GCC для компиляции — что, в отличие от Go, не слишком тривиально. В результате я просто использовал Docker со следующей командой для сборки под Linux:


$ docker run --rm -it -v ~/go:/go -w /go/src/gifty golang:1.9.1     go build -o gifty_linux -v *.go

Хорошие моменты


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


Сетевые и HTTP библиотеки особенно выглядят круто. Вы можете запустить net/http веб сервер (production-уровня и с поддержкой HTTP/2, учтите) буквально в пару строк кода.


Стандартная библиотека содержит большинство из необходимых вещей: html/template, ioutil.WriteFile, ioutil.TempFile, crypto/sha1, encoding/base64, smtp.SendMail, zlib, image/jpeg и image/png и можно продолжать и дальше. API библиотек очень понятны, и там где есть низкоуровневые API, они обычно завёрнуты в более высокоуровневые функции, для наиболее частых способов использования.


В итоге, написать веб бекенд без фреймворка на Go оказалось совсем несложно.


Я был приятно удивлён, как легко было работать с JSON в статически-типизированном языке: вы просто вызываете json.Unmarshal прямо в структуру, и с помощью рефлексии (reflection) правильные поля заполняются автоматически. Подгрузить конфигурацию сервера из json-файла было очень просто:


err = json.Unmarshal(data, &config)
if err != nil {
    log.Fatalf("error parsing config JSON: %v", err)
}

Кстати, об err != nil — это не было так ужасно, как некоторые люди придумывают (проверка встречается примерно 70 раз на мои 1900 строк кода). И это даёт очень хорошее чувство "это реально надёжно, я корректно обрабатываю каждую ошибку".


Кстати, так как каждый обработчик запроса работает в своей горутине, я также использовал вызовы panic() для таких вещей как вызовы к базе данных, которые "должны всегда работать". И на самом верхнем уровне, я ловил эти паники с помощью recover() и корректно логировал их и даже добавил немного кода, чтобы отправлять стектрейс мне на почту.


После Python и Flask, очень чесались руки использовать специальное значение для паники для Not Found или Bad Request ответов, но я сдержал эти позывы и решил идти более идиоматическим путём Go (правильные возвратные значения).


Также мне очень нравится единое синхронное API для всего, плюс великолепное ключевое слово go чтобы запускать вещи в фоновой горутине. Это сильно контрастирует с Python/C#/JavaScript-овыми асинхронными API — которые приводят к новым API для каждой функции ввода-вывода, что увеличивает поверхность API вдвое.


Формат в time.Parse() немного причудливый с идеей "референсной даты", но на практике это очень просто читать когда вы возвращаетесь к коду позже (нету вот этого "ещё раз, что же %b тут означает?")


Библиотека context отняла немного времени, чтобы въехать в неё, но она оказалась полезной для передачи различной дополнительной информации (данные сессии юзера и т.д.) всем вовлеченным в обработку запроса.


Общие причуды


У Go определенно их меньше, чем у Python (но, опять же, Go не 26 лет, как-никак), но всё же есть несколько. Вот некоторые, которые я заметил в процессе моего портирования.


Нельзя взять адрес результата функции или выражения. Вы можете взять адрес переменной или, как особый случай, литерала структуры, вроде &Point{2, 3}, но вы не можете сделать &time.Now(). Это немного раздражало, потому что заставляло создавать временную переменную:


now := time.Now()
thing.TimePtr = &now

Мне кажется, Go компилятор мог спокойно создавать её за меня и позволить писать thing.TimePtr = &time.Now().


Хендлер HTTP принимает http.ResponseWriter вместо возвращения ответа. API http.ResponseWriter немного странное для основных случаев, и вы должны запомнить правильный порядок вызова Header().Set, WriteHeader и Write. Было бы проще, если хендлеры просто возвращали некий объект с ответом.


Это также делает немного неудобным получение кода ответа HTTP после вызова хендлера (например для логирования). Приходится использовать фейковый ResponseWriter, который сохраняет код ответа.


Вероятно была веская причина такого дизайна (эффективность? Сочетаемость?), но я не могу с ходу так её увидеть. Я мог бы легко сделать обёртки для моих хендлеров, чтобы возвращали объект, но я решил так не делать.


Шаблонизатор вроде ничего, но тоже есть причуды Пакет html/template мне показался довольно хорошим, но у меня заняло время понять что такое "ассоциированные шаблоны" и для чего они вообще. Чуть больше примеров, в частности для наследования шаблонов, очень бы были кстати. Мне понравилось, что шаблонизатор достаточно хорошо расширяется (например, легко добавить свои функции).


Загрузка шаблонов немного странная, поэтому я обернул html/template в свой пакет для рендеринга, который загружал сразу всю директорию и базовый шаблон.


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


Пакет database/sql слишком легкий. Я не самый большой фанат ORM, но было бы неплохо, если бы database/sql мог использовать рефлексию и заполнять поля структур по аналогии с encoding/json. Scan() ну прямо совсем низкоуровневый. Хотя, есть пакет sqlx, который, похоже, делает именно это.


Тестирование слишком простое Хоть я и фанат go test и простоты тестирования в Go в целом, но, мне кажется, было бы хорошо иметь в штатном наборе хотя бы функции в стиле AssertEqual. В итоге, я просто написал свои AssertEqual и AssertMatches функции. Хотя, опять же, похоже, что есть сторонние пакеты, которые именно это и делают: stretchr/testify.


Пакет flag причудливый. По всей видимости, дизайн был основан на пакете для флагов командной строки в Google, но формат -одинДефис всё таки смотрится странно, учитывая то, что GNU-формат с -s и --long это практически стандарт. Опять же, есть масса замен этому пакету, включая drop-in замены, в которых даже код не нужно менять.


Встроенный роутер URL (ServeMux) слишком простенький, поскольку позволяет делать проверки только по фиксированным префиксам, но создание роутера, основанного на regexp было тривиальной задачей (дюжина строк кода).


Претензии от питониста


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


Но мне очень не хватало включений списков и словарей. Было бы здорово, если я бы мог превратить вот это:


gifts := []map[string]interface{}{}
for _, g := range registryGifts {
    gifts = append(gifts, g.Map())
}

в это:


gifts := [g.Map() for _, g := range registryGifts]

Хотя, на самом деле, таких мест было гораздо меньше, чем я ожидал.


Аналогично, sort.Interface чересчур многословен. Добавление sort.Slice() было правильным шагом. Но мне всё же нравится как легко сортировать по ключу в Python, без касания индексов слайсов вообще. Например, чтобы отсортировать список строк без учета регистра, в Python это будет:


strs.sort(key=str.lower)

а в Go:


sort.Slice(strs, func(i, j int) bool {
    return strings.ToLower(strs[i]) < strings.ToLower(strs[j])
})

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


Почему Go?


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


Резюмируя, некоторые из причин, по которым мне понравился Go, и по которым может понравится и вам:


  • хорошая кривая обучаемости: это маленький язык с вполне читабельной спецификацией и простой системой типов
  • быстрое время компиляции
  • некоторые уникальные и отличные фичи: слайсы, горутины и ключевое слово go, defer, := для лаконичного вывода типа, система типов на интерфейсах.
  • отличная и краткая документация для языка и стандартной библиотеки
  • отличный встроенный инструментарий:
    • go build: собрать программу (не нужен никакой Makefile)
    • go fmt: автоматически отформатировать код, больше нет войн о стиле
    • go test: запустить тесты во всех *_test.go файлах
    • go run: скомпилировать и запустить программу мгновенно, по ощущениям как скриптинг
    • dep: менеджер пакетов, скоро будет go dep
    • Хорошая и растущая экосистема (библиотеки для баз данных, веб фреймворки, AWS, Stripe, GraphQL и т.д.)
    • Стабильность: Go 1 уже существует более 5 лет и имеет строгое обещание совместимости. Авторы Go очень консервативны в плане того, что они добавляют в язык, и на это есть веские причины.
    • Философия языка не всем понравится, но она очень хорошо ложится на мою ментальную модель: простота, ясность и скорость.

Так что вперёд, попробуйте написать что-нибудь на Go (Write in Go)!

Комментарии (35)


  1. Shtucer
    13.11.2017 00:46

    Мне кажется, Go компилятор мог спокойно создавать её за меня и позволить писать thing.TimePtr = &time.Now().

    Мог бы, наверное. Но там принцип:


    Programs using times should typically store and pass them as values, not pointers. That is, time variables and struct fields should be of type time.Time, not *time.Time.


    1. Stronix
      13.11.2017 09:44

      А вот здесь про указатели на функции blog.golang.org/gos-declaration-syntax


  1. Tihon_V
    13.11.2017 14:11
    +1

    Недавно в целях изучения языка решил написать свой MitM-proxy на Go. В сравнении с Python 3.* — некоторые вещи на текущий момент выглядят некрасиво, к примеру нельзя сделать slice с отрицательным значением "arr[:-10]", регулярные выражения, которые не могут быть константами и благодаря "магии" внутри Go не перекомпилируются каждый раз (но для того, чтобы об этом узнать — нужно читать документацию), а также другие досадные мелочи, но есть масса преимуществ.
    Во-первых — он ОЧЕНЬ быстрый (если сравнивать со стандартными библиотеками Python)!
    Во-вторых — первое окружение которое очень просто настраивается (да-да — это камень в сторону venv/node_modules).
    Из инструментов для комфортной работы/отладки с ним — было достаточно atom и плагина go-plus.


    1. SirEdvin
      13.11.2017 22:11
      -2

      Во-вторых — первое окружение которое очень просто настраивается (да-да — это камень в сторону venv/node_modules).

      Что может быть удобнее, чем virtulaenvwrapper + .venv файлик?
      Подход go в духе, когда "будем хранить все зависимости в репе" может с практической точки зрения и простой, но в результате это выглядит хуже некуда и попытки настроить себе базовый проект у меня с первого раза превратились в страдания. Потому что переменные окружения, которые надо прописывать, а еще и нет единого стандарта пакетов.


      1. Tihon_V
        14.11.2017 12:11

        Мне на текущий момент приходится «переключатся» между проектами. И необходимость навешивать hook'и на выполнения pip/yarn после git clone/pull/checkout меня огорчают. Особенно при смене веток «dev/beta».
        ИМХО: В случае с Go — хранить зависимости в репе можно, т.к. на размер и скорость работы с проектом это влияет не существенно. А при наличии go dep в базовых утилитах к языку — придется решить и проблему стандарта пакетов и проблему с необходимостью отслеживать версии зависимых пакетов вручную. Думаю что будет здорово, если при использовании git, вместо загрузки зависимого пакета будет создаватся submodule с необходимой версией.

        А если есть необходимость использовать различные ENV-спецефические окружения, то удобнее — docker/lxc…


        1. SirEdvin
          14.11.2017 13:26

          А если есть необходимость использовать различные ENV-спецефические окружения, то удобнее — docker/lxc…

          Эм… а разве для работы go не нужен GOPATH в переменных окружения? Или это уже исправили?


          1. Tihon_V
            14.11.2017 16:35

            Нужен. От него зависит место установки пакетов.


    1. anjensan
      14.11.2017 16:58

      регулярные выражения… благодаря «магии» внутри Go не перекомпилируются каждый раз
      А точно Go не перекомпилирует? Вот честно не могу найти такого в документации, наверное плохо ищу?
      С другой стороны Python как раз таки имеет кеш для регулярок.


      1. Tihon_V
        14.11.2017 20:20
        +1

        Если правильно понял код к regexp, то тип Regexp в Go — хранит кэш для регулярных выражений, а при обращении к скомпилированному регулярному выражению возвращает в горутину копию объекта типа regexpRO доступную только для чтения (для того чтобы избежать блокировки объекта).

        P.S.: А у стандартной библиотеки Go — очень подробные комментарии :)


        1. anjensan
          14.11.2017 20:35

          Ну… это вполне логично, что после компиляции мы получаем объект «скомпилированная регулярка» и когда ее используем, то перекомпиляции не происходит… Но ведь так во многих языках, включая Javа, Python (это про что я лично в курсе), разве нет?

          Я подумал, что Вы про методы вроде `regexp.MatchString(pattern, str)`. Аналогичные методы в Python как раз имеют кеш, а вот в Go каждый раз идет полный парсинг регулярки.

          PS: Судя по всему `regexpRO` имеет место быть только для упрощения реализации Copy() (все константные поля копируются легким движением руки). Ну может еще кто-то находит такой вариант более читабельным.


  1. anjensan
    13.11.2017 15:11
    -1

    Сначала пишут

    всё чувствуется как железобетонно надежное… Моё внутреннее чутье показывает, что это частично из-за того, что в Go нет исключений
    Ну ок, допустим. Но потом
    я также использовал вызовы panic() для таких вещей как вызовы к базе данных, которые «должны всегда работать»
    Хм…
    на самом верхнем уровне, я ловил эти паники с помощью recover() и корректно логировал их и даже добавил немного кода, чтобы отправлять стектрейс мне на почту
    А, ну так конечно получается железобетонно. Не то что с исключениями.


    1. youROCK
      13.11.2017 15:20

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


      1. anjensan
        13.11.2017 15:26
        -1

        Так-то оно так. Просто непонятно откуда тогда взялось

        Кстати, об err != nil… даёт очень хорошее чувство «это реально надёжно, я корректно обрабатываю каждую ошибку»
        Совершенно непонятно, какие такие «каждые» ошибки обрабатывает автор.


        1. khim
          13.11.2017 18:11
          +1

          Все те ошибки при которых можно продолжить работу. В случае с Go есть два режима сообщения об ошибках из модуля:
          1. Код возврата: тут что-то случилось, но, в принципе, ничего страшного, если нужно — можешь продолжать работу.
          2. panic: всё — наш модуль «кончился» работу с ним продолжать невозможно, но если в программе есть другие модули — они могут ещё кому-то послужить.

          В случае же с исключениями задача подобного «разделения обязанностей» возможена на программиста, который модулем пользуется — а он, зачастую, не знает толком: можно ли модуль как-то «спасти» или он безвозвратно испорчен.

          Теоритически схема с исключениями более гибкая, на практике — это скорее мешает, чем помогает…


          1. anjensan
            13.11.2017 18:37

            Все те ошибки при которых можно продолжить работу.
            А кто определяет, можно ли после ошибки проолжить работу или нет? Вызывающая сторона или вызываемая?


            1. khim
              13.11.2017 18:59
              -2

              В случае с Java'ой — вызывающая. В случае с Go — вызываемая.

              Теоретически подход Java — «круче» и «гибчее». А на практике — подход Go надёжнее.


              1. anjensan
                13.11.2017 19:13
                +3

                А на практике — подход Go надёжнее.
                Интересно, а не могли бы Вы поснить это несколько поподробнее? Привести конретные примеры или use-case?


          1. TheShock
            13.11.2017 20:01

            В случае же с исключениями задача подобного «разделения обязанностей» возможена на программиста, который модулем пользуется — а он, зачастую, не знает толком: можно ли модуль как-то «спасти» или он безвозвратно испорчен.

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

            Ну вот к примеру, я формирую результаты боя в игре, для каждого юзера вызываю функцию `getStat(user)` из другого модуля, ее результат для меня не смертельно важен — если для каких-то юзеров она упала, я просто выведу знаки вопроса вместо циферок. И вот на Джаве — просто словил ексепшн и вывел вопросы. А программист на Го ведь самый умный, у него упал запрос к базе, который обычно не падает и он решает запаниковать. И вот весь расчет коту под хвост.

            А потому-что в Джаве хорошее, продуманное решение, а в Гоу — очередной мусор для хипстеров.


            1. serge-phi
              15.11.2017 15:07

              Довольно спорный вывод. В Go panic используется для сигнала об ошибке программиста, а не работы программы (кроме самых простых случаев, напр. panic(«command line argument error...»)). То есть обычно panic используется для эмуляции assert. Пришельцы из языков с поддержкой исключительных ситуаций пытаются использовать panic/recover для эмуляции исключительных ситуаций, но это не приветствуется.


              1. TheShock
                15.11.2017 21:22
                -3

                А почему вы это пишете мне? Это не я сказал, что паниковать необходимо при внезапно не сработавших вызовах к базе.

                И, очевидно, человек это придумал из-за крайне кривой и неудобной работы с ошибками в Гоу


                1. serge-phi
                  16.11.2017 10:14

                  Потому что я не согласен с тем, что Go — это мусор из-за отсутствия поддержки исключительных ситуаций. Полезность исключительных ситуаций вообще спорна.
                  google.github.io/styleguide/cppguide.html#Exceptions
                  llvm.org/docs/CodingStandards.html#do-not-use-rtti-or-exceptions


                  1. anjensan
                    16.11.2017 13:09
                    +1

                    Ну раз уж взываете к авторитетам и привели ссылки, то не поленитесь почитать что же по ним написано:

                    On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code… problematic to integrate the new project into existing exception-free code...
                    Т.е. вообще сами по себе исключения в С++ дают пользы больше, чем вреда, и их бы использовали… Но в одной конкретной корпорации есть куча легаси C++ кода, который с исключениями совсем не дружит.

                    In an effort to reduce code and executable size, LLVM does not use RTTI (e.g. dynamic_cast<>;) or exceptions
                    Т.е. из-за размеров бинарников не используют RTTI и исключения… Ну это разумеется применимо и к статически линкуемому Go, напичканому данамическими кастами (привет interface{}). Походу именно из-за отсутствия исключений бинарники у Go получаются такими миниатюрными, а я то все голову ломал.

                    PS: искренне прошу, не надо приводить бессодержательные доводы аля «ну для C++ преимущества исключений перевешивают конечно, но вот для Go нет! он ведь совсем другой, не такой как все!». Уж коли приводите ссылки для С++, то давайте в этих рамках и вести обсуждение.


                    1. divan0 Автор
                      16.11.2017 13:20

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


                      1. anjensan
                        16.11.2017 13:28

                        Конкретно сейчас я воюю с искажением фактов :)

                        Хорошо или плохо что в Go исключения таки есть (паника), но их не принято использовать — это отдельная тема. Хорошо или плохо, что и в других языках можно пользовать кодами возврата (например возвращать Either<> в Java), но их используют куда меньше чем исключения — тоже отдельная тема.

                        Но когда кто-то приводит ссылку для «аргументации через авторитет» (уже само по себе не очень), но в которой еще явно белым по черному написано «в целом исключения годнота»… Ну, меня коробит.

                        Уж коли написано сотни статей — вот их и приводите (если они конечно адекватные) :)


                        1. divan0 Автор
                          16.11.2017 13:33
                          +1

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


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


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


                          1. anjensan
                            16.11.2017 13:50

                            ссылка была приведена для подтверждения посыла «пользя исключений спорная тема»
                            Простите, но надо быть совсем недалеким, что бы не понимать что у любой идеи/техрешения есть и сильные и слабые стороны :)

                            Я тоже вот например совсем не согласен, что Go мусор из-за ситуации с обработкой ошибок… Он мусор, но по иным причинам :)

                            Еще раз — приводите правильные ссылки и вопросов не будет (или будут, но другие). Вы сами только что сказали что их прямо «сотни», т.е. минимум 200. Думаю есть из чего выбрать :)

                            И, кстати, «утверждение через авторитет» не является логической ошибкой — если «авторитет» действительно авторитет и обладает гораздо более весомым мнением, то это может быть вполне себе мощный аргумент.
                            Ну право, одно дело когда вы обратились к авторитету а он вам «ну в твоем случае я проанализировал все… надо делать такто и такто». Но нет ведь, мы просто смотрим со стороны что наш мастер-авторитет делает. Большую часть времени не понимаем почему мастер прянял то или иное решение, он ведь нам не отчитывается и ведет большую часть работы закрыто… А потом пытаемся повторить :)


                            1. divan0 Автор
                              16.11.2017 13:58
                              +1

                              Он мусор, но по иным причинам :)

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


                              Еще раз — приводите правильные ссылки и вопросов не будет

                              Ссылка вполне себе "правильная" — она кратко и наглядно демонстрирует и перечисляет плюсы и минусы.


                              Большую часть времени не понимаем почему мастер прянял то или иное решение,

                              Согласен.


                              1. anjensan
                                16.11.2017 14:46
                                -1

                                На оскорблениях хорошее коммьюнити не построишь.
                                Ой какая грубая уловка в риторике попытаться противопоставить собеседника сообществу, не надо так ;)

                                Про мусор — это мое личное мнение. И совершенно никаим боком ни к Clojure, ни к иному языку, ни к какому либо коммьюнити не относится :)

                                Своим определением («мусор») я не пытаюсь никого оскоробить или принизить язык. Он во многих местах сделан клево, я его прямо щас использую. И он очень даже к месту ;)

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


                              1. anjensan
                                16.11.2017 18:52

                                Ссылка вполне себе «правильная» — она кратко и наглядно демонстрирует и перечисляет плюсы и минусы.
                                Для С++. Плюсы и минусы там отнюдь не универсальные.
                                А Go, как известно, отнюдь не C++.


                          1. DarkEld3r
                            16.11.2017 14:06
                            +2

                            Kолёса у самолётов (не у всех, впрочем) всё-таки есть, а шасси — это слегка другое. (:


                            Мне тоже кажется, что языки, которые постулируют "отказ от исключений" не совсем честны в этом плане.


                    1. serge-phi
                      16.11.2017 14:22
                      +2

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


  1. ShadowsMind
    13.11.2017 15:22
    +1

    что было глотком свежего воздухе после битвы на мечах от 20 секунд инкрементальной и 20 минут полной компиляции в Scala.

    Автор явно утрирует и набрасывает. Если речь идет о таком небольшом проекте, то компиляция в Scala тоже будет занимать секунды. Не знаю о каких 20 минутах с нуля и 20 секундах инкрементальной он говорит, когда лично у меня, полная перекомпиляция проекта в ~20kloc с обильным использованием имплиситов, макросов и вот этого всего + то же ранее упомянутое, переформатирование всех сорцев — занимает ~50 с. на обычном домашнем ноуте (scala 2.12.4 и sbt 1.0.3).


    1. wing_pin
      13.11.2017 15:56

      Стыд и срам, у меня фронтенд c вебпаком и то быстрее собирается. Проект на 32 kloc (только JS) полностью собирается в среднем за 35 секунд.


      1. ShadowsMind
        13.11.2017 16:18
        +4

        Вот именно что «собирается», а не компилируется. Сравнение из разряда «моя подлодка плавает быстрее, чем ваш мотоцикл».


        1. wing_pin
          13.11.2017 17:04
          -1

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