расширяемый код что значит
Принципы написания кода
Стандарты кодирования
Типичные проблемы многих таких стайл-гайдов:
1. Плохо обоснована их целесообразность
2. Они декларируют конкретные правила, вместо принципов.
3. Эти правила плохо обоснованы и нередко построены на противоречащих принципах.
В упомянутой статье всё обоснование необходимости стандартизации заключается в:
Хорошее руководство по оформлению кода позволит добиться следующего:
1. Установление стандарта качества кода для всех исходников;
2. Обеспечение согласованности между исходниками;
3. Следование стандартам всеми разработчиками;
4. Увеличение продуктивности.
1. [тут располагается картинка про ещё один стандарт] «Стандарт нужен для того, чтобы был стандарт» — не обосновывает его наличие.
2. В любом более-менее крупном проекте всегда будет куча кода, не соответствующая текущим веяниям моды оформления: изменения стайл-гайда со временем, легаси код, код сторонних библиотек, автогенерированный код. Это неизбежно и не так уж и плохо, как на первый взгляд может показаться.
3. То же что и первый пункт.
4. Уже теплее, но опять же, не обосновывается почему продуктивность от этого должна вырасти, и главное — на сколько.
По своему опыту могу сказать, что самое худшее решение — привыкнуть писать и читать код в каком-то одном стиле, от чего любой код написанный «не по правилам» будет вызывать раздражение, тревогу, гнев и желание навязывать окружающим свои привычки. Куда полезнее научиться воспринимать исходники, игнорируя оформление. И для этого наличие разнооформленного кода даже полезно. Я не говорю, что надо расслабиться и писать всё в одну строку, но, чтобы так не делать, есть куда более практичные причины, чем «стандарт нужен, чтобы всё было стандартно».
Как написать грамотный стандарт:
1. Определился с целями
2. Сформулировать принципы и провалидировать их на соответствие целям
3. Сформулировать минимум правил, для реализации этих принципов
Итак, попробуем
Цель: снизить стоимость поддержки путём наложения на себя и команду ограничений.
В чём заключается поддержка:
1. написание нового кода
2. изменение существующего, в том числе и автоматическая
3. поиск нужного участка кода
4. анализ логики работы кода
5. поиск источника неверного поведения
6. сравнение разных версий одного кода
7. перенос кода между ветками
Какие принципы помогут достичь поставленной цели:
1. Строки файла должны быть максимально независимы.
Причина проста: если при изменении одной строки требуется изменение других, то это повышает риск конфликтов при слиянии веток. Каждый конфликт — это дополнительное иногда значительное время на его разрешение.
Несмотря на то, что эта простая идея проскакивает в одном из правил упомянутой в начале статьи, в другом же правиле мы видим явно противоречащую рекомендацию:
Вертикальное выравнивание — это может быть и красиво, но совершенно не практично по следующим причинам:
1. Добавление строки с более длинным именем (например, icon—person-premium) приведёт к изменению всех строк в группе.
2. Автоматическое переименовывание в большинстве случаев собьёт выравнивание (например, при изменении icon—person на icon—user в большинстве инструметов).
3. Иногда пробелы становятся неоправданно длинными, от чего воспринимать код становится сложнее.
Также тут можно заметить лишний семиколон (semicolon, точку с запятой). Единственная причина его появления в этом месте — слепое следование правилам стайл-гайда, не понимая его принципов.
В многострочных правилах, это действительно необходимо, чтобы добавляемые в конец строки не приводили к изменению уже существующих. Например:
Если вы пишете на javascript и можете позволить себе отказаться от ie8, то можете использовать хвостовую пунктуацию и в литералах:
Другой аспект этого принципа заключается в том, чтобы располагать на отдельных строках те сущности, которые меняются как правило независимо. Именно поэтому отдельные css-свойства не стоит располагать в одну строку. Более того, не стоит увлекаться и комплексными свойствами.
Ещё один яркий пример нарушения этого принципа — цепочки вызовов методов:
Тут мы постарались разместить каждое звено на отдельной строке, что позволяет добавлять/удалять/изменять звенья не трогая соседние строки, но между ними всё равно остаётся сильная связь из-за которой мы не можем, например, написать так:
Чтобы добавить такую логику придётся разбить цепочку на две и исправить их начала:
А вот при такой записи мы имеем полную свободу действий:
2. Не сваливать все яйца (код) в одну корзину (файл/директорию).
Если вам кажется, что в коде не хватает так называемых «секций», то скорее всего вы подобрались к верхнему порогу восприятия, когда, уже сложно находить нужные его участки. В этом случае естественным желанием является создание оглавления. Но оглавление в виде комментариев в начале файла не сравнится с оглавлением в виде списка файлов в директории. Располагая код в иерархии файловой системы вы довольно неплохо можете упорядочивать всё прибывающее число сущностей. Примерно то же самое вы скорее всего делаете и в рантайме, располагая код в иерархии неймспейсов, так что нет никакой причины иметь для одной сущности разные пространства имён: в рантайме и в файловой системе. Проще говоря, имена и иерархия директорий должна соответствовать именам и иерархии пространств имён.
Часто можно встретить размещение файлов в нескольких больших корзинах: «все картинки», «все скрипты», «все стили». И по мере роста проекта, в каждой из них появляется иерархия, частично одинаковая, но и с неизбежными отличиями. Задумайтесь: а так ли важен тип файла? Пространства имён куда важнее. Так зачем нужны эти типизированные корзины? Не лучше ли все файлы одного модуля хранить рядом, в одной директории, какими бы ни были их типы? Тем более, что типы могут меняться. Сравните:
Во втором случае, разрабатывая очередную формочку, вам не придётся метаться между директориями, раскладывая файлы в разные места. В случае удаления компоненты, вы не забудете удалить и картинки. Да и переносить компоненты между проектами становится гораздо проще.
3. Язык программирования — принципиально не естественный язык.
В отличие от письменной речи, которая читается строго последовательно, программный код на современных языках программирования представляет из себя двухмерную структуру. В них зачастую нет, например, необходимости ставить точки (семиколоны) в конце предложений:
JS частично понимает двухмерность кода, поэтому в нём семиколоны в конце строк являются тавтологиями:
А вот CSS не понимает, поэтому в нём, без них не обойтись:
Для улучшения восприятия токенов языка, пробелы могут быть расставлены совсем не по правилам письменной речи:
Для более удобной работы с автодополнением, слова могут изменять свой порядок, выстраиваясь от более важных и конкретных к менее важным и общим:
А правило именования коллекций с постфиксом «s» (что в большинстве случаев даёт множественную форму слова) в целях единообразия даёт безграмотные с точки зрения английского языка слова:
Но это меньшее зло по сравнению с требованием от каждого программиста хорошего знания всех английских словоформ.
5. Полные и одинаковые имена одной сущности в разных местах
Поиск по имени — довольно частая операция при работе с незнакомым кодом, поэтому важно писать код так, чтобы по имени можно было легко найти место, где оно определяется. Например, вы открыли страничку и обнаружили там класс «b-user__compact». Вам нужно узнать как он там появился. Поиск по строке «b-user__compact» ничего не выдаёт, потому, что имя этого класса нигде целиком не встречается — оно склеивается из кусочков. А всё потому, что кто-то решил уменьшить копипасту ценой усложения дебага:
Не делайте так. Если склеиваете имя из кусочков, то убедитесь, что эти кусочки содержат полный неймспейс того модуля, где он вводится в употребление:
По полученному классу «my-user__my-block-compact» сразу видно, что он склеен из двух кусков: один определён в модуле «my/block», а другой в «my/user» и оба легко находятся по соответствующим подстрокам. Аналогичная логика возможна и при использовании css-препроцессоров, где мы встречаемся с теми же проблемами:
Если же не используете css-препроцессоры, то тем более:
Чистый код: причины и следствия
Автор: Виктор Свирский, Senior Python Developer / Team Lead, DataArt
Сколько программистов, столько и определений, что такое чистый код. Часто, проводя собеседование, я слышу, что хороший код — это такой, который легко читается. Согласен, но как подсказывает мой личный опыт, это только вершина айсберга.
Первый звоночек, который нам сообщает, что код перестает быть чистым — это рост времени разработки новой функциональности и увеличение регрессионного скоупа при малейшем изменении в системе. Это следствие того, что технический долг накапливается, компоненты в системе очень тесно связаны, автотесты отсутствуют. Причины этого могут быть:
Что такое чистый код?
Получается, чтобы сказать, что код чистый и система спроектирована грамотно, легкого чтения кода недостаточно. Он должен обладать и другими качествами:
Стоит ли писать чистый код?
Однозначно стоит! Но не всегда и не везде стоит уделять чистоте слишком много внимания.
Не стоит забывать о целесообразности и сроке жизни вашего кода. Например, если перед вами стоит задача разработки концепции — PoC (Proof of concept), и вы доказываете, что выбранный стек технологий выполняет поставленную задачу, ваш код станет неактуален уже через неделю или две. Не стоит тратить силы на совершенствование этого функционала.
Бытует мнение, что не нужно следить за качеством кода или части системы, которые в скором времени будут заменены. И это неверно по нескольким причинам. Высокое качество исполнения сделает переход или интеграцию с новыми частями более простыми, бесшовными и быстрыми. Оно наверняка упростит жизнь в тех случаях, когда несколько версий кода придется поддерживать одновременно. Количество регрессионных ошибок с чистым кодом будет в разы меньше. Также не стоит забывать, что нет ничего более постоянного, чем временное. Возможно, задачи по улучшению этой части кода еще несколько месяцев будут лежать в бэклоге.
Что поможет улучшить ваш код?
Большинство программистов мечтают писать код быстро и максимально красиво, причем так, чтобы все идеально работало с первого раза. Но далеко не каждому удается сделать код не просто работающим, но и понятным. Как же добиться успеха в написании чистого кода? Есть два пути — самоорганизация и командная работа.
Самоорганизация
Рассмотрим несколько возможных способов улучшить индивидуальное качество кода. Эти рекомендации подойдут разработчику любого уровня.
Не спешите решать задачи в лоб. Задавайте вопросы старшим разработчикам и самому себе. Всегда важно понимать причинно-следственную связь тех или иных решений. Хорошо понимая проблему, вы сможете эффективно ее решить.
Любой опыт лучше, чем его отсутствие.
Командная работа
Большинство задач решается в команде. Очень важно разделять ответственность за качество между ее участниками. Чем больше команда, тем сложнее поддерживать продукт в хорошем состоянии. Рассмотрим несколько подходов удержания кода в вышеуказанных условиях.
Во время проверки кода необходимо учитывать несколько вещей:
Суть непрерывной интеграции в том, что она позволяет быстро получить множество отзывов о текущем состоянии кода.
Непрерывная интеграция работает, когда вы следуете двум простым правилам:
Важно иметь список соглашений о кодировании. Но прежде чем вы начнете составлять список, все в команде должны понимать значимость этого соглашения. Не рассчитывайте, что такое соглашение будет принято с первого раза, вас ожидает множество дискуссий.
Составьте список соглашений о кодировании, в которых вы обозначаете то, как переменные должны объявляться, соглашения об именах и т. д. Количество правил, которые вы можете добавить в этот список, не ограничено и может варьироваться. Просто делайте то, что работает для вас и вашей команды. Не стесняйтесь добавлять новые правила в список соглашений, если команде это подходит. Это же касается и удаления соглашений из списка.
После того, как вы получили свой список соглашений о кодировании, крайне важно придерживаться их. Наиболее предпочтительный способ — проверить соглашения о кодировании с помощью статических анализаторов и непрерывной интеграции, поскольку он не требует каких-либо ручных действий.
Чем меньше ошибок в коде, тем выше его качество. Тщательное тестирование отфильтровывает критические ошибки и гарантирует, что код работает так, как задумано.
Наличие четкой стратегии тестирования важно, когда дело доходит до улучшения качества кода. Как минимум, ваш код должен быть модульным. Еще лучше, если вы хотите использовать и другие способы, например интеграционное или регрессионное тестирование.
Наличие ошибок в вашем коде, вероятно, неизбежно. Поэтому анализ и способ обработки этих ошибок очень важны. Если вы хотите улучшить свои навыки, важно учиться на собственных ошибках.
Когда возникает ошибка, проанализируйте ее с помощью нескольких вопросов:
Есть несколько метрик, которые вы можете использовать для количественной оценки качества вашего кода. С такой задачей легко справляется SonarQube. Он с легкостью поможет вам собрать все необходимо важные метрики:
Используется при тестировании программного обеспечения. Она показывает процент исходного кода программы, который был выполнен в процессе тестирования. Задайте планку, ниже которой процентное соотношение ваших тестов не опускается.
Ошибки в коде чем-то сродни углеродному следу. Избежать совсем невозможно, а лишний выхлоп сам по себе не убьет ни человечества, ни окружающей его природы. Тем не менее, снизить негативный эффект от своего пребывания на планете сегодня кажется естественной потребностью. Примерно так же и написание чистого кода оказывается ответственностью каждого разработчика. Независимо от того, какой именно путь вы выберете, необходимо стремиться писать работающий и понятный код.
Хорошо, если удастся не превращать чистоту в фетиш, учитывая срок жизни нашего кода и оценивая целесообразность дальнейших улучшений. Главное помнить о людях: пользователях, которых может подвести внезапный отказ даже небольшой части разработанной нами системы, и инженерах, которым предстоит эту систему поддерживать.
Рефакторинг практического кода, часть 3 — расширяемость
Во второй части этой серии я поделился вопросами, касающимися рефакторинга кода для удобства чтения. В этой части мы проведем некоторое время с другим аспектом, расширяемостью. Мы воспользуемся тем же практическим подходом к вопросу / обсуждению, что и в предыдущей части, чтобы вы смогли как можно быстрее войти в новый мир рефакторинга.
Расширяемый код — это фрагмент кода, который следует многократно используемым, логическим, хорошо известным шаблонам, будь то стандартные шаблоны проектирования или нормальный логический поток. Модульный код имеет тенденцию быть сильно расширяемым, а монолитный код имеет тенденцию быть нерасширяемым, но монолитный код может быть более эффективным, поэтому для решения этой проблемы некоторые практики позволяют разрабатывать модульным способом и развертывать монолитным способом, чтобы мы могли получить лучшее обоих миров.
Основными аспектами, которые мы должны обсудить в отношении расширяемого кода, являются: логическая расширяемость (нормальный логический поток и шаблоны проектирования), модульное проектирование, а также разделение и инкапсуляция.
Логическая расширяемость
1. Следуют ли большинство блоков кода нормальному логическому потоку?
Когда вы имеете дело с небольшими логическими проблемами, убедитесь, что вы используете правильные конструкции (если, for, foreach, while и т. Д.). Под «правильными конструкциями» я подразумеваю, что вы должны использовать для работы наиболее логичную и общепринятую языковую функцию. Например, цикл по простым массивам должен выполняться с помощью foreach; это обычный нормальный поток, тогда как использование цикла for для такой простой итерации не является нормальным потоком в языке, подобном PHP. Использование while еще более чуждо для такой простой задачи. У вас могут быть свои причины, и в этом случае отзовитесь от предыдущей части о документировании любой таможенной практики, и вы в порядке.
2. Соответствуют ли сложные решения проблем стандартным шаблонам проектирования?
Когда я впервые начал работать с PHP, я не очень хорошо знал шаблоны проектирования. Теперь я считаю, что использование шаблонов проектирования является обязательным условием для крупномасштабных проектов, потому что они обычно понимаются и учитывают будущее развитие.
Распространенная сложная проблема, для решения которой вы должны использовать четко определенный стандартный шаблон, — это создание различных экземпляров некоторого класса. Но почему и когда использовать шаблон фабричного дизайна? Это, вероятно, спорно, но общее руководство, если у вас есть различные реализации одного и того же интерфейса и вы требуете реализации, которые будут созданы динамически объекты, то картина может быть подходящим. Другой случай может быть при генерации ряда динамических объектов одного и того же класса, но когда это число известно только во время выполнения. Например, современное веб-приложение с интенсивным графическим интерфейсом может потребовать динамического создания строк входных данных для записей базы данных. Примеры бесконечны, когда шаблоны проектирования могут быть полезны.
Модульная конструкция
1. Следуют ли структуры кода модульному дизайну?
Модульная конструкция означает, что вы делите свое приложение на модули. Большое приложение, состоящее из небольших приложений, легче разрабатывать, а также расширять и поддерживать. Каждый модуль должен собрать кучу связанных функций и функциональных возможностей и объединить их в один объект.
Основные функциональные возможности и точки входа в приложение также могут рассматриваться как модули. Затем вы можете добавить функциональность в будущем, добавив новые модули. Некоторые люди называют модули, используемые таким образом, плагинами. Какой бы дизайн и структуру вы ни выбрали для своего приложения, вам необходимо убедиться, как модули / плагины загружаются и выгружаются, какова их базовая структура и т. Д., И учитывать это, прежде чем разрабатывать основной модуль.
Всякий раз, когда вы видите группы кода в каком-то модуле, действующем как единая его суб-сущность и потребляемом этим верхним модулем с минимальными параметрами, почему бы не разделить его на новый модуль? Обычно, когда у меня есть дочерняя сущность, которая разделяет несколько классов, выполняющих некоторые побочные задачи, я без колебаний перемещаю ее в новый модуль.
Служебные модули — это аккуратное решение для потерянного кода в хорошо разработанном модульном приложении. Всякий раз, когда у меня есть какой-то осиротевший код, я перемещаю его в служебный модуль, который обрабатывает фрагменты кода и мини-задачи. Модуль состоит в основном из осиротевших функций и небольших классов. Когда эти задачи достаточно велики, я начинаю перемещать их в отдельный модуль в непрерывном процессе рефакторинга.
2. Является ли зависимость модулей минимальной?
Модули должны быть максимально автономными. Зависимости программных модулей являются естественными и хорошими, например, модуль «Инвентаризация» зависит от модуля «Бухгалтерский учет» для получения однородной системы электронной коммерции, но многие жесткие зависимости являются плохими. Они значительно затрудняют отладку и развертывание.
Чтобы обеспечить меньше межмодульных зависимостей, вы должны время от времени перебирать свою кодовую базу, чтобы увидеть, существуют ли какие-либо жесткие зависимости между модулями. Очистите их, если можете, а если нет, то вы должны объединить оба модуля в один модуль с более общим именем. Например, в приложении электронной коммерции у вас могут быть модуль «Предметы» и модуль «Инвентаризация», а классы из инвентаря интенсивно используют классы из предметов и наоборот. Я бы слил оба и переименовал модуль «Инвентарь», в котором есть подмодуль для работы с предметами.
Разделение и инкапсуляция
1. Достаточно ли отделены функции, методы и классы?
Добавление функции подкачки для отображения результатов, поступающих из базы данных, является очень распространенной задачей. На самом деле, в начале своей карьеры PHP, я писал код для разбивки результатов; код вначале был процедурным и представлял собой очень специфические функции для работы с базой данных и результатами. Затем я решил отделить алгоритм разбиения на страницы от каждого из компонентов, где я его использовал, с вариацией шаблона стратегии. Всякий раз, когда вы обнаруживаете свою самодублирующуюся логику или код, вам, вероятно, необходимо выполнить какое-то собственное разделение, чтобы повысить возможности повторного использования и расширяемости кода.
2. Достаточно ли отделены модули и компоненты?
Сохраняя зависимости до минимума, вы отделяете правильный путь. Там нет 100% разделения между любыми двумя связанными вещами; связь естественна, поэтому вы всегда должны отделять ее, но не намного, чтобы не усложнить выполнение кода. В качестве руководства отсоединяйте до тех пор, пока ваши модули и компоненты вашей кодовой базы не смогут общаться друг с другом, не дублируя много общего.
Имейте в виду, что снижение зависимостей прямо пропорционально удобству использования и расширяемости всякий раз, когда сложность не увеличивается. Когда сложность начинает увеличиваться, отношения начинают быть прямо пропорциональными.
Резюме
В этой части мы обсудили рефакторинг вашего кода для расширения, сосредоточив обсуждение на трех основных аспектах: логическая расширяемость, модульное проектирование, а также разделение и инкапсуляция. Надеюсь, теперь вы начинаете лучше понимать, как разрабатывать и поддерживать лучшие приложения. В заключительной части мы обсудим, как провести рефакторинг для повышения эффективности без ущерба для читаемости и расширяемости.
Главные характеристики качественного кода
Как часто вы поражаетесь, читая чужой код, и думаете «господи, ну и каша. ». Скорее всего, достаточно часто. И можете ли вы быть уверенным, что никто не думал также когда читал ваш код? Другими словами, насколько вы уверены в чистоте своего кода? Можно быть уверенным только если полностью понимаешь, что значит чистый код.
Сложно дать точное определение чистому коду, и, скорее всего, сколько программистов — столько определений. Однако, некоторые принципы достаточно универсальны. Я собрал девять самых релевантных и описал ниже.
1.Плохой код делает слишком много, чистый код сфокусирован
Каждый класс, метод и любая другая сущность должна оставаться неискаженной. Она должна следовать принципу единственной обязанности. Вкратце, можно сказать так: если подумать о причинах изменения класса, то нельзя придумать больше одной хорошей причины.
Но я бы не ограничивал определение классами. В свой последней статье Ральф Вестфал (Ralf Westphal) представил более широкое определение принципа единственной обязанности:
Функциональная единица на определенном уровне абстракции должна отвечать за один аспект требований системы. Аспект требований это признак или свойство требования, которое может изменяться независимо от других аспектов.
Если хотите узнать больше, то советую прочитать его статью.
2. Язык, на котором вы написали код, должен выглядеть как будто его создали для решения этой проблемы.
Не язык делает программу простой, а программист, который делает так, что язык выглядит просто.
(цитата Роберта C. Мартина)
Это означает, что не нужно использовать хаки, из-за которых код и язык обычно выглядят неуклюже. Если вы считаете, что что-то можно сделать только хаком или заплаткой, то обычно это значит, что вы недостаточно времени провели в попытках найти хорошее, чистое решение.
3. Не нужно избыточности
Код должен подчиняться правилу DRY (Don’t repeat yourself — не повторяйся). Если это так, то модификация любого элемента системы не требует изменения других, логически не связанных элементов.
4. Читать ваш код должно быть приятно
Когда читаешь ваш код, должно быть ощущение, что читаешь «Гарри Поттера» (да, знаю, я немного переборщил). Должно быть ощущение, что его написали, чтобы любой разработчик мог с легкостью прочитать, не проводя часы в попытках разобраться.
Для этого нужно стараться подчиняться принципам KISS (Keep It Simple, Stupid!) и YAGNI (You Ain’t Gonna Need It — Вам это не понадобится). Принцип KISS гласит, что большинство систем работают лучше всего если сохранять их простоту, а не развивать сложность.
То есть, простота должна быть целью в дизайне, и нужно избегать ненужных усложнений. YAGNI это практика, мотивирующая фокусироваться на простейших вещах, которые позволяют вашему софту работать.
5. Другой разработчик может легко расширить ваш код
Вы не пишите код для себя, или еще хуже — для компилятора. Вы пишите код для других разработчиков. Не будьте эгоистом — подумайте о людях. Не пытайте других разработчиков, выдавая плохо поддерживаемый и плохо расширяемый код. К тому же, через несколько месяцев этим другим разработчиком можете оказаться вы сами.
6. Нужно минимизировать зависимости
7. Меньше — лучше
Код должен быть минимальным. Классы и модули должны быть короткими, в идеале — всего несколько строк кода. Код должен быть хорошо разделен (в том числе внутри класса). Чем лучше вы делите код, тем легче его читать. Этот принцип хорошо влияет на пункт 4 — другим программистам будет проще понять его.
8. Необходимы юнит- и приемочные тесты
Как можно узнать, удовлетворяет ли наш код требованиям, если не писать тесты? Как можно поддерживать и расширять его, не боясь, что все сломается? Код без тестов — просто не чист. Если хотите узнать больше о принципах юнит-тестирования, то советую прочитать очень хорошую статью Three Pillars of Unit Tests, написанную одним из моих коллег.
9. Код должен быть выразительным
Выразительность кода означает, что в нем используются имена со смыслом. Эти имена должны выражать намерение. Они не должны запутывать. Они должны быть различимыми. Выразительность документирует код и делает отдельную документацию менее необходимой. Если хотите узнать больше о теме самодокументированного кода, то советую прочитать эту статью.
Так что же такое чистый код?
В целом, одно последнее качестве можно назвать итогом всего вышесказанного:
Чистый код написан тем, кому не плевать.
цитата Майкла Фетерса (Michael Feathers).
Он написан тем, кто относится к коду как к искусству, и кто обращает внимание на все детали.
Тема чистого кода — очень сложна, и выходит за рамки описанного в этой статье. Так что, если вы считаете, что существуют другие характеристики чистого кода, то, пожалуйста, поделитесь ими в комментариях.