30 понятых идей после прочтения «Clean Code».

Уже много лет назад вышла классическая книга «Чистый код» от дяди Боба. Я прочитал ее первый раз лет 15 назад. И недавно решил перечитать. Поскольку я люблю записывать всё, что читаю, я подумал, что это была бы отличная идея подытожить сделать краткое резюме после прочтения Clean Code.

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

Чистый подход к программированию

Вероятно, следующим человеком, который прочтет код, который вы написали, будете вы сами

Основная задача профессии разработчик — написание кода, однако немало времени тратиться на его чтение. Фактически, большую часть времени разработчики уделяют чтению кода, а не его фактическому написанию.

Сколько раз вы перечитывали код, который только что написали? Очевидно, что соотношение склоняется в пользу чтения, а не фактической разработки.

после прочтения Clean Code

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

Поскольку вы, вероятно, будете следующим человеком, который прочтет код, который вы написали, убедитесь, что вы сможете понять его даже через два месяца. Я уверен, что почти каждый программист (по крайней мере, я) писал код в 3 часа ночи, чтобы проснуться на следующий день и часами пытаться разобраться, что происходит.

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

Код обладает большим количеством параметров, чем просто «работает» или «не работает»

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

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

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

Существует больше параметров и руководящих принципов, определяющих, что является хорошим кодом.

Лучше быть понятным, чем умным

Как программные инженеры, мы занимаемся решением проблем, творчеством и интенсивным мышлением. Многократно, сталкиваясь с сложными проблемами, наш код может стать «умным» — то есть, выполнять очень умные действия, которые понимает только тот, кто написал этот код.

после прочтения Clean Code - овца

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

Ваши коллеги будут иметь трудности с рефакторингом кода, а также это замедлит проект, и вы будете прикованы к этому куску кода навсегда, пока не умрёте или не уйдёте из компании.

Мы должны убедиться, что после того, как мы решим проблему, решение будет казаться ясным и простым для того, кто читает этот кусок кода.

Предвидение и доверие играют ключевую роль

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

Я надеюсь, что сейчас ясно, что читателем будет также ваше будущее «Я». Если вы не доверяете своему собственному коду, тогда вам удачи.

Так как мы можем построить доверие и уверенность у читателя? Позволив им правильно предвидеть, что произойдет. Когда читатель понимает код, знает, что код должен делать, и правильно предвидит, что произойдет дальше, между сторонами строится доверие.

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

Чистый код не появляется сразу и сам по себе

Теперь, когда мы разъяснили, почему чистый код является ключевым для нашего успеха как разработчиков, перед нами встает роковой вопрос:

Как писать чистый код?

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

Вечные принципы

Разделяйте ответственности

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

Однако принцип прост: Какая логика реализована мной сейчас? Какая основная ответственность здесь? Есть ли другие ответственности, которые реализованы? Если есть, разделите их.

Код похож на статью

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

Код должен следовать тому же принципу.

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

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

Правило бойскаутов

Принцип бойскаутов при поездках на экскурсии был «оставить место чище, чем вы его нашли».

Оказывается, дядя Боб в своей книге думает примерно так же о коде — всегда оставлять код лучше, чем вы его нашли. Как уже обсуждалось ранее, чистый код — это процесс. Он предполагает непрерывное совершенствование и развитие, как вами, так и другими в организации.

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

Чистый, Переиспользуемый и Поддерживаемый код

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

Чистый код — это код с хорошей структурой, понятный, хорошо названный и не содержащий сюрпризов.

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

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

Никогда не дублируйте

Дублирование фактически означает, что код не является чистым. Следуйте принципу DRY — Don’t Repeat Yourself (не повторяйся). Дублирование требует функции, класса, компонента — что угодно! просто не дублируйте!

Когда вы обнаружите, что делаете даже самую маленькую последовательность ctrl+c и ctrl+v, интуитивно задайте себе вопрос, есть ли лучшая альтернатива. Большинство времени ответ будет безусловным «ДА».

Избегайте логической зависимости

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

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

Именование

Выбирайте описательные и неоднозначные имена

Имена должны описывать, что делается или содержится. Частью ясности является то, что имя не допускает других интерпретаций и направляет читателя только к одному возможному заключению.

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

# плохо
days = 10  # осталось дней 

# хорошо 
daysSinceCreation = 10
daysSinceUpdate = 5
ageInDays = 3500
elapsedDays = 28

Делайте различия в зависимости от контекста

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

Однако они также не должны иметь слишком похожие имена.

# не используют контекст
getActiveAccount()
getActiveAccountInfo()
getActiveAccountData()

В приведенном выше примере не ясно, какие различия между Account, AccountInfo и AccountData. Они говорят практически о том же самом. Что такое данные (data)? Что такое информация (info)? В чем отличие аккаунта (account) и данных аккаунта (account data)? Столько вопросов из-за этих двусмысленных имен.

# не учитывается структура
getActiveAccount()
getActiveAccounts()
getActivityAccount()

# учитывается структура
getActiveAccount()
getManyActiveAccounts()
getAccountActivity()

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

Используйте имена, которые можно найти

При выборе имен переменных в нашем коде нужно убедиться, что они не зарезервированы, не слишком короткие (1-2 буквы) и не слишком общие — выбирайте вариант в зависимости от их значения и области применения.

В приведенном ниже примере приложение представляет собой платформу управления курсами для студентов.

# плохие названия
max = 7
students = 7

# хорошие примеры
MAX_CLASSES_PER_STUDENT = 7

Я не думаю, что у вас возникнут проблемы с нахождением этой переменной, в то время как max и students очень вероятно являются общими словами в этом приложении.

Избегайте магических значений

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

