В этой статье я привожу выдержки (цитаты) из книги "Чистый код" Роберта Мартина (Robert Cecil Martin).
Весь текст сохранен в виде вырезок из книги без каких либо изменений. Примеры коды мои собственные.
Расплата за хаос
Если вы занимались программированием более двух-трех лет, вам наверняка доводилось вязнуть в чужом — или в своем собственном — беспорядочном коде.
Замедление может быть весьма значительным. За какие-нибудь год-два группы, очень быстро двигавшиеся вперед в самом начале проекта, начинают ползти со скоростью улитки. Каждое изменение, вносимое в код, нарушает работу кода в двух-трех местах. Ни одно изменение не проходит тривиально. Для каждого дополнения или модификации системы необходимо «понимать» все хитросплетения кода — чтобы в программе их стало еще больше. Со временем неразбериха разрастается настолько, что справиться с ней уже не удается. Выхода просто нет.
По мере накопления хаоса в коде производительность группы начинает снижаться, асимптотически приближаясь к нулю. В ходе снижения производительности начальство делает единственное, что оно может сделать: подключает к проекту новых работников в надежде повысить производительность. Но новички ничего не понимают в архитектуре системы. Они не знают, какие изменения соответствуют намерениям проектировщика, а какие им противоречат. Более того, они — и все остальные участники группы — находятся под страшным давлением со стороны начальства. В спешке они работают все небрежнее, отчего производительность только продолжает падать.
Основной парадокс
Программисты сталкиваются с основным парадоксом базовых ценностей. Каждый разработчик, имеющий сколько-нибудь значительный опыт работы, знает, что предыдущий беспорядок замедляет его работу. Но при этом все разработчики под давлением творят беспорядок в своем коде для соблюдения графика. Короче, у них нет времени, чтобы работать быстро!
Настоящие профессионалы знают, что вторая половина этого парадокса неверна. Невозможно выдержать график, устроив беспорядок. На самом деле этот беспорядок сразу же замедлит вашу работу, и график будет сорван. Единственный способ выдержать график — и единственный способ работать быстро — заключается в том, чтобы постоянно поддерживать чистоту в коде.
ГРЭДИ БУЧ
Чистый код прост и прямолинеен. Чистый код читается, как хорошо написанная проза. Чистый код никогда не затемняет намерения проектировщика; он полон четких абстракций и простых линий передачи управления.
МАЙКЛ ФИЗЕРС
Чистый код всегда выглядит так, словно его автор над ним тщательно потрудился. Вы не найдете никаких очевидных возможностей для его улучшения. Все они уже были продуманы автором кода.
Правило бойскаута
Хорошо написать код недостаточно. Необходимо поддерживать чистоту кода с течением времени. Все мы видели, как код загнивает и деградирует с течением времени. Значит, мы должны активно поработать над тем, чтобы этого не произошло.
У бойскаутов существует простое правило, которое применимо и к нашей профессии:
Оставь место стоянки чище, чем оно было до твоего прихода
Содержательные имена
Имена должны передавать намерения программиста
Имя переменной, функции или класса должно отвечать на все главные вопросы. Оно должно сообщить, почему эта переменная (и т. д.) существует, что она делает и как используется. Если имя требует дополнительных комментариев, значит, оно не передает намерений программиста.
Избегайте дезинформации
Программисты должны избегать ложных ассоциаций, затемняющих смысл кода.
Не используйте слова со скрытыми значениями, отличными от предполагаемого
Используйте удобопроизносимые имена
Людям удобно работать со словами. Значительная часть нашего мозга специализируется на концепции слов, а слова по определению удобопроизносимы.
Было бы обидно не использовать ту изрядную часть мозга, которая развивалась для разговорной речи. Следовательно, имена должны нормально произноситься.
Выбирайте имена, удобные для поиска
У однобуквенных имен и числовых констант имеется один специфический недостаток: их трудно искать в большом объеме текста.
Избегайте схем кодирования имен
У нас и так хватает хлопот с кодированием, чтобы искать новые сложности. Кодирование информации о типе или области видимости в именах только создает новые хлопоты по расшифровке.
Венгерская запись
В доисторические времена, когда в языках действовали ограничения на длину имен, мы нарушали это правило по необходимости — и не без сожалений. В Fortran первая буква имени переменной обозначала код типа. В ранних версиях BASIC имена могли состоять только из одной буквы и одной цифры. Венгерская запись (HN, Hungarian Notation) подняла эту проблему на новый уровень. Венгерская запись играла важную роль во времена Windows C API, когда программы работали с целочисленными дескрипторами (handle), длинными указателями, указателями на void или различными реализациями «строк» (с разным применением и атрибутами). Компиляторы в те дни не поддерживали проверку типов, поэтому программистам были нужны «подсказки» для запоминания типов.
В современных языках существует куда более развитая система типов, а компиляторы запоминают типы и обеспечивают их соблюдение. Более того, появилась тенденция к использованию меньших классов и более коротких функций, чтобы программисты видели точку объявления каждой используемой переменной. Объекты обладают сильной типизацией, а рабочие среды развились до такой степени, что могут выявить ошибку типа еще до начала компиляции! Таким образом, в наши дни венгерская запись и другие формы кодирования типов в именах превратились в обычные пережитки прошлого. Они усложняют изменение имени или типа переменных, функций и классов. Они затрудняют чтение кода. Наконец, они повышают риск того, что система кодирования собьет с толку читателя кода.
Код должен быть как можно более выразительным. Слишком длинные выражения, венгерская запись, «волшебные числа» — все это скрывает намерения автора.
Имена методов
Имена методов представляют собой глаголы или глагольные словосочетания.
Выберите одно слово для каждой концепции
Если вы используете переменную ЗаказанноеКоличество обозначая количество товара в заказе, придерживайтесь этого названия во всем модуле. Если же будет в одной части кода переменная ЗаказанноеКоличество, в другом просто Количество, в дальше КЗаказу это будет вызывать недоумение и лишнюю трату времени на сопоставления этих названий.
Функции
Компактность!
Первое правило: функции должны быть компактными. Второе правило: функции должны быть еще компактнее.
Желательно, чтобы длина функции не превышала 20 строк.
Блоки и отступы
Из сказанного выше следует, что блоки в командах if, else, while и т. д. должны состоять из одной строки, в которой обычно содержится вызов функции. Это не только делает вмещающую функцию более компактной, но и способствует документированию кода, поскольку вызываемой в блоке функции можно присвоить удобное содержательное имя.
Правило одной операции
Функция должна выполнять только одну операцию. Она должна выполнять ее хорошо. И ничего другого она делать не должна.
Чтение кода сверху вниз: правило понижения
Код должен читаться как рассказ — сверху вниз.
За каждой функцией должны следовать функции следующего уровня абстракции. Это позволяет читать код, последовательно спускаясь по уровням абстракции в ходе чтения списка функций. Я называю такой подход «правилом понижения».
Сказанное можно сформулировать и иначе: программа должна читаться так, словно она является набором абзацев, каждый из которых описывает текущий уровень абстракции и ссылается на последующие абзацы следующего нижнего уровня.
Команды switch
Написать компактную команду switch довольно сложно. Даже команда switch всего с двумя условиями занимает больше места, чем в моем представлении должен занимать один блок или функция.
Аргументы функций
В идеальном случае количество аргументов функции равно нулю (нульарная функция). Далее следуют функции с одним аргументом (унарные) и с двумя аргументами (бинарные). Функций с тремя аргументами (тернарных) следует по возможности избегать. Необходимость функций с большим количеством аргументов (полиарных) должна быть подкреплена очень вескими доводами — и все равно такие функции лучше не использовать.
плохо
ВыполнитьРасчет(Параметр1, Параметр2, Парметр3, Праметр4)
хорошо
ВыполнитьРасчет(ПараметрыРасчета)
Аргументы-флаги
Аргументы-флаги уродливы. Передача логического значения функции — воистину ужасная привычка. Она немедленно усложняет сигнатуру метода, громко провозглашая, что функция выполняет более одной операции. При истинном значении флага выполняется одна операция, а при ложном — другая!
плохо
УчитыватьСкидки= Истина;
ВыполнитьРасчет(УчитыватьСкидки);
хорошо
Если УчитыватьСкидки Тогда
ВыполнитьРасчетСУчетомСкидок();
Иначе
ВыполнитьРасчетБезУчетаСкидок();
КонецЕсли;
Бинарные функции
Функцию с двумя аргументами понять сложнее, чем унарную функцию. Например, вызов writeField(name) выглядит более доступно, чем writeField(outputStream, name). Хотя смысл обеих форм понятен, первая форма просто проскальзывает под нашим взглядом, моментально раскрывая свой смысл. Во второй форме приходится сделать непродолжительную паузу, пока вы не поймете, что первый
Тернарные функции
Разобраться в функции с тремя аргументами значительно сложнее, чем в бинарной функции. Проблемы соблюдения порядка аргументов, приостановки чтения и игнорирования увеличиваются более чем вдвое. Я рекомендую очень хорошо подумать, прежде чем создавать тернарную функцию.
Глаголы и ключевые слова
Выбор хорошего имени для функции способен в значительной мере объяснить смысл функции, а также порядок и смысл ее аргументов. В унарных функциях сама функция и ее аргумент должны образовывать естественную пару «глагол/существительное».
ПровестиДокументы(Документы);
Избавьтесь от побочных эффектов в функциях
Побочные эффекты суть ложь. Ваша функция обещает делать что-то одно, но делает что-то другое, скрытое от пользователя. Иногда она вносит неожиданные изменения в переменные своего класса — скажем, присваивает им значения параметров, переданных функции, или глобальных переменных системы. В любом случае такая функция является коварной и вредоносной ложью, которая часто приводит к созданию противоестественных временных привязок и других зависимостей.
Разделение команд и запросов
Функция должна что-то делать или отвечать на какой-то вопрос, но не одновременно. Либо функция изменяет состояние объекта, либо возвращает информацию об этом объекте. Совмещение двух операций часто создает путаницу.
Используйте исключения вместо возвращения кодов ошибок
Возвращение кодов ошибок функциями-командами является неочевидным нарушением принципа разделения команд и запросов. Оно поощряет использование команд в предикатных выражениях if :
Такие конструкции не страдают от смешения глаголов с прилагательными, но они приводят к созданию структур слишком глубокой вложенности. При возвращении кода ошибки возникает проблема: вызывающая сторона должна немедленно отреагировать на ошибку.
плохо
Результат= ПроизвестиРасчет();
Если Результат = Неопределено Тогда
Сообщить(«Ошибка»);
КонецЕсли;
хорошо
Функция ПроизвестиРасчет()
Попытка
Исключение
Сообщить(«»)
КонецПопытки
КонецФункции
Изолируйте блоки try/catch
Блоки try / catch выглядят весьма уродливо. Они запутывают структуру кода и смешивают обработку ошибок с нормальной обработкой. По этой причине тела блоков try и catch рекомендуется выделять в отдельные функции.
Функция ОпределитьПриоритет(ВидОперации)
Если ВидОперации = 1 Тогда
ИначеЕсли ВидОперации = 2 Тогда
Иначе
КонецЕсли
КонецФункции
Обработка ошибок как одна операция
Функции должны выполнять одну операцию. Обработка ошибок — это одна операция. Значит, функция, обрабатывающая ошибки, ничего другого делать не должна. Отсюда следует, что если в функции присутствует ключевое слово try, то оно должно быть первым словом в функции, а после блоков catch / finally ничего другого быть не должно (как в предыдущем примере).
Не повторяйтесь
Дублирование иногда считается корнем всего зла в программировании. Было создано много приемов и методологий, направленных на контроль и устранение дублирования.
Похоже, с момента изобретения подпрограмм все новшества в разработке программного обеспечения были направлены исключительно на борьбу с дублированием в исходном коде.
Комментарии
Не комментируйте плохой код — перепишите его. (Брайан У. Керниган и П. Дж. Плауэр)
Ничто не помогает так, как уместный комментарий. Ничто не загромождает модуль так, как бессодержательные и безапелляционные комментарии. Ничто не приносит столько вреда, как старый, утративший актуальность комментарий, распространяющий ложь и дезинформацию.
Грамотное применение комментариев должно компенсировать нашу неудачу в выражении своих мыслей в коде. Обратите внимание на слово «неудачу». Я абсолютно серьезно. Комментарий — всегда признак неудачи. Мы вынуждены использовать комментарии, потому что нам не всегда удается выразить свои мысли без них, однако гордиться здесь нечем.
Почему я так настроен против комментариев? Потому что они лгут. Не всегда и не преднамеренно, но это происходит слишком часто. Чем древнее комментарий, чем дальше он расположен от описываемого им кода, тем больше вероятность того, что он просто неверен. Причина проста: программисты не могут нормально сопровождать комментарии.
Комментарии не компенсируют плохого кода
Одной из распространенных причин для написания комментариев является низкое качество кода. Вы пишете модуль и видите, что код получился запутанным и беспорядочным. Вы знаете, что разобраться в нем невозможно. Поэтому вы говорите себе: «О, да это стоит прокомментировать!» Нет! Лучше исправьте свой код!
Объясните свои намерения в коде
плохо
РаспределитьСуммуПропорциональноКоэффициентам(РаспределяемаяСумма, КоэффициентыРаспределения, Точность);
хорошо
РаспределитьСуммуПропорциональноКоэффициентам(РаспределяемаяСумма, МассивКоэффициентов, Точность);
Юридические комментарии
Иногда корпоративные стандарты кодирования заставляют нас вставлять комментарии по юридическим соображениям. Например, заявление об авторских правах — необходимая информация, которая вполне может размещаться в комментарии в начале каждого файла с исходным кодом.
// Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.
// Публикуется на условиях лицензии GNU General Public License версии 2 и выше.
Информативные комментарии
Иногда бывает полезно включить в комментарий пояснение к коду.
Предупреждения о последствиях
Иногда бывает полезно предупредить других программистов о нежелательных
последствиях от каких-либо действий. Например, следующий комментарий объясняет, почему конкретный тестовый сценарий был отключен:
// Не запускайте, если только не располагаете
// излишками свободного времени.
Комментарии TODO
Иногда бывает полезно оставить заметки «на будущее» в форме комментариев
// TODO - На данный момент эта функция не используется.
Усиление
Комментарий может подчеркивать важность обстоятельства, которое на первый взгляд кажется несущественным
Ссылки на авторов
// Добавлено Петей 10.05.2019
Системы контроля исходного кода отлично запоминают, кто и когда внес то или иное исправление. Нет необходимости загрязнять код подобными ссылками.
Закомментированный код
В программировании редко встречаются привычки более отвратительные, чем закрытие комментариями неиспользуемого кода. Никогда не делайте этого!
Нелокальная информация
Если вы должны написать комментарий, проследите за тем, чтобы он описывал находящийся поблизости код. Не излагайте информацию системного уровня в контексте локального комментария.
Заголовки функций
Короткие функции не нуждаются в долгих описаниях. Хорошо выбранное имя компактной функции, которая выполняет одну операцию, обычно лучше заголовка с комментарием.
УдалитьВременныйКаталог(Знач ПутьККаталогу);
Форматирование
Мы хотим, чтобы читатель, заглянувший «под капот» программы, был поражен увиденным — нашей аккуратностью, логичностью и вниманием к мелочам. Мы хотим, чтобы на него произвела впечатление стройность кода. Мы хотим, чтобы он уважительно поднял брови при просмотре модулей. Мы хотим, чтобы наша работа выглядела профессионально. Если вместо этого читатель видит беспорядочную массу кода, словно написанного шайкой пьяных матросов, то он заключит, что такое же неуважение к мелочам проникло и во все остальные аспекты проекта.
Вы должны позаботиться о том, чтобы ваш код был хорошо отформатирован. Выберите набор простых правил, определяющих формат кода, и последовательно применяйте их в своей работе. Если вы работаете в составе группы, то группа должна выработать согласованный набор правил форматирования, соблюдаемых всеми участниками. Также полезно иметь средства автоматизации, которые применяют правила форматирования за вас.
Вертикальное форматирование
Типичная длина кода составляет 200 строк, с верхним пределом в 500 строк. Хотя это не должно считаться раз и навсегда установленным правилом, такие показатели весьма желательны. Маленькие файлы обычно более понятны, чем большие.
Газетная метафора
Представьте себе хорошо написанную газетную статью. Естественно, статья читается по вертикали. В самом начале обычно располагается заголовок с общей темой статьи; он помогает вам решить, представляет ли статья интерес для вас. В первом абзаце приводится краткое изложение сюжета на уровне общих концепций, без приведения каких-либо подробностей. По мере продвижения к концу статьи объем детализации непрерывно растет, пока вы не узнаете все даты, имена, цитаты и т. д.
Исходный файл должен выглядеть как газетная статья. Имя файла должно быть простым, но содержательным. Одного имени должно быть достаточно для того, чтобы читатель понял, открыл ли он нужный модуль или нет. Начальные блоки исходного файла описывают высокоуровневые концепции и алгоритмы. Степень детализации увеличивается при перемещении к концу файла, а в самом конце собираются все функции и подробности низшего уровня в исходном файле. Газета состоит из множества статей, в большинстве своем очень коротких. Другие статьи чуть длиннее. И лишь немногие статьи занимают всю газетную страницу. Собственно, именно этим газеты так удобны. Если бы они состояли из одной длинной статьи с неупорядоченной подборкой фактов, дат и имен, то мы бы просто не смогли их читать.
Вертикальное разделение концепций
Практически весь код читается слева направо и сверху вниз. Каждая строка представляет выражение или условие, а каждая группа строк представляет законченную мысль. Эти мысли следует отделять друг от друга пустыми строками.
Вертикальное сжатие
Если вертикальные пропуски разделяют концепции, то вертикальное сжатие подчеркивает тесные связи. Таким образом, строки кода, между которыми существует тесная связь, должны быть «сжаты» по вертикали.
&НаКлиенте
Процедура СписокНоменклатураВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
**
ВыбратьКоличество();
КонецПроцедуры
&НаКлиенте
Процедура ВыбратьКоличество()
Оповещение= Новый ОписаниеОповещения("ВыборКоличестваЗавершение", ЭтотОбъект);
***
КонецПроцедуры
&НаКлиенте
Процедура ВыборКоличестваЗавершение(Результат, ДопПараметры) Экспорт
***
КонецПроцедуры
Вертикальные расстояния
Вам когда-нибудь доводилось метаться по классу, прыгая от одной функции к другой, прокручивая исходный файл вверх-вниз, пытаясь разобраться, как функции связаны друг с другом и как они работают, — только для того, чтобы окончательно заблудиться в его запутанных нагромождениях? Когда-нибудь искали определение функции или переменной по цепочкам наследования? Все это крайне неприятно, потому что вы стараетесь понять, как работает система, а вместо этого вам приходится тратить время и интеллектуальные усилия на поиски и запоминание местонахождения отдельных фрагментов.
Концепции, тесно связанные друг с другом, должны находиться поблизости друг от друга по вертикали.
Объявления переменных.
Переменные следует объявлять как можно ближе к месту использования.
Зависимые функции.
Если одна функция вызывает другую, то эти функции должны располагаться вблизи друг от друга по вертикали, а вызывающая функция должна находиться над вызываемой (если это возможно). Тем самым формируется естественная структура программного кода. Если это правило будет последовательно соблюдаться, читатели кода будут уверены в том, что определения функций следуют неподалеку от их вызовов.
&НаКлиенте
Процедура ПересчитатьЦеныВКорзине()
КонецПроцедуры
&НаКлиенте
Процедура ПересчитатьЦеныВСтрокеКорзины(ТекущаяСтрока)
КонецПроцедуры
Концептуальное родство.
Некоторые фрагменты кода требуют, чтобы их разместили вблизи от других фрагментов. Такие фрагменты обладают определенным концептуальным родством. Чем сильнее родство, тем меньше должно быть вертикальное расстояние между ними.
Вертикальное упорядочение
Как правило, взаимозависимые функции должны размещаться в нисходящем порядке. Иначе говоря, вызываемая функция должна располагаться ниже вызывающей функции. Так формируется логичная структура модуля исходного кода – от высокого уровня к более низкому. Как и в газетных статьях, читатель ожидает, что самые важные концепции будут изложены сначала, причем с минимальным количеством второстепенных деталей. Низкоуровневые подробности естественно приводить в последнюю очередь. Это позволяет нам бегло просматривать исходные файлы, извлекая суть из нескольких начальных функций, без погружения в подробности.
Горизонтальное форматирование
Строки лучше делать по возможности короткими.
Длинна строки должна быть от 80 символов до 120 символов.
Горизонтальное разделение и сжатие
Горизонтальные пропуски используются для группировки взаимосвязанных элементов и разделения разнородных элементов.
Пример сжатие при арифметических
Горизонтальное выравнивание операциях.
Нет необходимости в горизонтальном выравнивании
Отступы
Код должен содержать отступы. Благо сейчас за отступы отвечают редакторы кода.
Правила форматирования в группах
У каждого программиста есть свои любимые правила форматирования, но если он работает в группе, то должен руководствоваться групповыми правилами.
Группа разработчиков согласует единый стиль форматирования, который в дальнейшем применяется всеми участниками.
Код программного продукта должен быть оформлен в едином стиле. Он не должен выглядеть так, словно был написан несколькими личностями, расходящимися во мнениях по поводу оформления.
Обработка ошибок
Используйте исключения вместо кодов ошибок
Начните с написания команды try-catch-finally
Размещая код в секции try команды try - catch - finally, вы утверждаете, что выполнение программы может прерваться в любой точке, а затем продолжиться в секции catch.
Блоки try в каком-то отношении напоминают транзакции. Секция catch должна оставить программу в целостном состоянии, что бы и произошло в секции try. По этой причине написание кода, который может инициировать исключения, рекомендуется начинать с конструкции try - catch - finally. Это поможет вам определить, чего должен ожидать пользователь кода, что бы ни произошло в коде try.
Не возвращайте null
На мой взгляд, при любых обсуждениях обработки ошибок необходимо упомянуть о неправильных действиях программистов, провоцирующих ошибки.
На первом месте в этом списке стоит возвращение null. Я видел бесчисленное множество приложений, в которых едва ли не каждая строка начиналась с проверки null.
Если ваша кодовая база содержит подобный код, возможно, вы не видите в нем ничего плохого, но это не так! Возвращая null, мы фактически создаем для себя лишнюю работу, а для вызывающей стороны — лишние проблемы. Стоит пропустить всего одну проверку null, и приложение «уходит в штопор».
Не передавайте null
Возвращать null из методов плохо, но передавать null при вызове еще хуже. По возможности избегайте передачи null в своем коде.
***
Если честно, то ко многим принципам описанным в этой заметки я пришел сам, за более чем десятилетий стаж работы программистом. Значит рано или поздно каждый должен выработать в себе определённые принципы помогающие писать понятный и поддерживаемый код.
Эти принципы несколько расходятся с теми, что описаны в стандартах разработки 1С. Но лично мне они нравятся больше. Например, принцип "Зависимые функции" позволяет не раскидывать логически связанные функции по модулям, избавляет от долгих переходов. В книжке автор приводит пример, когда регистрировал свои действия при правке кода и с ужасом обнаружил, что больше всего времени тратимся на скроллинг мышкой. Да и многие другие моменты очень помогают ориентироваться в хорошо написанном коде. В передаче кучи параметров в функции я стал по другому относится. Не возвращать Неопределенно тоже заставляет задуматься.
В общем, статья к размышлению.