Хабр, привет! Меня зовут Костя Козин, я старший PHP-разработчик Lamoda Tech. За 17 лет в коммерческой разработке я писал на JS, PHP, и теперь на Golang. Сегодня в Lamoda Tech мы учим PHP-разработчиков писать на Go, и под это создали целый онбординг. Довольно часто в локальных обсуждениях я слышал, как тяжело дается переход с PHP на Go, а у некоторых первые месяцы работы с Go вовсе отбивают желание работать с языком. Поэтому я поделюсь подводными камнями, которые ожидают вас при переходе с одного языка на другой, и с которыми сталкивался я сам и мои коллеги. Если знать о них заранее, то смена стека может пройти не так болезненно, а знакомство с Golang станет более предсказуемым.

Подводный камень №1. «В Golang так принято»

При изучении Golang вы довольно часто будете сталкиваться с этим аргументом. Действительно, в языке много идиоматики и общепринятых правил, но практически всегда есть дополнительный контекст и исключения. Даже в ядре существуют отдельные места, которые написаны не так, как рекомендуется писать на Golang. Но чаще всего этому есть объяснение, которое можно нагуглить. Проверяйте аргументы «так принято» и «это есть в ядре» — они могут означать лишь рекомендации в определенных случаях. 

Пример. Вы не раз услышите, что в Golang переменным дают очень короткие имена. Гоферы приводят довод о том, что «так принято», но у этого правила есть абсолютно разумное объяснение, которое можно найти, например, в Effective Go. В Golang существуют дополнительные ограничения, задающие условия — когда, как и насколько сокращать. Главный смысл в том, чтобы сокращения были понятны. Как только появляются вопросы, нужно остановиться. 

Посмотрим на кусок кода из одного нашего сервиса, который я сознательно ухудшил для наглядности. 

1. В первой части кода функция square, которая принимает w, h, возвращает s. Все очевидно, вопросов не возникает.

2. Другой пример. У нас есть функция сохранения, в которой мы выполняем sql-запрос и обрабатываем возможную ошибку. В условии мы вызываем некую функцию toUniqueViolationError, которая возвращает некую C.

Что такое C? Неизвестно. Более того, ниже мы берем F, присваиваем нечто из M по ключу C. Как это понять? Никак. Очевидно, что M — это какая-то мапа, а вот насчет остального остается только гадать или искать по другому коду.

Для тех, кому интересно разобраться с этим примером: C — это column, колонка, в которой произошла ошибка, а F — это field, поле для этой колонки в dto уровня домена.

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

Подводный камень №2. Специфичность языка и маркетинг

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

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

  • Один тип цикла вместо четырех не упрощает логику, зато теряется очевидная семантика, которая есть у разных типов циклов.

  • Простое распараллеливание не решает соответствующие проблемы распределенных систем вроде тех же race condition, с ними нужно работать отдельно. 

  • При простом синтаксисе требуется выстраивание системы классификации и обработки ошибок, и это отдельный квест.

Еще одно утверждение гоферов: Golang выразительный. Само понятие выразительности кода дискуссионное, и каждый под этим подразумевает что-то свое. Чтобы не разочаровываться, стоит понимать — конкретно в Golang выразительность означает наглядность и отсутствие «магии».

Пример №1. В PHP это был бы цикл while, в Golang это только for. 

Пример №2. Представим, что в PHP задана строка в условии. Чтобы узнать, как это условие сработает, нужно понимать, как именно PHP будет конвертировать строку в boolean-значение. В Golang такое не пройдет: в if нужно передавать строго boolean-значение.

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

  • DI-контейнеры с автосвязыванием (autowiring) существуют, но их практически не используют.

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

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

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

Подводный камень №3. Golang не снизит количество багов

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

Поэтому нужно понимать, что если вы ожидаете от перехода на Go повышение безопасности, то можете быть разочарованы. Максимум, что вы заметите — строгую типизацию в Go, если писали на PHP без нее, а также наличие детектора race condition. Баги уйдут исключительно из-за нестрогого сравнения типов в PHP, а детектор race condition помогает далеко не во всех случаях.

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

Пример: В моей практике была история, когда три опытных гофера в один сервис добавили десять строчек на Golang. При тестировании они нашли баги, но месяц не могли понять, что не так, пока кто-то из них не вспомнил об одной из особенностей языка.

