- 19 мин
- 6034
Источник: 30 Coding Concepts I Learned After Reading «Clean Code»
Автор: Raphael Asso
Иллюстрации: Анна Сербинова
Недавно я закончил читать классический «Чистый код» дяди Боба (англ. Uncle Bob, Роберт Сесил Мартин (англ. Robert Cecil Martin) — консультант и автор в области разработки ПО — прим. ред.).
Поскольку мне нравится записывать все, что я читаю, я подумал, что было бы неплохо резюмировать книгу и создать по ней руководство.
Очевидно, что этот список неполный. Он, вероятно, будет развиваться и трансформироваться во многих отношениях по мере того, как я узнаю и открою для себя больше стилей, схем и точек зрения по работе с кодом.
Поскольку чистый код должен быть выложен, эта статья будет изложена точно так же. Сначала я расскажу о макропринципах — всеобъемлющих идеях и концепциях чистого кода. По мере того, как мы будем углубляться в эту статью (как и в случае с кодом), также будут раскрываться детали, тактика и практическая информация.
Чистый подход к кодированию
Вы, вероятно, будете следующим человеком, который прочитает код, который пишете — сделайте одолжение себе в будущем.
Хотя разработчики пишут код для своей профессии, они также довольно часто читают код. Фактически, большинство разработчиков больше читают, чем пишут.
Сколько раз вы читали только что написанный код? Очевидно, что соотношение больше в пользу чтения, чем в пользу реального кодирования.
Если большая часть нашего времени тратится на понимание и чтение кода, нам лучше убедиться, что когда мы на самом деле кодируем, он будет однозначным для всех наших коллег. Поскольку вы, вероятно, будете следующим человеком, который прочитает код, который вы пишете, убедитесь, что вы поймете его даже через два месяца. Я уверен, что почти каждый программист (по крайней мере, я) писал код в 3 часа ночи, чтобы проснуться на следующий день и часами пытаться понять, что происходит.
Убедитесь, что ваш код чист и ясен, это повысит вашу продуктивность и даст долгосрочные результаты. Сделайте себе одолжение и начните писать чистый код.
Есть больше параметров для кодирования, чем «работает» или «не работает»
Код не следует оценивать исключительно по принципу «работает» или «не работает». Конечно, код, который не работает, не годится — потому что он не работает.
Но код, который действительно работает, не обязательно хорош, и это очень важное различие. Как только наш код подтвердит свою функциональность, наша работа как разработчиков не будет завершена.
Профессиональный разработчик следит за тем, чтобы после прохождения всех тестов код был ясным, чистым и поддерживаемым. Фактические принципы написания чистого кода будут рассмотрены далее, а пока мы не должны оценивать код только по его работоспособности.
Есть больше параметров и руководящих принципов для того, что является хорошо написанным фрагментом кода.
Лучше ясно, чем умно
Как инженеры-программисты, мы занимаемся решением проблем, творчески и интенсивно думаем. Часто, когда мы сталкиваемся со сложными проблемами, наш код может оказаться «умным» — например, делать очень умные вещи, при которых только человек, написавший код, может понять, что вообще происходит.
Это нежелательный результат, даже если проблему сложно решить. Истинное мастерство и профессионализм заключаются в том, чтобы решить сложную проблему и четко и ясно продемонстрировать ее решение, чтобы другие люди могли просмотреть код и принять участие в его работе. Это не потому, что нам нужно хорошо относиться к нашим коллегам и делиться с ними удовольствием (хотя это тоже хорошая причина), а скорее, если вы единственный человек, который понимает код, это навредит вам в долгосрочной перспективе.
Вашим коллегам будет не только трудно переработать код, но это также ограничит проект и сделает его медленнее — и вы будете привязаны к фрагменту кода навсегда, пока не умрете или не покинете компанию.
Мы должны быть уверены, что после того как мы решим проблему, решение покажется понятным и простым каждому, кто прочтет этот фрагмент кода.
Предчувствие и доверие имеют большое значение
Когда кто-то читает ваш код, между вами и читателем происходит процесс установления доверия. Доверие в основном выражается в уверенности. У читателя есть некоторая степень уверенности в написанном вами коде, и когда уверенность высока, проект может двигаться быстрее.
Я надеюсь, что к настоящему времени ясно, что читатель — это также ваше будущее. Если вы не доверяете собственному коду, то удачи вам.
Итак, как мы можем завоевать доверие читателя? Позволяя им правильно предвидеть то, что произойдет. Как только читатель поймет код, знает, что он должен делать, и правильно предвидит, что будет дальше, между сторонами устанавливается доверие.
Предотвращайте сюрпризы, непоследовательности и двусмысленности в коде, которые могут смутить читателя. Если ожидается, что функция или класс будет выполнять определенное действие, но читатель удивлен, что выполняются другие действия - то доверие пошатнулось, и читатель больше не может доверять программисту (“как они могли?”). “Они” ошиблись в этом фрагменте кода, и теперь читатели будут находиться в режиме постоянного угадывания на протяжении всего обзора.
Ну, конечно, это не происходит само собой. Это требует практики, постоянных итераций, доработки и постоянного следования принципам чистого кода. Дядя Боб четко упоминает что даже он не пишет чистый код с первого раза.
Чистый код не возникает сразу, сам по себе
Теперь, когда мы выяснили, почему чистый код имеет решающее значение для нашего успеха как разработчиков, мы столкнулись с роковым вопросом:
Как написать чистый код?
Вечные принципы
Отдельные проблемы
Каждый раздел кода должен касаться определенной проблемы программного обеспечения. Написание функций или классов, которые вызывают различные проблемы, не является примером чистого кода. Говорим ли мы о модулях, классах, функциях — каждый на своем уровне абстракции.
Тем не менее, принцип прост: какова логика того, что я сейчас пишу? Что здесь является главным предметом для беспокойства? Есть ли у меня другие проблемы? Если да, разделите их.
Код похож на статью
Статья или блог имеют четкую структуру. Основная тема находится вверху, и по мере продвижения по статье мы можем заметить, что основная тема разбита на подразделы, а внутри них мы имеем более подробную информацию о каждой части.
Код должен следовать тому же принципу.
Вначале у нас будет основная проблема в фрагменте кода. По мере нашего продвижения функция и классы повлекут за собой более подробную информацию о применении и сохранят четкое разделение проблем — точно так же, как у каждого блога есть подзаголовок и параграф, который тесно с ним связан.
Каждый кусок кода должен быть разбит на классы и функции, которые имеют дело с конкретной проблемой, и по мере того, как мы продвигаемся в рамках этих классов и функций, они имеют дело только с этой проблемой.
Правило бойскаута
Когда я был бойскаутом, я был инструктором для детей с особыми потребностями. Когда мы ездили на экскурсии, мы руководствовались принципом: «Оставьте место чище, чем вы его нашли».
Очевидно, что Дядя Боб в своей книге думает о коде примерно так же: всегда оставляйте код лучше, чем вы его нашли. Как обсуждалось ранее, чистый код — это процесс. Он заключается в постоянном улучшении и развитии вами и другими сотрудниками организации.
Поэтому оставьте код лучше, чем вы его нашли. Плохие имена переменных, бесполезные комментарии, закомментированный код — чем дольше он остается, тем неприятнее становится. Будьте инициативными. Будьте бойскаутом.
Чистый, повторно пригодный и поддерживаемый в рабочем состоянии
Всегда следите за тем, чтобы ваш код соответствовал этим рекомендациям. Убедившись, что это так, вы проверите большинство принципов этой книги. Чистый код — это код с хорошей структурой, ясный, хорошо названный и не содержащий сюрпризов.
Повторно используемый код заставляет вас разделять проблемы, поддерживать правильный уровень абстракции и не дает вам писать код, который логически зависит от других разделов (подробнее об этом позже).
Поддерживаемый код позволяет другим вносить свой вклад и понимать, что происходит. При этом учитывается, что программное обеспечение со временем трансформируется. Поэтому ожидайте изменений и убедитесь, что код можно будет легко реорганизовать, если они произойдут.
Никогда не дублируйте (или Никакого копипаста)
Дублирование означает, что код не чистый. Следуйте принципу — не повторяйтесь. Дублирование требует функции, класса, компонента — чего угодно! Только не дублируйте!
Всякий раз, когда вы обнаруживаете, что делаете малейшее последовательное действие ctrl c и ctrl v, интуитивно спрашивайте себя, есть ли лучший вариант. В большинстве случаев ответ будет однозначным — ДА.
Избегайте логической зависимости
Логическая зависимость означает, что часть кода полагается на отдельную часть кода, содержащую определенную бизнес-логику, предполагающую, что код не изменится и, следовательно, имплементация будет в порядке.
Когда мы пишем код, мы должны убедиться, что он «сам по себе» — что у нас есть правильный уровень абстракции, что код не знает частей информации, которые он не должен знать (в зависимости от его уровня абстракции и проблемности) и что если код будет реорганизован, у нас не будет необычного количества побочных эффектов в других функциях и классах.
Наименование
Выберите описательные и однозначные имена
Имена должны описывать, что делается или что содержится. Часть того, чтобы быть ясным, - это не допустить какой-либо другой интерпретации имени и направить читателя к только одному возможному выводу.
Обратите внимание, что приведенный ниже пример не требует каких-либо комментариев или объяснений. он описательный и приводит только к одному выводу.
// BAD
let days;// elapsed time in days
//CLEAN
let daysSinceCreation;
let daysSinceUpdate;
let ageInDays;
let elapsedDays;
Делайте значимые различия
Важно иметь имена, отличные по контексту и структуре. По очевидным причинам мы не хотим путать контекст и значение одной переменной/функции с другой. Однако у них не должно быть очень похожего названия.
// Not distincting context
getActiveAccount();
getActiveAccountInfo();
getActiveAccountData();
В приведенном примере неясно, в чем заключается различие между Account, AccountInfo, and AccountData. Они говорят примерно одно и то же. Что такое data? Что за info? Что такое учетная запись, если не данные учетной записи, и чем они отличаются? Так много вопросов от этих неоднозначных имен.
// Not disticnting structure
getActiveAccount()
getActiveAccounts()
getActivityAccount()
//With distinction
getActiveAccount()
getManyActiveAccounts()
getAccountActivity()
Также следует учитывать функцию автозаполнения во всех редакторах кода. Наличие очень похожих имен может привести к ошибкам самовнушения, неправильному чтению и, как следствие, к вызову некорректных функций.
Используйте имена с возможностью поиска
В то время как мы называем переменные в нашем коде, мы должны убедиться, что они не закодированы, не слишком короткие (1–2 буквы) и не слишком распространены — в зависимости от их значительности и объема.
В следующем примере приложение представляет собой платформу управления курсом для студентов.
//Bad examples
const max=7;
const students=7;
//Clean example
const MAX_CLASSES_PER_STUDENT=7;
Я не думаю, что у вас возникнут проблемы с поиском этой переменной, хотя max и students, скорее всего, будут распространенными словами в этом приложении.
Никаких магических значений
Магические значения — это значения, которые «волшебным образом» появляются в коде и должны передавать какой-то смысл, но никто не может сказать, какой, потому что это просто числа/строки.
Любой ценой избегайте разбрызгивания магических значений по всему коду. Обязательно назначьте переменную этим значениям для ясности и упрощения рефакторинга.
//BAD
if(val === 38) console.log('congratulations!')
//CLEAN
const JACKPOT=38;
if(val === JACKPOT) console.log('congratulations!')
Не разглашайте информацию о реализации
То, как мы реализуем код, время от времени изменяется и полностью трансформируется. Привязка определенного способа реализации является ограничивающим и фактически устаревает со временем.
//BAD
spliceProductFromArr()
let productArr=[];
//CLEAN
removeProductById()
let products=[];
Будьте осторожными с соглашениями об именах
Если мы разрабатываем приложение, которое в основном связано с автомобилями, каково будет наше соглашение об именах? Автомобиль? Транспортное средство? Машина? Что бы это ни было, пожалуйста, считайте car обеспечивающим соответствие.
Избегайте создания ментальных карт
Переменные, которые требуют от читателя (и кодера) запоминания их значения, обычно являются плохими именами переменных. Единственное исключение, которое приходит на ум, i и j, которые являются итераторами в цикле.
Кроме того, имена должны отражать реальность, быть соответствующими и достаточно информативными, чтобы избежать мысленного картирования или запоминания любого рода.
Функции
Должны быть маленькими
Небольшие функции помогут вам правильно рассказать историю. Они добавляют порядок, разделение проблем, надлежащую абстракцию и правильную структуру в ваш код.
Если ваша функция не мала, вероятно, она не соответствует другим важным принципам, которые мы вскоре обнаружим. Маленькие функции, если они названы правильно, делают код понятным, легко поддающимся отладке и простым для понимания.
Вы можете задаться вопросом: «Насколько маленькой?». По словам автора «Чистого кода», дядюшки Боба, функция никогда не должна содержать больше 20 строк кода.
Если это так, по словам Боба, вы, вероятно, можете разделить ее на другую функцию. Что касается точного числа, я уверен, что это всего лишь правило красного флажка, чтобы держать нас в курсе, если мы пишем большие функции.
Первое правило функций — они должны быть небольшими. Второе правило — они должны быть меньше этого размера.
Делают одно действие
Функции должны выполнять одно и только одно действие. Функции должны делать одну и только одну вещь. Иногда очень сложно убедиться, что функция выполняет только одно действие, но важно убедиться, что мы не выполняем несколько действий одновременно. Правило Боба? Если выполняется несколько задач, то у вас, вероятно, есть 2 функции, а не одна.
Функции должны делать одно дело. Они должны делать его хорошо. Они должны делать только это.
Должны быть названы правильно
Все функции выполняют действия. в этом их цель — функционировать. Следовательно, при наименовании функций следует использовать глаголы, указывающие на то, что они делают.
get, set, save, remove, load…вы поняли суть, верно?
Но одним распространенных исключений, которое широко известно, является слово is— проверка на наличие логического условия. По соглашению функция должна возвращать логическое значение, относящееся к условию (никаких сюрпризов с неправильным ожиданием). Таким образом, предполагается, что неверный пароль (password) вернет логическое значение.
Следует свести к минимуму аргументы
В идеале, чем меньше аргументов, тем лучше. Однако, если мы хотим сделать наши функции многоразовыми и независимыми от любой другой бизнес-логики, могут потребоваться некоторые аргументы.
Таким образом, можно использовать до двух аргументов, трех следует избегать, а для добавления большего количества - следует провести тщательное исследование.
Идея, лежащая в основе этого принципа, заключается в том, что функции в любом случае очень маленькие, поэтому, если вам нужны 3 аргумента, которые жизненно важны для бизнес-логики, вероятно, разделение основной функции было выполнено плохо.
Это означает, что логика слишком сильно связана друг с другом, и, возможно, разделение в другом месте кода лучше.
Не создавайте побочных эффектов
Функции не должны иметь никаких побочных эффектов или последствий для других процессов, кроме их основной ответственности. Согласно правилу «Делай одно дело», они не должны делать ничего, кроме назначенных им задач.
Это помогает избежать сюрпризов, легче отлаживать и точно отслеживать, кто и что делает.
Чистый способ написания функций
Допустим, что очень сложно написать функции, которые придерживались бы этих принципов с первой попытки. Скорее всего, мы будем слишком много думать и планировать гораздо больше, чем нужно, чтобы получить этот результат сразу.
Но правда в том, что она не должна быть идеальной с первого раза, когда мы пишем функцию. Как и в письменной форме, кодирование имеет свои уровни, черновики, уточнения (рефакторинг) и развитие, пока не станет четким и блестящим.
Дядя Боб красноречиво говорит об этом: «Написание программного обеспечения похоже на любой другой вид письма. Когда вы пишете статью, вы сначала записываете свои мысли, а затем развиваете их, пока они не станут хорошо читаемыми. Первый черновик может быть неуклюжим и неорганизованным; но вы его продумайте, перестройте и доработайте, пока он не будет читаться так, как вы того хотите».
Классы
Должны быть маленькими
Первое правило классов заключается в том, что они должны быть небольшими. Второе правило — они должны быть меньше меньшего. Звучит знакомо? Классы должны следовать тому же принципу, что и функции — делайте их как можно меньше.
Однако есть одно небольшое различие — классы могут делать больше чем одну вещь. Они, вероятно, будут делать больше чем одно дело большую часть времени. При этом, они должны следовать принципу единой ответственности.
Принцип единой ответственности
У классов должна быть одна ответственность, и не более того. Вот где разделение задач является актуальной проблемой... Как разработчики, мы должны убедиться, что классы несут только одну ответственность — создавать небольшие и чистые классы.
Ответственность — это термин, который в данном контексте означает повод для изменений. Для изменения класса должна быть только одна причина. Если несколько причин могут вызвать изменение класса, это означает, что у класса слишком много обязанностей. Это краткий способ убедиться, что класс несет единственную ответственность.
Согласованность
Насколько хорошо логика внутри класса «склеена» вместе, и имеются ли внутри нее соединительные точки? Насколько хорошо методы реализованы с помощью логики и класса, находятся ли они в нужном месте?
Хороший индикатор, который может помочь ответить на эти вопросы, — это количество переменных экземпляра в классе. В идеале мы предпочли бы уменьшить количество переменных экземпляра и использовать каждую из них в методах класса. Это сплоченность в классе.
Методы сильно связаны с классом, они используют переменные экземпляра, а логика культивируется и используется всеми методами. Это верный показатель того, что все методы и проблемы находятся в нужном месте.
«Стратегия сохранения небольших размеров функций и сокращения списков параметров может иногда приводить к увеличению числа переменных экземпляра, которые используются подмножеством методов. Когда это происходит, это почти всегда означает, что есть хотя бы один другой класс, пытающийся выйти из большего класса. Вам следует попытаться разделить переменные и методы на два или более классов, чтобы новые классы были более связными».
Общие концепции чистого кода
Лучший комментарий — это комментарий, который вы не писали
Комментарии в коде, за исключением технических аспектов, должны быть максимально смягчены. Их сложно поддерживать, они обычно пытаются скрыть недостаточно чистый код.
Если вы чувствуете необходимость написать комментарий, объясняющий, что делает код, это должно вызвать у вас напоминание красного флажка, и вам следует тщательно изучить свой код и убедиться, что он достаточно чист.
Мы не имеем в виду комментарии, которые являются частью документации библиотеки, или технические комментарии по лицензированию.
Комментарии должны быть смягчены. Главный принцип заключается в том, что если вам нужно объяснить, что делает код, он недостаточно ясен. Код должен объяснять сам себя. Это должно быть самоочевидным для проверки другими коллегами.
Понять алгоритм
Понимание алгоритма — важный шаг в написании чистого кода. Помимо очевидных причин, по которым нам необходимо понимать алгоритм, человек может писать чистый код, только если он действительно его понимает.
Если мы потратим время на изучение алгоритма и убедимся, что знаем его тонкости, мы сможем разбить его на более мелкие части. Отсюда путь к написанию чистого кода довольно прост.
Наша способность писать чистый код проистекает из нашего понимания алгоритма и выбора точных позиций для его деконструкции на более мелкие части.
Остерегайтесь уровней абстракции
Уровень абстракции — важная концепция, которую следует учитывать при планировании и написании кода. Когда мы пишем функцию, мы рассматриваем ее место в общей схеме решения.
Это высокоуровневая функция, которая тесно связана с алгоритмом? Или это функция, которая отвечает за служебную цель (например, парсинг командной строки, проверку условий, выполнение определенных вычислений)?
Принцип уровня абстракции — это еще один способ убедиться, что мы сохраняем все наши функции и классы на месте и что наши функции выполняют только одно действие. Если функция смешивает уровни абстракции, она определенно выполняет более чем одну задачу.
Мышление в терминах абстракции — это инструмент, который помогает нам лучше организовать структуру функций и спланировать сборку всего пазла.
Чтобы получить практический опыт, мы всегда будем начинать с функций более высокого уровня, которые находятся на самом высоком уровне абстракции. Каждая функция должна опускаться только на один уровень ниже, пока она не вызовет другую функцию.
Как мы уже говорили, подробные детали алгоритма и конкретной реализации будут раскрыты ниже в исходном коде. Наводящий вопрос: что меня волнует в этой логике? Конечно, мы заботимся о каждой строчке. Но какова основная часть алгоритма, в которой заключена основная логика? Это высший уровень абстракции.
Гармоническое сочетание всей логики. Проверка границ, обработка ошибок, парсинг, вычисления — все это на другом уровне абстракции. Они должны быть извлечены в разные функции, каждая из которых имеет свой уровень абстракции.
Рассмотрим следующий код:
//Mongoose call to create a product
async function createProduct(productToSave) {
try {
const product = new Product(productToSave)
return await product.save()
} catch (e) {
_handleError('failed to create product in db', e)
}
}
Вы заметили _handleError функцию? Помимо точного указания, в чем была ошибка, функция createProduct не знает, как обрабатывается ошибка. Она просто передает ошибку и позволяет работать другой функции на более низком уровне абстракции.
Почему? Потому что это не ее ответственность. Функция получает товар и возвращает сохраненный товар из БД. Ошибки? Отлично. Это не ее ответственность. Он активирует _handleError функцию, чтобы делать именно то, что значится в ее заголовке: обрабатывать ошибки.
function _handleError(msg, e = 'initiated')
{ logger.error(msg, e)
throw Error(msg, e) }
Имеет ли указанная выше функция какое-либо представление о том, что регистратор делает с ошибкой? Нисколько.
Как мы, вероятно, поняли, это не его задача — регистрировать ошибку. Его задача — обработать ошибку — вызвать соответствующие функции на следующем более низком уровне абстракции и передать информацию.
Заключение: будьте последовательны
В заключение, я считаю, что это — хороший рецепт для чистого кода и для того, чтобы стать хорошим программистом. У каждого свой стиль, уникальная точка зрения и разный подход к решению проблем. Следовательно, соблюдение всех принципов, упомянутых в этой статье, является ключом к возможности писать поддерживаемый, многоразовый, понятный и чистый код.
Удачи!