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

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

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

Начала

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

  • Крайне императивно. Несмотря на то, что вы можете писать декларативный код, Go изначально является крайне необходимым.
  • Строгая типизация: Go - это язык со строгой типизацией, и, хотя мне не нравился тот факт, что мне приходилось каждый раз явно указывать тип возвращаемых значений, в конце концов я нашел его более удобным, поскольку я могли выявить проблемы до того, как они возникли.
  • Реализация интерфейса не является явной. Я считал это недостатком вначале, потому что я думал, что люди могут неправильно использовать одну структуру вместо другой только потому, что она выполняет подпись (что не всегда отражает намерение выполнить контракт ). Мой опыт показывает, что это очень маловероятно, потому что в одноцелевом приложении нет места для путаницы. Меня также беспокоило то, что сразу после изменения интерфейса вам приходилось преследовать все структуры, которые его реализуют, чтобы заставить их снова выполнить контракт, но, вероятно, это связано с IDE.
  • Использование заглавных букв для видимости пакетов: Лично мне не нравится несогласованность некоторых объектов / функций, имена которых начинаются с заглавной буквы, а у других - с строчной.
  • Аннотации структур для маршалинга / демаршалинга создают ощущение, что объект связан с деталями реализации: Я все еще думаю, что это правда.
  • Отсутствуют значения по умолчанию для аргументов функции / методов: это правда, но если ваша функция требует много дополнительных параметров, вероятно, с ней что-то не так. Тем не менее, в ситуациях, когда вам действительно нужно передать необязательные аргументы, вы можете использовать аргументы zero valuecheck, аргументы указателя или аргументы функции с переменным числом аргументов.
  • if err !=nil {...} везде: Сначала это кажется странным, но в какой-то момент вы понимаете, что это то же самое, что делать try/catch для исключений. Основное отличие - по ощущениям - заключается в том, что вы повторяете эту проверку много раз, тогда как с помощью try / catch вы можете обрабатывать большой блок и обрабатывать разные исключения по-разному, однако это иногда означает, что вы теряете контекст, в котором может быть выбрано исключение, из-за которого метод:
<?php
try {
    functionA();
    functionB();
} catch (ExceptionTypeC $e) { 
    ...
} catch (ExceptionTypeD $e) {
    ...
}

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

<?php
try {
    functionA();
} catch (ExceptionTypeC $e) {
    ...
}
try {
    functionB();
} catch (ExceptionTypeD $e) {
    ...
}

Что по сути

err := functionA()
if err != nil {
    ...
}
err = functionB()
if err != nil {
    ...
}
  • Функции для работы с массивами и манипуляции с ними в целом: Такие функции, как array_map или array_filter - это то, что я пропустил, но вы все равно можете сделать это, как описано здесь.
  • Нет тернарного оператора (? :): Я действительно пропустил это, так как считаю его более читаемым, и мне все еще не нравится идиоматическое предложение сначала назначить значение по умолчанию, а затем переопределить его в зависимости от условий. .
  • Управление версиями и поставками. Это, наверное, то, чего я скучал больше всего. Использование внешних зависимостей в Go означает, что вам нужно выбирать между включением всего кода, используемого в библиотеке, и использованием мастера репозитория. Оба подхода кажутся мне неудобными, но между ними я предпочитаю вендорный. Однако мне не нравятся массовые PR, в том числе продаваемые файлы, которые повсюду в репозиториях на github с использованием этого подхода.

С другой стороны, что мне понравилось в Go с самого начала, так это:

  • В Go нет наследования (слава богу за это): вместо этого вам придется использовать некоторые другие методы, такие как embedded objects, которые мне больше нравятся composition. Я много раз слышал о том, что наследование является одним из самых больших преимуществ ООП. На мой взгляд, это очень ошибочное утверждение.
  • Возможность возвращать несколько значений: мне очень не хватало этого в C и PHP. Конечно, вы можете вернуть массив или список значений в PHP, но это выглядит очень странно и не идиоматично. Также может потребоваться проверка типов во время выполнения.
  • Строгая типизация. Это область, в которой PHP улучшается; PHP7 включает типы возврата, и я очень благодарен за это. PHP 7.1 даже включает возвращаемый тип, допускающий значение NULL. Тем не менее, проверка выполняется во время выполнения, а не во время компиляции, и, хотя существуют такие инструменты, как phpstan, которые помогают преодолеть это ограничение, действительно приятно, что в Go это встроенная функция языка.
  • map / list with types: Это то, чего мне очень не хватало в PHP - нет способа объявить map[string]Something, заставляя вас выполнять проверки во время выполнения для элементов массива.
  • Компилятор Go вынуждает вас отбрасывать неиспользуемые функции, переменные и импорт: Сначала это было неприятно (пока мы копались с некоторыми примерами), но через несколько дней я действительно нашел это удобным, поскольку сделайте код проще и понятнее.
  • Избегайте циклических зависимостей: даже если язык позволяет это сделать, я настоятельно рекомендую избегать этого.

