Как сделать так, чтобы ваш код оставался простым и поддерживаемым

Перевод статьи «How to keep your code simple and maintainable».

Разрабатывая новое приложение, вы начинаете с маленькой и простой кодовой базы. Но со временем код растет и становится все более сложным. Бороться с возрастающей сложностью тяжело — так же, как и следить за тем, чтобы кодовая база оставалась поддерживаемой в долгосрочной перспективе. В этой статье я собрал несколько принципов, которые помогают мне справляться с этими задачами.

Учитывайте стоимость поддержки кода, а не только стоимость разработки

Покупая машину, мы довольно легко и естественно обращаем внимание как на ее собственную стоимость, так и на величину будущих сопутствующих расходов (вид топлива, налоги, обслуживание). Делая выбор, вы можете посчитать, насколько дороже вам будет обходиться крутая, но «прожорливая» машина по сравнению с маленькой и экономичной.

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

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

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

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

Не рассматривайте запросы клиентов как данность: обсуждайте их

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

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

Начинайте с простого и стройте архитектуру, которую можно будет развивать

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

Часто бывает так, что наилучшее решение приходит в голову, как только вы уже создадите приложение. Мне нравится выступление Стефана Тилкова на эту тему — «Good Enough Architecture» («Достаточно хорошая архитектура»). Я нахожу его очень вдохновляющим. Вместо того чтобы пытаться все контролировать и продумывать наперед, сразу смиритесь с тем, что программы и обстоятельства постоянно меняются. Не пытайтесь сразу придумать совершенную архитектуру. Делайте ее такой, чтобы она могла развиваться и меняться в соответствии с изменением внешних обстоятельств.

Как это осуществить на практике? Создайте самый простой рабочий вариант (KISS), а также имейте в виду принцип YAGNI.

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

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

Учитесь распознавать ненужную сложность

Это непросто. Нет никаких четко описанных подходов, следуя которым вы непременно будете замечать ненужную сложность (accidental complexity). Вам поможет лишь опыт. Но научиться ее нюхом чуять — важно.

Следует различать ненужную и необходимую сложность. Необходимая сложность (essential complexity) — это абсолютный минимум сложности, который требуется для создания определенного функционала. Ненужная сложность — это дополнительная сложность, которую вносим мы сами, выбирая определенные шаблоны, фреймворки или библиотеки.

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

Когда пишете код, спрашивайте себя, пропорциональна ли сложность вашего кода сложности того, что вы хотите сделать. Если нет — пришла пора рефакторинга или поиска библиотеки для более тщательного абстрагирования того, что вы делаете.

Если у вас появляется ощущение, что ваш код становится излишне сложным, отступите на шаг. Мне в таких случаях помогает опыт работы с несколькими языками и фреймворками. То есть, в голове появляются мысли типа «Это как-то слишком сложно, а вот в языке Х на это ушло бы лишь несколько строк кода. Нет ли здесь иного пути?». Если вы знакомы с тем, как подобные вещи делаются в других языках, это может помочь вам найти готовое решение.

Photo by Riku Lu on Unsplash

Старайтесь не использовать продвинутые свойства языка

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

Старайтесь не использовать продвинутый функционал вашего языка, фреймворков и библиотек. Благодаря этому ваш код станет более надежным. В нем будет легче разобраться, поскольку для понимания происходящего в коде нужно будет меньше знаний. В целом ваш код также станет более мощным, потому что его будет легче интегрировать с другими библиотеками и инструментами. Эта идея лежит в основе принципа «наименьшей мощности».

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

Следите за тем, в каком направлении развивается ваша архитектура

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

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

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

Раньше я считал, что создание программного обеспечения похоже на строительство дома. Вы рисуете чертеж, строите по нему дом — и все готово. Но это даже близко не похоже на реальность. Я слышал и другое сравнение, и оно куда ближе к теме: «Программы не статичны, они живые». Программа никогда не станет идеальной, она всегда в движении. Приглядывайте за ее развитием и за тем, чтобы перемены происходили не хаотично. После каждого внесенного изменения кодовая база должна становиться немного лучше, чем она была до этого.

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх