Меню

Чистый код обработка ошибок

Роберт Мартин. «Чистый код. Создание, анализ, рефакторинг»

Предисловие

Мелочи важны. 80% работы программиста — поддержка существующего кода. Применение японского принципа 5S к программированию:

  • организация (правила, именование)
  • аккуратность (порядок в коде, все на своих местах, легко найти)
  • чистка (без мусорных комментариев)
  • стандартизация (стиль кодирования, соглашения в команде и сообществе)
  • дисциплина (следование правилам, размышления, готовность к изменениям).
    Чистота кода редко важна начальству, надо сделать здесь и сейчас. Но говно всегда возвращается и техдолг растет. Качество = небезразличие. Чистый код божественен (лол). Книга — практическое описание принципов программирования Lean.

Введение

Метрика хорошего кода — количество «чертей» в минуту
Профессионализм = знания + опыт. Писать чистый код — тяжело. 3 части книги: теория, практика, эвристические правила.

Глава 1. Чистый код

Код будет всегда (может быть в другом виде, но будет). Хороший код важен. Плохой код мешает работе, но все равно его пишут — потому что пускай работает хоть как-то, чем совсем никак. Работы по рефакторингу откладываются «на потом» (значит, никогда). По мере накопления хаоса в коде производительность стремиться к нулю. Потом случается глобальная переработка (перехуячить все), куда бросают лучшие силы. Возникает гонка между группой поддержки существующего продукта и группой разработки нового (привет, БМП). На чистку кода надо выделять время, даже вопреки требованиям руководства/бизнеса (аналогия: пациент недоволен тем, как врач долго моет руки). Не устранять опасность грязного кода — непрофессионализм. Единственная возможность соблюдать график и не терять производительность — улучшать код. Отличать плохой код от хорошего != уметь писать хорошо. Чувство кода — умение видеть проблемные места и предлагать пути улучшения. Может быть врожденным или приобретенным.
Чистый код в определениях топ-программистов:

  • Бьерн Страуструп: элегантность, прямолинейность, эффективность. Чистый код хорошо решает одну задачу.
  • Грэди Буч (книга Object oriented analysis and design with applications): простота, четкие абстракции, удобочитаемость, идеи программиста легко и логично считываются
  • Дэйв Томас: понятность, поддерживаемость, тесты, минимум зависимостей и простой краткий API, компактность и грамотность
  • Майкл Физерс (книга Working effectively with legacy code): чистый код выглядит тщательно написанным, нет очевидных способов улучшения, небезразличность
  • Рон Джеффрис: тесты, без нарушения DRY, выразительность, минимум сущностей
  • Уорд Каннингем (вдохновитель Design Patterns): делает примерно то, что ожидает читатель кода. Не запутан, логичен, чтение не доставляет трудностей, прост, красив.
    Правило бойскаута: оставь место стоянки чище, чем оно было до твоего прихода. Не сразу глобально, а постепенно, постоянно.

Глава 2. Содержательные имена

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

Имена должны передавать намерения программиста

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

Избегайте дезинфрмации

Нельзя использовать имена, вызывающие ложные ассоциации. Не использовать в именовании устоявшиеся термины не к месту (например, accountList, если речь действительно не идет о списке – конкретной структуре данных). Остерегаться малозаметных различий в именах – это тоже дезинформация + мешает автокомплиту IDE. Остерегаться похожих символов (1l, 0O).

Используйте осмысленные различия

Классический пример ошибки: product1 и product2. Чуть сложнее: Product, ProductInfo, ProductData – добавление слов не дает никакой полезной информаци.

Сложное правило. Плохие примеры ясны, а как делать хорошо?

Используте удобопроизносимые имена

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

В другой книжке есть яркий пример, который живет и процветает – аббревиатура SQL. Принятое на Западе произношение: «сиквел»; у нас же я слышал только «эс-кю-эль».

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

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

Избегайте схем кодирования имен

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

В тайпскрипте принято интерфесы называть так: IProduct. Пример уместного кодирования, полагаю.

Венгерская запись

В доисторические времена в названии переменной кодировали как раз мета-инфу, например в Fortran первая буква означала тип. Темные времена прошли, не надо так.

Префиксы членов класса

Та же штука, раньше была практика переменным класса давать префикс m_.

Интерфейсы и реализации

А вот и пример с IProduct. В нормальных ОО-языках давать префикс интерфейсам считается зашкваром. Менее зашкварно дать префикс-постфикс реализации.

Это уже наверное про конкретные случаи, особенности языков и парадигм. Для Typescript всё-таки не зашквар, думаю.

Избегайте мысленных преобразований

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

Имена классов

Имена классов – существительные и их комбинации, не глагол. Избегать общих слов типа Info, Data, Manager, Processor.

Имена методов

Глаголы, глагольные словосочетания. По стандарту javabean: для чтения get, для записи set, для предикатов (логических утверждений) – is.

Избегайте остроумия

Локальные мемы вжопу. Сюда же – сленг и просторечия.

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

Выберите одно слово для каждой концепции

Автокомплит поможет подставить имя метода, но не расскажет, чем fetch отличается от get, а controller от manager. Терминология должна быть строгой.

Избегайте каламбуров

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

Используйте имена из пространства решения

Не стесняться использовать термины CS, названия паттернов, алгоритмов, математические термины. Не забывая при этом про избегание дезинформации.

Используйте имена из пространства задачи

Термины предметной области приложения – тоже ок.

Добавьте содержательный контекст

При взгляде на переменную должно быть понятно, в каком смысловом контексте она используется. Например, мы коды статусов оъединяли и экспортировали единым объектом HTTP. Так же норм добавлять контекстные префиксы: customerName, customerPhone.

Не добавлять избыточный контекст

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

Напоследок

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

Глава 3. Функции

Функции – первый уровень структуризации программы.

Компактность

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

Блоки и отступы

В блок if, else, while следует помещать одну строку – вызов другой функции. Это сделает исходную функцию компактнее и послужит документированием кода. Нужно избегать вложенность: упростит чтение кода и опять же сделает функцию более компактной.

Правило одной операции

Функция должна выполнять только одну операцию, делать это хорошо и не делать ничего другого. Что считать одной операцией: это действие на одном уровне абстракции. Чтобы определить, что в фунции выполняется несколько операций, надо попробовать извлечь из нее другую функцию, которая не являлась бы переформулировкой реализации. Если в функции возможно осмысленное разделение на секции (например: объявления, инициализация, и т.д.) – значит, фунция выполняет несколько операций.

Вторую часть (про определение одной операции) понимаю чуть смутно. Нужно найти пример и вернуться к этому разделу.

Один уровень абстрации на функцию

Ещё раз – все команды функции (может быть несколько этапов, это не противоречит правилу одной операции) лежат в одном уровне абстрации. Например: getHtml – высокий, getName – средний, appendSymbol – низкий.

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

