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

Дизайнер обложки Grok

© Сергей Кирницкий, 2025

© Grok, дизайн обложки, 2025

ISBN 978-5-0068-7698-9

Создано в интеллектуальной издательской системе Ridero

Введение: Код как культурный артефакт

В.1. Вопрос, который редко задают

Почему существует более семисот языков программирования?

Вопрос кажется странным. Машине безразлично, на чём написан код – она исполняет байт-код, последовательность нулей и единиц. Компилятор переводит любой синтаксис в одни и те же машинные инструкции. С точки зрения процессора нет никакой разницы между программой на Python и программой на Go, между Haskell и JavaScript. Результат один – электрические импульсы в кремниевых схемах.

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

Это не рациональное поведение, если языки – просто инструменты. Плотник не спорит о философских основаниях молотка. Хирург не пишет манифестов о скальпеле. Но программисты – спорят и пишут. И делают это десятилетиями.

Значит, языки программирования – не просто инструменты. Они что-то большее. Но что именно?

Каждый язык программирования – это материализованный ответ на вопрос: как должен думать человек, общаясь с машиной? И разные люди в разных обстоятельствах дают разные ответы. Кен Томпсон и Деннис Ритчи в Bell Labs начала семидесятых спрашивали: как переписать операционную систему, чтобы она стала переносимой между машинами? Гвидо ван Россум в амстердамском исследовательском центре конца восьмидесятых спрашивал: как научить людей программировать, не отпугнув их синтаксическим шумом? Джеймс Гослинг в Sun Microsystems начала девяностых спрашивал: как написать код, который переживёт смену железа?

Ответы получились непохожими. И каждый ответ нёс в себе следы вопроса – как река несёт следы истока.

Си появился в мире, где память измерялась килобайтами, а каждый такт процессора был на счету. «C is quirky, flawed, and an enormous success» («Си странен, несовершенен и чрезвычайно успешен»), – напишет позже Деннис Ритчи. В этой фразе – вся судьба языка: несовершенство, которое создатель осознавал с первого дня, и триумф, которого никто не планировал. Java появилась в мире корпоративной разработки, где код должен был работать годами без изменений, а команды программистов сменялись быстрее, чем проекты. Отсюда многословность, которую одни считают ясностью, другие – избыточностью. JavaScript появился за десять дней в мае 1995 года, потому что Netscape нужен был скриптовый язык для браузера – и нужен был срочно. Брендан Эйх вспоминал потом, что почти не спал. За эти десять дней он заложил фундамент языка, на котором сегодня работает практически весь интернет.

Десять дней. Семьсот языков. Тридцать лет споров о типизации. Что-то не сходится, если считать это просто инженерией.

Потому что это не просто инженерия. Это культура.

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

Зачем люди продолжают создавать новые языки? Зачем Гвидо ван Россум в 1989 году решил, что миру нужен ещё один язык, когда уже существовали Perl и Tcl? Зачем Брендан Эйх в 1995-м не использовал Scheme или Java, которые уже были готовы? Зачем инженеры Google в 2007 году начали проектировать Go, имея в распоряжении весь арсенал существующих языков?

Ответ всегда один: существующие языки не выражали того, что хотели выразить создатели новых. Не технически – философски. Perl выражал принцип «есть много способов сделать это». Ван Россум хотел выразить противоположное: должен быть один очевидный способ. Java выражала идею корпоративной надёжности и многословной явности. Гослинг хотел, чтобы код был понятен любому инженеру, присоединившемуся к проекту через пять лет. Go выражал идею, что сложность C++ стала непереносимой, и язык должен помещаться в голове программиста целиком.

Новый язык возникает, когда кто-то чувствует: существующие языки воплощают не те ценности. Это культурный акт, не технический.

В.2. Что такое культура языка программирования

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

Культура языка программирования – это совокупность явных принципов, неявных конвенций, инструментов и истории, которые определяют, как на этом языке принято писать и думать. Принято – ключевое слово. Не «как можно» и не «как правильно», а «как принято». Это социальное понятие, не техническое.

