Код. Культура, скомпилированная в байты
Код. Культура, скомпилированная в байты

Полная версия

Код. Культура, скомпилированная в байты

Язык: Русский
Год издания: 2025
Добавлена:
Настройки чтения
Размер шрифта
Высота строк
Поля
На страницу:
2 из 3

Этот выбор имеет глубокие последствия. Код на Ruby, написанный разными программистами, может выглядеть очень по-разному. Один напишет традиционный цикл, другой использует итератор с блоком, третий – метод из стандартной библиотеки. Всё это будет работать, всё это будет идиоматичным Ruby. Но читать чужой код становится сложнее: нужно понимать не только язык, но и стиль конкретного автора.

«Matz is nice and so we are nice» – «Матц добрый, и мы тоже добрые».

MINASWAN – акроним, ставший неофициальным девизом Ruby-сообщества. Это не просто декларация доброжелательности. Это признание того, что культура языка определяется не только синтаксисом, но и людьми, которые его используют. Мацумото создал атмосферу, в которой вежливость и взаимопомощь стали нормой. Сообщество унаследовало эту атмосферу.

Принцип наименьшего удивления – ещё один столп философии Ruby. POLA, или Principle of Least Astonishment, означает, что язык должен вести себя так, как программист интуитивно ожидает. Если программист думает, что определённый код должен работать определённым образом, – он должен работать именно так.

«I designed Ruby to minimize the surprise for me. Not for everyone – for me. But I believe that most programmers think similarly» – «Я проектировал Ruby так, чтобы минимизировать удивление для меня. Не для всех – для меня. Но я верю, что большинство программистов думают похоже», – уточнял Мацумото.

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

Контраст с Python здесь особенно заметен. Python и Ruby возникли почти одновременно, оба – скриптовые языки с динамической типизацией, оба стремятся к читаемости и удобству. Но их философии различаются в фундаментальном вопросе: что важнее – единообразие или выразительность?

Python говорит: единообразие. Когда весь код выглядит одинаково, его легче читать, легче поддерживать, легче передавать между командами. Цена – ограничение творческой свободы программиста.

Ruby говорит: выразительность. Когда программист может выбрать способ, который кажется ему красивым, он пишет с удовольствием. Цена – код может быть менее предсказуемым, требовать больше усилий для понимания.

Ни одна из позиций не «правильнее» другой. Это разные ответы на один вопрос: как должны выглядеть отношения между языком и программистом? Python ставит язык в позицию строгого наставника, который знает, как правильно. Ruby ставит язык в позицию услужливого инструмента, который подстраивается под мастера.

Ruby on Rails – фреймворк, сделавший Ruby знаменитым за пределами Японии, – воплотил эту философию в практике веб-разработки. «Convention over configuration» – «соглашение важнее конфигурации» – ещё один манифест: не заставляй программиста принимать решения там, где есть разумный вариант по умолчанию. Rails был самоуверенным – он предлагал один правильный способ строить веб-приложения, но этот способ был спроектирован так, чтобы приносить удовольствие.

Парадокс Rails в том, что его принцип «соглашение важнее конфигурации» ближе к философии Python, чем к философии Ruby. Rails ограничивает свободу программиста ради последовательности. Но он делает это в рамках Ruby, который эту свободу даёт. Результат – фреймворк, который одновременно опинионирован и гибок, строг в структуре и выразителен в деталях.

Сам Мацумото никогда не претендовал на универсальность своих идей. «Ruby is not perfect. It’s just one way of thinking about programming. But I believe it’s a good way» – «Ruby не идеален. Это лишь один способ думать о программировании. Но я верю, что это хороший способ», – признавал он.

1.3. Go Proverbs: простота как дисциплина

В ноябре 2015 года Роб Пайк выступил на Gopherfest в Сан-Франциско с докладом под названием «Go Proverbs». Он вышел на сцену не с презентацией, а со списком коротких фраз – пословиц, как он их назвал. Каждая описывала какой-то аспект того, как следует думать при программировании на Go.

«Don’t communicate by sharing memory, share memory by communicating» – «Не общайтесь через разделяемую память, разделяйте память через общение». «Clear is better than clever» – «Ясное лучше умного». «A little copying is better than a little dependency» – «Немного копирования лучше, чем немного зависимости». «The bigger the interface, the weaker the abstraction» – «Чем больше интерфейс, тем слабее абстракция». «Errors are values» – «Ошибки – это значения».

