![](https://habrastorage.org/getpro/habr/upload_files/c4d/199/ac4/c4d199ac427ab245b5793daa7dee393f.png)
Много лет назад, учась computer science на старших курсах, я долго изучал различные вакансии онлайн, надеясь найти подходящую должность стажёра-программиста.
Кроме вакансий для стажёров я иногда случайно нажимал на объявления о вакансиях «сеньор-разработчика». Помню, больше всего меня поражало то, что первой строкой шло требование определённого количества лет работы: «Эта должность требует 5+ лет опыта».
Полному новичку, ни дня не проработавшему в этой отрасли, такие требования к опыту казались избыточными. Но хотя это немного приводило меня в уныние, я не мог не пофантазировать: «Наверно, пять лет работы программистом — это впечатляющее достижение? Должно быть, для таких людей писать код проще пареной репы».
Время летело, не успел моргнуть глазом, как прошло больше десятка лет. Сегодня я с гордостью могу сказать, что работаю программистом уже 14 лет. Спустя годы боёв на фронтах разработки ПО я осознал, что многие её аспекты сильно отличаются от того, что я представлял на старших курсах, а именно:
С опытом программирование не становится намного проще, о «проще пареной репы» можно только мечтать.
Написание кода для множества «больших проектов» — это не только неинтересное, но и опасное занятие, гораздо менее увлекательное, чем решение алгоритмических задач в LeetCode.
Мышление только с технической точки зрения не сделает тебя хорошим программистом, некоторые вещи гораздо важнее технологий.
Поразмыслив, я пришёл гораздо к большему множеству мыслей о программировании. В этой статье я вкратце изложу восемь из них.
1. Писать код легко, но писать хороший код — сложно
Когда-то программирование было высокоспециализированным навыком с высоким барьером входа. В прошлом, если среднестатистический человек хотел научиться программированию, чаще всего для этого нужно было читать книги и документацию. Однако большинство книг по программированию было довольно заумным и недружественным к новичкам, из-за чего многие из них сдавались, так и не получив удовольствия от программирования.
Но сегодня обучение кодингу становится гораздо более доступным. Для обучения теперь не нужно зубрить учебники, появилось много новых способов: просмотр видеотуториалов, прохождение интерактивных курсов в Codecademy или даже игры в кодинг в CodeCombat — любой может найти подходящий ему метод обучения.
![«Мам, я не просто играю, а учусь программированию, посмотри на правую половину экрана» «Мам, я не просто играю, а учусь программированию, посмотри на правую половину экрана»](https://habrastorage.org/getpro/habr/upload_files/6a6/6a1/5c9/6a66a15c9152e67b10a75569bd13d585.png)
Языки программирования тоже становятся более дружелюбными к пользователю. Классические языки наподобие C и Java перестали быть первым выбором большинства начинающих, теперь популярны многие более простые и доступные динамические языки. IDE и другие инструменты тоже усовершенствовались. Благодаря всем этим факторам кривая обучения программированию стала гораздо более пологой.
Иначе говоря, с программирования спала его мистическая аура, превратив его из тайного знания, доступного лишь избранным, в навык, которому может научиться каждый.
Но снижение входного барьера и дружественность языков программирования не значит, что хороший код может писать каждый. Если вы участвуете в «энтерпрайзных» программных проектах, я задам вам вопрос: «Каково качество кода в проектах, с которыми вы работаете ежедневно? Там больше хорошего или плохого кода?»
Не знаю, каким будет ваш ответ, но позвольте поделиться своим.
Хороший код по-прежнему встречается редко
В 2010 году я сменил работу, перейдя в большую Интернет-компанию.
До прихода в эту компанию я работал лишь в стартапах максимум из десятка человек, поэтому у меня были высокие ожидания от своего нового работодателя, особенно относительно качества ПО. Я думал: «Это продукты для поддержки "большого" проекта, используемые миллионами пользователей, поэтому качество кода должно быть гораздо выше, чем то, что я видел раньше!»
Мне понадобилась всего неделя, чтобы понять, насколько я ошибался. Качество кода так называемого «большого» проекта было далеко от того, что я ожидал. Когда я открыл IDE, функции состояли из сотен строк кода и повсюду были разбросаны таинственные числовые значения, из-за чего разработка даже мельчайшей фичи казалась геракловым подвигом.
После этого я работал в других компаниях и видел различные программные проекты, и пришёл к осознанию: какой бы большой ни была компания или насколько впечатляющим бы ни был проект, встреча с хорошим кодом на практике по-прежнему остаётся редким событием.
Что такое хороший код?
Давайте вернёмся к вопросу о том, каковы же признаки хорошего кода. В этом контексте часто цитируют фразу Мартина Фаулера:
«Любой дурак может написать программу, которую поймёт компилятор. Хорошие программисты пишут программы, которые смогут понять другие программисты».
Я считаю, что это заявление может служить опорной точкой для оценки качества кода: он должен быть читаемым, понятным и чётким. Первый принцип написания хорошего кода: нужно писать его для читателя.
Кроме читаемости есть и множество других размерностей, учитываемых при оценке качества кода:
Соблюдение правил языка программирования: используются ли в коде рекомендованные для конкретного языка практики? Правильно ли применяются фичи языка и синтаксический сахар?
Простота изменения: учитывается ли в структуре кода необходимость будущих изменений и легко ли его изменять?
Разумная структура API: разумна ли и проста ли в использовании структура API? Хороший API удобен для простых сценариев и его можно при необходимости расширить под сложные сценарии.
Адекватная производительность: соответствует ли производительность кода текущим потребностям бизнеса и имеет ли он пространство для совершенствования в будущем?
Избегание переусложнения структуры: страдает ли код от переусложнённой структуры или преждевременной оптимизации?
Если вкратце, хороший код даётся нелегко программисту любого уровня. Для написания хорошего кода требуется тонкий баланс между множеством размерностей, тщательное проектирование и постоянное совершенствование.
Учитывая всё это, существует ли быстрый путь к освоению ремесла кодинга?
Быстрый путь к написанию хорошего кода
Я считаю, что программирование во многих смыслах похоже на писательское ремесло. В обоих используются текст и символы для передачи идей, хотя и немного разными способами.
Я люблю задавать такой вопрос: «Вы когда-нибудь слышали о писателе, который не читает? Слышали о писателе, который заявляет, что читал только свои работы, но не работы других?» Вероятно, вы ответите «нет».
Изучив вопрос, вы обнаружите, что многие профессиональные писатели проводят свои дни в постоянном цикле чтения и писания. Они тратят существенную часть каждого дня на чтение разнообразных текстов, а затем пишут.
Программисты — это те же «литераторы», но они часто пренебрегают чтением. Однако чтение — необходимая часть совершенствования навыков программирования. Кроме проектов, с которыми мы сталкиваемся по работе, нам следует читать более классические программные проекты, чтобы узнать больше о проектировании API, архитектуре модулей и техниках написания кода.
Полезно регулярно читать не только код и технические документы, но и книги по программированию, чтобы поддерживать привычку к чтению. Я считаю, что статья Джеффа Этвуда «Programmers Don't Read Books -- But You Should», написанная 15 лет назад, актуальна и сегодня.
Быстрый путь к совершенствованию навыков программирования скрыт в бесконечном цикле Чтение <-> Программирование
.
![](https://habrastorage.org/getpro/habr/upload_files/ab1/7b1/ed0/ab17b1ed0acc7cc584d311f5ffcbbca4.png)
2. Суть программирования — в «создании»
В повседневной работе программиста многие вещи могут наполнить вас чувством удовлетворённости и даже заставить воскликнуть «программирование — это самое лучшее в мире!» Например, это может быть устранение особо сложного бага или удвоение производительности кода при помощи нового алгоритма. Но из всех этих достижений ни одно не сравнится с актом творения чего-то собственными руками.
Когда программируешь, возможности создания чего-то нового есть повсюду, потому что создание — это не просто выпуск новой программы. Написание многократно используемой вспомогательной функции или проектирование чистой модели данных тоже относятся к категории создания.
Для программистов сохранение рвения к «созданию» крайне важно, потому что помогает нам:
Учиться эффективнее: самый эффективный способ изучения новой технологии — создание с её помощью реального проекта. Обучение через процесс создания обеспечивает наилучшие результаты.
Сталкиваться с необычным: многие изменившие мир оперсорсные проекты изначально создавались их авторами из чистого интереса, как это было в случае Линуса Торвальдса и Linux или Гвидо ван Россума и Python.
![](https://habrastorage.org/getpro/habr/upload_files/07e/0de/856/07e0de8560a02305a31963f3da0f9f9e.png)
Во время рождественских каникул 1989 года нидерландец Гвидо ван Россум ввёл несколько первых строк языка Python. Изначально он предполагал, что тот станет потомком языка ABC, но постепенно Python «поглотил» весь мир.
Хотя у «создания» есть множество преимуществ и у программистов есть много возможностей в нём участвовать, многие часто не осознают, что означает быть «творцом». Есть одна известная история: философ спросил каменщиков, чем они занимаются. Некоторые из них чётко понимали, что строят собор, а другие думали, что просто кладут камень. Многие программисты похожи на вторых: видят только камень, но не собор.
Как только вы начнёте видеть в себе творца, ваша точка зрения на мир сильно изменится. Например, при добавлении сообщений об ошибках в API творцы часто избегают ловушки и не стремятся «просто сделать свою работу», задавая себе более важные вопросы: «Какой конкретно способ работы с продуктом я хочу создать для пользователя? Какие сообщения об ошибках лучше всего помогут мне достичь этой цели?»
Как любой полезный шаблон программирования, «менталитет творца» может стать важной движущей силой вашей карьеры. Так что задайтесь вопросом: «Каким будет моё следующее творение»?
3. Крайне важно создать эффективную среду для проб и ошибок
Однажды я участвовал в разработке Интернет-продукта, имевшего красивую структуру, множество фич и ежедневно использовавшийся огромным количеством людей.
Но несмотря на рыночный успех, качество разработки было ужасным. Если залезть в репозиторий бэкенда и проверить каждую папку, то не нашлось бы ни единой строки юнит-тестов, не говоря уже о других процессах автоматизированного тестирования. Бизнес-логика была крайне сложной, что приводило к переплетению неожиданных зависимостей кода. При разработке новой фичи часто приходилось рисковать имевшейся функциональностью.
![— Над чем работаешь?— Пытаюсь устранить проблемы, которые создал, когда пытался устранить проблемы, которые создал, когда пытался... — Над чем работаешь?— Пытаюсь устранить проблемы, которые создал, когда пытался устранить проблемы, которые создал, когда пытался...](https://habrastorage.org/getpro/habr/upload_files/1f2/a00/067/1f2a000677943a84eea2989ea6fff5f1.png)
— Пытаюсь устранить проблемы, которые создал, когда пытался устранить проблемы, которые создал, когда пытался...
В результате этого и разработчики и команда продукта вынуждены были находиться в состоянии полной боевой готовности при каждом выпуске проекта, что создавало напряжённую атмосферу. Процесс релиза вызывал сильное волнение, часто приходилось выполнять аварийные откаты. Работая в такой среде, можешь и не расти с технической точки зрения, но определённо проверяешь свою психологическую устойчивость.
Программирование должно быть интересным, но при кодинге такого проекта никто удовольствия не испытывал. Почему же программирование теряет свою интересность?
Идеальный процесс программирования ≈ «решению задач с LeetCode»
LeetCode — известный веб-сайт обучения программированию, предлагающий множество задач с различными уровнями сложности, большинство из которых связано с алгоритмами. Пользователи могут выбирать интересующую задачу, писать код напрямую в браузере (есть поддержка различных языков) и исполнять его. Если все тесты пройдены, то решение считается успешным.
![Решение задач в LeetCode Решение задач в LeetCode](https://habrastorage.org/getpro/habr/upload_files/2b5/74e/a4a/2b574ea4accd9b1f9cbb8060fce9d19f.png)
Решение задач в LeetCode похоже на игру, это сложно и увлекательно. Весь процесс оказывается примером идеального программирования:
Разделение целей: каждая задача — независимая сущность, что позволяет разработчикам полностью погружаться в одну задачу.
Быстрая и точная обратная связь: после каждого изменения кода разработчики могут быстро получать обратную связь от автоматизированных тестов.
Нулевая стоимость проб и ошибок: если в коде есть синтаксические ошибки или логические изъяны, это не приносит никаких отрицательных последствий, снижая ментальную нагрузку.
Однако сидя перед экраном, вы можете решить, что я говорю какие-то банальности.
«Ну и что? Разве не так мы решаем задачи LeetCode и пишем скрипты? Что в этом такого особенного?» Ещё вы можете добавить: «Знаете, насколько сложны проекты нашей компании? У них огромные масштабы и бесчисленное количество модулей. Понимаете, о чём я? Мы обслуживаем миллионы пользователей в день, со множеством баз данных и тремя типами очередей сообщений. Разумеется, в реальной разработке чуть больше проблем, чем в задачках!»
И в самом деле, разработка ПО бывает очень разной и не всегда такой простой и приятной, как решение задач в LeetCode. Однако это не значит, что мы не должны стремиться хотя бы немного совершенствовать среду, в которой программируем.
Чтобы улучшить процесс программирования, совершенствуя среду, можно воспользоваться следующими концепциями и инструментами:
Модульное мышление: правильное проектирование каждого модуля в проекте для снижения связанности и повышения ортогональности.
Принципы проектирования: применение на микроуровне классических принципов проектирования и шаблонов наподобие принципов «SOLID».
Автоматизированное тестирование: пишите хорошие юнит-тесты, по возможности используйте методики имитации реализаций и покрывайте критически важные пути бизнес-логики автоматизированным тестированием.
Короткие циклы обратной связи: переход на более быстрые инструменты компиляции, оптимизация производительности юнит-тестов и всё необходимое для снижения времени ожидания между изменением кода и обратной связью.
Архитектура микросервисов: при необходимости разбивайте крупный монолит на множество микросервисов, имеющих уникальные обязанности, чтобы снизить уровень сложности.
Сделав упор на среде программирования и намеренно создавая «рай кодинга», обеспечивающий эффективные пробы и ошибки, можно сделать работу столь же увлекательной, как и решение задач LeetCode. Это один из самых ценных подарков, которые опытные программисты могут сделать своим командам.
4. Избегайте ловушки перфекционизма в кодинге
Стремление к идеалу качества кода похвально, но будьте осторожны и не попадитесь в ловушку перфекционизма. Кодинг — это не вид искусства, стимулирующий к бесконечному поиску совершенства. Писатель может годами совершенствовать шедевр, но программисты, слишком фиксирующиеся на коде, могут вызывать проблемы.
Никакой код не идеален. Чаще всего код достаточно хорош, если он отвечает текущим потребностям и в нём есть место для расширения в будущем. Я несколько раз видел кандидатов, называющих себя в резюме «защитниками чистого кода». Хотя я даже через экран мог почувствовать их стремление к качеству кода, глубоко внутри я надеюсь, что они уже давно оставили позади ловушку перфекционизма.
5. Технология важна, но люди ещё важнее
В разработке ПО хорошо известен используемый при проектировании принцип единственной ответственности (Single Responsibility Principle, SRP). Его определение можно свести к одному предложению: «У каждого модуля ПО должна быть лишь одна причина для изменения».
Чтобы освоить SRP, важно понимать, что же такое «причина для изменения». Очевидно, что программы не могут жить самостоятельно; им не нужно меняться самим и они на это не способны. Любая причина для изменения приходит от связанных с ПО людей, и именно они остаются истинными зачинщиками изменений.
Рассмотрим простой пример: взгляните на два представленных ниже класса. Какой из них нарушает принцип SRP?
Словарный класс данных, поддерживающий два типа операций: сохранение данных и извлечение данных;
Класс профиля сотрудника, поддерживающий два типа операций: обновление личной информации и рендеринг изображения карточки профиля.
Большинству людей первый пример покажется вполне нормальным, а второй, очевидно, нарушает принцип SRP. К этому выводу можно прийти практически интуитивно, без какого-то строгого анализа или доказательств. Однако если всё-таки его должным образом проанализировать, проблема со вторым примером становится очевидной — у него есть две причины для изменения:
Руководство считает, что поле «личный телефон» в профиле не может содержать недопустимых чисел и требует добавить простую логику валидации.
Сотрудник считает, что раздел «имя» в изображении карточки профиля слишком мал и хочет увеличить размер шрифта.
«Именно люди требуют изменений. И вам не стоит запутывать этих людей и себя, смешивая код, который важен разным людям по разным причинам», — «Принцип единственной ответственности»
Самое главное в понимании принципа SRP — это то, что нужно понять людей и роли, которые они играют в разработке ПО.
Ещё один пример: в последние годы есть большой ажиотаж вокруг темы микросервисов. Однако во многих обсуждениях обычно делают упор на саму технологию, забывая о взаимосвязи между архитектурой микросервисов и людьми.
По сути, архитектуру микросервисов отличают от других концепций более чёткие границы между отдельными модулями после разбиения крупного монолита на независимые микросервисы. В отличие от крупной команды, обслуживающей монолитную систему, множество мелких организаций, каждая из которых поддерживает собственные микросервисы, способны работать гораздо эффективнее.
Говорить о различных технических преимуществах и фичах микросервисов без контекста размера конкретной организации (то есть «людей») означает ставить телегу впереди лошади.
Разумеется, технология важна. Мы технические специалисты, поэтому нас привлекают красивые диаграммы архитектур и творческий код. Но не стоит забывать и о другом критическом факторе разработки ПО — о «людях». При необходимости смещайте своё внимание с «технологии» на «людей», это может оказаться крайне полезно.
6. Учиться необходимо, но важна методика обучения
Сегодня все говорят об «учёбе на протяжении всей жизни», а программистам особенно необходимо это постоянное стремление к знаниям. Компьютерные технологии стремительно эволюционируют, и фреймворк или язык программирования, популярный три года назад, вполне мог устареть месяц назад.
Чтобы превосходно справляться со своей работой, программистам нужно изучать огромное количество тем в различных областях. Если взять для примера сферу бэкенда, с которой я больше знаком, то компетентный бэкенд-разработчик должен иметь большой опыт, по крайней мере, в следующем:
Одном или нескольких языках программирования бэкенда/реляционных базах данных наподобие MySQL/популярных компонентах хранения наподобие Redis/шаблонах программирования/User experience/проектировании ПО/операционных системах/основах работы сетей/распределённых системах/ …
Хотя нужно много учиться, по моим наблюдениям, большинство программистов на самом деле любит получать новые знания (или, по крайней мере, не сопротивляется этому), так что менталитет здесь не помеха. Однако иногда простого «стремления учиться» недостаточно; при обучении нужно уделять особое внимание «эффективности затрат» на учёбу.
Стремление к эффективности затрат на учёбу
На графике ниже показана связь между результатами обучения и приложенными усилиями.
![](https://habrastorage.org/getpro/habr/upload_files/4fd/be4/6e3/4fdbe46e33939bbb382baa167fe27234.png)
Из графика видно, что отдача от достаточно небольших инвестиций растёт быстро. Однако когда результаты превосходят некий порог, необходимые для совершенствования вложения растут экспоненциально.
По этой причине я рекомендую при начале изучения чего-то нового ответить себе на такой вопрос: «В какой точке графика мне следует остановиться?», а не учиться беспрестанно.
Океан знаний безграничен. Некоторые темы требуют годов непрерывного обучения и совершенствования, а других достаточно лишь коснуться, чтобы получить достаточное понимание. Точная оценка и распределение ограниченной энергии обучения иногда более важны, чем сама упорная учёба.
Выбор подходящих учебных материалов
Определившись с целями обучения, нужно найти подходящие учебные материалы. Хочу поделиться собственной историей неудачи в этом.
Когда-то у меня возник стойкий интерес к проектированию взаимодействия с продуктами и я решил, что хочу узнать о нём больше. Поэтому я выбрал классическую книгу из этой сферы, «About Face 4: The Essentials of Interaction Design», уверенный, что мои навыки проектирования взаимодействий будут быстро совершенствоваться.
Однако всё пошло не так, как запланировано. Когда я открыл эту классику, то обнаружил, что не могу продраться даже через первую главу. Правду говорят — не бери на себя слишком много.
Из этой неудачи я получил опыт: когда учишься чему-то новому, лучше выбирать материалы, больше доступные и подходящие для начинающих, а не стремиться к самым классическим и авторитетным.
Отрефлексировав предыдущий опыт, я считаю, что для начинающих будут очень полезны и ценны следующие книги:
«The Non-Designer's Design Book»: про дизайн
«Don't Make Me Think, Revisited»: про веб-UX
Наверно, все хотят быть осведомлёнными, знать всё. Но время и энергия, которую мы можем выделить на учёбу, всегда ограничены; мы не можем и не должны быть специалистами во всём.
7. Чем раньше вы начнёте писать юнит-тесты, тем лучше
Мне очень нравится юнит-тестирование. Я считаю, что написание юнит-тестов глубоко повлияло на мою карьеру программиста. Если взять за точку отсчёта начало создания юнит-тестов, то последующая часть моей карьеры была гораздо увлекательней предыдущей.
Существует множество преимуществ написания юнит-тестов, например, они продвигают улучшения в структуре кода, служат в качестве документации к коду и так далее. Кроме того, исчерпывающее юнит-тестирование крайне важно для создания упомянутой выше «эффективной среды проб и ошибок».
Я написал множество статьей про юнит-тестирование, так что не буду здесь повторяться. Дам лишь один совет: если вы никогда не пробовали писать юнит-тесты или никогда не воспринимали тестирование серьёзно, рекомендую начать уже завтра.
8. Самый большой враг программистов
В большинстве шуток программистов в виде злодеев предстают менеджеры по продукту. Они постоянно меняют требования к продукту, каждый день придумывают новые идеи и приводят программистов в состояние неопределённости.
Из-за этих шуток образ непостоянного менеджера по продукту стал главным врагом программистов. Как будто если требования к продукту перестанут меняться, рабочая среда мгновенно превратится в утопию.
Хотя время от времени подшучивать над менеджерами забавно, я хочу сказать чётко: менеджеры по продукту — не наши враги.
В каком-то смысле, изменения — неотъемлемая часть ПО. Из-за этого разработка ПО фундаментально отличается от постройки домов. После завершения строительства никто не говорит: «Давайте снесём здание и перестроим заново! С той же конструкцией, но используем на 30% меньше стали и бетона!»
Поэтому менеджеры по продукту и нестабильные требования — не враги программистов. Более того, способность писать легко модифицируемый и адаптируемый к изменениям код — одно из ключевых отличий прекрасного программиста от хорошего.
Так кто же самый большой враг программистов?
Сложность
Как сказано в книге «Совершенный код», суть разработки ПО заключается в управлении сложностью. Худший враг программиста — неконтролируемая сложность.
Давайте рассмотрим факторы, приводящие к постоянному росту сложности проекта:
Непрерывное добавление новых фич: чем больше фич, тем больше кода, а чем больше кода, тем обычно больше сложности.
Стремление к высокой доступности: для достижения высокой доступности в код вводятся дополнительные технические компоненты (например, очереди сообщений).
Стремление к высокой производительности: для повышения производительности добавляется кэширование и соответствующие модули кода, а некоторые модули разделяют и переписывают на более быстрых языках.
Многократно откладываемый рефакторинг: из-за плотных графиков проектов срочный рефакторинг постоянно откладывается, накапливая растущий технический долг.
Пренебрежение автоматизированным тестированием: никто не пишет юнит-тесты и не заботится о тестировании.
По сути, когда сложность проекта достигает определённого уровня, в воздухе раздаётся оглушающий звук обрушения. Та самая проблема, которой никто не касается и которую все боятся, рано или поздно создаёт огромную пустоту.
И кто же вырыл эту яму?
![Различия в стратегической и тактической работе Различия в стратегической и тактической работе](https://habrastorage.org/getpro/habr/upload_files/587/d52/b32/587d52b3267a11e8e2b769f8a24ba2ae.png)
Процесс замедления роста сложности
Хотя сложность неизбежно растёт, существует множество практик, способных замедлить этот процесс. Если перечисленные ниже шаги смогут совершать все, то в долгосрочной перспективе сложность можно удерживать в разумных пределах:
Освоение актуального языка программирования и инструментов, написание чистого кода
Использование подходящих шаблонов проектирования и парадигм программирования
Недопущение дублирования кода, абстрактных библиотек и фреймворков
Правильное применение принципов чистой архитектуры и Domain-Driven Design
Написание хорошей документации и комментариев
Разработка высококачественных и эффективных юнит-тестов
Отделение того, что меняется, от того, что не меняется
В заключение
В 2020 году я провёл в своей команде презентацию «Десять выводов после десяти лет работы программистом». Когда я выложил слайды во внутреннюю сеть компании, одна из коллег сказала, что их недостаточно и что она надеется на полноценную статью. Я ответил ей, что напишу её. Прошло три года, и я наконец выполнил своё обещание.
Когда я готовил презентацию, то закончил все слайды, но понятия не имел, что поместить на последнюю страницу. Наконец, я решил, что на пустом белом фоне будет большими буквами написано: «Десятка лет слишком мало, чтобы освоить программирование в совершенстве». И теперь, приближаясь к середине второго десятка лет работы, я по-прежнему иногда считаю программирование сложным: мне предстоит ещё многое изучить и двигаться дальше.