Явные принципы фиксируются в документах. Python имеет PEP 20, известный как Zen of Python – девятнадцать афоризмов, написанных Тимом Питерсом в 1999 году. Двадцатый афоризм намеренно оставлен пустым. «Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex.» («Красивое лучше уродливого. Явное лучше неявного. Простое лучше сложного.») Эти строки – не просто рекомендации по стилю. Это философское заявление о том, что такое хороший код. И что важнее – это заявление встроено в сам язык: достаточно написать «import this», чтобы увидеть его на экране. Философия языка доступна в самом языке.

Ruby имеет принцип, который его создатель Юкихиро Мацумото повторял в бесчисленных интервью: «Ruby is designed to make programmers happy» («Ruby создан, чтобы делать программистов счастливыми»). Счастье программиста – не побочный эффект, а цель разработки. В интервью 2003 года он объяснял: «You want to enjoy life, don’t you? If you get your job done quickly and your job is fun, that’s good isn’t it? That’s the purpose of life, partly.» («Вы ведь хотите наслаждаться жизнью? Если вы быстро делаете работу и работа доставляет удовольствие – это ведь хорошо? Отчасти в этом и смысл жизни.») Язык программирования как инструмент для хорошей жизни – это философское утверждение, которое определяет каждое решение в дизайне Ruby.

Go имеет Go Proverbs – набор афоризмов, который Роб Пайк представил на конференции Gopherfest в 2015 году. «Clear is better than clever. A little copying is better than a little dependency. Don’t communicate by sharing memory, share memory by communicating.» («Ясное лучше умного. Немного копирования лучше, чем немного зависимости. Не общайтесь через разделяемую память, разделяйте память через общение.») Каждый афоризм – концентрированное философское утверждение о том, как следует писать программы. Не синтаксическое правило, а принцип мышления.

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

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

История создания языка – такая же часть его культуры, как и синтаксис. Go появился в Google в 2007—2009 годах, и это не случайная деталь биографии. К тому моменту в Google работали десятки тысяч инженеров, кодовая база измерялась миллиардами строк, компиляция C++ занимала часы. Роб Пайк вспоминал, что идея Go возникла во время одной такой бесконечной компиляции. «We were not going to continue writing C++” («Мы не собирались продолжать писать на C++»), – говорил он. Go – не абстрактное упражнение в дизайне языков. Это ответ на конкретную боль конкретной компании в конкретный момент времени. Понять Go невозможно, не понимая этого контекста.

Erlang возник в Ericsson, где телекоммуникационные системы должны были работать с надёжностью «девять девяток» – 99,9999999% времени безотказной работы. Это означает менее секунды простоя за тридцать лет. При таких требованиях нельзя позволить себе остановку системы для перезагрузки после ошибки. Отсюда философия Erlang: пусть процессы падают, их перезапустят супервизоры. «Let it crash» («Пусть падает») – не признание поражения, а стратегия надёжности. Система, которая умеет падать и восстанавливаться, надёжнее системы, которая пытается никогда не падать.

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

Здесь важно сделать оговорку. Когда мы говорим о культуре Python или культуре Go, мы говорим о языках как артефактах, не о людях. Не «программисты на Python думают так», а «Python как язык воплощает такую философию». Разница принципиальна. Программист может использовать Python и не разделять его философию – писать непитонический код, игнорировать PEP 8, нарушать конвенции. Это его право. Но язык останется тем, чем является – артефактом, несущим определённые ценности. Книга исследует артефакты, не людей.

Ещё одна оговорка: описание культуры не означает её оценки. Python не лучше Go, и Go не лучше Rust. Статическая типизация не правильнее динамической, и объектно-ориентированное программирование не превосходит функциональное. Каждый подход – ответ на определённые вопросы в определённом контексте. Описать эти ответы – задача книги. Судить их – нет.

В.3. Карта путешествия

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

Зачем это нужно? Гвидо ван Россум сформулировал точно: «You primarily write your code to communicate with other coders, and, to a lesser extent, to impose your will on the computer» («Прежде всего вы пишете код для общения с другими программистами и лишь во вторую очередь – чтобы навязать свою волю компьютеру»). Код – прежде всего коммуникация между людьми. Машина – лишь посредник. Если принять эту точку зрения, культура перестаёт быть опциональным украшением. Она становится основой понимания. Программист, который знает культуру языка, видит за техническими решениями философские выборы. Почему Python использует значимые отступы? Почему Go отказался от исключений? Почему Rust требует явного управления временем жизни ссылок? За каждым из этих решений стоит не только механизм, но и мировоззрение.

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

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