Вывод: к безопасности Gо нет претензий, но в сравнении с PHP он не многим лучше. Количество багов может даже увеличиться, но это пройдет с опытом. А чтобы процесс погружения прошел легче, после базового освоения советую почитать любую книгу о подводных камнях в Golang. Например, «100 ошибок Go и как их избежать» Тейвы Харшани. На нее вам потребуется пара месяцев точно.

Подводный камень №4. На старте вас может тошнить от собственного кода, и это нормально

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

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

Еще один важный нюанс. Вы можете испытывать чувство, что с вами что-то не так, что Go не ваш язык, пока все вокруг будут восторженно говорить о нем. Причина в том, что сообщество гоферов крайне лояльное. Если задать вопрос о какой-либо проблеме в Go, 9 разработчиков из 10 скажут: «А мне нравится». Это непривычно для PHP- или Java-разработчиков, которые привыкли достаточно критично относиться к своему языку. Поэтому не калибруйте свои ощущения только по восторженным откликам сообщества.

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

Подводный камень №5. Низкое качество кода сразу после перехода

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

Вывод: Ваш прошлый опыт и профессиональная интуиция будут подсказывать, что качество кода просело, но это временно. Чтобы решить эту проблему, переключитесь в «режим джуна». Дайте себе такую установку: у вас нет наработанного опыта, и вы с большим трудом вспоминаете то, что давным-давно читали насчет хорошего кода. Сейчас будет проще начинать с нуля. Со временем качество кода выправится.

Выводы

  1. Не верьте сразу на слово аргументу «так принято», старайтесь проверять и перепроверять информацию. Стремитесь на уровне концепта понять, почему некоторые вещи делаются так, а не иначе.

  2. Язык гораздо более специфичен, чем кажется на первый взгляд. Дайте себе время на адаптацию, минимум полгода. 

  3. Особого роста безопасности по сравнению с PHP вы не увидите, а иногда будете попадать в неочевидные ловушки, специфичные для Go.

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

  5. Будьте готовы к тому, что маркетинг Go не всегда отражает реальность, а сообщество гоферов очень лояльно. Фильтруйте информацию через собственный опыт.