Чтение кода сверху вниз: правило понижения

Код должен читаться как рассказ – сверху вниз спускаясь по уровням абстрации. Иначе говоря: программа = набор ТО-абзацев, каждый из которых описывает текущий уровень абстрации и ссылается на следующий.

ТО-абзац – дефиниция. Например: renderPageWithSettings – функция, генерирующая код страницы в соответствии с переданными параметрами.

Команды switch

Написать компактный switch или цепочку if/else очень сложно, потому чтоswitch рулит несколькими командами, как правило. Но можно сокрыть эти команды в низкоуровневом классе и не дублировать в коде. Здесь поможет полиморфизм. Переносим switch в абстрактную фабрику, в которой вызовы нужных команд проходят полиморфную передачу через интерфейс.

Здесь тоже не до конца понятен пример и как делать в js. Надо вернуться и разобраться.

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

Имя должны давать понять, что происходит в функции. Не бояться использовать длинные имена, не бояться тратить на это время. Попробовать несколько вариантов. Иногда поиск понятного имени приводит к реструктуризации кода. Ещё нужно быть последовательным в именовании, использовать одинаковую фразеологию. «Функция должна делать примерно то, что вы ожидаете».

Аргументы функций

Идеал – функция без аргументов(нуль-арная). Допустимы унарные и бинарные функции. Тернарных и пониарных функций нужно избегать. При чтении кода аргументы приходится каждый раз интерпретировать. Часто аргумент и имя функции находятся на разных уровнях абстракции. Большое количество аргументов усложняет тестирование, ведь надо покрыть все кейсы, во всех сочетаниях. Фунции с выходными аргументами – вообще зло.

_Выходные – аргументы, чьи значения изменились после выполнения функции. _

Стандартные унарные формы

Два распространенных случая использования унарной функции: проверить некторое условия, связанное с аргументом, и обработка аргумента с последующим вызвращением. Нужно выбирать имена, четко отражающих эти разлиция. Ещё один частый вариант – событие. Функция принимает аргумент, и производит некоторые изменения состояния системы. Опять же – имя должно давать понять, что перед нами именно событие. Следует избегать иных форм унарных функций (речь опять же про выходные аргументы).

Аргументы-флаги

Вызов фунции типа render(true) – это ж пиздец. Сбивает с толку, нужно лезть смотреть сигнатуру + в функции явно будет выполнения нескольких операций.

Бинарные функции

Бинарные функции сложнее в понимании. Совсем лютый ужас – когда в вызове таких функций первый параметр должен игнорироваться. В двух аргументах функции нужен логичный естественный порядок, например: getCoords(latitude, longitude). Если связь условна, то легко перепутать. Бинарные функции – не зло, но следует попробовать преобразовать их в унарные. Например, поиграться с ООП.

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

Тернарные функции

Разобраться с тремя аргументами ещё сложнее.

Объекты как аргументы

Если есть 2-3-n аргументов – может их упаковать в отдельный класс? Это может показаться жульничеством, но если переменные передаются совместно, и жить друг без друга не могут, значит это отдельная концепция, заслуживающая своего имени.

Списки аргументов

Если в функцию передается переменное количество аргументов, то есть смысл заключить их в List

Глаголы и ключевые слова

Хорошее имя функции способно рассказать о порядке/обязательности аргумента. Для этого могут служить глаголы и ключевые слова. Например: writeField(name), assertExpectedEqualsActual(expected, actual).

Избавьтесь от побочных эффектов

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

Выходные аргументы

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

Разделение команд и запросов