Первая часть – «Философии» – исследует, что языки говорят о своих создателях. Манифесты, синтаксис, имена – всё это тексты, которые можно читать и интерпретировать. Zen of Python и Go Proverbs – не просто советы по стилю, а концентрированные философские позиции. Выбор между фигурными скобками и значимыми отступами – не технический, а мировоззренческий. Система типов языка отражает убеждения о природе программ и предсказуемости мира.

Вторая часть – «Истоки» – исследует, откуда пришли идеи. Три традиции переплетаются в истории программирования. Системная традиция идёт от Си и Unix: близость к машине, минимализм, доверие программисту. Академическая традиция идёт от Lisp и ML: формальная корректность, математические основания, чистота абстракций. Индустриальная традиция идёт от Java и корпоративных потребностей: масштаб, предсказуемость, экосистема. Современные языки – наследники и синтезаторы этих традиций. Rust сочетает системный минимализм с академической системой типов. TypeScript переносит академические идеи в индустриальную реальность JavaScript.

Третья часть – «Конфликты» – исследует, что происходит, когда философии сталкиваются. Войны парадигм – объектно-ориентированное против функционального, статическая типизация против динамической – это не технические споры. Это конфликты картин мира. За вопросом «нужны ли типы» стоит вопрос о том, можно ли предсказать поведение программы до её запуска. За вопросом «объекты или функции» стоит вопрос о том, как моделировать реальность – как совокупность вещей или как поток преобразований. Open source становится ареной, на которой эти конфликты разворачиваются публично. Корпорации создают языки, и языки несут отпечаток корпоративных культур.

Четвёртая часть – «Эволюция» – смотрит в будущее. Гибридные языки строят мосты между мирами: TypeScript между типизированным и нетипизированным, Kotlin между многословием Java и лаконичностью современных языков. Новый фундамент – Rust, Zig, Mojo – отказывается от legacy ради правильного дизайна с нуля. Искусственный интеллект размывает границы: когда Copilot пишет код, чья это культура? И главный вопрос: движутся ли языки к конвергенции или к специализации? Синтаксис становится похожим, но расходятся ли философии?

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

Си говорит о семидесятых: ограниченные ресурсы, доверие инженеру, близость к машине. Java говорит о девяностых: корпоративный мир, портируемость, масштаб. Rust говорит о десятых: кризис безопасности, открытые сообщества, строгость компилятора как защита от человеческих ошибок.

Каждый язык – снимок момента человеческой мысли, материализованный в синтаксисе и семантике.

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

ЧАСТЬ I: ФИЛОСОФИИ

Глава 1. Манифесты

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

Манифест – это застывшая философия. Когда Тим Питерс записал девятнадцать афоризмов, которые мы знаем как Zen of Python, он не просто составил список советов. Он зафиксировал систему ценностей, которая определяла решения Гвидо ван Россума на протяжении десятилетия. Когда Роб Пайк произносил Go Proverbs перед аудиторией Gopherfest, он артикулировал то, что команда Go считала правильным – и, что важнее, то, от чего она сознательно отказалась.

Манифесты редко читают целиком. Программисты цитируют отдельные строки – «Explicit is better than implicit» («Явное лучше неявного»), «Clear is better than clever» («Ясное лучше умного») – как заклинания, не задумываясь о контексте. Но если прочитать эти документы внимательно, в них обнаруживается нечто большее, чем набор максим. В них видна картина мира, которую создатели языка считали правильной.

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

1.1. Zen of Python: ясность как ценность

В 1999 году Тим Питерс отправил в рассылку comp.lang.python письмо, которое изменило способ разговора о Python. Он назвал его «The Zen of Python», и письмо содержало девятнадцать коротких утверждений о том, каким должен быть хороший код. Двадцатое место Питерс оставил пустым. Он никогда не объяснил почему, но само это молчание стало частью философии: иногда лучший ответ – отсутствие ответа.

Текст был включён в Python Enhancement Proposal под номером 20 и получил особый статус. В отличие от других PEP, которые предлагают конкретные изменения в язык, PEP 20 не предлагает ничего – он описывает. Это не план действий, а система координат. И в знак признания этого статуса разработчики встроили Zen of Python в сам интерпретатор: достаточно написать «import this», чтобы прочитать его.

«Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated.» – «Красивое лучше уродливого. Явное лучше неявного. Простое лучше сложного. Сложное лучше запутанного.»

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

«Readability counts» – «Читаемость имеет значение».

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

«There should be one – and preferably only one – obvious way to do it» – «Должен существовать один – и желательно только один – очевидный способ сделать это».

Эта строка – сознательный контраст с девизом Perl: «There’s more than one way to do it» – «Есть больше одного способа сделать это». Perl гордился тем, что давал программисту свободу выбора. Python занял противоположную позицию: если существует много способов сделать одно и то же, каждый программист выберет свой, и чтение чужого кода превратится в разгадывание головоломки. Один очевидный способ – это не ограничение, а освобождение. Не нужно выбирать, не нужно спорить о стиле, не нужно переучиваться при переходе в новую команду.

Чтобы понять Zen of Python, нужно знать контекст его создания. Гвидо ван Россум начал работу над Python в декабре 1989 года в Центре математики и информатики в Амстердаме. Он искал занятие на рождественские каникулы, и этим занятием стало создание языка.

Но Python не возник из пустоты. Ван Россум участвовал в разработке ABC – образовательного языка, созданного в том же центре. ABC был спроектирован, чтобы научить людей программировать, не отпугивая их. Многие черты Python – значимые отступы, простой синтаксис, интерактивный режим – пришли из ABC. Но ABC никогда не вышел за пределы академии. Ван Россум хотел создать язык с той же философией доступности, но пригодный для реальной работы.

«I had this idea that Python should be easy to learn and easy to read» – «У меня была идея, что Python должен быть лёгким для изучения и лёгким для чтения», – говорил ван Россум в интервью. Лёгкость обучения и лёгкость чтения – не одно и то же, но он стремился к обоим. Лёгкость обучения означала минималистичный синтаксис: меньше специальных символов, меньше исключений из правил, меньше «магии». Лёгкость чтения означала, что код должен выглядеть почти как псевдокод – понятный человеку, который никогда не видел Python.

«I was aiming for a language that would be useful for people who didn’t want to be computer scientists» – «Я стремился к языку, который был бы полезен людям, не желающим становиться учёными-информатиками», – объяснял он в другом интервью. Python задумывался не для профессиональных программистов, а для учёных, системных администраторов, всех, кому нужно автоматизировать рутину, но кто не готов тратить годы на изучение сложных языков.

Эта философия определила множество конкретных решений. Почему в Python нет объявления переменных? Потому что это лишний шаг, который отпугивает новичков. Почему нет фигурных скобок для блоков кода? Потому что отступы и так используются для читаемости – почему бы не сделать их обязательными? Почему нет перегрузки операторов в стиле C++? Потому что это усложняет понимание кода: «a + b» должно означать сложение, а не что-то неожиданное.

«Special cases aren’t special enough to break the rules. Although practicality beats purity» – «Особые случаи не настолько особые, чтобы нарушать правила. Хотя практичность побеждает чистоту».

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

Python никогда не был чистым языком. В нём есть множественное наследование, хотя это усложняет понимание кода. В нём есть lambda, хотя Гвидо ван Россум неоднократно сожалел о её синтаксисе. В нём есть глобальная блокировка интерпретатора (GIL), которая мешает параллельному выполнению, – компромисс, принятый ради простоты реализации в эпоху, когда многоядерные процессоры были экзотикой.

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

1.2. Ruby Way: счастье программиста

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

«I wanted to have fun programming. I wanted to minimize my frustration. I wanted to maximize my joy» – «Я хотел получать удовольствие от программирования. Я хотел минимизировать разочарование. Я хотел максимизировать радость», – объяснял он много лет спустя.

Ruby появился в Японии и долгое время оставался почти неизвестным за её пределами. Первая книга на английском вышла только в 2000 году, через пять лет после публичного релиза. Но когда западный мир наконец открыл Ruby, он обнаружил язык с необычной философией: язык, спроектированный вокруг эмоций программиста.

«Ruby is designed to make programmers happy» – «Ruby создан, чтобы делать программистов счастливыми».

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

