Pull to refresh

Начинаем любить Go

Reading time 5 min
Views 27K
Original author: Jeremy Mikkola
Если что, это вольный перевод статьи из блога Jeremy Mikkola.

Эта статья о том, как некоторое время спустя я распробовал и таки полюбил язык Go (аки golang).

Жалобы

Еще год назад у меня было полно жалоб на тот или иной аспект Go. В основном я жаловался на то, что при том, что ты не можешь использовать Go как «настоящий системный язык» (в плане написать-на-нем-операционку), тебе все равно приходится думать о том, о чем обычно приходится думать в системных языках: например, использовать тут указатель или нет. Система типов лежит в непродуктивном ущелье между достаточной строгостью, чтобы стать «автоматичной» (привет адептам Haskell!) и достаточной динамичностью, чтобы не отвлекать (как в Python). Какое-то время я тупил на таких вещах, как проход по юникодовой строке не символами, а байтами, и всякое такое. Я обвинял Go в том, что он так-то не всегда следует своему принципу простоты, так как в нем есть много разных узких штук, вроде make(). И конечно же я жаловался на то, что при работе с Go постоянно приходится повторять блоки if err != nil { return err }.

Поначалу я точно не мог назвать себя фанатом Go.

В техническом плане я согласен с тем, что большинство моих жалоб корректны. Я согласен с тем, что систему типов можно было бы придумать и покруче, а от указателей не помешало бы немного абстрагироваться. Но как только я начинаю понимать, что (не все, но многие) проблемы, на которые я жаловался, на самом деле вроде как даже и не непрягают. В действительности это напоминало нытье на целый лес, из-за пары некрасивых деревьев. В повседневном использовании языка большинство вещей, о которых я так волновался, вообще не давали о себе знать. Я ни разу не встречал баг, так или иначе связанный с проходом по строке. В реальном коде ты почти не кастишь interface{} так часто, как хотелось бы тем ребятам, которые дрочат на системы типов. Ах да, нет беды в том, чтобы вызывать make() в одном месте и new() в другом. Больше всего я ругался на те архитектурные решения языка, которые усложняли создание абстракций. Ты не можешь просто так сделать структуру данных, которая станет альтернативой к встроенным структурам данных (привет кодогенерации!). Без обобщений ты не сможешь строить большие обобщенные абстракции. Скорее всего, это сделано намеренно.

Серия 217: Новая надежда (на Go)

Теперь я думаю о Go иначе. Это не системный язык, это не динамический язык, и это даже не обязательно язык для веба, это язык, который стоит против абстракций. Как только я ни подумаю о Java, первая вещь, которая приходит в голову это это гигантская замыленная система, образованная благодаря экстремальным примерам обязательного обобщения и любви к созданию объектных иерархий ради объектных иерархий. Да, я знаю, что абстракции очень и очень удобны: они позволяют нам делать много разных вещей, используя несколько строчек кода. Основная проблема в том, что они очень-очень сложные!

Когда интерфейс — всего лишь интерфейс какой-то абстракции, даже не ее реализация, слишком большой, чтобы полностью поместиться в твою голову, абстракция заставляет программиста страдать! Строки кода, использованные на решение определенной задачи, это довольно убогая метрика того, как тяжело было эту проблему решить. Написав в 2 или даже в 10 раз больше кода с более незамысловатыми абстракциями (или вообще без оных), решение может в конце концов оказаться эффективнее. На досуге можешь посмотреть «Simple Made Easy» товарища по имени Rich Hickey, который классно обрисовал разницу между простотой и легкостью.

Как вы думаете, что проще понять для программиста, который только пришел в проект и не знает ничего о тех гигантских абстракциях, которые вы тут нагородили: 10 строчек кода, который имеет смысл только если ты знаешь абстракции или все-таки 20-100 строчек кода, которые говорят сами за себя? Это то место, где Go всходит на небосвод! Спасая твою бренную душу от абстракций (ценой «пары строчек кода»), код на Go сохраняет смысл и логику вплоть до самых мелких кусочков программы. За это программисту приходится платить… написанием кода (внезапно)! Учитывая то, что ты поначалу потратил значительные усилия, чтобы разобраться в той или иной абстракции, когда ты будешь вынужден использовать абстракцию поменьше, твоя продуктивность резко упадет, так как тебе придется реализовать еще больше логики.

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

Чтение кода

Эту штуку точно стоит оптимизировать. Код никогда не удаляется сразу после написания, люди его читают. Давайте вспомним Исаака Азимова и его «Три закона роботехники»:

  1. Робот не может причинить вред человеку или своим бездействием допустить, чтобы человеку был причинён вред.
  2. Робот должен повиноваться всем приказам, которые даёт человек, кроме тех случаев, когда эти приказы противоречат Первому Закону.
  3. Робот должен заботиться о своей безопасности в той мере, в которой это не противоречит Первому и Второму Законам.

А я придумал «Три закона кодонаписания»:

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

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

Обычно эти правила таки применяют, только в обратной последовательности. Сначала разрабатывают язык, который позволяет легко доносить суть комьютеру. Потом, уже работая в этих рамках, программу делают максимально быстрой и краткой. И наконец, в качестве «бонуса», мы немного допиливаем язык в плане его, собственно, понимания. А вот Go, например, выбирает правильную последовательность. Программы на Go, в первую очередь, легко читаются людьми, затем легко разбираются компьютером (Go заботится об уменьшении времени компиляции) и только в последнюю очередь — быстрые и краткие. Интересный факт: нельзя сказать, что программы на Go не быстрые, Go очень даже быстрый язык. Go может быть излишне «многословен», но на то есть причина.

Как только мы ставим Второй Закон на первое место, то получаем «преждевременную оптимизацию», или ее не такого популярного братца — «преждевременное обобщение». Оба, в общем-то, являются корнем зла в программировании. Как только мы ставим Третий Закон на первое место, то получаем ассемблер. Нравится? Мне нет!

Год назад

Когда я изначально писал свои впечатления о Go, я еще не знал о другом новом системном языке — Rust.

Я уверен, что если бы я тогда увидел Rust, по крайней мере, в том виде, в котором мы видим Rust сегодня, то я бы определенно предпочел бы его Go. В языке Rust есть все: «человеческий» вывод типов, генерики, примеси, макросы, разные замечательные compile-time проверки. Ты даже можешь попросить компилятор проверить, что возвратные значения определенных типов нигде не игнорируются! Rust отвечает на почти все вопросы, которые у меня были к Go. В таком случае, зачем я все еще выбираю Go, начиная новый проект?

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

Размер имеет значение

Что уж говорить, для определенных проектов я все-таки выберу Rust. Особенно для очень больших проектов, где внушительное доверие абстракциям будет иметь больше смысла. У языка Rust есть классная система типов, которая приносит больше плодов именно в огромных (100.000+ строчек кода) проектах. Но даже с абстракциями, код на Rust не обязательно будет яснее, чем код на Go, так как первый обменивает немного краткости на такие мощные вещи, как генерики.

Размер имеет значение, но имеет значение не размер проекта как такового, а размер области видимости, которую ты должен держать в голове, чтобы осознавать происходящее. Go классно уменьшает эту область для маленьких и средних проектов. Да, попечатать придется, но ведь это не самое сложное в программировании!
Tags:
Hubs:
+25
Comments 73
Comments Comments 73

Articles