Пайк не изобрёл эти принципы для доклада. Они кристаллизовались за шесть лет развития Go – с момента, когда в 2007 году три инженера Google начали обсуждать, каким должен быть новый язык, и до 2015-го, когда Go уже использовался в критической инфраструктуре компании.

«Clear is better than clever» – «Ясное лучше умного».

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

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

К 2007 году Google стал одной из крупнейших технологических компаний мира. Инфраструктура, на которой работали поиск, Gmail, YouTube, включала миллионы серверов и миллиарды строк кода. Значительная часть этого кода была написана на C++.

C++ – мощный язык. Он даёт программисту контроль над каждым байтом памяти, позволяет строить абстракции любой сложности, поддерживает десятки парадигм программирования. Но в масштабах Google эта мощность стала проблемой.

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

Роберт Гриземер, Роб Пайк и Кен Томпсон – трое инженеров с легендарными послужными списками – начали обсуждать альтернативу. Пайк и Томпсон работали в Bell Labs и участвовали в создании Unix и Си. Гриземер работал над виртуальной машиной Java HotSpot. Они знали, как устроены языки программирования изнутри, и они устали от сложности.

«Complexity is multiplicative» – «Сложность мультипликативна», – говорил Пайк. Сложность не складывается, она умножается. Каждая новая возможность языка взаимодействует со всеми существующими, создавая комбинаторный взрыв краевых случаев. C++ начинался как простое расширение Си, но к 2007 году стал одним из самых сложных языков в истории.

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

В Go нет наследования классов. Нет generic-типов – они появились только в 2022 году, через тринадцать лет после первого релиза. Нет исключений. Нет перегрузки функций. Нет неявных преобразований типов. Каждое из этих «нет» – сознательное решение.

«A little copying is better than a little dependency» – «Немного копирования лучше, чем немного зависимости».

Этот принцип звучит как ересь для программиста, воспитанного на идее повторного использования кода. DRY – Don’t Repeat Yourself, «не повторяй себя» – стало мантрой индустрии. Но Пайк говорит: иногда лучше скопировать несколько строк, чем создавать зависимость от пакета, который потянет за собой другие зависимости, которые потянут ещё, и в итоге для вывода «Hello, world» понадобится десять мегабайт библиотек.

Go не против абстракций – он против абстракций ради абстракций. Каждая абстракция должна оправдывать себя. Если копирование проще и понятнее – копируй.

«Errors are values» – «Ошибки – это значения».

Обработка ошибок в Go – вероятно, самый спорный аспект языка. Там, где другие языки используют исключения или монады, Go предлагает простейший механизм: функция возвращает два значения – результат и ошибку. Если ошибка не nil, что-то пошло не так. Программист проверяет это условие после каждого вызова, который может завершиться неудачей. Паттерн «if err!= nil» повторяется в Go-программах десятки, сотни раз. Критики называют его многословным, уродливым, утомительным. Сторонники – честным.

Пайк объяснял эту философию так: «Errors are values. They can be programmed, not just handled» – «Ошибки – это значения. Их можно программировать, а не просто обрабатывать». Ошибка – не исключительная ситуация, выбивающая программу из нормального потока. Ошибка – обычное значение, с которым можно работать как с любым другим. Проверить, обернуть, агрегировать, отложить.

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

«Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite» – «Стиль gofmt – ничей любимый, но сам gofmt – любимец всех».

gofmt – форматтер кода, встроенный в стандартную поставку Go. Он приводит любой код к единому стилю: определённые отступы, определённые переносы, определённое расположение скобок. Настроек почти нет. Код, пропущенный через gofmt, выглядит одинаково независимо от того, кто его написал.

Результат стиля gofmt никому не нравится полностью. Кто-то предпочитает другие отступы, кто-то – другое расположение скобок. Но сам инструмент нравится всем, потому что он прекращает споры. Не нужно обсуждать стиль на код-ревью. Не нужно писать style guide. Не нужно настраивать линтер. Вопрос закрыт: есть gofmt, всё остальное – шум.

Это воплощение философии Go в миниатюре. Язык не даёт свободу выбора там, где выбор не нужен. Он принимает решение за программиста – не потому, что программист глуп, а потому, что время программиста ценно. Пусть лучше думает о бизнес-логике, чем о том, ставить ли пробел перед скобкой.

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