Ruby наследует идеи из множества источников: Perl дал ему выразительность и работу с текстом, Smalltalk – объектную модель, Lisp – метапрограммирование. Но сочетание этих элементов уникально. В Ruby всё является объектом – даже числа, даже nil, даже классы. Это не просто техническое решение, это философское: мир программы однороден, в нём нет «особых случаев».

«I believe people want to express themselves when they program. They don’t want to fight with the language» – «Я верю, что люди хотят выражать себя, когда программируют. Они не хотят сражаться с языком», – говорил Мацумото.

В Ruby можно написать одно и то же многими способами. Это прямая противоположность Python с его принципом «один очевидный способ». Где ван Россум видел хаос и непоследовательность, Мацумото видел свободу и уважение к программисту. Почему язык должен диктовать единственный правильный путь? Пусть каждый выберет тот способ, который кажется ему красивым.

Этот выбор имеет глубокие последствия. Код на 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 настолько выразительна, что позволяет кодировать в типах многие инварианты, которые в других языках проверяются только тестами.

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

История типизации – это история поиска баланса между этими полюсами.

Робин Милнер, работая в Эдинбургском университете с 1973 года, искал золотую середину. Он разрабатывал ML – метаязык для системы доказательства теорем LCF. Ему нужен был язык, который был бы надёжен, как статически типизированные языки, но удобен, как динамические. Результатом стала система вывода типов, которая позже получила название Хиндли-Милнера (по имени Милнера и логика Роджера Хиндли, который независимо пришёл к похожим результатам).

Идея была элегантна: компилятор сам определяет типы выражений, анализируя, как они используются. Программисту не нужно писать аннотации – но программа всё равно статически типизирована. Если вы пишете функцию, которая складывает два аргумента, компилятор выведет, что аргументы должны быть числами. Если вы потом попробуете передать ей строку – ошибка компиляции. Но вам не пришлось объявлять типы явно.

Это был революционный компромисс. ML и его потомки – Haskell, OCaml, F# – предлагали статические гарантии без синтаксического бремени. Программист писал код почти как на динамическом языке, наслаждаясь лёгкостью и скоростью. Но компилятор выводил типы и проверял их согласованность, обеспечивая надёжность статической типизации. Лучшее из двух миров.

Современные языки всё чаще выбирают эту модель. Go выводит типы локальных переменных: достаточно написать x:= 5, и компилятор поймёт, что x – целое число. Kotlin делает то же самое, добавляя более мощную систему типов с null safety. Rust сочетает вывод типов с одной из самых строгих систем проверок в индустрии – его borrow checker отслеживает владение и время жизни значений, предотвращая целые классы ошибок памяти.

Swift, созданный Apple в 2014 году, тоже использует вывод типов. Как и Scala, сочетающая объектно-ориентированное и функциональное программирование. Вывод типов стал мейнстримом – не потому, что программисты ленивы, а потому, что он позволяет писать выразительный код без потери безопасности.

Но самое интересное развитие последних лет – постепенная типизация, gradual typing. Это подход, который признаёт: иногда нужна гибкость динамики, иногда – гарантии статики. Почему бы не иметь и то, и другое?

TypeScript добавляет типы к JavaScript, но делает их опциональными. Можно начать с полностью динамического кода – он будет работать. Потом постепенно добавлять аннотации: сначала для публичных API, потом для внутренних функций, потом везде. Строгость нарастает по мере готовности кодовой базы и команды. Это не компромисс – это путь миграции.

MyPy делает то же для Python. Sorbet – для Ruby. Hack, созданный Facebook¹, добавил постепенную типизацию к PHP. Dart начинал с опциональных типов, хотя позже перешёл к обязательным. Постепенная типизация оказалась не академическим экспериментом, а практическим инструментом для огромных кодовых баз, которые нельзя переписать за один день.

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

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

Когда работаешь со статическим языком, начинаешь думать типами. Сначала проектируешь структуры данных, потом пишешь функции. Типы становятся документацией: сигнатура функции говорит, что она принимает и что возвращает. IDE использует типы для автодополнения, рефакторинга, навигации по коду. Рефакторинг становится безопасным – компилятор укажет на все места, которые нужно изменить. Типы становятся языком общения между частями системы и между членами команды.