Какие подводные камни вы прочувствовали на себе? Поделитесь своей болью в комментариях.

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


  1. CrazyElf
    15.10.2024 09:32

    Я писал на многих языках за свою карьеру. В частности когда-то давно на C, Asm, PHP, Perl. Сейчас пишу на C#, Python, JavaScript. Пытался несколько раз изучить Golang: ставил компилятор, открывал таториал, cмотрел на код... и в ужасе закрывал таториал до следующей попытки. Кровь из глаз и всё такое. Впрочем, я и Java не смог полюбить, хотя казалось бы она похожа на C#. Но Golang - это нечто, конечно. Буду ещё пытаться.


    1. blind_oracle
      15.10.2024 09:32

      Я на Го писал лет 7, сейчас ушёл с него. Сложно понять, что же может там вызывать кровь из глаз - он же простой как пятка. Эдакий Си с прибамбасами.


  1. Desprit
    15.10.2024 09:32

    Вывод: нужно помнить, что подходы, принятые в Golang, чаще всего зависят от контекста. И когда вы слышите, что «тут так принято», лучше не верить на слово, а загуглить и разобраться в вопросе.

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

    p.s. почему код в картинках то?


  1. qeeveex
    15.10.2024 09:32

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


    1. jizuscreed Автор
      15.10.2024 09:32

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


  1. allishappy
    15.10.2024 09:32

    Из самого необычного я бы выделил 2 пункта, к которым так и не смог привыкнуть до конца:

    1. Имя пакета - часть имени сущности. И если в пакете auth надо создать какой-то сервис аутентификации, то надо назвать его просто Service, а не AuthService. Из-за этого местами непривычно читать код, так как часть имени любой сущности написана в самом верху кода (пруф)
    2. В именах пакетов не должно быть подчёркиваний, тире и пр. Только одно слово (пруф)

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

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

    Ну и местами смущает работа с ошибками. Например, функция http.ListenAndServe всегда возвращает ошибку, что вроде как укладывается в концепцию go, но лично меня интуитивно такое отталкивает. И простого if err != nil иногда не хватает (см. rows.Err() в либах для работы с БД).

    А вообще обидно, что аналогов у языка нет. Если ты хочешь язык общего назначения со статической типизацией без современного ООП (как в Java, C#, C++), то выбора особо и нет. Kotlin больше под мобилки заточен, Swift явно создавался для экосистемы Apple. Так что популярность go вполне себе логична


    1. FanatPHP
      15.10.2024 09:32

      Блин, зачем я стал читать это

      Nullable columns are annoying and lead to a lot of ugly code. If you can, avoid them. I

      Очередное "мы в это не можем и поэтому объявим плохим".


  1. Blumfontein
    15.10.2024 09:32

    А какую метрику увеличили перейдя с PHP на Go?


    1. tkovacs
      15.10.2024 09:32

      Команду разработки и зарплаты :)


    1. siberianlaika
      15.10.2024 09:32

      Не отвечу за Lamoda, но в проектах где мне довелось участвовать в миграциях с PHP основным мотивом было снижение ресурсов по памяти и CPU. И это удавалось сделать, хотя процесс перевода не был быстр и сам требовал много ресурсов на разработку. Например, из личного опыта, Lazada как пример большого проекта практически съехавшего с PHP, переезд занял несколько лет. Сейчас работаю в компании где монолит на PHP тоже имеет место и бизнес не готов его убрать совсем и вижу в сравнении, что на Go-сервисах в той же инфраструктуре потребление ресурсов ниже. Но вложение в переезд может обойтись еще дороже, чем поддержка того что есть.


      1. SerafimArts
        15.10.2024 09:32

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

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

        Ну чтобы не быть голословным: Обычный рефакторинг кода на PHP, который ранее отвечал за ~0.300с сейчас у меня отвечает за 0.004с - 0.007с. Тупо рефакторинг куска бизнес-логики с выносом в отдельный сервис без смены стека.

        Hidden text

        Так что лично я крайне скептически отношусь к таким аргументам, т.к. в таких случаях никогда не приводятся сравнения БЕЗ рефакторинга кода. И никогда не сравнивается результат в целом: Был стек из 10 железок в кластере -> стал стек из 1000 железок или наоборот, 1 железка.

        Более того, практика того же Avito, что переезд с PHP на Go с параллельным нарезанием на микросервисы всего и вся, показывает, что потребление всех ресурсов (включая человеческие) и стоимость поддержки в таком варианте возрастает кратно (пруф: https://www.youtube.com/watch?v=yLrSp174yc0).

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


        1. allishappy
          15.10.2024 09:32

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


        1. gudvinr
          15.10.2024 09:32

          PHP на порядки эффективнее в утилизации памяти, нежели Go из-за специфики своей работы (что взял, то моментально отдал)

          Но есть нюанс. В Go, в отличие от Java и C#, не происходит выделения всего в куче.

          Компилятор может сгенерировать код так, что "взял-отдал" будет происходить на стеке и тогда GC вообще не будет принимать участие в этом движе.

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

          Поэтому в целом Go не то что бы плох в плане менеджмента памяти.


  1. NeoCode
    15.10.2024 09:32

    "Подводные камни" в высшей степени странные. ИМХО чистый субъективизм.

    Я немного знаю PHP, но основные языки для меня - C/C++, поэтому появление Go и возможность использовать его в вебе вместо PHP/Python для меня был как глоток свежего воздуха.

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

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

    А вот важнейшее на мой возгял преимущество Go перед PHP/Python - это даже не статическая типизация (хотя она тоже очень важна), а то что в языке нормально, ЯВНО объявляются переменные. Т.е. переменная явно объявляется один раз (оператором var или :=), а все остальные упоминания имени - это ее использование. В случае опечатки в имени не возникнет новая переменная, а будет ошибка компиляции.


    1. jizuscreed Автор
      15.10.2024 09:32

      Конечно субъективизм, никто и не спорит. Это топ жалоб именно php-шников, тут вся статья рождена из доклада на внутреннее php-комьюнити на тему "какой боли ждать и к чему вам готовиться морально".
      Безусловно, у тех, кто переходит из C, ощущения будут совсем другие.


    1. 0xBAB10
      15.10.2024 09:32

      явная декларация не гарантирует ничего

      func main() {
      	a := 1
      	func() {
      		// ошибочка вышла - опечатались := вместо =
      		a := 2
      		doSmth(a)
      	}()
      	if a == 2 {
      		println("ok")
      	} else {
      		println("obosrams")
      	}
      }

      что выведет эта програмка на "безопасном" го?

      вопрос решается сторонними тулзами (линтерами), которые на php/python есть

      а для го есть?


      1. jizuscreed Автор
        15.10.2024 09:32

        да, пример с затенением тут так и напрашивается
        но линтеры есть и даже ide такое подсвечивают