Несмотря на то, что мне нравится в языке Go, я хотел бы упомянуть пару вещей, которые мне не нравились из того, что называется идиоматическим го:

  • Я не согласен с one letter variable, я думаю, что за последние 40 лет индустрия программного обеспечения согласилась с тем, что разработчики тратят гораздо больше времени на чтение кода, чем на его написание, а это означает, что код должен быть написан с расчетом на читателей. Я не хочу вспоминать, что r, скорее всего, означает Request, а w - Writer.
  • Связь иногда ассоциируется с идиоматическим подходом, легко найти примеры, в которых бизнес-логика тесно связана с инфраструктурой или деталями реализации. Лично я стараюсь, даже при написании Go, максимально разделить свой код с помощью DDD и CQRS, и, я не думаю, что Go когда-либо предназначался для использования таким образом, это вполне выполнимо. Принципы хорошего кода применимы при работе с любым языком, и Go не должен быть исключением.

Мое мнение о посте Ивана Яроша

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

Go родился из-за разочарования существующими языками и средами для системного программирования.

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

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

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

Go был разработан для простых одноцелевых приложений (также называемых микросервисами), и это не дефект, а особенность. Мне нравится подход «сначала монолит», и я бы точно не стал писать монолит на Go.

Но еще один момент, который я хочу отметить, заключается в том, что в Go вы ни при каких обстоятельствах не должны даже пытаться повторно использовать код или определять интерфейс. Рано или поздно он укусит тебя за задницу. […] Например, Go был бы идеальным языком для использования для микросервиса, целью которого является получение событий из хранилища событий в архитектуре с источником событий и их нормализация в потребляемые сообщения для брокера сообщений для распространения среди заинтересованных третьих сторон. Теперь представьте микросервис, в котором вам нужно подключиться к базе данных, получить результаты, выполнить некоторую бизнес-логику и отправить несколько сообщений.

В Typeform мы используем Go не только для задач / инструментов низкого уровня, но и для написания микросервисов с использованием DDD и CQRS. Go доказал свою совместимость с этим, а также заставил меня понять, что иногда мы злоупотребляем классами, чтобы создать так называемые объекты домена. Кроме того, одна функция, которая мне нравится в Go (и на самом деле из C), - это то, насколько легко создавать пользовательские типы и связывать их с определенным поведением.

Здесь стоит упомянуть о том, как мы понимаем концепцию микросервиса. Рекомендую этот доклад от Udi Dahan о том, что такое микросервис, а что нет.

Когда дело доходит до Go, вы хотите написать спагетти-код. Go имеет композицию вместо наследования и полиморфизма, но вы будете бегать по кругу, если попытаетесь создать любой повторно используемый код. Вы сильно проиграете.

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

Есть причина, по которой в Go отсутствуют «настоящие» фреймворки. Конечно, у вас есть Go Kit, Micro, Gin и несколько других, но они никогда не смогут даже приблизиться к Symfony, Laravel, RoR, Django или Flask.

Вам не нужна структура для приложений Go, поскольку они имеют единственную цель, маловероятно, что вы найдете маршрутизацию приложений Go, отправку событий, обслуживание ресурсов и рендеринг HTML одновременно. Микросервисы делают очень специфические вещи, и для этого вам не понадобится полнофункциональный фреймворк, такой как Rails или Symfony. Такие фреймворки предназначены для написания монолитов, а Go для этого не предназначен.

Выводы

После нескольких месяцев работы с Go, некоторые из моих антипатий к языку стали мне нравиться, поэтому прямо сейчас, если меня спросят, что я предпочитаю, я отвечу: Это зависит от обстоятельств.

  • Я действительно верю в подход сначала монолит, и если бы мне пришлось писать монолит, я бы не писал его на Go, потому что он не предназначен для этого.
  • Я был бы совершенно счастлив написать микросервис на PHP, если для реализации не требуются определенные функции, такие как высокая доступность, параллелизм, асинхронные задачи, многопоточность и т. Д. Если бы это было так, я бы определенно использовал Go, а не потому, что это невозможно в PHP. (хотя параллелизм определенно невозможен в PHP SAPI), но потому, что в Go он более естественен из-за простоты обмена данными по каналам (недавно я видел этот разговор, где @adrianco показывает, как отправлять каналы над каналами) над сложной ограничительной моделью pthreads.