Когда работаешь с динамическим языком, думаешь значениями. Что здесь лежит прямо сейчас? Как это преобразовать? Тесты заменяют статическую проверку. Duck typing – «если что-то ходит как утка и крякает как утка, это утка» – позволяет писать обобщённый код без формального описания интерфейсов. Не важно, какого типа объект. Важно, что он умеет делать. Это другой способ думать о программах – более гибкий, более ситуативный, более близкий к тому, как работает реальный мир.

Ни один подход не лучше другого. Они отвечают на разные вопросы, решают разные проблемы, подходят для разных ситуаций.

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

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

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

Каждый ответ имеет свою логику. Каждый создаёт свою культуру программирования.

2.3. Обработка ошибок как отношение к неудаче

Ошибки неизбежны.

Файл не найден. Сеть недоступна. Пользователь ввёл текст вместо числа. Память закончилась. Диск заполнен. Соединение разорвано. Сервер не отвечает. Данные повреждены.

Как язык программирования предлагает справляться с неизбежным? Ответ на этот вопрос многое говорит о философии языка. Это не техническая деталь – это мировоззренческая позиция.

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

Это подход, рождённый из минимализма. Никаких специальных механизмов, никакой магии, никакого скрытого потока управления. Ошибка – это просто значение, как и любое другое. Функция вернула -1? Это значение. Что с ним делать – решает вызывающий код. Может проверить. Может проигнорировать. Может передать дальше.

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

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

result, err:= someFunction ()
if err!= nil {
    return err
}

Эти три строчки – if err!= nil – повторяются в Go-коде десятки, сотни раз. Они стали мемом, предметом шуток и критики. Противники Go называют это многословием, шумом, церемонией, которая засоряет код и отвлекает от сути.

Создатели Go видят в этом добродетель.

«Errors are values», – написал Роб Пайк в статье 2015 года. Ошибки – это значения. Не исключительные ситуации, не что-то особенное, а обычные значения, с которыми можно работать, которые можно передавать, комбинировать, оборачивать, анализировать. Философия Go в том, что ошибки – нормальная часть потока выполнения. Они не исключительны. Они ожидаемы. И код должен явно показывать, как с ними обращаются.

Когда вы видите if err!= nil в коде на Go, вы видите место, где программист подумал об ошибке. Принял решение. Обработал или передал дальше. Это не скрыто где-то в стеке вызовов, не спрятано в блоке catch на другом конце файла. Это здесь, на виду, часть основного потока кода.

Философия исключений, реализованная в Java, Python, C#, C++, противоположна. Исключения – это механизм для ситуаций, которые не должны происходить в нормальном потоке. Файл должен открыться. Сеть должна работать. Память должна выделиться. Если что-то пошло не так – это исключение из нормы. Выбрасываем объект исключения, и он «всплывает» по стеку вызовов, пока кто-то его не поймает.

Эта модель позволяет писать «счастливый путь» – код, который описывает, что происходит, когда всё хорошо. Обработка ошибок отделена от основной логики, вынесена в блоки try-catch. Основной код становится чище, намерение – яснее. Вот что программа делает. А вот, отдельно, что происходит, если что-то идёт не так.

Но есть цена.

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

Легко забыть, какие исключения может выбросить функция. Легко пропустить ошибку, которая тихо поднимется наверх и уронит программу в неожиданном месте. Java пыталась решить эту проблему через checked exceptions – обязательное объявление исключений в сигнатуре функции. Но программисты возненавидели это и научились обходить: ловить исключение, оборачивать в RuntimeException, выбрасывать снова. Добровольно-принудительная проверка оказалась хуже, чем никакой проверки.

Rust и Haskell предлагают третий путь – алгебраические типы для ошибок. Тип Result в Rust может содержать либо успешное значение Ok, либо ошибку Err. Тип Option может содержать значение Some или ничего None. И компилятор требует обработать все случаи.

Это кажется похожим на коды возврата, но есть критическое отличие: компилятор не позволит игнорировать ошибку. Если функция возвращает Result, программист обязан что-то с ним сделать. Обработать оба варианта через pattern matching. Или явно передать ошибку выше с помощью оператора?. Или явно проигнорировать с помощью unwrap – но тогда программа упадёт с паникой, если ошибка произойдёт. Каждый выбор явен. Каждый выбор – осознанное решение.

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