Три философии, один вопрос

Zen of Python, Ruby Way, Go Proverbs – три документа, три ответа на вопрос о том, каким должен быть язык программирования.

Python говорит: язык должен быть ясным. Один способ сделать что-то лучше многих. Явность лучше магии. Читаемость – не роскошь, а необходимость. Код – это коммуникация между людьми, машина лишь посредник.

Ruby говорит: язык должен приносить счастье. Программист – творец, и язык должен помогать творить, а не мешать. Выразительность важнее единообразия. Если можно сделать что-то красиво – нужно сделать красиво.

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

Каждая из этих философий выросла из конкретного контекста. Python создавался для людей, которые не хотят быть профессиональными программистами, – и стал языком, на котором пишут профессионалы во всех областях. Ruby создавался для одного человека, который хотел получать удовольствие от программирования, – и создал сообщество, объединённое идеей счастья. Go создавался для решения конкретной проблемы масштаба Google – и оказался востребован далеко за пределами компании.

Манифесты не описывают языки исчерпывающе. В Python есть сложности, которые Zen не предусматривал. В Ruby есть боль, которую MINASWAN (Matz Is Nice And So We Are Nice – «Мацумото добрый, и мы тоже добрые») не облегчает. В Go есть многословие, которое Go Proverbs не отменяют. Но манифесты задают направление. Они говорят: вот что мы считаем важным, вот какими мы хотим быть.

Выбирая язык, программист выбирает не только синтаксис. Он выбирает философию – способ думать о коде, о проблемах, о решениях. И чтобы выбрать осознанно, стоит прочитать манифесты. Не цитаты из них, вырванные из контекста, – сами документы, целиком. В них – голоса тех, кто принимал решения. В них – ответы на вопрос «почему».

Zen of Python доступен каждому владельцу Python: import this. Go Proverbs – на YouTube, в записи того самого выступления Роба Пайка. Слова Мацумото разбросаны по десяткам интервью, но их суть неизменна: «Ruby is designed to make programmers happy» – «Ruby создан, чтобы делать программистов счастливыми».

Три манифеста, три философии, один вопрос: как должен думать человек, общаясь с машиной?

Ответы различаются. Но каждый из них – честная попытка ответить.

Глава 2. Синтаксис как мировоззрение

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

Но за каждым синтаксическим выбором стоит философское решение.

Фигурные скобки или отступы – это не просто способ показать компилятору, где заканчивается условие. Это ответ на вопрос: кому мы доверяем? Программисту, который сам организует свой код, или языку, который навязывает единообразие? Статическая типизация или динамическая – это не вопрос производительности. Это вопрос о природе мира: познаваем ли он заранее или раскрывается только в действии? Коды возврата или исключения – это не техническая деталь реализации. Это позиция относительно неизбежности ошибок и того, как с ними жить.

Синтаксис – это застывшая философия. И как любая философия, он формирует мышление тех, кто его использует.

2.1. Скобки, отступы и границы блоков

В 1972 году Деннис Ритчи работал над языком Си в Bell Labs. Ему нужен был способ обозначить границы блоков кода – условий, циклов, функций. Он выбрал фигурные скобки. Это решение казалось очевидным: B, предшественник Си, уже использовал их, унаследовав от BCPL. Скобки были явными, недвусмысленными, их нельзя было случайно стереть или не заметить на экране телетайпа.

Контекст 1972 года существенен. Программисты работали с терминалами, которые показывали восемьдесят символов в строку. Перфокарты ещё не ушли в прошлое полностью. Пробелы и табуляции были ненадёжны – разные системы интерпретировали их по-разному, разные терминалы отображали их с разной шириной. Фигурные скобки решали практическую проблему: они были видимы, однозначны, устойчивы к ошибкам передачи данных. Один символ – открытие блока. Другой символ – закрытие. Никакой двусмысленности.

Это решение определило полвека программирования. Java, JavaScript, C#, Go, Rust – все они унаследовали фигурные скобки от Си. Целое семейство языков думает о структуре кода одинаково: вот открывающая скобка, вот закрывающая, между ними – тело блока. Отступы при этом остаются рекомендацией, конвенцией, делом вкуса. Компилятору всё равно, как вы расставите пробелы. Он смотрит только на скобки.

