Disclaimer: эта статья ставит перед собой целью поделиться мыслями, возникшими в процессе попытки осмыслить понятие красивого кода. Приведенные мысли не претендуют быть истиной в последней инстанции. Надеюсь лишь на то, что эти мысли, размышления и доводы, возможно, помогут кому-то взглянуть на сам процесс написания кода немного с другой стороны. Далее не следует ни одного формального правила вида «Пишите код так, и будет вам счастье». По данной тематике уже написан большой объем литературы от гораздо более уважаемых авторов.
Всех заинтересованных в рассуждениях на тему, что такое красота кода, в чем она может выражаться, почему все известные практики не в силах закрыть раз и навсегда этот вопрос, прошу под кат.
Хорошие и плохие практики
"… и спросила кроха: — Что такое хорошо и что такое плохо?" — Маяковский В. В.И мы часто задаемся вопросом, как правильно оформить свой код. С целью ответить на этот вопрос в мире написано уже большое количество профессиональной литературы. Взять, к примеру, самые популярные труды на эту тематику, которые у большинства на слуху:
- Чистый Код — Роберт К. Мартин
- Совершенный Код. Мастер Класс — Стив Макконнелл
- Приемы Объектно Ориентированного Программирования. Паттерны проектирования — Банда Четырех
- Программист-прагматик. Путь от подмастерья к мастеру — Эндрю Хант, Дэвид Томас
Думаю, найдется огромное количество книг, которые могут дополнить этот список. Но повествование пойдет не о том, что нужно применять тот или иной подход. Не о том, насколько та или иная книга хороша или плоха. В ходе размышлений можно прийти к выводу о том, что даже использование всех хороших практик, описанных в этих книгах, не гарантирует вам красивый код. И о том, что все это может быть и во вред и во благо. Получается, что если не существует определенной «формулы успеха», то возможно, стоит начать хотя бы с наброска мыслей о том, чего же именно хочется достичь, стараясь написать красивый код. Мы знаем, что такое код, но никто из нас не может сказать, что же такое красивый код.
Предпосылки к мыслям
Как бы кто ни хотел выделить программирование в отдельную независимую дисциплину, оно сводится к инженерии. Соответственно, общие мысли и идеи в области инженерии справедливы как для автомобилестроения и строительства, так и для программирования. Все это — о приложении научных знаний к созданию чего-то практического, в какой-то степени материального (если речь идет о разработке ПО, то могут возникнуть чисто философские споры о материальности). С практической точки зрения, чем система (конструкция, идея) проще — тем удобней, легче с ней работать и обслуживать ее, и тем красивее она кажется.
Приведу более сложный пример. В моей университетской программе был крайне полезный предмет, который назывался «Комбинаторные алгоритмы». Большая часть этого курса была посвящена изучению теории графов и различных алгоритмов на графах. Разговоры о теории графов уже были ранее на других предметах, но исключительно поверхностно. То есть все мои одногруппники уже представляли, что это такое, и с чем это есть. Кто-то даже делал лабораторные работы по реализации какого-либо алгоритма на графах. Но именно на одной из пар по комбинаторным алгоритмам преподаватель неожиданно спросил: «А кто может сказать, зачем вообще нужны эти графы?». Как ни странно, ответить не смог никто из группы, повисло гробовое молчание. Ответ был очевиден и прост — графы нужны для моделирования. Сами по себе графы никому не нужны, как и умение реализовать обход в ширину или глубину. Но, в применении ко многим жизненным ситуациям, бывает крайне удобно отказаться от кучи ненужных деталей и оставить только абстрактную суть.
Можно привести еще множество подобных примеров из человеческой жизни и природы, которые будут объединены общей мыслью: зачастую простое решение является наиболее красивым. Именно в момент, когда я узнаю, насколько просто устроен тот или иной объект, кажущийся настолько необходимым в жизни, в голову закрадывается мысль о том, что он сделан красиво. Пока речь не идет об эстетике, исключительно о «подкапотном» устройстве.
Некогда я наткнулся на книгу Kevlin Henney — 97 Things Every Programmer Should Know: Collective Wisdom from the Experts. Эта книга представляет собой 97 небольших глав, написанных разными людьми и связанных лишь тем, что все они о программировании. Своего рода житейские мудрости от людей с большим опытом программирования. Сразу же оговорюсь, что такое подробное описание — ни в коем случае не реклама. Но не упомянуть эту книгу я не могу, потому что одна из ее глав призывает задуматься о том, что красивый код — простой код. В общем-то и называется она «Beauty is in Simplicity» (Красота заключается в простоте). Автор начинает главу с цитаты Платона о том, что все красивые вещи — просты:
Beauty of style and harmony and grace and good rhythm depend on simplicity — ПлатонИменно этот афоризм еще больше разжег мой интерес в области красивого кода и применимости понятия «красивого решения» к коду, а как следствие, эквивалентности красивого кода простому.
Что такое красота?
Итак, мы подошли к самому вопросу о красоте кода. Чтобы максимально объективно понять, является ли красота синонимом простоты в нашем вопросе, следует начать с основного понятия. Обратимся к википедии за определением:
Красота? — эстетическая категория, обозначающая совершенство, гармоничное сочетание аспектов объекта, при котором последний вызывает у наблюдателя эстетическое наслаждение.Говоря о коде, мы не можем рассуждать исключительно с эстетической позиции. Красивый (идеальный, хороший) код сочетает в себе как эстетику, так и семантику. Семантика кода также должна относится к категории «красивого решения».
Думаю, многие согласятся, что кроме практической ценности код (точнее, результат его работы и процесс создания) носит творческий характер. Наиболее близкой творческой аналогией к разработке ПО является создание литературного произведения. У этих ветвей творчества, несомненно, много общего. Для простоты, код программы можно сравнить с книгой. И то и другое является симулякром мыслей. Разница лишь в том, что, грубо говоря, результат работы кода — какой-то результат на экране устройства, а результат прочтения книги —
Почему красивый код это важно?
В выражении мыслей на бумаге, несомненно, стоит ориентироваться на правила языка, а также на структурированность и простоту выражения. От этого напрямую зависят впечатления и мысли, которые передадутся читателю, и какие образы возникнут у него в голове. С кодом все немного сложнее. Конечного пользователя нашего ПО никак не волнует, насколько красиво оно написано, и какие ощущения получает разработчик от работы с этим кодом. Конечного пользователя интересует только качество выпущенного продукта, как он работает, насколько удобно им пользоваться.
В отличие от литературы, качество кода это характеристика, которая важна разработчику или команде, которая над ним работает. Это не обязательно должны быть разработчики. От обилия багов в ходе разработки может страдать вся команда (например, проджект-менеджеры, начальство и инвесторы). Однако в случае со специалистами из других областей, красота кода касается их лишь косвенно. В первую очередь жертвами некрасивого кода становятся разработчики. Почему для них это так важно? Большую часть времени при разработке продукта мы тратим на разбор кода, навигацию в нем, в поисках места, где нужно написать или исправить необходимые строки.
Все было бы намного проще, если бы разработкой определенного продукта занимался один человек. Весь исходный код был бы однородным и понимаемым для него. Но, чтобы это самое ПО могло разрабатываться в реальные сроки, а не десятилетия, разработкой одного ПО зачастую занимается команда из нескольких разработчиков. Это вносит свои коррективы. Например, понятие о красоте (в частности и о красоте кода) у каждого разработчика может быть свое. Но так как работа идет в командах — устанавливаются стандарты, чтобы настроить команду думать
более или менее одинаково. Это позволяет сделать так, что при чтении кода разработчик не всегда может понять^ написал ли код он или кто-то еще и, кто именно, и дает ощущение общности, целостности команды. Мы говорим «Код нашего приложения», а не «мой кусок кода» или «мой скрипт». Грубо говоря, мы пытаемся получить общее понятие о красоте группы людей, связанных одним ПО и чаще всего, больше ничем. Получается, что в понятии красоты кода есть субъективность, но мы стараемся минимизировать ее в пределах команды. Но возможно ли сделать понятие красоты кода полностью объективным? К чему мы прибегаем в попытках добиться объективной красоты и насколько это работает? Чтобы прийти к ответу на эти вопросы, стоит рассмотреть то немногое общее, что есть абсолютно у всех людей. А именно: мозг и то, как он обрабатывает информацию.
С какой задачей лучше всего справляется мозг?
В интернете можно найти несчетное множество статей о том, как работает мозг при распознавании информации. К примеру, можно обратиться к этой статье.
Наш мозг перерабатывает огромное количество информации и для того, чтобы оптимизировать свою деятельность, он использует шаблоны. Эти шаблоны формируются по ходу нашей жизни. Отличным примером может служить процесс чтения. Чем больше литературы на одном языке прочитал человек, тем выше его скорость чтения. Чем больше прочитанных книг, тем больше прочитанных слов и тем чаще они встречались вам. Визуальное распознавание часто встречающихся слов происходит в разы быстрее, чем чтение новой терминологии (особенно это касается незнакомой вам предметной области). Чем больше книг одного автора прочитал человек, тем быстрее он сможет читать другие произведения этого автора. Это справедливо потому, что у каждого автора ограниченный словарный запас и стиль написания текста.
Если посмотреть глубже, то читать мы учимся с того, что учим алфавит. Точнее, мы учимся распознавать написанные буквы, а уж потом беремся за распознавание слов. С распознаванием букв связан еще один интересный факт — скорость распознавания букв мозгом также зависит от шрифта, если мы говорим о печатном тексте, или почерка — для рукописных текстов. Причем, когда дело касается рукописных текстов, ситуация может доходить до абсурда — почерк может быть насколько плох, что мы просто не сможем разобрать, что написано (привет докторам). Процесс обучения ребенка чтению, скорее всего, именно то жизненное явление, которое вдохновило людей придумать нейросети. Те, кому эта тема интересна, могут посмотреть материалы по поводу того, как распознается текст с помощью нейросетевых алгоритмов.
Когда мы читаем слова, то только на начальных этапах обращаем внимание на все буквы. И читать нас учат соответствующим образом — сначала по буквам, потом по слогам и только затем словами. Есть интересное исследование, тезис которого может ответить сам за себя:
По рзелульаттам илссеовадний одонго анлигйсокго унвиертисета, не иеемт занчнеия, в кокам пряокде рсапожолены бкувы в солве. Галвоне, чотбы преавя и пслоендяя бквуы блыи на мсете. Осатьлыне бкувы мгоут селдовтаь в плоонм бсепордяке, все-рвано ткест чтаитсея без побрелм. Пичрионй эгото ялвятеся то, что мы не чиатем кдаужю бкуву по отдльенотси, а все солво цликеом.И это еще одно доказательство того, что мозг работает шаблонами. Всю новую информацию, которую мы получаем мозг, также пытается прогнать через существующие шаблоны, прибегая к 3 основным принципам:
- удаление — мы удаляем из памяти неиспользуемую уникальную (наименее шаблонную) информацию
- искажение — касается преувеличения или преуменьшения чего-либо. К примеру, в нашем обиходе очень часто используются слова «никогда», «всегда», «ничего», «все», «всё». Если вдуматься, то каждое употребление такого слова — неправда или искажение фактов.
- обобщение — вся новая информация пытается сводиться мозгом к чему-то, с чем он уже работал. На аналогиях проще запоминать и вспоминать.
Из этих фактов, выходит, что красивый код должен состоять из шаблонов, чтобы нам было комфортно с ним работать. Таким шаблоном может служить общий стиль кода в команде разработчиков. Если отступы в коде будут разными для каждого исходного файла или строки в нем, то ориентироваться в нем будет достаточно непросто. Еще одним шаблоном может служить распространенный совет — программируйте в терминах предметной области (code in terms of domain). Это справедливо потому, что некоторые слова могут иметь разный смысл в зависимости от контекста (отличный пример — слова «лук» или «замок»).
От противного
После мыслей о шаблонах, я задумался о том, с каким кодом будет работать сложнее всего. В голове промелькнула мысль об обфускации кода. Ведь этим занимаются исключительно затем, чтобы усложнить другим программистам понимание внутренней работы вашей системы. Кроме того, обфусцированный код наиболее наглядно показывает всю важность визуального восприятия кода.
В качестве приемов обфускации можно привести такой набор:
- Длинные и бессмысленные названия перменных, классов, методов и функций. Мало того, что длинные слова сложнее читать, так еще таких сочетаний букв и цифр вы в жизни явно не встречали.
- Полностью убираются пробелы, отступы и переносы строк. В таком случае, мы видимо просто мешанину из букв, цифр и символов. В худшем случае, это может быть целый экран без единого свободного пространства. Тогда вам придется разбирать этот текста посимвольно, на что потратится прорва времени. По мимо того, под конец дня работы с таким кодом вы будете чувствовать себе вымотанным и уставшим. Кто-то скажет, что у него «взорвался мозг».
- Возможно, вызовы некоторых методов или функций заменятся на их исходный текст. Немного странный момент. По факту, это изуродованные огромные шаблоны, но с условием их бессмысленного повторения. Делает текст крайне избыточным. Пока вы распознаете огромный шаблон, мысли о предыдущем контексте уже покинули ваш мозг.
Выходит, что красивый код должен быть создан из простых неизбыточных повторений. На эту тему даже существует широко известный акроним DRY — Don't repeat yourself. Однако, всегда ли это верно, если посмотреть на мир шире?
Отличный пример — музыка. Она не может обойти без повторений. Тут стоит оговориться, что из этого утверждения есть исключения. Простейший пример такого исключения — виртуозные соло. Они запоминаются фанатами, несмотря на то, что зачастую не содержат повторений. Но в общем случае, правило работает. Если взглянуть на музыку, набирающую все большие и большие обороты — Rap/Hip-hop — можно наиболее наглядно убедиться в этом. Структура минуса практически полностью состоит из незатейливых коротких повторяющихся фрагментов, что далеко не всегда характеризует его красотой. В качестве вырожденного примера, можно вспомнить метроном — никто не станет утверждать, что его равномерное и монотонное тиканье является прекрасной музыкальной композицией. Можно ли считать увеличение количества повторений в музыке — ее оптимизацией или улучшением? Первой можно посмотреть на классическую музыку, полную неожиданных ходов, уникальных партий и малого числа повторений. Текущая тенденция — практически не зависимо от жанра, произведения стали короче. Их называют теперь песнями и они полностью шаблонны с точки зрения структуры — есть вступление, припев, простой лейтмотив. Берем эти составляющие в различных вариациях и комбинациях и получаем более или менее универсальную формулу. Такие произведения проще «заходят» слушателям. Именно по этому, ценителей популярной музыки много больше ценителей классики.
Еще одним примером подобного явления постоянного повторения и упрощения может служить индустрия игр. Все больше и больше набирает популярность жанр казуальных игр плавно перетекающий в жанр гипер-казуальных. Это игры, нацеленные на повторение простой цепочки быстрых действий, жестко ограниченных коротким промежутком времени. В то время как сложные вариативные игры и малоповторяющиеся игры, например, жанр стратегий, сейчас переживают
не лучшие времена.
Если есть пример неработоспособности подхода простых неизбыточных паттернов, всегда ли такой подход должен работать для кода?
Как добиться простого ООП кода и что может в этом помочь?
С момента погружения в профессию вокруг нас кружат сложные и замысловатые, на первый взгляд, принципы и подходы. Старшие специалисты, под крылом которых мы обучаемся, говорят о различных подходах, паттернах и книгах, которые стоит прочесть, чтобы преуспеть на поприще ООП. Думаю, самые часто звучащие слова — паттерны проектирования и SOLID. Далеко не всем сразу становится до конца ясно, зачем это все нужно. Но ведь общий смысл заключается в написании красивого и правильного кода. А каким образом нам это помогает, и всегда ли? Все магические паттерны и аббревиатуры нацелены на то, чтобы научить нас думать о программировании такими же шаблонами, как и при чтении и анализе обычного текста.
Всегда ли паттерны проектирования — благо?
С самого начала карьеры разработчиков учат паттернам. Попробуем разобраться, зачем. Любой паттерн проектирования нацелен на то, чтобы сущность была максимально компактной по описанию и простой. Не делала то, чего не должна и делала то, что от нее требуется, максимально лаконичным способом. Все это — не только о том, чтобы решение было архитектурно устойчивым, но и о том, чтобы решение было простым в понимании. От проекта к проекту вы используете примерно один и тот же набор паттернов. Частое повторение этих паттернов ускоряет процесс проектирования и процесс обработки существующего кода в мозге, сокращая время поиска того или иного нужного участка кода.
Сама по себе разработка ПО ради разработки ПО никому не интересна, разве что в учебных целях, которые можно оставить за рамками рассуждений. У разработки ПО всегда есть какая-то бизнес-цель. Здесь стоит отметить, что, говоря о бизнес цели, речь не всегда идет о деньгах. Возможно, цель — заработать репутацию, помочь кому-либо или просто похвастаться перед родными/товарищами («Смотри, я написал игру!») и так далее. По большей части, именно бизнес-цель является основополагающим фактором к выбору архитектуры приложения. Сначала анализируются требования, на основе которых можно сделать вывод о том, будет ли софт поддерживаться, каков будет цикл разработки. На все эти факторы накладывается информация о ресурсах — команды или собственных знаний. Все эти факторы явно или неявно (если вы еще только начинаете путь в разработке ПО, то можете не задумываться об архитектуре, но точно скоро начнете) влияют на то, какие подходы и паттерны вы будете использовать в ходе разработки. На основе выбранной архитектуры, подходов и паттернов появляется кодовая база ПО.
До того, как разговор зашел о паттернах проектирования, красоту кода можно было рассматривать в абстрактном плане. То есть, можно было гипотетически представить некоторый файл с исходным кодом, вырванный из контекста. Руководствуясь исключительно визуальной составляющей кода, не обращая внимания на его семантику, мы можем сделать вывод, является код красивым или нет. Именно когда в один прекрасный момент абстрактный исходник становится частью кодовой базы (частью чего-то большего, частью ПО), в уравнение красоты кода начинают вводиться новые переменные.
Предположим, на проект приходит новый разработчик, который никогда не видел код этого проекта. Первым делом ему необходимо войти в проект, иными словами — изучить кодовую базу. В ходе изучения разработчик сформирует свое мнение о проекте, точнее о том, из чего он состоит. И в этот момент у разработчика могут возникать следующие мысли — «Это решение некрасивое», или наоборот «Ух ты! Какое элегантное (красивое) решение...». В данном случае красота относится к архитектуре, которая выражается по средствам кода. То есть в понятие красоты кода может вмешиваться также понятие семантической красоты — насколько решение правильно архитектурно. Правильно ли подобран подход или паттерн и выдержан ли он.
Это как раз то самое место, где все становится наиболее сложно:
- Если вы не знакомы с тем или иным подходом, то его идея может быть неочевидна, непонятна и восприниматься в негативном контексте или позитивном. Такой случай наиболее непредсказуемый.
- Вы можете быть знакомы с подходом и воспринимать его отрицательно, к примеру, анти-паттерны.
- Вы можете быть знакомы с подходом и воспринимать его положительно.
В последних двух случаях сделать вывод и выразить свои мысли по поводу кода и его красоты вам будет значительно легче.
Получается, что именно для этого всех нас и заставляют учить паттерны проектирования — чтобы все мы говорили на одном языке, стандартизированно. Кроме того, паттерн (шаблон) — то, с чем так любит работать наш мозг.
Однако мы подобрались к моменту, когда все может быть не так просто, как кажется. Какой-либо файл исходного кода может сочетать в себе реализацию нескольких паттернов. Опустим вопрос корректности такого решения, это очень сильно зависит от ситуации и может быть как правильным, так и неправильным решением. Разбавление реализации одного шаблона реализацией другого может усложнять восприятие кода, делая его некрасивым в наших глазах. Неправильно реализованный паттерн еще больше усугубляет ситуацию. Вам до последнего может казаться, что вы понимаете код, так как он подчинен паттерну, а потом оказывается, что все это время вы неправильно его воспринимали. В это мгновение красивый код теряет весь свой шарм и становится не таким уж и красивым, а скорее — с душком. В этом случае отсутствие паттерна могло выглядеть красивее, чем неправильно реализованный паттерн. Еще один аспект касается неверных подходов или анти-паттернов. По сути, анти-паттерн — паттерн, который не следует использовать. Но тем не менее, это паттерн, который определенным образом придает читаемости и понимаемости, то есть простоты и красоты коду. Выходит дело, что использование паттернов в коде проекта — палка о двух концах.
Стиль написания API
Говоря о красивом оформлении API, чаще всего в голову приходит либо какой-то сервис, либо библиотека (или фреймворк). Сервис чаще всего представляется чем-то внешним для кода, поэтому в нашем контексте лучше рассмотреть библиотеку. Выбор того или иного вида API будет влиять не только на структуру кода библиотеки, но и на внешний вид клиентского кода, использующего библиотеку. Навскидку могу привести 2 стиля.
Классический стиль заключается в том, что мы обрамляем библиотеку в одну определенную сущность (класс) и все методы, которые есть в этом классе, выполняют определенное действие, возвращая определенный результат, не зависящий от состояния основной сущности библиотеки. Большинство различных плагинов сделано именно в классическом стиле, поэтому проблем с пониманием такого стиля возникнуть не должно.
Второй стиль — текучие интерфейсы. Такой подход заключается в том, что на выходе работы библиотеки мы получаем тот же самый объект, который и вызвал действие, только настроенный теперь немного иначе. Для примера можно привести реализацию LINQ в языке C#. В нем действия цепочкой производятся над одной и той же коллекцией (IEnumerable). На каждом шаге запроса мы каким-либо образом модифицируем коллекцию, пока не получим требуемый результат. Еще один пример из C# — контейнер внедрения зависимостей под названием Zenject.
В нем биндинги зависимостей написаны в текучем стиле:
Container.Bind<Foo>().AsSingle().NonLazy().FromInstance(FooInstance");
Очень известный пример текучего интерфейса из Swift — популярный фреймворк Alamofire.В нем часто можно увидеть такие конструкции:
Alamofire.request(
.GET, "http:foo.com", parameters: ["include_docs": "true"],encoding: .URL).validate().responseJSON { (response) -> Void in... }
В большинстве случаев подходящий стиль можно выбрать исходя из сложившейся ситуации. Но никто не мешает переписать API библиотеки с одного стиля на другой не меняя функциональности, и тогда это становится вопросом вкуса или красоты. В целом, текучие интерфейсы не так распространены как классический стиль, однако в зависимости от большого числа факторов могут сделать код красивее или уродливей. Вывести универсальную формулу здесь также не получится.
Языковые средства по уходу за красотой
Мы уже пришли к выводу, что для красоты нам нужны шаблоны, без них никуда. Шаблоны должны быть узнаваемыми, простыми и короткими. С шаблонами проектирования все оказалось неоднозначно. Но кроме архитектурных шаблонов на помощь приходят еще и конструкции самого языка, на котором вы пишете. Один и тот же паттерн проектирования, реализованный на одном языке может выглядеть красиво и некрасиво в зависимости от конкретных языковых инструментов.
Общим для большинства, если не всех языков программирования, то для большинства, языковым средством, не влияющим на семантику кода, но влияющим на его красоту является комментирование. Добавление хорошего комментария в нужном месте способно улучшить внешний вид и восприятие кода. Но стоит написать комментарий в отрыве от общего стиля (например c-подобные языки позволяют описывать комментарии тремя разными способами), как он тут же портит общую картину и визуально отвлекает от восприятия самого кода.
В качестве жертвы давайте рассмотрим некоторые средства, которые нам предоставляет язык C#. Одно из простейших средств этого языка — ключевое слово var. Он позволяет избежать явного указывания типа переменной при ее объявлении, используя var для любого типа или интерфейса.
С одной стороны, этот инструмент помогает убрать практически все имена типов в телах методов. С другой стороны — заставляет задумываться о том, к какому типу относится та или иная переменная. Подавляющее большинство разработчиков, соболезнующих C#, считает использование var хорошей практикой. То есть этот языковой инструмент можно считать больше полезным, чем вредным, в плане красоты кода.
Директива #region #endregion позволяет указывать блоки кода, которые можно будет сворачивать в IDE. Является одним из наиболее спорных инструментов. В некоторых командах использование этой директивы строго-настрого запрещается, в других советуется на обязательной основе. Одна из аргументаций отказа от использования этой директивы — то, что она разбавляет код семантически бесполезными вставками, которые мешают сконцентрироваться на самом коде. В качестве аргумента «за» можно привести практику, где методы класса группируются по регионам таким образом, чтобы можно было легко и без правок кода посмотреть отдельно его внутреннюю реализацию, либо внешнее API, либо реализацию того или иного паттерна (которая зачастую может выглядеть шаблонно и ее убирают в регион, чтобы глаза не мозолила). Подведя итоги, можно сказать, что инструмент более чем спорный.
Последний инструмент, которые следует точно рассмотреть — лямбда-выражения и анонимные методы. В общих чертах можно сказать, что оба инструмента предназначены для того, чтобы максимально компактно описать метод, зачастую без его явного объявления. Для этих целей нам на помощь могут прийти такие фишки, как опускание сигнатуры, типов входных и выходных параметров, заставляя компилятор выводить все это в явном виде за нас. Инструмент, который многие склонны считать неотъемлемой частью языка, тем не менее, имеет наиболее спорную позицию в плане красоты кода. К примеру, LINQ сложно представить себе без лямбда выражений, они компактны и лаконичны. Но если злоупотреблять их использованием, весь код в классе можно легко превратить в нечитаемую кашу.
За рамками разбора остаются такие языковые средства, как расширяющие методы (методы-расширения), foreach vs for, анонимные методы, тернарный оператор, сцепление конструкторов и другие. Все их также объединяет спорность использования в определенных частях кода. Отсюда можно сделать вывод, что не существует и идеального синтаксического сахара, который делает код бесспорно красивее.
В качестве дополнительного материала на тему синтаксического сахара не могу пропустить молодой язык программирования — Swift. По большей части он не предлагает уникальных языковых конструкций. Однако его возможность опускать символ ";" в конце строки кода больше всего разорвала мое сформировавшееся представление о процессе написания кода. Знаю, что при обычном прочтении этой особенности она не воспринимается как что-то из ряда вон выходящее. Но в ходе боевой разработки на этом языке на первых порах перестроиться было достаточно сложно. Не писать ";" в конце строки кода является рекомендуемой практикой в этом языке. Казалось бы, что это сущая мелочь, но ведь с ней также можно провести параллель вне контекста программирования. Эта параллель заключается в правилах пунктуации большинства языков мира. Мы привыкли ставить точки в конце предложения, годами формируя шаблон распознавания законченной мысли в тексте. Возможно, в простой возможности опустить ";" в коде скрывается скрытое правило, которое можно выразить следующим образом: писать только одну сроку кода на одной строке файла исходного кода. И эта рекомендация выражается ультимативно в виде практики не ставить ";" в конце строки, не давая возможности дописать в эту строку что-то еще. Однако есть в Swift и удачные синтаксические нововведения, например, оператор guard.
Если речь зашла о ";" в конце строки кода, сложно не упомянуть и язык Python. Этот язык изобилует всевозможным синтаксическим сахаром. Немалая часть этого сахара спорная, даже, с позволения сказать, явно не нужная, лишь усложняющая прочтение кода. Однако все это неоднозначно в контексте того, что порог вхождения в язык крайне низок. Складывается впечатление, что разработчики языка приложили максимум усилий к тому, чтобы вы могли писать код на Python, как можно меньше внимания обращая на синтаксис. Еще из особенностей этого языка я бы отметил отсутствие явного выделения блоков кода (не используются скобки "{}"). Вместо явного выделения Python живет отступами. С одной стороны, это лучше воспринимается визуально, потому что в тексте меньше бессмысленных служебных символов. С другой стороны, приходится обращать внимание на отступы, чем в большинстве языков явно заниматься не нужно. В этом языке не обошлось и без разрывов C-шаблонов. Вместо try-catch оператора имеем try-except. По названию оператор выглядит логично, но перестроиться сложно.
В качестве последнего спорного примера языкового средства, которое встречается во многих языках, приведу обобщенные классы и методы (Generics). Сами по себе обобщения призваны избавить нас от надобности дублировать сильно шаблонный код. То есть, как минимум в плане избавления от дублирования кода в разных местах, обобщения служат как инструмент, позволяющий сделать код более красивым. Но сама по себе концепция типа, параметризованного другим типом, воспринимается довольно сложно. Каждый раз, натыкаясь на обобщение в коде, приходится тратить больше времени на осознание семантики, чем на осознание семантики не обобщенного кода.
Учитывая обозначенные выше пункты можно утверждать, что синтаксический сахар является также спорным средством, способным сделать код и более, и менее красивым.
Программная сторона, которая может нам помочь
Вся субъективность подхода к написанию кода и понятия о его красоте может сбивать с толку. Особенно остро эта проблема возникает в ситуации, когда в слаженную команду приходит новый разработчик. Зачастую новичку ничего не остается, кроме как мимикрировать под уже написанный код и по ходу подстраиваться к существующему стилю. Это то, что не любит большинство людей, приходя на новое место работы. Все ваши шаблоны восприятия, нейронные цепочки, рефлексы, которые сформировались за годы опыта, начинают приносить вред вместо былой пользы. Благо хотя бы в этой области можно ввести немного общих практик, избавленных от субьективизма.
Более того, об этом за нас уже давно подумали и позаботились. Современные IDE далеко ушли от обычных текстовых редакторов и могут сильно облегчить задачу по поддержанию стиля кода. Подсветка синтаксиса, пожалуй, является наиболее древним инструментом из запасов IDE. Несмотря на свою простоту и привычность, идея в ней все та же — сформировать определенный шаблон, помогающий писать код. Самый простой пример — подчеркивание определенного слова в коде красным при ошибке. Помимо этого, многие сомнительные места, на которые стоит обратить внимание, помечаются желтым. Все это вместе — шаблон, который сформировался у нас в голове еще с самого детства с помощью светофора.
В плане красоты кода современные IDE также стараются помочь чем могут. Для примера можно взять достаточно молодую IDE от JetBrains — Rider. Тут на помощь приходят и упрощение логических условий, и помощь в подборе названия переменной, проверка орфографии в комментариях и названиях переменных, автоматическое выравнивание и многое другое.
Что касается именования переменных, выравнивания кода, вставки табуляции вместо пробелов и пробелов в слитный код — это все является основным пунктом, который помогает оформлять текст как код, в таком стиле, какой вам будет угоден. И большую часть таких правок IDE сделает в фоне за вас. Выходит дело, что ее нужно просто научить тому, что вы хотите, и впоследствии она станет для вас незаменимым помощником, который сэкономит вам кучу времени. Детали обучения IDE вашему стилю не относятся к предмету этой статьи. Но если задуматься об этой концепции в целом, то процесс получается крайне схожим с процессом внедрения нового разработчика в команду. Для этих целей незаменимым средством является документ с соглашением о стиле кода. С разницей лишь в том, что разработчик получит его в виде документа, а IDE сгенерирует или сохранит его в файл внутренней конфигурации, в формате, понятном только ей одной.
Таким образом, неоспоримым шагом на пути к красивому коду является его стандартизация. Очень много it компаний по всему миру пренебрегают написанием документа, стандартизирующего стиль кода. Бывает, что катастрофа приобретает ужасающие масштабы — количество проектов, параллельно разрабатывающихся в компании, равно количеству стилей кодирования, применяемых внутри компании. В таких условиях переход разработчика из одного проекта в другой становится еще более нетривиальным делом. Если за стандартизацией кодовой базы никто не следит, тогда код имеет тенденцию превращаться в свалку (bulk code), и адаптация на новом проекте ощущается как переход в другую фирму, вызывая больше стресса, чем могла бы. Вместе с ростом уровня стресса при переходе на новый проект растет и срок адаптации.
Таких проблем можно избежать, имея в запасе всего лишь документ с соглашением о стиле кода. Этот документ можно также считать инструментом разработки. Имея в арсенале такой документ и современную IDE, можно сильно облегчить себе жизнь — автоматизировать стиль кода на уровне IDE. Это позволит снизить порог вхождения разработчика в команду и уменьшить количество претензий к разработчику в ходе code-review. Мысль об автоматизации и использовании инструментов разработки по максимуму далеко не нова, ее можно встретить в множестве источников, включая легендарную книгу «Программист-прагматик».
Подытожим: инструменты разработки несут исключительно положительное влияние на красоту кода. При этом фактор субъективности никак не влияет на полезность этих инструментов.
Программная сторона, которая может все испортить.
С хорошей программной частью мы уже ознакомились. Но все ли технические решения направлены на то, чтобы сделать код красивее? Случается так, что когда продукт практически готов к релизу, выясняется, что он не соответствует техническим характеристикам того или иного устройства, которое нам так важно поддерживать. Особенно остро такая проблема выражается в области разработки игрового ПО. У кого-то могут возникнуть достаточно резонные возражения о том, что можно сразу писать оптимальное ПО и заниматься оптимизацией по ходу разработки, добиваясь максимально оптимального решения. К таким возражениям можно привести цитату контр-аргумент:
Преждевременная оптимизация — корень всех зол. -Дональд Кнут
Споры о том, правдиво это высказывание или нет, не касаются красоты кода и лежат за рамками текущих рассуждений.
Но оптимизация может пагубно сказываться на красоте кода. Это происходит далеко не всегда, но существуют некоторые практики, которые с большой вероятностью могут выражаться в далеко не самый читаемый код. Примеры таких оптимизаций:
- Выравнивание данных/переменных в памяти
- использование Data Oriented Design (DOD)
- внедрение асинхронных или многопоточных действий
Эти оптимизации довольно сложны в реализации, но необходимы при разработке высокопроизоводительных вычислений и в ряде других ситуаций. Но, к примеру, чрезмерное использование многопоточного взаимодействия имеет свойство сильно усложнять код. Получаем ситуацию, когда красота кода может быть принесена в жертву технической оптимизации.
К кому можно обратиться за советом?
Мы определились с тем, что независимо от субъективизма понятия о красоте кода существуют практики помогающие ее поддерживать — документ о стиле кода и правильно настроенная IDE. Эта связка действительно может облегчить жизнь, но не в силах гарантировать 100% результат. Причиной тому является человеческий фактор. Разработчик может быть знаком с документом о стиле кода, иметь правильно настроенную IDE и, тем не менее, игнорировать все правила и предупреждения IDE. Причин тому может быть как минимум 2:
- Недобросовестный разработчик. Может быть, разработчик еще не привык к новому для него стилю и пишет в привычной ему манере. А может, он просто не считает поддержание общего стиля важным пунктом, тем, на что стоит обращать внимание.
- Определенное стечение обстоятельств. Большая часть из нас знает или пробовала Agile и практикуют работу по спринтам с жестко фиксированным сроком и набором задач. В жизни же бывают ситуации, когда пришел запрос «сверху» о том, что нужно прямо здесь и вчера реализовать важную фичу, которая не была занесена в текущий спринт. Ни в одной компании не могут гарантировать, что такого не произойдет. При реализации таких фич очень часто случаются такие вещи, как «решение на коленке» и «хотфикс». Зачастую такие вещи делаются в угоду скорости и следить за красотой такой правки просто нет времени.
Не важно, какая именно причина побудила написать разработчика некрасивый код и залить его в общую кодовую базу. При этом, как только такая правка попадает в релиз, велика вероятность, что именно в таком некрасивом виде она так и останется. Казалось бы, что может пойти не так, если все мы понимаем, что такое происходит только в исключительных ситуациях и, что делать так нельзя? Суть проблемы заключается в том, что это прецедент, который имел место быть. Значит что велика вероятность того, что спустя некоторое время, когда все забудут контекст имевшей место ситуации, некоторый разработчик в ходе работы наткнется на этот некрасивый участок кода. Тут в его голову могут закрасться неправильные мысли о том, что если такое уже есть в проекте, то от добавления еще одной порции хуже не станет. Рецепт катастрофы прост — повторять последнее действие n раз, пока весь проект не будет состоять из таких «исключительных» мест — разбитых окон.
Такой эффект очень хорошо описывается в Теории Разбитых Окон. С разбитыми окнами можно бороться достаточно просто — нам просто нужен охранник, который будет следить за кодом. Касаемо программирования, такое решение хорошо вписывается в понятие code-review. Не могу назвать ни одного плохого побочного эффекта практики проведения регулярных code-review. Единственное, к чему могут прикопаться руководящие люди — трата существенного объема времени, на проведение code-review и исправление замечаний по нему. Таким людям бывает достаточно сложно объяснить, что такое технический долг, и откуда он берется. Но в контексте красоты кода, code-review является сугубо положительной практикой.
Кроме человека, который проводит code-review на проекте (или нескольких человек), за советом вы можете обратиться также к разработчикам языка или гуру программирования. Конечно, не стоит расчитывать на то, что вы можете написать письмо или позвонить напрямую этим самым гуру. Но для большинства языков программирования существуют официальные или общепринятые
стандарты стиля кода, написанные авторитетными людьми. Примеры:
- Python — PEP8
- C# — StyleCop
- Swift — The Official raywenderlich.com Swift Style Guide
- Objective-C — The official raywenderlich.com Objective-C style guide
Следование общепринятым стандартам стиля кода является хорошей практикой, помогающей следить за красотой кода. Хотя в общепринятых стандартах также присутствует субъективизм и сделать вывод о том, что код, написанный по общим стандартам будет красивым, нельзя.
Заключение без вывода
Все рассмотренные мысли на тему красоты кода приводят исключительно к ее субъективности даже в терминах его простоты. Простота, в свою очередь, вытекает из того, насколько легко понять код и насколько он компактен (или наоборот). Все описанные средства, выбор между тем или иным подходом формируют почерк, придавая узнаваемость и поддержку со стороны других разработчиков. В то же время выходит, что понятие красоты в коде все-таки поддается некоторой, хотя и очень слабой, стандартизации. В общих чертах можно предположить, что шаблонность в большей степени коррелирует с красотой кода, хотя и тут возможны вариации.
Достичь консенсуса между всеми разработчиками мира, возможно, не получится никогда. Но подходить к выводу понятия красоты кода стоит осмысленно. Возможно, задумавшись о том, почему вы предпочитаете писать так или иначе, вы станете писать более читаемый код и выражать свои мысли в коде четче. Или такое осмысление поможет вам принять выбор в сторону определенного из существующих стилей.
Комментарии (13)
megahertz
16.05.2019 08:40Хабрапользователи, а в каких проектах по вашему мнению максимально красивый код?
yarkov
16.05.2019 09:34+1В тех, которые ты хочешь начать, но откладываешь, обещая начать в понедельник.
«Уж новый проект я точно не запорю и сделаю идеально» — говоришь ты сам себе.
Но проходит день за днем, а проект не начат, потому что ты еще не готов писать идеальный код.rjhdby
16.05.2019 14:22Более правдиво будет не "в тех, которые ты хочешь начать, но откладываешь", а "в следующем". Причем "следующий" каждый раз новый.
berez
16.05.2019 11:08Помню, смотрел код утилиты GNU ls — офигел, насколько все красиво и понятно написано.
При этом код красивый, но буквально каждое правило форматирования, которым они пользуются, вызывает у меня отторжение. :)
ЗЫ: Посмотрел сейчас в тот код — мама дорогая… Ужас же. Но тогда я, помню, насмотрелся кода всяческих драйверов на Си, вот глазыньки и вытекли…
JustDont
16.05.2019 11:15Когда-то я заморачивался этим вопросом. Потом дошел до более практичного — если проект работает и делает что-то крутое крутым способом, то вообще плевать, насколько «красив» его код. Пусть там даже будут костыли для костылей кругом.
Carduelis
16.05.2019 11:21Вот знаю, где очень некрасивое API — devextreme-react-grid. Мало того, что половина логики лежит внутри JSX, так еще и, чтобы реализовать одну из out-of-box фич, как поиск по таблице и выбор строк, нужно:
a) вставить несколько JSX-компонентов (каких — читай документацию, обработку ошибок не придумали)
б) сделать это в определенном порядке (иногда в консоли появится ошибка о неправильном порядке вызове функций)
в) понять, что эти разные компоненты связаны только по соглашению в названии переменных
г) потом понять, что это работает не всегда: для SelectionState нужен IntegratedSelection, а для SearchState уже нужен IntegratedFiltering.
в) написать пару goto конструкций (шутка)
berez
16.05.2019 11:02По сути, антипаттерн — паттерн, который не следует использовать. Но, тем не менее, это паттерн, который определенным образом придает читаемости и понимаемости, то есть простоты и красоты коду.
Ага, антипаттерн «спагетти-код» придает программе особую читабельность… :)
Выходит дело, что ее нужно просто научить тому, что вы хотите, и впоследствии она станет для вас незаменимым помощником, который сэкономит вам кучу времени.
А потом разработчик приходит в команду хардкорных серверных разработчиков, у которых из IDE — только Vim в консольке… Слезы, паника, отрицание, гнев, торг, принятие — и катарсис…
Очень много it компаний по всему миру пренебрегают написанием документа, стандартизирующего стиль кода.
Хм… А у меня другое впечатление: сначала пишется кодинг-стайл-гайд, а уже потом создается IT-компания. :)
А вот уже потом происходит то, о чем вы пишете: в каждой команде сей стайл-гайд творчески перерабатывается, и в результате каждая команда пишет по-своему.
sshmakov
16.05.2019 11:37О качестве (я считаю, что красивый код прежде всего качественный код) лучше всего сказано в "Дзен и искусство ухода за мотоциклом". Суть в том, что качество невозможно определить формальными правилами, хотя многие могут определить качественен результат или нет.
Ketovdk
16.05.2019 12:00Директива #region #endregion позволяет указывать блоки кода, которые можно будет сворачивать в IDE.
Я видел использование этой директивы, чтобы спрятать большие строки (например GraphQL-запрос на другой сервис), чтобы остальную часть кода можно было нормально читать. Причем в идеале такие запросы вынести в отдельный класс (или XML), но в ситуации с отдельным классом все-равно имеет смысл запаковать в region, наверное
AcckiyGerman
16.05.2019 15:28+1Я бы применил к статье принципы, в ней описанные, и тем самым сократил бы ее раз в 10. А вместо простанных рассуждений вставил бы примеры красивого кода.
lovejump
16.05.2019 18:27Для меня красивый код это прежде всего эффективный и алгоритмически-верный код. У программистов есть своя профессиональная болезнь, от которой трудно излечиться — графоманство. Программист-графоман это угроза всей IT-индустрии, так как такие как он заботятся о красоте написанного кода, о том как выглядит он в редакторе, а не о том, как он работает в реальной жизни. Потребитель не смотрит ваш код, он смотрит на конечный результат. И если ваш красивый код убивает Боинг и всех пассажиров, то от такой «красоты» лучше держаться подальше…
izvolov
16.05.2019 18:43Beauty of style and harmony and grace and good rhythm depend on simplicity — Платон
Платон писал на современном английском? И вправду гений.
SbWereWolf
Интересный взгляд на красоту кода.
Красивый код, это когда всё по феншую. А понятия о феншуе у каждого свои. Со своим уставом в чужой монастырь не ходят, поэтому общий вывод такой, что люди совместно работающие над одной кодовой базой должны иметь договорённости о том как код оформлять и какими методами (архитектурными шаблонами) решать стандартные задачи, договорённость о том как всё в целом должно быть организовано (архитектура приложения в целом)