Но есть и радикально иной подход – философия «пусть падает», рождённая в Erlang.

Erlang был создан в Ericsson в середине 1980-х для телекоммуникационных систем. Телефонные станции должны работать всегда. Не «почти всегда». Не «99% времени». Всегда. Требование было сформулировано как «пять девяток» – 99.999% доступности, что означает не более пяти минут простоя в год. А в некоторых случаях говорили о «девяти девятках» – 99.9999999%, что означает доли секунды простоя за десятилетия.

Как достичь такой надёжности? Джо Армстронг, один из создателей Erlang, пришёл к парадоксальному выводу.

«Мы были, кажется, единственными людьми в мире, проектировавшими систему, которая могла бы восстанавливаться после программных ошибок», – вспоминал он. Команда Erlang постоянно задавала один и тот же вопрос: «Что произойдёт, если это сломается?» И почти всегда получала ответ: «Наша модель предполагает отсутствие сбоев».

Армстронг понял: нельзя предотвратить все ошибки. Программы пишут люди, люди ошибаются. Оборудование выходит из строя. Сеть рвётся. Вместо того чтобы пытаться предотвратить каждую возможную ошибку – что невозможно – нужно строить систему, которая выживает при ошибках.

Отсюда философия «let it crash» – пусть падает. Не пытайся обработать каждую ошибку. Пусть процесс упадёт. Другой процесс – супервизор – заметит падение и перезапустит упавший. Процессы изолированы друг от друга, не делят память, общаются только сообщениями. Падение одного не затрагивает другие.

Это требует особой архитектуры: тысячи лёгких изолированных процессов, деревья супервизоров, отсутствие разделяемого состояния. Но в такой архитектуре код становится проще. Не нужно писать защитный код для каждой возможной ошибки. Пиши «счастливый путь» – что программа должна делать. Если что-то пошло не так – процесс умрёт и возродится в чистом состоянии.

Армстронг описывал это так: представьте идеальную организацию, где каждый сотрудник делает свою работу. Если сотрудник не справляется, его увольняют и нанимают нового. Менеджеры следят за сотрудниками и при необходимости заменяют их. Директора следят за менеджерами. И так далее. Система остаётся работоспособной, даже если отдельные части выходят из строя.

Четыре философии, четыре ответа на один вопрос.

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

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

Rust и Haskell говорят: ошибки – структурированные данные, пусть компилятор следит, чтобы ты их не пропустил. Это безопасность через типы, это гарантии на этапе компиляции, это невозможность забыть.

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

Каждый подход формирует код и мышление.

Программист на Go привыкает видеть обработку ошибок на каждом шагу – это часть текстуры кода, как знаки препинания в тексте. Программист на Python привыкает думать об исключениях как о потоке, который идёт параллельно основному, – иногда пересекаясь с ним в блоках try-catch. Программист на Rust привыкает к тому, что компилятор не даст забыть об ошибке, – это строгий, но справедливый учитель. Программист на Erlang привыкает думать о системах, не о функциях – о том, как части взаимодействуют и как восстанавливаются после сбоев.

Выбор модели обработки ошибок – это выбор отношения к неудаче. Неудача – это то, что нужно проверять постоянно? Или исключительное событие? Или типизированные данные? Или нормальная часть жизни системы?

Ответ определяет не только синтаксис. Он определяет архитектуру. Он определяет культуру программирования на языке. Он определяет, как программисты думают о надёжности и что считают «правильным» кодом.

Синтаксис как мировоззрение

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

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

Синтаксис – это не косметика языка. Это его мировоззрение, отлитое в форму.

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

Программист на Haskell видит мир как композицию чистых функций, как поток преобразований данных. Программист на Java видит мир как иерархию объектов, как взаимодействие сущностей с состоянием и поведением. Программист на Си видит мир как последовательность операций над памятью, как байты и указатели. Программист на Erlang видит мир как сеть процессов, обменивающихся сообщениями.

В этом – сила и ограничение языков программирования. Они дают нам инструменты мышления. Мощные инструменты. Но эти инструменты формируют мысль. Молоток видит везде гвозди. Функциональный программист видит везде функции. Объектно-ориентированный – везде объекты.

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

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

Код – это культура, выраженная в синтаксисе.

Глава 3. Имена и метафоры

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