Можно написать программу на Си, где весь код идёт в одну строку. Компилятор не возразит. Можно расставить отступы хаотично, вразнобой. Компилятор скомпилирует. Фигурные скобки – единственный источник истины о структуре. Всё остальное – косметика для человеческих глаз.

Семнадцать лет спустя, в декабре 1989 года, Гвидо ван Россум начал работу над Python в Центре математики и информатики в Амстердаме. Он сделал противоположный выбор: никаких скобок для обозначения блоков. Только отступы. Структура кода должна быть видна сразу, без необходимости искать парные символы.

Ван Россум не изобрёл эту идею. Он унаследовал её от ABC – образовательного языка, над которым работал в том же исследовательском центре. «Я создал базовый синтаксис, использовал отступы для группировки вместо фигурных скобок или блоков begin-end», – вспоминал он позже в интервью. ABC, в свою очередь, возможно, заимствовал эту концепцию от языка occam. Но именно Python сделал значимые отступы массовым явлением.

Почему ван Россум сохранил этот выбор, хотя мог отказаться от него, как отказался от других особенностей ABC – например, от ключевых слов в верхнем регистре? Потому что отступы решали реальную проблему. «Я привык к этой возможности, работая с ABC», – объяснял он в одном из интервью, – «она устраняла определённый тип бессмысленных споров, распространённых среди программистов на C: куда ставить фигурные скобки».

За этим стоит глубокое убеждение: код читают чаще, чем пишут. Если программисты всё равно используют отступы для визуальной структуры – а они используют, потому что иначе код нечитаем, – почему бы не сделать их обязательными? Форма становится содержанием. То, что вы видите, – это то, что исполняется. Нет разрыва между визуальным восприятием и логической структурой.

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

Haskell, появившийся годом позже Python, в 1990 году, тоже использует значимые отступы – но иначе. В Haskell действует так называемое «правило офсайда» (off-side rule), термин, введённый Питером Ландином ещё в 1966 году. Отступы определяют границы блоков после ключевых слов where, let, do и of. Но в отличие от Python, Haskell предлагает альтернативу: можно использовать фигурные скобки и точки с запятой явно. Большинство программистов на Haskell этого не делают – консенсус в том, что значимые отступы делают код красивее. Но выбор есть.

Это различие показательно. Python категоричен: отступы – единственный способ. Haskell гибок: отступы – предпочтительный способ, но не единственный. За этим стоят разные философии. Python создавался для людей, которые учатся программировать, – им не нужен выбор, им нужна ясность. Haskell создавался для исследователей и экспертов – им нужна гибкость, возможность выразить намерение разными способами.

Философия фигурных скобок – это философия свободы и явности. Программист сам решает, как форматировать код. Компилятор доверяет ему. Границы блоков обозначены явно, их можно увидеть даже в однострочной записи. Да, это порождает бесконечные споры о стиле: скобка на той же строке или на новой? Отступ в два пробела или в четыре? Пробел перед скобкой или нет? Но эти споры – цена свободы. Каждая команда, каждый проект может выбрать свой стиль.

Философия значимых отступов – это философия принудительной ясности. Язык не доверяет программисту форматировать код правильно. Или, точнее, язык не видит разницы между «неправильно отформатированным» и «неправильным» кодом. Если отступ не соответствует логике – это синтаксическая ошибка, а не вопрос стиля. Споры о форматировании невозможны, потому что формат – часть синтаксиса. Код, который компилируется, автоматически правильно отформатирован.

Третий подход – ключевые слова – выбрали Pascal с его begin и end, Ruby с его do и end, Ada с её begin и end. Это компромисс: явные маркеры, как скобки, но более читаемые для человека. Вместо абстрактных символов – слова естественного языка. Код читается почти как проза: «если условие, тогда начало… конец». Никлаус Вирт, создатель Pascal, верил в самодокументирующийся код. Ключевые слова делают структуру понятной даже тому, кто видит программу впервые.

Каждый подход отражает ценности эпохи и создателей.

Си появился в мире ограниченных ресурсов, где каждый символ на счету, где надёжность важнее красоты. Телетайпы печатали медленно. Память измерялась килобайтами. Фигурная скобка – один символ вместо пяти букв слова begin. В таком мире минимализм – не эстетический выбор, а необходимость.