Частный случай принципа одной операции. Функция может изменять состояние объекта или возвращать информацию о нем, но не одновременно. Получится ад. Например, сделать set(name), и если успех – вернуть true. Потом начинается использование типа if (set('unclebob') {...} – жесть.

Используйте исключения вместо возвращения кодов ошибок

Вовзвращать коды ошибок и обрабытывать их – частный случай разделения команд и запросов. Вместо этого лучше воспользоваться try/catch.

Изолируйте блоки try/catch

try/catch смешивает обработку ошибок с нормальным выполнением функции, поэтому из следует выносить в отдельную функцию. Обработка ошибок должна быть одной операцией, и после catch/finally не должно быть ничего другого.

Хороший прием. Посмотреть пример в книге, и написать свой – очень должен пригодится.

Магнит зависимостей Error.java

Обычно создают класс/перечисление всех кодов ошибок, и потом используют во всех остальных классах. Тут есть нюанс (который я не очень уловил, поскольку не знаю Java. Вернуться и проанализировать.)

Не повторяйтесь (DRY)

Дублирование кода увеличивает его объем, делает сложным поддержку/рефакторинг, увеличивет вероятность ошибки. Не надо так.

Структурное программирование

Спорное правило. Оно говорит о том, что функция должна иметь одну точку входа и одну точку выхода. Следствие: в функции должен быть единственный return, в циклах/свичах нельзя использовать break/continue, команды goto – под запретом. Дядя Боб не против, если ты не будешь следовать этому правилу, при условии что функции будут компактными.

Как научиться писать такие функции?

Программирование – тоже сочинительство. Сначала излагаешь мысли, потом «причёсываешь». Пытаться сразу писать чисто не надо.

Завершение

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

Глава 4. Комментарии

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

Комментарии не компенсируют плохого кода

Не нужно писать пояснения к запутанно написаному модулю: лучше отрефакторить и сделать проще. Не тратьте время на комментирование, тратьте на исправление.

Объясните свои намерения в коде

Следить за наименованием функций и переменных – они могут избавить от комментирования.

Хорошие комментарии

Юридические комментарии

Например, заявление об авторских правах. Чаще всего, достаточно короткого комментария со ссылкой на полный текст.

Информативные комментарии

Некоторая полезная информация о реализации. Например: подсказать, что возвращает функция, или какие параметры принимает.

Представление намерений

Раскрывает намерения вашего архитектурного или логического решения. Отвечает на вопрос: «А почему тут так?»

Прояснения

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

Предупреждения о последствиях

Информирование о нежелательных последствиях выполнения того или иного кода.

Комментарии TODO

// TODO как план на будущее: сделать необходимо, но не получится прямо сейчас.

Усиление

То же самое, что и представление намерений, но там где это незаметно и можно проебать.

Комментарии Javadoc в общедоступных API

Пишешь апишку – напиши комментарии к ней.

Плохия комментарии

О, их будет гораздо больше.

Бормотание

Хочешь написать комментарий – пиши понятно. Не допускайте таких комментариев, для понимания которых придется залезть в другие фукнции/модули.

Избыточные комментарии

Комментарии капитана Очевидность. Дублирует название функции.

Недостоверные комментарии

Ложь в комментарии – устаревшая информация или просто невнимательность.

Обязательные комментарии

Бессмысленная херня, когда зачем-то комментируют каждую функцию.

Журнальные комментарии

Когда-то программисты дописывали в начале модуля журнал всех изменений. Сейчас есть гит и не надо так.

Шум

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

Не используйте комментарии там, где можно использовать фукцию или переменную

В этом заголовке – содержание всей главы

Позиционные маркеры

// Actions ////////////
Форматирование кода с помощью комментариев-заголовков – только там, где это приносит ощутимую пользу.

Комментарии за закрывающей фигурной скобкой

Типа, чтобы не потеряться в многострочной функции/цикле/т.п.:
while { .... } // while
Лучше переписать функцию покороче.

Ссылки на авторов

Опять же: наследие эпохи до VCS.

Закомментированный код

Если код не нужен – удоли.

Комментарии HTML

Когда в тексте комментария встречается разметка – это трудно читать.

Нелокальная информация

Комментарий должен описывать тот кусок кода, рядом с которым написан.

Слишком много информации

Иногда в комменты запихивают чуть ли не статьи с Википедии. Не надо так.

Неочевидные комментарии

Если комментарий нуждается в объяснении – он плохой.

Заголовки функций

Нужно нормально выбирать имена.

Заголовки Javadoc во внутреннем коде

Не приносит пользы, если это не общедоступный код.

Глава 5. Форматирование

Уважение к деталям – хорошо. Зафиксируйте в своей группе правила оформления кода и седуйте им.

Цели форматирования

Стиль кодирования и удобочитаемость имеют большое влияние на сопровождаемость и расширяемость кода

Вертикальное форматирование

Приводится небольшое исследование размера исходных файлов программ на Java (Junit – меньше, Tamcat – больше)

Газетная метафора

Структура файла должна напоминать газетную статью: заглавие, краткое содержание в первом абзаце, а дальше уже факты, подробности. Чем дальше к концу – тем подробнее и ниже уровень абстрации.

Вертикальное разделение концепций

Строка = выражение, условие. Группа строк = законченная мысль. Мысли следует разделять пустыми строками.

Вертикальное сжатие

Вертикальное разрежение разделяет концепции, сжатие – наоборот, подчеркивает тесную связь.

Вертикальные расстояния

По коду трудно навигироваться, приходится постоянно прыгать между кусками кода и файлами. Связанные друг с другом концепции должны распологаться по соседству.
Переменные объявлять как можно ближе к месту использования.
Переменные экземпляров – в начале класса, так как используются многими методами класса (в С++ поступают наоборот, размещая переменные внизу. Так или иначе, надо их держать вместе и не прятать где-то в середине).
Вызывающая функция должна находится над вызываемой.
Концептуально родственные участки должны распологаться поблизости (например, группа функций, выполняющих аналогичные операции).

Вертикальное упорядочение

Еще раз: от высоких абстракций к низким, от вызывающих функций к вызываемым.

Горизонтальное форматирование

Исследование длин строк в файлах. Оптимально – до 120 символов.

Горизонтальное разделение и сжатие

Группировки связанных элементов и разделения разнородных.
Знаки присваивания окружаются пробелами, аргументы не отделяются от имени функции, сами аргументы разделяются пробелами.
В формулах: множители не разделяются пробелами (высокий приоритет операции), слагаемые – разделяются (низкий приоритет). Такой фичи чаще всего нет в инструментах автоматического форматирования, а жаль.

Горизонтальное выравнивание

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

Отступы

Отступы создают иерархию: классы > методы > блоки внутри методов. Не стот нарушать систему отступов для коротких блоков, вытягивая их в одну строку.

Вырожденные области видимости

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

Правила форматирования в группах

Код продукта должен быть оформлен в одном стиле. Потратьте 10 минут и зафиксируйте правила оформления в вашей команде.

Эмма Ваградян

Эмма Ваградян


iOS разработчик IT-компании «Доктор на работе»

Одно из самых ярких впечатлений на меня произвела книга консультанта и автора в области разработки ПО Роберта Мартина «Чистый код. Создание, анализ и рефакторинг», написанная в 2012 году. Начиная работу, автор пишет:

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

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

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

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

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

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

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

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

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

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

Наименование

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

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

Наименования лучше не сокращать, а прописывать полностью. Например, если используется слово «product», название «pr» будет не очень понятным для читателя. Важно, чтобы название полностью отражало ответственность сущности.

Функции

По мнению Р. Мартина, функции должны быть максимально короткими. Их длина не должна быть больше двадцати строк, а сами строки не должны быть длиннее ста пятидесяти символов. В противном случае ее лучше делить на части. Кроме того, важно, чтобы функция выполняла только одну операцию. Что касается входных параметров, в идеале их не должно быть. Но если входные параметры требуются, то их должно быть не больше трех. Чем больше входных параметров, тем тяжелее читать функцию.

Комментарии

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

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

Форматирование кода, классы и обработка ошибок

При форматировании кода необходимо придерживаться общей стилистики проекта, его общепринятой архитектуры. Например, если в проекте используется архитектура V.I.P.E.R., то нужно использовать ее, иначе можно испортить код. Если же написание проекта начинается с нуля, то необходимо заранее продумать, какую архитектуру использовать и какой стилистики придерживаться.

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

Наконец, при обработке ошибок всегда важно использовать exсeptions (исключения) вместо возвращения кода ошибок напрямую. Обработка ошибок всегда должна представлять собой только одну операцию. В функции, которая ответственна за обработку ошибки, ничего другого после произведения обработки выполняться не должно. Хотя на это вам намекнет и сам компилятор.

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

JavaScript
Nuances of Programming

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

Skillfactory.ru

В этой статье мы рассмотрим, как легко находить ошибки и изящно их обрабатывать. 

Исключения лучше, чем возврат кода ошибки

Исключения лучше потому, что они дают нам знать, что ошибка существует, и нам нужно её обработать. 

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

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

Например, если мы возвращаем коды ошибок в функциях, получим подобный код: 

const LESS_THAN_ZERO = 'LESS_THAN_ZERO';
const TOO_MANY = 'TOO_MANY';
const NOT_A_NUMBER = 'NOT_A_NUMBER';
class FruitStand {
setNumFruit(numFruits) {
if (typeof numFruits !== 'number') {
return NOT_A_NUMBER;
}
if (numFruits < 0) {
return LESS_THAN_ZERO;
}
if (numFruits > 100) {
return TOO_MANY;
}
this.numFruits = numFruits;
}
}
const fruitStand = new FruitStand();
const error = fruitStand.setNumFruit(1);
if (error !== LESS_THAN_ZERO && error !== TOO_MANY && error !== NOT_A_NUMBER) {
console.log(fruitStand.numFruits);
}

Нам придётся возвращать все коды ошибок в методе setNumFruit. К тому же прежде, чем сделать что-то после определения класса, нужно будет проверить все коды ошибок.

Skillfactory.ru

Вместо этого используем исключения: 

const LESS_THAN_ZERO = 'LESS_THAN_ZERO';
const TOO_MANY = 'TOO_MANY';
const NOT_A_NUMBER = 'NOT_A_NUMBER';
class FruitStand {
setNumFruit(numFruits) {
if (typeof numFruits !== 'number') {
throw new Error(NOT_A_NUMBER);
}
if (numFruits < 0) {
throw new Error(LESS_THAN_ZERO);
}
if (numFruits > 100) {
throw new Error(TOO_MANY);
}
this.numFruits = numFruits;
}
}
const fruitStand = new FruitStand();
try {
const error = fruitStand.setNumFruit(1);
console.log(fruitStand.numFruits);
} catch (ex) {
console.error(ex);
}

Мы устранили необходимость проверять все коды ошибок, обернув код, который мы хотим запустить, в блок try. Теперь ошибку просто отловить. И это особенно важно, когда код становится всё более сложным. 

Пишите блок Try-Catch-Finally

Стоит оборачивать try в коде, генерирующем исключения, которые мы хотим отловить. Он создаёт собственную область видимости для переменных в области блока, поэтому на объявленное с помощью let или const можно ссылаться только в блоке try.

На переменные, объявленные с var, можно ссылаться вне блока — мы не получим ошибку. Такой код выдаст 1:

try {
var x = 1;
} catch (ex) {
console.error(ex);
}
console.log(x);

А этот код выдаст Uncaught ReferenceError: x is not defined:

try {
let x = 1;
} catch (ex) {
console.error(ex);
}
console.log(x);

Не игнорируйте пойманные ошибки 

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

Сообщения об ошибках позволяют нам узнать об ошибке и корректно обработать её.

Примеры выше, например console.error, вызовут следующее: 

try {
const error = fruitStand.setNumFruit(1);
console.log(fruitStand.numFruits);
} catch (ex) {
console.error(ex);
}

Это один из способов сообщить об ошибке. Также можно использовать другие библиотеки для сообщения об ошибках.

Не игнорируйте отклонённые промисы 

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

Например, об ошибке можно сообщить, написав следующее:

Promise.reject('fail')
.catch(err => {
console.error(err);
})

Или для функций async напишем:

(async () => {
try {
await Promise.reject('fail')
} catch (err) {
console.error(err);
}
})()

Предоставление контекста с помощью исключений

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

Можно получить трассировку стека с исключениями в JavaScript, но она может не предоставлять всю необходимую информацию. 

Заключение

Бросание исключений лучше, чем возврат кода ошибок, так как они позволяют использовать блок try...catch для обработки ошибок. Это намного проще, чем проверка множества кодов ошибок. 

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

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

Наконец, отклонённые ошибки промисов нужно обрабатывать так же, как и остальные исключения. 

Читайте также:

  • Знакомство с промисами в JavaScript
  • 3 вида циклов for в JavaScript
  • Рекомендации по изучению JavaScript

Перевод статьи John Au-Yeung: JavaScript Clean Code: Error Handling

Software Craftmanship Guide() {

Extracts from book Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin. My intention is not to provide a summary, but to share the notes that I want to keep in mind.

Table of Contents

  1. Introduction
  2. Clean Code
  3. Meaningful Names
  4. Methods
  5. Comments
  6. Formatting
  7. Objects and Data Structures

Introduction

  • 80% or more of what we do is maintenance
  • Code is never perfect
  • The only valid measurement of code quality: WTFs / minute
  • Learning to write clean code is hard work. It requires more than just the knowledge of principles and patterns. You must sweat over it. You must practice it yourself, and watch yourself fail. You must watch others practice it and fail. You must see them stumble and retrace their steps. You must see them agonize over decisions and see the price they pay for making those decisions the wrong way.

[⬆]

Clean Code

  • LeBlanc’s law: Later equals never
  • Most managers want the truth, even when they don’t act like it. Most managers want good code, even when they are obsessing about the schedule. They may defend the schedule and requirements with passion; but that’s their job. It’s your job to defend the code with equal passion. It is unprofessional for programmers to bend to the will of managers who don’t understand the risks of making messes.
  • All developers feel the pressure to make messes in order to meet deadlines. True professionals know that the second part of the conundrum is wrong. You will not make the deadline by making the mess. Indeed, the mess will slow you down instantly, and will force you to miss the deadline. The only way to make the deadline is to keep the code as clean as possible at all times.
  • Bad code tempts the mess to grow. When others change bad code, they tend to make it worse. Metaphor: A building with broken windows looks like nobody cares about it. So other people stop caring. They allow more windows to become broken. Eventually they actively break them.
  • Clean code should be: Pleasing to read, does one thing well, minimal, looks like it was written by someone who cares
  • If we all checked in our code a little cleaner than when we checked it out, the code simply could not rot. The cleanup doesn’t have to be something big. Change one variable name for the better, break up one function that’s a little too large, eliminate one small bit of duplication.

[⬆]

Meaningful Names

  • Use intention revealing names. If a name requires a comment, then the name does not reveal its intent.
    // bad
    int d; // elapsed time in days

    // good
    int elapsedTimeInDays;
    // bad
    public List<int[]> getThem() {
		List<int[]> list1 = new ArrayList<int[]>(); 
			for (int[] x : theList)
				if (x[0] == 4) list1.add(x);
		return list1; 
	}

    // good
    public List<int[]> getFlaggedCells() {
		List<int[]> flaggedCells = new ArrayList<int[]>(); 
			for (int[] cell : gameBoard)
				if (cell[STATUS_VALUE] == FLAGGED) 
					flaggedCells.add(cell);
		return flaggedCells; 
	}
  • Make meaningful distinctions

    • Imagine you have Product class. If you have another called ProductInfo or ProductData, you have made the names different without making them mean anything different. In the absence of specific conventions, the variable moneyAmount is indistinguishable from money, customerInfo is indistinguishable from customer, accountData is indistinguishable from account, and theMessage is indistinguishable from message.
  • Methods should have verb or verb phrase names like postPayment, deletePage, or save. When constructors are overloaded, use static factory methods with names that describe the arguments. Consider enforcing their use by making the corresponding constructors private.

	// bad
	Complex fulcrumPoint = new Complex(23.0);

	// good
	Complex fulcrumPoint = Complex.FromRealNumber(23.0);
  • Pick one word per concept. It’s confusing to have fetch, retrieve, and get as equivalent methods of different classes.

  • People are afraid of renaming things for fear that some other developers will object. Do not share that fear and be grateful when names change (for the better).

[⬆]

Methods

  • The first rule of methods is that they should be small. The second rule of functions is that they should be smaller than that.

  • Methods should not be 100 lines long. Methods should hardly ever be 20 lines long.

  • Methods should do only one thing. They should do it well. They should do it only.

  • How to determine if a method is doing only one thing? Describe the method by describing it as a brief TO paragraph: TO RenderPageWithSetupsAndTeardowns, we check to see whether the page is a test page and if so, we include the setups and teardowns. In either case we render the page in HTML.

  • Don’t be afraid to make a name long. A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment. The smaller and more focused a method is, the easier it is to choose a descriptive name. Don’t be afraid to spend time choosing a name.

  • The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.

	// bad
	Circle makeCircle(double x, double y, double radius); 

	// good
	Circle makeCircle(Point center, double radius);
  • Passing a boolean into a function is a truly terrible practice. It immediately complicates the signature of the method, loudly proclaiming that this function does more than one thing. It does one thing if the flag is true and another if the flag is false! The method call render(true) is just plain confusing to a poor reader. Mousing over the call and seeing render(boolean isSuite) helps a little, but not that much. We should have split the function into two: renderForSuite() and renderForSingleTest().

  • Choosing good names for a function can go a long way toward explaining the intent of the function and the order and intent of the arguments.

	// ok
	void assertEquals(expected, actual)

	// better
	void assertExpectedEqualsActual(expected, actual)
  • Anything that forces you to check the function signature is equivalent to a double-take. In the days before object oriented programming it was sometimes necessary to have output arguments. However, much of the need for output arguments disappears in OO languages because this is intended to act as an output argument.
	// Does this function append s as the footer to something? Or does it append some footer to s?
	void appendFooter(s);

	// good
	report.appendFooter();
  • Functions should either do something or answer something, but not both.
	// bad
	public boolean set(String attribute, String value);
	
	// “If the username attribute was previously set to unclebob” or 
		// “set the username attribute to unclebob and if that worked then...”
	if (set("username", "unclebob"))...

	// good
	if (attributeExists("username")) {
		setAttribute("username", "unclebob");
		...
	}
  • Prefer exceptions to returning error codes
	// Bad
	if (deletePage(page) == E_OK) {
		if (registry.deleteReference(page.name) == E_OK) {
			if (configKeys.deleteKey(page.name.makeKey()) == E_OK){
			logger.log("page deleted");
			} else {
			logger.log("configKey not deleted");
			}
		} else {
			logger.log("deleteReference from registry failed");
		}
	} else {
		logger.log("delete failed");
		return E_ERROR;
	}
	
	// Good
	try {
		deletePage(page);
		registry.deleteReference(page.name);
		configKeys.deleteKey(page.name.makeKey());
	}
	catch (Exception e) {
		logger.log(e.getMessage());
	}
  • Functions should do one thing. Error handing is one thing. Thus, a function that handles
    errors should do nothing else
    . This implies (as in the example above) that if the keyword
    try exists in a function, it should be the very first word in the function and that there
    should be nothing after the catch/finally blocks.

  • Duplication may be the root of all evil in software.

  • «When I write functions, they come out long and complicated. They have lots of
    indenting and nested loops. They have long argument lists. The names are arbitrary, and
    there is duplicated code. But I also have a suite of unit tests that cover every one of those
    clumsy lines of code. So then I massage and refine that code, splitting out functions,
    changing names, eliminating duplication. I shrink the methods and reorder them. Sometimes I break out whole
    classes, all the while keeping the tests passing. In the end, I wind up with functions that
    follow the rules I’ve laid down in this chapter. I don’t write them that way to start.
    I don’t think anyone could.»

  • Functions are the verbs of that language, and classes are the nouns. Master programmers think
    of systems as stories to be told rather than programs to be written.

[⬆]

Comments

  • «Don’t comment bad code — rewrite it.»

  • The proper use of comments is to compensate for our failure to express ourself in
    code
    . Comments are always failures. We must have them because we cannot always figure out how to express
    ourselves without them, but their use is not a cause for celebration. So when you find yourself in a position where
    you need to write a comment, think it through and see whether there isn’t some way to turn
    the tables and express yourself in code
    .

  • The older a comment is, and the farther away it is from the code it describes,
    the more likely it is to be just plain wrong. The reason is simple. Programmers can’t realistically
    maintain them.

  • Comments Do Not Make Up for Bad Code! One of the more common motivations for writing comments is bad code.
    We write a module and we know it is confusing and disorganized. We know it’s a mess. So we say to ourselves,
    “Ooh, I’d better comment that!” No! You’d better clean it! Clear and expressive code with few comments is
    far superior to cluttered and complex code with lots of comments. Rather than spend your time writing the
    comments that explain the mess you’ve made, spend it cleaning that mess.

  • Explain yourself in code

	// Bad:
	
	// Check to see if the employee is eligible for full benefits
		if ((employee.flags & HOURLY_FLAG) &&
		(employee.age > 65))
	
	// Good:
	if (employee.isEligibleForFullBenefits())
  • Avoid redundant comments: These comments below probably takes longer to read than the code itself
	// Utility method that returns when this.closed is true. Throws an exception
	// if the timeout is reached.
	public synchronized void waitForClose(final long timeoutMillis)	throws Exception {
		if(!closed) {
			wait(timeoutMillis);
		if(!closed)
			throw new Exception("MockResponseSender could not be closed");
		}
	}
  • Avoid mandated comments: It is just plain silly to have a rule that says that every function must have a javadoc, or
    every variable must have a comment.
	/**
	*
	* @param title The title of the CD
	* @param author The author of the CD
	* @param tracks The number of tracks on the CD
	* @param durationInMinutes The duration of the CD in minutes
	*/
	public void addCD(String title, String author, int tracks, int durationInMinutes) {
		CD cd = new CD();
		cd.title = title;
		cd.author = author;
		cd.tracks = tracks;
		cd.duration = duration;
		cdList.add(cd);
	}
  • Don’t use a comment when you can use a function or variable
	// Bad:
	
	// does the module from the global list <mod> depend on the subsystem we are part of?
	if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
	
	// Good:
	ArrayList moduleDependees = smodule.getDependSubsystems();
	String ourSubSystem = subSysMod.getSubSystem();
	if (moduleDependees.contains(ourSubSystem))
  • Few practices are as odious as commenting-out code. Don’t do this! Others who see that commented-out code won’t
    have the courage to delete it. They’ll think it is there for a reason and is too important to delete
    .
    So commented-out code gathers like dregs at the bottom of a bad bottle of wine. There was a time, back in the sixties,
    when commenting-out code might have been useful. But we’ve had good source code control systems for a very long time now.
    Those systems will remember the code for us.

[⬆]

Formatting

  • Dependent Functions: If one function calls another, they should be vertically close,
    and the caller should be above the callee, if at all possible. This gives the program a natural flow.

[⬆]

Objects

to be written

[⬆]

Error Handling

  • Many code bases are com- pletely dominated by error handling. When I say dominated, I don’t mean that error han- dling is all that they do. I mean that it is nearly impossible to see what the code does because of all of the scattered error handling. Error handling is important, but if it obscures logic, it’s wrong.

  • Don’t return null. I can’t begin to count the number of applications I’ve seen in which nearly every other line was a check for null.

public void registerItem(Item item) { if (item != null) {
	ItemRegistry registry = peristentStore.getItemRegistry(); 
	if (registry != null) {
		Item existing = registry.getItem(item.getID());
		if (existing.getBillingPeriod().hasRetailOwner()) {
			existing.register(item); }
		} 
	}
}

If you are tempted to return null from a method, consider throwing an exception or returning a SPECIAL CASE object instead. If you are calling a null-returning method from a third-party API, consider wrapping that method with a method that either throws an exception or returns a special case object.

Bad:

List<Employee> employees = getEmployees(); 
if (employees != null) {
	for(Employee e : employees) { 
		totalPay += e.getPay();
	} 
}

Right now, getEmployees can return null, but does it have to? If we change getEmployee so that it returns an empty list, we can clean up the code:

List<Employee> employees = getEmployees(); 
for(Employee e : employees) {
	totalPay += e.getPay(); 
}

};

Bitdeli Badge

Software Craftmanship Guide() {

Extracts from book Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin. My intention is not to provide a summary, but to share the notes that I want to keep in mind.

Table of Contents

  1. Introduction
  2. Clean Code
  3. Meaningful Names
  4. Methods
  5. Comments
  6. Formatting
  7. Objects and Data Structures

Introduction

  • 80% or more of what we do is maintenance
  • Code is never perfect
  • The only valid measurement of code quality: WTFs / minute
  • Learning to write clean code is hard work. It requires more than just the knowledge of principles and patterns. You must sweat over it. You must practice it yourself, and watch yourself fail. You must watch others practice it and fail. You must see them stumble and retrace their steps. You must see them agonize over decisions and see the price they pay for making those decisions the wrong way.

[⬆]

Clean Code

  • LeBlanc’s law: Later equals never
  • Most managers want the truth, even when they don’t act like it. Most managers want good code, even when they are obsessing about the schedule. They may defend the schedule and requirements with passion; but that’s their job. It’s your job to defend the code with equal passion. It is unprofessional for programmers to bend to the will of managers who don’t understand the risks of making messes.
  • All developers feel the pressure to make messes in order to meet deadlines. True professionals know that the second part of the conundrum is wrong. You will not make the deadline by making the mess. Indeed, the mess will slow you down instantly, and will force you to miss the deadline. The only way to make the deadline is to keep the code as clean as possible at all times.
  • Bad code tempts the mess to grow. When others change bad code, they tend to make it worse. Metaphor: A building with broken windows looks like nobody cares about it. So other people stop caring. They allow more windows to become broken. Eventually they actively break them.
  • Clean code should be: Pleasing to read, does one thing well, minimal, looks like it was written by someone who cares
  • If we all checked in our code a little cleaner than when we checked it out, the code simply could not rot. The cleanup doesn’t have to be something big. Change one variable name for the better, break up one function that’s a little too large, eliminate one small bit of duplication.

[⬆]

Meaningful Names

  • Use intention revealing names. If a name requires a comment, then the name does not reveal its intent.
    // bad
    int d; // elapsed time in days

    // good
    int elapsedTimeInDays;
    // bad
    public List<int[]> getThem() {
		List<int[]> list1 = new ArrayList<int[]>(); 
			for (int[] x : theList)
				if (x[0] == 4) list1.add(x);
		return list1; 
	}

    // good
    public List<int[]> getFlaggedCells() {
		List<int[]> flaggedCells = new ArrayList<int[]>(); 
			for (int[] cell : gameBoard)
				if (cell[STATUS_VALUE] == FLAGGED) 
					flaggedCells.add(cell);
		return flaggedCells; 
	}
  • Make meaningful distinctions

    • Imagine you have Product class. If you have another called ProductInfo or ProductData, you have made the names different without making them mean anything different. In the absence of specific conventions, the variable moneyAmount is indistinguishable from money, customerInfo is indistinguishable from customer, accountData is indistinguishable from account, and theMessage is indistinguishable from message.
  • Methods should have verb or verb phrase names like postPayment, deletePage, or save. When constructors are overloaded, use static factory methods with names that describe the arguments. Consider enforcing their use by making the corresponding constructors private.

	// bad
	Complex fulcrumPoint = new Complex(23.0);

	// good
	Complex fulcrumPoint = Complex.FromRealNumber(23.0);
  • Pick one word per concept. It’s confusing to have fetch, retrieve, and get as equivalent methods of different classes.

  • People are afraid of renaming things for fear that some other developers will object. Do not share that fear and be grateful when names change (for the better).

[⬆]

Methods

  • The first rule of methods is that they should be small. The second rule of functions is that they should be smaller than that.

  • Methods should not be 100 lines long. Methods should hardly ever be 20 lines long.

  • Methods should do only one thing. They should do it well. They should do it only.

  • How to determine if a method is doing only one thing? Describe the method by describing it as a brief TO paragraph: TO RenderPageWithSetupsAndTeardowns, we check to see whether the page is a test page and if so, we include the setups and teardowns. In either case we render the page in HTML.

  • Don’t be afraid to make a name long. A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment. The smaller and more focused a method is, the easier it is to choose a descriptive name. Don’t be afraid to spend time choosing a name.

  • The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.

	// bad
	Circle makeCircle(double x, double y, double radius); 

	// good
	Circle makeCircle(Point center, double radius);
  • Passing a boolean into a function is a truly terrible practice. It immediately complicates the signature of the method, loudly proclaiming that this function does more than one thing. It does one thing if the flag is true and another if the flag is false! The method call render(true) is just plain confusing to a poor reader. Mousing over the call and seeing render(boolean isSuite) helps a little, but not that much. We should have split the function into two: renderForSuite() and renderForSingleTest().

  • Choosing good names for a function can go a long way toward explaining the intent of the function and the order and intent of the arguments.

	// ok
	void assertEquals(expected, actual)

	// better
	void assertExpectedEqualsActual(expected, actual)
  • Anything that forces you to check the function signature is equivalent to a double-take. In the days before object oriented programming it was sometimes necessary to have output arguments. However, much of the need for output arguments disappears in OO languages because this is intended to act as an output argument.
	// Does this function append s as the footer to something? Or does it append some footer to s?
	void appendFooter(s);

	// good
	report.appendFooter();
  • Functions should either do something or answer something, but not both.
	// bad
	public boolean set(String attribute, String value);
	
	// “If the username attribute was previously set to unclebob” or 
		// “set the username attribute to unclebob and if that worked then...”
	if (set("username", "unclebob"))...

	// good
	if (attributeExists("username")) {
		setAttribute("username", "unclebob");
		...
	}
  • Prefer exceptions to returning error codes
	// Bad
	if (deletePage(page) == E_OK) {
		if (registry.deleteReference(page.name) == E_OK) {
			if (configKeys.deleteKey(page.name.makeKey()) == E_OK){
			logger.log("page deleted");
			} else {
			logger.log("configKey not deleted");
			}
		} else {
			logger.log("deleteReference from registry failed");
		}
	} else {
		logger.log("delete failed");
		return E_ERROR;
	}
	
	// Good
	try {
		deletePage(page);
		registry.deleteReference(page.name);
		configKeys.deleteKey(page.name.makeKey());
	}
	catch (Exception e) {
		logger.log(e.getMessage());
	}
  • Functions should do one thing. Error handing is one thing. Thus, a function that handles
    errors should do nothing else
    . This implies (as in the example above) that if the keyword
    try exists in a function, it should be the very first word in the function and that there
    should be nothing after the catch/finally blocks.

  • Duplication may be the root of all evil in software.

  • «When I write functions, they come out long and complicated. They have lots of
    indenting and nested loops. They have long argument lists. The names are arbitrary, and
    there is duplicated code. But I also have a suite of unit tests that cover every one of those
    clumsy lines of code. So then I massage and refine that code, splitting out functions,
    changing names, eliminating duplication. I shrink the methods and reorder them. Sometimes I break out whole
    classes, all the while keeping the tests passing. In the end, I wind up with functions that
    follow the rules I’ve laid down in this chapter. I don’t write them that way to start.
    I don’t think anyone could.»

  • Functions are the verbs of that language, and classes are the nouns. Master programmers think
    of systems as stories to be told rather than programs to be written.

[⬆]

Comments

  • «Don’t comment bad code — rewrite it.»

  • The proper use of comments is to compensate for our failure to express ourself in
    code
    . Comments are always failures. We must have them because we cannot always figure out how to express
    ourselves without them, but their use is not a cause for celebration. So when you find yourself in a position where
    you need to write a comment, think it through and see whether there isn’t some way to turn
    the tables and express yourself in code
    .

  • The older a comment is, and the farther away it is from the code it describes,
    the more likely it is to be just plain wrong. The reason is simple. Programmers can’t realistically
    maintain them.

  • Comments Do Not Make Up for Bad Code! One of the more common motivations for writing comments is bad code.
    We write a module and we know it is confusing and disorganized. We know it’s a mess. So we say to ourselves,
    “Ooh, I’d better comment that!” No! You’d better clean it! Clear and expressive code with few comments is
    far superior to cluttered and complex code with lots of comments. Rather than spend your time writing the
    comments that explain the mess you’ve made, spend it cleaning that mess.

  • Explain yourself in code

	// Bad:
	
	// Check to see if the employee is eligible for full benefits
		if ((employee.flags & HOURLY_FLAG) &&
		(employee.age > 65))
	
	// Good:
	if (employee.isEligibleForFullBenefits())
  • Avoid redundant comments: These comments below probably takes longer to read than the code itself
	// Utility method that returns when this.closed is true. Throws an exception
	// if the timeout is reached.
	public synchronized void waitForClose(final long timeoutMillis)	throws Exception {
		if(!closed) {
			wait(timeoutMillis);
		if(!closed)
			throw new Exception("MockResponseSender could not be closed");
		}
	}
  • Avoid mandated comments: It is just plain silly to have a rule that says that every function must have a javadoc, or
    every variable must have a comment.
	/**
	*
	* @param title The title of the CD
	* @param author The author of the CD
	* @param tracks The number of tracks on the CD
	* @param durationInMinutes The duration of the CD in minutes
	*/
	public void addCD(String title, String author, int tracks, int durationInMinutes) {
		CD cd = new CD();
		cd.title = title;
		cd.author = author;
		cd.tracks = tracks;
		cd.duration = duration;
		cdList.add(cd);
	}
  • Don’t use a comment when you can use a function or variable
	// Bad:
	
	// does the module from the global list <mod> depend on the subsystem we are part of?
	if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
	
	// Good:
	ArrayList moduleDependees = smodule.getDependSubsystems();
	String ourSubSystem = subSysMod.getSubSystem();
	if (moduleDependees.contains(ourSubSystem))
  • Few practices are as odious as commenting-out code. Don’t do this! Others who see that commented-out code won’t
    have the courage to delete it. They’ll think it is there for a reason and is too important to delete
    .
    So commented-out code gathers like dregs at the bottom of a bad bottle of wine. There was a time, back in the sixties,
    when commenting-out code might have been useful. But we’ve had good source code control systems for a very long time now.
    Those systems will remember the code for us.

[⬆]

Formatting

  • Dependent Functions: If one function calls another, they should be vertically close,
    and the caller should be above the callee, if at all possible. This gives the program a natural flow.

[⬆]

Objects

to be written

[⬆]

Error Handling

  • Many code bases are com- pletely dominated by error handling. When I say dominated, I don’t mean that error han- dling is all that they do. I mean that it is nearly impossible to see what the code does because of all of the scattered error handling. Error handling is important, but if it obscures logic, it’s wrong.

  • Don’t return null. I can’t begin to count the number of applications I’ve seen in which nearly every other line was a check for null.

public void registerItem(Item item) { if (item != null) {
	ItemRegistry registry = peristentStore.getItemRegistry(); 
	if (registry != null) {
		Item existing = registry.getItem(item.getID());
		if (existing.getBillingPeriod().hasRetailOwner()) {
			existing.register(item); }
		} 
	}
}

If you are tempted to return null from a method, consider throwing an exception or returning a SPECIAL CASE object instead. If you are calling a null-returning method from a third-party API, consider wrapping that method with a method that either throws an exception or returns a special case object.

Bad:

List<Employee> employees = getEmployees(); 
if (employees != null) {
	for(Employee e : employees) { 
		totalPay += e.getPay();
	} 
}

Right now, getEmployees can return null, but does it have to? If we change getEmployee so that it returns an empty list, we can clean up the code:

List<Employee> employees = getEmployees(); 
for(Employee e : employees) {
	totalPay += e.getPay(); 
}

};

Bitdeli Badge

Перевод статьи «How to write clean code».

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

Чистый код предполагает масштабируемую организацию. Это означает, что внесение в него даже существенных изменений не приводит к хаосу. Умение писать такой код — одно из ключевых отличий разработчика-сеньора.

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

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

1. Код должен быть не только рабочим, но и читаемым. Эти два качества одинаково важны

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

Как быть и что делать

Хороший нейминг + классы и функции с единой ответственностью + написание тестов.

2. Позже значит никогда

Будем откровенны: все мы порой обещаем себе, что вернемся и почистим код позже, но в конечном итоге забываем об этом.

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

Как быть и что делать

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

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

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

Что касается аргументов функции, их идеальное количество — 0. Дальше идет 1, 2, но нужно стараться, чтобы аргументов было не больше 3.

Как быть и что делать

Функции нужно писать в соответствии с принципами единой ответственности и открытости/закрытости.

4. Дублирование кода — зло

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

Как быть и что делать

Следите за тем, чтобы код писался в соответствии с принципом DRY, был изолированным и модульным.

5. Единственный хороший комментарий — о найденном способе избежать комментариев

«Нет ничего полезнее хорошего комментария в нужном месте. Но комментарии даже в наилучшем случае — неизбежное зло».

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

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

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

Как быть и что делать

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

6. Объект раскрывает поведение, но не данные

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

Как быть и что делать

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

7. Тестирование

Код тестов так же важен, как и тот, что идет в производство. Поэтому он должен меняться и расти по мере развития проекта.

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

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

Как быть и что делать

Нужно следовать принципам чистых тестов FIRST. Тесты должны быть:

  • Быстрыми (Fast). Тесты должны выполняться быстро. Если вам приходится слишком долго ждать выполнения теста, вы вряд ли станете запускать его почаще.
  • Независимыми / изолированными (Independent). Тесты должны быть как можно более изолированными и не зависящими друг от друга.
  • Повторяемыми (Repeatable). Тесты должны быть повторяемыми в любой среде — в разработке, стейджинге и продакшене.
  • Очевидными (Self-Validating). Результатом выполнения теста должно быть булево значение. Тест должен быть или пройден, или провален.
  • Исчерпывающими (Thorough). Нужно стремиться охватить тестами все крайние случаи, все проблемы безопасности, каждый use case (вариант использования) и happy path (самый благоприятный сценарий работы кода).

8. Обработка ошибок и исключений

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

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

Как быть и что делать

Создавайте информативные сообщения об ошибках и передавайте их вместе с вашими исключениями. Указывайте операцию, в которой произошел сбой, и тип ошибки.

9. Классы

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

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

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

Как быть и что делать

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

10. Форматирование

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

Локальные переменные должны появляться вверху функции.

Переменные экземпляра должны декларироваться вверху класса.

Короткие строки предпочтительнее длинных. Обычно верхняя граница — 100-120 символов, длиннее уже не стоит делать.

Как быть и что делать

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

Принципы разработки программ

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

Именование переменных

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

«Выбирать имя для переменной следует так же заботливо, как и для своего первенца».

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

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

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

Зависимости должны быть сведены к минимуму

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

Аккуратность

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

Уборка

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

Стандартизация

При написании кода следует придерживаться стиля и практик, установленных для репозитория.

Самодисциплина

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


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

Вот несколько советов и идей о том, как обращаться с некоторыми кодами ошибок в чистом коде.

1. Используйте исключения вместо кодов возврата

Пожалуйста, смотрите листинг кода ниже, DeviceController.java


public class DeviceController {
...
public void sendShutDown(){
//Check the state of the device
if(handle != DeviceHandle.INVALID){
//Save the device status to the record field
DeviceRecord record = retrieveDeviceRecord(handle);
//If not suspended, shut down
if(record.getStatus() != DEVICE_SUSPENDED){
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}else {
logger.log("Device suspende.");
}
} else{
logger.log("invalid handle for:" + DEV1.toString());
}
}
...
}

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

В следующем листинге кода показан случай, когда в методе возникает исключение.

Список кодов: DeviceController.java (с обработкой исключений)


public class DeviceController {
...
public void sendShutDown(){
try{
tryToShutDown();
}catch(DeviceShutDownError e){
logger.log(e);
}
}

private void tryToShutDown() throws DeviceShutDownError{
DeviceHandle handle = getHandle(DEV1);
DeviceRecord record = retrieveDeviceRecord(handle);

pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);

}

private DeviceHandle getHanle(DeviceID id){
...
throw new DeviceShutDownError("invalid handle for: " + id.toString());
...
}

private DeviceRecord retrieveDeviceRecord(DeviceHandle handle){
...
throw new DeviceShutDownError("Device suspended.");
...
}
...
}

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

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