Выбор ключевого слова для объявления функции может показаться мелочью. Какая разница – def, func, fn или function? Машине действительно всё равно. Но человеку – нет. Имена формируют то, как мы думаем о коде. А конвенции именования – тот невидимый каркас, который держит программы читаемыми десятилетия спустя после их написания.

Выбирая имена, программист совершает акт классификации. Он решает, к какой категории принадлежит сущность, какие её свойства существенны, какие – нет. Имя переменной temp говорит: это временное, не обращай внимания. Имя customerLifetimeValue говорит: это важно, это центральная концепция бизнеса. Между этими полюсами – бесконечный спектр решений, каждое из которых отражает понимание программистом задачи.

3.1. Ключевые слова как концептуальный выбор

В 1967 году в Норвежском вычислительном центре Оле-Йохан Даль и Кристен Нюгор работали над языком для симуляции дискретных событий. Им нужно было слово для обозначения шаблона, по которому создаются объекты симуляции. Они выбрали class. Позже Даль вспоминал: «Мы выбрали термины class и objects of classes для нашей новой Simula. Понятие подкласса было особенно привлекательным для нас».

Это был не просто технический выбор. Слово «класс» несёт в себе идею классификации, таксономии, иерархии. Классы предполагают, что мир можно разделить на категории, что объекты принадлежат к определённым типам, что есть родовые понятия и видовые различия. Когда программист объявляет class Animal, он невольно начинает думать категориями Аристотеля.

Влияние этого выбора оказалось колоссальным. Simula повлияла на Smalltalk, Smalltalk – на C++, C++ – на Java, Java – на C#, C# – на всё остальное. Термин «класс» стал настолько привычным, что программисты перестали замечать метафору, которую он несёт. Но метафора продолжает работать. Иерархии наследования, которые программисты строят в объектно-ориентированных языках, отражают таксономическое мышление, заложенное в самом слове.

Альтернатива – struct. В языке Си структура – это просто способ группировки данных. Никакой иерархии, никакого наследования, никакого поведения. Данные и только данные. Когда Деннис Ритчи в 1972 году выбирал это слово, он думал о памяти, о байтах, о том, как данные расположены физически. Структура – это карта участка памяти, не более того.

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

Go, созданный спустя почти сорок лет, сознательно отказался от классов в пользу структур. Роб Пайк и его коллеги не хотели, чтобы программисты мыслили иерархиями наследования. В Go нет слова class, есть только struct и interface. Это не ограничение языка – это философское заявление. Композиция вместо наследования. Поведение определяется тем, что объект делает, а не тем, от какого предка он происходит.

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

Ключевые слова для объявления функций демонстрируют тот же спектр философий. Python использует def – сокращение от define, определить. Нейтральное, техническое слово. Вы определяете функцию, как определяете переменную. Никакой особой церемонии. Это соответствует общей философии Python: минимум синтаксического шума, максимум ясности.

Гвидо ван Россум выбрал def под влиянием ABC – образовательного языка, созданного в том же голландском исследовательском центре CWI, где он работал. ABC был спроектирован для обучения программированию, и каждое его решение оптимизировалось для понятности новичкам. Python унаследовал эту заботу о читаемости, и def – часть этого наследия.

Go пишет func – то же сокращение, та же нейтральность, но ещё лаконичнее. Четыре символа вместо пяти. В языке, где простота возведена в принцип, каждый символ на счету. Go избегает сокращений ради сокращений, но func оправдан частотой использования. Функции в Go пишутся постоянно, и экономия одного символа на каждой из них складывается в заметное уменьшение визуального шума.

Rust идёт ещё дальше: fn. Два символа. Минимум возможного. Это не небрежность – это сознательный выбор для языка, где функции пишутся постоянно и везде. Когда пользователи спрашивали, почему не полное слово function, разработчики отвечали ссылками на традицию системного программирования, где краткость ценится выше явности. Rust наследует культуру Си и Unix, где имена короткие и ёмкие.

Конец ознакомительного фрагмента.

Текст предоставлен ООО «Литрес».

Прочитайте эту книгу целиком, купив полную легальную версию на Литрес.

Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.

Примечания

1

Сервис принадлежит организации, деятельность которой запрещена на территории РФ; здесь и далее.

Вернуться