Убедитесь, что для ясности и легкости рефакторинга назначены переменные для этих значений.

# Плохо
if val == 38:
    logger.info('congratulations!')

# Хорошо
JACKPOT = 38
if val == JACKPOT:
    logger.info('congratulations!')

Не раскрывайте реализацию

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

# плохо 
spliceProductFromArr()
productArr = [] 

# хорошо 
removeProductById()
products = []

Аккуратно используйте Правило именования

Если мы разрабатываем приложение, которое связано главным образом с автомобилями, какое будет наше соглашение об именовании? Car? Vehicle? Automobile? Каким бы оно ни было, пожалуйста, используйте единое обозначение, например, car, и придерживайтесь его.

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

Избегайте странных названий

Переменные, для которых читателю (и кодеру) требуется запоминать их значение, как правило, имеют плохие имена переменных. Единственное исключение, которое приходит на ум, это i и j, которые являются итераторами в цикле.

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

Функции

Функции должны быть маленькими

Маленькие функции помогают правильно рассказать историю. Они способствуют порядку, разделению ответственности, правильной абстракции и правильной структуре вашего кода.

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

Вы можете спросить, насколько маленькими? Ну, согласно автору Clean Code, дяде Бобу, функция не должна быть больше 20 строк кода.

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

«Первое правило функций — это то, что они должны быть маленькими. Второе правило — они должны быть меньше, чем это.»

Функции должны делать только одну вещь

Функции должны делать только одну вещь. Иногда очень сложно убедиться, что функция делает только одну вещь, но это важный принцип, чтобы не делать несколько вещей одновременно. Правило Боба? Если функция делает более одной вещи, то, вероятно, у вас есть 2 функции, а не одна.

«Функции должны делать одну вещь. Они должны делать это хорошо. Они должны делать это только.»

Должны быть названы правильно

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

get, set, save, remove, load… вы понимаете суть, верно?

Однако есть одно распространенное исключение, которое широко известно, — это слово is, которое используется для проверки логического условия.

Соглашение состоит в том, что функция должна возвращать булевое значение относительно условия (нет сюрпризов с неправильными ожиданиями). Таким образом, ожидается, что функция isValidPassword(password) вернет логическое значение.

Следует минимизировать количество аргументов

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

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

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

Не создавайте побочных эффектов

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

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

Чистый способ написания функций

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

Но правда в том, что функция не обязана быть совершенной сразу после того, как мы её написали. Как и в написании текстов, кодирование имеет свои уровни, черновики, усовершенствования (рефакторинг) и разработку до тех пор, пока оно не станет точным и блестящим. Дядя Боб выразил это красиво:

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

Классы

Должны быть маленькими

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

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

Принцип единственной ответственности

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

Ответственность — это термин, который в данном контексте означает причину для изменения. Должна быть только одна причина для изменения класса. Если несколько причин может привести к изменению класса, это означает, что класс имеет слишком много ответственности. Это краткий способ убедиться, что у класса есть только одна ответственность.

Связность (Cohesion)

Насколько хорошо логика внутри класса «склеена» вместе и имеет соединительные точки внутри него? Насколько методы реализованы с логикой и классом, и находятся в правильном месте?

Хороший показатель, который может помочь ответить на эти вопросы — это количество переменных экземпляра в классе. В идеале, мы предпочли бы уменьшить количество переменных экземпляра и использовать каждую из них в методах класса. Это и есть связность в классе.

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

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

Общие концепции для чистого кода

Лучший комментарий — это комментарий, который вы не написали

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

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

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

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

Понимание алгоритма

Понимание алгоритма — это ключевой шаг при написании чистого кода. Помимо очевидных причин, почему нам нужно понимать алгоритм, чистый код можно написать только в том случае, если мы его действительно понимаем.

Если мы вкладываем время в изучение алгоритма и убеждаемся, что мы знаем его тонкости, мы сможем разложить его на более мелкие части. Отсюда путь к написанию чистого кода становится достаточно прямолинейным.

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

Остерегайтесь уровней абстракции

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

Это функция высокого уровня, которая тесно связана с алгоритмом? Или это функция, которая отвечает за вспомогательную цель (например, разбор командной строки, проверка условий, выполнение определенных вычислений)?

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

Мыслим в терминах абстракции — это инструмент, который помогает нам лучше организовать структуру функций и спланировать, как собирается весь пазл.

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

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

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

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

Рассмотрим следующий код:

async def createProduct(productToSave):
  try:
    product = Product(productToSave)
    return await product.save()
  except Exception as e:
    raise _handleError('failed to create product in db', e)

Заметили функцию _handleError? Помимо указания точной причины ошибки, функция createProduct не знает, как обрабатывается ошибка. Она просто передает ошибку и позволяет другой функции на более низком уровне абстракции обработать ее.

Почему так происходит? Потому что это не ее ответственность. Функция получает продукт и возвращает сохраненный продукт из базы данных. Ошибки? Отлично. Это не ее ответственность. Она активирует функцию _handleError, чтобы сделать ровно то, что подразумевается ее названием: обрабатывать ошибки.

def _handleError(msg, e = 'initiated'):
    logger.error(msg, e)
    raise Error(msg, e)

Обладает ли функция выше какой-либо представлением о том, что делает регистратор с ошибкой? Совершенно нет.

Как мы, вероятно, поняли, ее задача не записывать ошибку в журнал. Ее задача — обработать ошибку, вызвать соответствующие функции на следующем более низком уровне абстракции. И передать информацию дальше.

Заключительное слово после прочтения Clean Code: Будьте последовательны

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

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

Удачи!


Опубликовано

в

от

Метки:

Комментарии

Добавить комментарий