2. Определите класс исключения в соответствии с потребностями звонящего.

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

Давайте посмотрим на очень распространенный код.

Список кодов:

ACMEPort port = new ACMEPort(12);

try{
port.open();
}catch(DeviceResponseException e){
reportPortError(e);
logger.log("Device response exception",e);
}catch (ATM1212UnlockedException e){
reportPortError(e);
logger.log("Unlock exception",e);
}catch(GMXError e){
reportPortError(e);
logger.log("Device response exception",e);
}finally {
...
}

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

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

Список кодов:


LocalPort port = new LocalPort(12);
try{
port.open();
}catch (PortDeviceFailure e){
reportError(e);
logger.log(e.getMessage(),e);
}finally{
...
}

Класс LocalPort - это простой упакованный класс, который захватывает и транслирует исключения, создаваемые классом ACMEPort:
public class LocalPort{
private ACMEPort innerPort;

public LocalPort(int portNumber){
innerPort = new ACMEPort(portNumber);
}
}

public void open(){
try{
innerport.open();
}catch(DeviceResponseException e){
throw new PortDeviceFailure(e);
}catch (ATM1212UnlockedException e){
throw new PortDeviceFailure(e);
}catch(GMXError e){
throw new PortDeviceFailure(e);
}
}
...
}

Подобно классу упаковки, который мы определили для ACMEPort, он очень полезен. Это также можно рассматривать как прокси для стороннего API. На самом деле, это хорошая практика для упаковки стороннего API. Когда сторонний API упакован, его зависимость от него уменьшается: в будущем вы можете безболезненно переходить на другие базы кода. А при тестировании собственного кода упаковка также помогает симулировать сторонние вызовы.

3. Не возвращайте ноль

Для NullPointerException оно должно быть знакомо всем. Так как же избежать исключений нулевого указателя в максимально возможной степени? Один из них — избегать максимально возможного возврата нулевых значений. Вместо этого вы можете использовать специальный объект case. Если вы вызываете сторонний API и возвращаете нулевое значение, вы можете вызвать исключение или вернуть объект особого случая в новом методе.Например, если вы возвращаете список в Java, если вы возвращаете ноль, вы можете использовать вместо этого Floods.emptyList (). Таким образом, вызывающая сторона не должна проверять, является ли полученный результат нулевым.

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

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

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Чистота речи основные типы лексических ошибок реферат
  • Читать ошибки ниссан икстрейл т30