Python появился в мире, где главным ресурсом стало время программиста, где читаемость важнее компактности. К 1989 году память подешевела, экраны стали большими, а программы – сложными. Узким местом стал не компьютер, а человек. Ван Россум оптимизировал для человека.

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

Ruby появился в мире, где счастье программиста объявлялось явной целью. Юкихиро Мацумото хотел, чтобы код был приятен глазу и разуму. Ruby использует end для закрытия блоков, но позволяет опускать его в однострочных конструкциях. Гибкость ради красоты.

Показательно, как развивались языки со временем. Си остался верен скобкам – это часть его идентичности, его наследия, его культуры. Python никогда не рассматривал возможность добавить скобки как альтернативу – это разрушило бы его философию. Haskell сохранил оба варианта, но сообщество однозначно предпочитает отступы.

А Go, созданный в 2009 году людьми из той же традиции Bell Labs, что и Си, пошёл дальше. Он не просто сохранил скобки, но добавил gofmt – инструмент, который автоматически форматирует код единственным «правильным» способом. Не рекомендуемым. Не предпочтительным. Единственным.

«Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite», – гласит один из Go Proverbs. Стиль gofmt – ничей любимый, но сам gofmt – всеобщий любимый. В этой формуле – признание, что споры о стиле бессмысленны, и одновременно решение: пусть машина решит за всех. Никто не получает свой любимый стиль. Но никто и не спорит. Это не компромисс – это капитуляция перед неразрешимостью конфликта.

Go – эволюция философии Си. Создатели Go поняли то, чего не знали создатели Си: свобода форматирования оказалась не ценностью, а источником конфликтов. Десятилетия священных войн о стиле. Тысячи часов, потраченных на споры о расположении скобок. Go устранил саму возможность спора. Автоформатирование при сохранении файла. Один стиль для всех. Мир через единообразие.

Этот подход распространился. Python получил black – «бескомпромиссный форматтер», который тоже не предлагает выбора. JavaScript получил prettier. Rust получил rustfmt. Индустрия пришла к пониманию, которое Go сформулировал первым: лучший способ закончить спор о форматировании – сделать его невозможным.

2.2. Типизация как картина мира

Когда программист объявляет переменную, он делает утверждение о мире. «Эта переменная – целое число» – это не просто техническая аннотация. Это обещание. Это контракт. Это способ сказать компилятору, машине и другим программистам: вот что здесь будет лежать, вот как с этим можно обращаться, вот чего от этого можно ждать.

Языки программирования радикально расходятся в том, когда и как требовать это обещание. И за этим расхождением – глубокие философские различия.

Статически типизированные языки требуют обещаний заранее. Программа не скомпилируется, пока типы не согласованы. Компилятор проверяет контракты до того, как хотя бы строчка кода будет выполнена. Это Си, Java, Go, Rust, Haskell – языки, которые верят, что ошибки нужно ловить как можно раньше. Чем раньше найдена ошибка, тем дешевле её исправить. Ошибка на этапе компиляции стоит минуты. Ошибка в продакшене стоит часы, дни, репутацию.

Динамически типизированные языки откладывают проверку. Тип переменной определяется в момент выполнения, по значению, которое она содержит. Это Python, Ruby, JavaScript, Perl, Lisp – языки, которые верят в гибкость, в способность программиста понимать свой код. Зачем заставлять программиста объяснять очевидное? Зачем писать int x = 5, когда и так понятно, что 5 – целое число?

За этим техническим различием – два разных мировоззрения. Два разных ответа на вопрос о природе программ и природе ошибок.

Статическая типизация исходит из предположения, что мир познаваем. Структура данных может быть описана заранее. Контракты между частями системы могут быть формализованы. Программа – это доказательство, и типы – часть этого доказательства. Если программа компилируется – значительная часть ошибок уже исключена. «Если компилируется – работает», говорят программисты на Haskell, лишь немного преувеличивая. Система типов Haskell настолько выразительна, что позволяет кодировать в типах многие инварианты, которые в других языках проверяются только тестами.

Динамическая типизация исходит из предположения, что мир изменчив, непредсказуем, не укладывается в заранее заданные схемы. Данные приходят в разных формах. Структуры эволюционируют. Жёсткие контракты сковывают. Программист лучше понимает свой код, чем любой статический анализатор. Гибкость важнее гарантий. Тесты важнее типов. Работающий код важнее формально корректного.

На страницу:
2 из 3