CodingStyle/C/C++/MartinRules

Материал из Etersoft wiki
Перейти к навигацииПерейти к поиску

__NOTOCNUM__

Общее

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

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

Комментарии к заголовкам функций

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

Зависимые функции

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

Тесты тоже любят чистоту

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

Одна концепция на тест

В одном тесте должна тестироваться одна концепция, а количество директив assert на концепцию должно быть минимальным.


Комментарии

С1: Неуместная информация

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


С2: Устаревший комментарий

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

СЗ: Избыточный комментарий

Избыточным считается комментарий, описывающий то, что и так очевидно.

Например:

i++; // Увеличение переменной i
....

С4: Плохо написанный комментарий

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

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

Фрагменты закомментированного кода выводят меня из себя. Кто знает, когда был написан этот код? Кто знает, есть от него какая-нибудь польза или нет? Однако никто не удаляет закомментированный код — все считают, что он понадобится кому-то другому. ...


Рабочая среда

Е1: Построение состоит из нескольких этапов

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

svn get mySystem
cd mySystem
ant all

E2: Тестирование состоит из нескольких этапов

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

Функции

F1: Слишком много аргументов

Функции должны иметь небольшое количество аргументов. Лучше всего, когда аргументов вообще нет; далее следуют функции с одним, двумя и тремя аргументами.

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

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

F3: Флаги в аргументах

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

F4: Мертвые функции

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


Разное

G1: Несколько языков в одном исходном файле

Современные среды программирования позволяют объединять в одном исходном файле код, написанный на разных языках. Например, исходный файл на языке Java может содержать вставки XML, HTML, YAML, JavaDoc, English, JavaScript и т. д. Следует свести к минимуму как количество, так и объем кода на дополнительных языках в исходных файлах.

G2: Очевидное поведение не реализовано

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

G3: Некорректное граничное поведение

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

G4: Отключенные средства безопасности

Отключать средства безопасности рискованно. Ручное управление serialVersion- UID бывает необходимо, но оно всегда сопряжено с риском. Иногда отключение некоторых (или всех!) предупреждений компилятора позволяет успешно построить программу, но при этом вы рискуете бесконечными отладочными сеансами.

G5: Дублирование

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

G6: Код на неверном уровне абстракции

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

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

G7: Базовые классы, зависящие от производных

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

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

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

G9: Мертвый код

Мертвым кодом называется код, не выполняемый в ходе работы программы. Он содержится в теле команды if, проверяющей невозможное условие. Он содержится в секции catch для блока try, никогда не инициирующего исключения. Он содержится в маленьких вспомогательных методах, которые никогда не вызываются, или в никогда не встречающихся условиях switch/case. Дело в том, что мертвый код не обновляется при изменении архитектуры. Он был написан в то время, когда система была другой. Обнаружив мертвый код, сделайте то, что положено делать в таких случаях: достойно похороните его. Удалите его из системы.

G10: Вертикальное разделение

Переменные и функции должны определяться вблизи от места их использования. Локальные переменные должны объявляться непосредственно перед первым использованием и должны обладать небольшой вертикальной областью видимости. Объявление локальной переменной не должно отдаляться от места ее использования на сотню строк.

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

G11: Непоследовательность

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

G12: Балласт

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

G13: Искусственные привязки

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

G14: Функциональная зависть

Это один из «запахов кода», описанных у Мартина Фаулера [Refactoring]. Для методов класса должны быть важны переменные и функции того класса, которому они принадлежат, а не переменные и функции других классов. Когда метод использует методы доступа другого объекта для манипуляций с его данными, то он завидует области видимости класса этого объекта. Он словно мечтает находиться в другом классе, чтобы иметь прямой доступ к переменным, с которыми он работает.

G15: Аргументы-селекторы

Ничто так не раздражает, как висящий в конце вызова функции аргумент false. Зачем он здесь? Что изменится, если этот аргумент будет равен true? Смысл селектора трудно запомнить, но дело не только в этом — селектор указывает на объединение нескольких функций в одну.

G16: Непонятные намерения

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

G18: Неуместные статические методы

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

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

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

G20: Имена функций должны описывать выполняемую операцию

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

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

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

G22: Преобразование логических зависимостей в физические

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

G23: Используйте полиморфизм вместо if/Else или switch/Case

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

G24: Соблюдайте стандартные конвенции

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

G25: Заменяйте «волшебные числа» именованными константами

Вероятно, это одно из самых древних правил разработки. Помню, оно встречалось мне еще в 60-х годах, в учебниках COBOL, FORTRAN и PL/1 для начинающих. В общем случае присутствие «сырых» чисел в коде нежелательно. Числа следует скрыть в константах с содержательными именами.

G26: Будьте точны

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

G27: Структура важнее конвенций

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

G28: Инкапсулируйте условные конструкции

В булевской логике достаточно трудно разобраться и вне контекста команд i f или while. Выделите в программе функции, объясняющие намерения условной конструкции. Например, команда

if (shouldBeDeleted(timer)) 

выразительнее команды

if (timer. hasExpi red О && Itimer.isRecurrentO) 

G29: Избегайте отрицательных условий

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

G30: Функции должны выполнять одну операцию

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

G31: Скрытые временные привязки

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

G32: Структура кода должна быть обоснована

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

G33: Инкапсулируйте граничные условия

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

G34: Функции должны быть написаны на одном уровне абстракции

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

G35: Храните конфигурационные данные на высоких уровнях

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

G36: Избегайте транзитивных обращений

В общем случае модуль не должен обладать слишком полной информацией о тех компонентах, с которыми он взаимодействует. Точнее, если А взаимодействует с В, а В взаимодействует с С, то модули, использующие А, не должны знать о С (то есть нежелательны конструкции вида а. getB(). getCC). doSomethi ng();). Иногда это называется «законом Деметры». Прагматичные программисты используют термин «умеренный код»


Имена

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

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

N2: Выбирайте имена на подходящем уровне абстракции

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

N3: По возможности используйте стандартную номенклатуру

Имена проще понять, если они основаны на существующих конвенциях или стандартных обозначениях. Например, при использовании паттерна ДЕКОРАТОР можно включить в имена декорирующих классов слово Decorator. Например, имя AutoHangupModemDecorator может быть присвоено классу, который дополняет класс Modem возможностью автоматического разрыва связи в конце сеанса.

N4: Недвусмысленные имена

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

N5: Используйте длинные имена для длинных областей видимости

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

N6: Избегайте кодирования

Информация о типе или области видимости не должна кодироваться в именах. Префиксы вида t_ или f бессмысленны в современных средах.

N7: Имена должны описывать побочные эффекты

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

Тесты

Т1: Нехватка тестов

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

Т2: Используйте средства анализа покрытия кода

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

ТЗ: Не пропускайте тривиальные тесты

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

Т4: Отключенный тест как вопрос

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

Т5: Тестируйте граничные условия

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

Т6: Тщательно тестируйте код рядом с ошибками

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

Т7: Закономерности сбоев часто несут полезную информацию

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

Т8: Закономерности покрытия кода часто несут полезную информацию

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

Т9: Тесты должны работать быстро

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

http://www.artiom.pro/2012/08/rober-martin-clean-code-handbook-of.html