Программисты в первую очередь работают с языком. Поэтому написание программ похоже на любой другой вид письменной работы. Сначала вы излагаете свои мысли как есть, а затем «причесываете» до тех пор, пока текст не будет легко читаться. Качество кода – результат проявления небезразличного отношения к делу и показатель профессионализма.
Почему важна читаемость кода
Чтение кода происходит чаще, чем написание. Есть большая разница между обучением программированию и реальной работой в компании. Вначале мы и пишем, и читаем собственные программы. Но чем дальше мы продвигаемся, тем чаще нам приходится не писать, а читать код. Чем легче код читается, тем проще с ним работать другим людям.
Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете
Чем проще читать код, тем проще его сопровождать. Понятный, читаемый код легче тестировать, в нем легче отлавливать ошибки – они не скрываются в его запутанной структуре. Плохо оформленный код неприятно изучать, читать, тестировать, сложно дополнять. Рано или поздно плохой код становится проще переписать.
Эстетическое восприятие кода влияет на удобство работы. Казалось бы, гораздо важнее производительность, возможность модификации, расширения… Но все эти показатели улучшаются, если код соответствует нашему чувству прекрасного. Глядя на качественно написанный код, можно быстро понять алгоритм и то, как работает программа для разных входных данных. Чистый код читается, как хорошо написанная проза: слова превращаются в зрительные образы.
Стиль кода определяет его будущее. Стиль и дисциплина продолжают жить в коде, даже если в нем не осталось ни одной исходной строки.
С чего начать: документация по стилю оформления кода
Все дороги программиста ведут к документации. В каждом языке существует свой стандарт оформления кода. Для Python используется документ PEP-8, для PHP – стандартные рекомендации PSR-1 и PSR-2, для Java – Java Coding Conventions, для JavaScript – Airbnb JavaScript Style Guide или Google JavaScript Style Guide. Документ для вашего языка вы найдете по поисковому запросу <Название языка> Code Style.
Когда вы работаете в группе разработчиков, нужно использовать принятые в команде правила. Стиль должен быть единым, как будто код был написан одним здравомысленным человеком.
В популярных IDE заложена возможность автоматической настройки стиля кода под стандарты – общие или предложенные командой. Разберитесь, как настроить среду под необходимое оформление. Потраченное время сэкономит многие часы рутинной работы.
Применение стандартов – лучший подход для новичка. Читающий не будет отвлекаться на оформление и сразу погрузится в тонкости выбранных подходов, а не расстановок переносов. Изложенные ниже правила понадобятся для того, чтобы понять, как действовать в тех случаях, когда стандарт не дает никаких рекомендаций.
Роберт Мартин «Чистый код. Создание, анализ и рефакторинг»
Как Библиотека программиста, мы не могли обойтись без упоминания замечательной книги Роберта Мартина о чистом коде и анализе программ. В книге приводятся примеры для языка Java, но большинство идей справедливы для любых языков.
Книга в сообществе Книги для программистов
Книга на Ozon
Всё что изложено ниже, в значительной мере представляет сжатый конспект этой книги с дополнениями из нашего опыта в проектировании программ. Итак, приступим к практикам.
Главное правило чистого кода: выразительные имена
Содержательность. К выбору названий любых объектов нужно подходить со всей ответственностью. Выразительные имена позволяют писать код, не требующий комментариев.
Полезно не только исходно выбирать ясные имена, но и заменять названия на более удачные, если они нашлись позже. Современные среды программирования позволяют легко заменить название переменной во всём коде, так что это не должно быть проблемой.
Сравните. До:
public List < int[] > getThem() {
List < int[] > list1 = new ArrayList < int[] > ();
for (int[] x: theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
После:
public List < int[] > getFlaggedCells() {
List < int[] > flaggedCells = new ArrayList < int[] > ();
for (int[] cell: gameBoard)
if (cell[STATUS_VALUE] == FLAGGED)
flaggedCells.add(cell);
return flaggedCells;
}
В первом примере непонятно, что вообще происходит, хотя в этом коде нет ни сложных выражений, ни каких-либо странностей. В результате правок сам код никак не изменился. Если знать, что это часть игры «Сапер», то теперь из кода понятно: здесь обрабатывается список ячеек игрового поля. Этот код можно улучшать и далее, но уже в результате простого переименования переменных стало понятно, что происходит.
Избегайте любых двусмысленностей и ложных ассоциаций. Если в объекте перечисляется список, но сам объект не является списком, нельзя в составе его названия употреблять слово list
– это запутывает читающего.
Остерегайтесь малозаметных различий – имена объектов должны существенно отличаться друг от друга. По этой причине плохи длинные имена с повторяющимся элементами – чтобы сличить их друг с другом, тратятся лишние силы и время. Избегайте использования в именах переменных строчной буквы L и прописных I, O – они часто путаются с единицей и нулем.
Путаница также возникает, если несколько синонимичных слов и выражений используются для обозначениях разных сущностей, например, controller
, manager
и driver
.
Имя должно легко произноситься. Используйте для названий слова. Если названия состоят из сокращений, каждый начинает произносить их по-своему, что затрудняет взаимопонимание. А при чтении кода каждый раз «спотыкаешься» о такое название.
Имя должно быть удобным для поиска. Слишком короткие имена трудно искать в большом объеме текста. Однобуквенные имена можно использовать только для локальных переменных в коротких методах и для счетчиков циклов (i, j, k
). Обычно называя объект одной буквой, вы всего лишь создаете временный заменитель. Но не бывает ничего более постоянного, чем что-то «временное». Проверяйте грамотность написания выбранных слов.
Правильно выбирайте часть речи. Классы и объекты желательно называть существительными и их комбинациями: Account
, WikiPage
, HTMLParser
. Имена функций и методов лучше представлять глаголами или глагольными словосочетаниями: delete_page
, writeField(name)
. Для методов чтения/записи и предикатов используйте стандартные префиксы get
, set
, is
.
Заменяйте «магические» числа именованными константами. Одно из самых древних правил разработки. Магическими называют числа, о которых сходу нельзя сказать, что они означают. Например: 100
, 1.1
, 42
, 1000000
. Выделяйте такие числа в соответствующие константы с конкретным названиями. Например, вместо числа 86400
в теле кода приятнее встретить константу SECONDS_PER_DAY
.
Не стоит следовать этому правилу, как и любому другому, безоговорочно. В формулах некоторые константы лучше воспринимаются в числовой записи.
Одно слово для каждой концепции. Для одной и той же идеи, реализующей одну механику, используйте одно слово. Например, для добавления элементов одинаковым образом – метод add
. Однако, если механика и семантика изменились, потребуется и другое слово (например, insert
, append
), описывающее новую концепцию.
Ваш код будут читать программисты. Не стесняйтесь использовать термины из области информатики, общепринятые названия алгоритмов и паттернов. Такие имена сообщают информацию быстрее, чем сам код.
Помещайте имена в соответствующий контекст. Например, имена street
, house_number
, city
понятнее смотрятся внутри класса Address
.
Избегайте остроумия и каламбуров в названиях. Шутки имеют свойство быть понятными лишь ограниченное время и для конкретной аудитории, знакомой с первоисточником. Отдавайте предпочтение ясности перед развлекательностью. Шутки можно приберечь для презентации, которая происходит «здесь и сейчас». Хороший код способен выйти далеко за границы вашей культуры.
Среды разработки продолжают развиваться. Уже нет никакой необходимости кодировать типы в именах, создавать префиксы для членов классов. Всю нужную информацию можно получить из цветового выделения или контекстно-зависимых подсказок сред разработки. Добавление префиксов убивает удобство поиска по автодополнению – выпадает слишком много имен, начинающихся с одинаковых символов.
Функции
Компактность. Уже в 80-е годы считалось, что функция должна занимать не более одного экрана. Экраны VT100 состояли из 24 строк и 80 столбцов. В наши дни на экране можно разместить гораздо больше инфорфмации, но лучше ограничиться тем же объемом. Самоограничение позволяет видеть точку объявления каждой используемой переменной и держать в уме всю «историю», которую рассказывает функция.
Вполне вероятно, что тот, кто будет сопровождать ваш код, не будет иметь возможности работать на большом мониторе. Например, ему необходимо одновременно разместить на одном рабочем столе экрана ноутбука несколько окон. Среды разработки позволяют установить ограничение, «верхнюю планку» (то есть правую 😉 ).
Блоки if, else, while должны иметь минимальный размер, чтобы информацию о них можно было держать в уме. Старайтесь избегать отрицательных условий – на их восприятие обычно уходит чуть больше времени, чем на положительные. То есть запись if (buffer.shouldCompact())
предпочтительнее записи if (!buffer.shouldNotCompact()
.
Правило одной операции. Плохой код пытается сделать слишком много всего, намерения программиста расплываются для читателя. Поэтому стоит ввести важное правило:
Функция должна выполнять только одну операцию, выполнять ее хорошо, и ничего другого она делать не должна.
Каждая функция должна делать то, что вы от нее ожидали из названия. Если функция действует не так, как она названа, читатель кода перестает доверять автору программы, ему приходится самостоятельно разбираться во всех подробностях реализации.
Я люблю, чтобы мой код был элегантным и эффективным. Логика должны быть достаточно прямолинейной, чтобы ошибкам было трудно спрятаться; зависимости — минимальными, чтобы упростить сопровождение; обработка ошибок — полной в соответствии с выработанной стратегией; а производительность — близкой к оптимальной, чтобы не искушать людей загрязнять код беспринципными оптимизациями. Чистый код хорошо решает одну задачу.
Исключения вместо кодов ошибок. Используйте исключения (try-catch
, try-except
) вместо возвращения кодов ошибок. Возвращение кодов приводит к слишком глубокой вложенности.
К тому же при использовании исключений код обработки ошибок изолируются от ветви нормального выполнения. Сами блоки лучше выделять в отдельные функции. Вместе с исключением нужно передавать контекст ошибки – сообщение, содержащее сведения об операции и типе сбоя.
Соблюдайте уровни абстракции. Одна функция – один уровень абстракции. Смешение уровней абстракции создает путаницу, функция обрастает слишком большим количеством второстепенных подробностей. Старайтесь соблюдать ясную иерархию.
Код читается сверху вниз. По мере чтения уровни абстракции должны меняться равномерно. Каждая функция должна быть окружена функциями единого уровня абстракции.
Ограничивайте число аргументов. Чем больше аргументов у функции, тем сложнее с ней работать. Необходимость функций с количеством аргументов большим двух должна быть подкреплена очень вескими доводами. Каждый новый аргумент критически усложняет процедуру тестирования. Если функция должна получать более двух аргументов, скорее всего, эти аргументы образуют концепцию, заслуживающую собственного имени.
Комментарии
Это непопулярное мнение, но в большинстве случаев комментарии – зло. Код должен быть самодокументированным. Комментарий – всегда признак неудачи: мы не смогли написать код так, что он понятен без комментариев. Проверьте, можно ли выразить свое намерение в самом коде.
В чём проблема? Программисты умеют сопровождать код, но не комментарии. В растущем коде комментарии быстро устаревают, частично или полностью переставая соответствовать ситуации. Только код правдиво сообщает своим содержанием, что он в действительности делает. Лучше потратить время на исправление запутанного кода, чем добавлять к плохому коду комментарии.
Однако есть несколько видов комментариев, которые выглядят достаточно оправданными.
TODO-комментарии. Бывает так: нужно было успеть к дедлайну, пришлось писать код быстро, поэтому в нем остались дыры. То есть всё работает, но реализация ущербная. Укажите все недоработки и создайте под них задачи. Каждый комментарий указывает на недоработку или потенциальную уязвимость.
Юридические комментарии. Корпоративные стандарты могут принуждать вставлять комментарии по юридическим соображениям. Ограничьтесь в таком комментарии описанием лицензии и ссылкой на внешний документ.
Предупреждения о последствиях. Иногда бывает полезно предупредить других программистов о нежелательных последствиях:
// Не запускайте, если только не располагаете
// излишками свободного времени.
Комментарий также может подчеркивать важность обстоятельства, которое на первый взгляд кажется несущественным.
По-настоящему плохие комментарии
Бывают такие типы комментариев, которые лучше никогда не делать.
Закомментированный программный код. «Когда-нибудь в будущем раскомментирую этот код, приведу всё в порядок. Или вдруг эта идея кому-то поможет». Любой закомментированный код только ухудшает ситуацию. Все изменения хранятся в контроле версий – удаляйте такой код на корню. Это просто мусор: «потом» равносильно «никогда». Если что-то действительно нужно сделать, создайте краткий TODO-комментарий и задачу.
Мертвые функции – идентичные по смыслу предыдущему пункту функции и методы, которые не вызываются в программе. Пользуйтесь системой контроля версий и без зазрений совести удаляйте любой код, который не используется во время выполнения программы.
Избыточные комментарии. Задайте себе вопрос: стал ли код понятнее после прочтения комментария? Часто комментарии просто загромождают код и скрывают его смысл, излагая очевидные вещи. Иногда в комментарии включаются описания не относящихся к делу подробностей. Но профессионал бережет не только свое, но и чужое время, и не отвлекает читающего без повода.
Журнальные комментарии и ссылки на авторов. Некоторые программисты добавляют комментарий в начало файла при редактировании. Или указывают, кто и когда внес исправления. Когда-то это было оправдано, но теперь у нас есть системы контроля версий – это гораздо лучший способ обозначить границы зоны ответственности каждого.
Позиционные маркеры. Иногда любят отмечать определенные группы и позиции в исходных файлах:
// Классы //////////////////////////////////
Качественно организованный код способен внятно рассказать историю без балластных заголовков.
Уровень файлов программ
Минималистичность. Чем меньше кода, тем лучше. Имя файла должно быть простым, но содержательным. Маленькие файлы обычно более понятны, чем большие. Но размер файла, конечно, не должен быть самоцелью.
Код должен быть максимально линейным. Чем больше вложенность кода, тем сложнее его читать. Следите за тем, как двигаются ваши глаза. В хорошем коде вы двигаетесь строка за строкой, лишь изредка возвращаясь к предыдущим строчкам. Вложенность более трех уровней указывает на то, что с кодом нужно поработать: переписать условия проверок и циклов (использовать return и функциональное программирование), разбить код на меньшие методы.
Отдельные «мысли» следует отделять друг от друга пустыми строками. Каждая пустая строка – зрительная подсказка: описание одной концепции закончилось, далее следует новая. При просмотре кода взгляд концентрируется на первых строчках – в них больше всего информации, как в началах абзацев этого текста.
Тесно связанные концепции, должны располагаться вблизи друг друга. Не заставляйте читателя прыгать между файлами или постоянно скроллить файл. По той же причине переменные нужно объявлять как можно ближе к месту использования. Однако переменные экземпляров лучше объявлять в одном месте, обычно в начале класса, так как в хорошо спроектированном классе переменные используются большинством методов класса.
Пробелы для группировки взаимосвязанных элементов. Пробелы улучшают читаемость кода, если они стоят вокруг операторов присваивания, после запятых при перечислении переменных. В формулах пробелы используются для подчеркивания приоритета: не ставятся между множителями, но отбивают знаки сложения и вычитания.
Отступы. Размер отступов должен соответствовать позиции кода в иерархии. Это общая практика, которая позволяет быстро пропускать области видимости, не относящиеся к текущей ситуации. Не поддавайтесь искушению нарушить правила расстановки отступов для коротких команд.
Некоторые замечания по поводу архитектуры и тестов
В системе должны выполняться все тесты. Тесты – главный способ, с помощью которого можно понять, что система контролируема. А только контролируемую систему можно проверить.
Три закона тестирования по методологии TDD. Тестовый код не менее важен, чем код продукта. Соблюдение следующих трех правил позволяет организовать работу так, чтобы тесты охватывали все аспекты кода продукта:
- Не пишете код продукта, пока не напишете отказной модульный тест.
- Не пишите модульный тест в объеме большем, чем необходимо для отказа.
- Не пишите код продукта в объеме большем, чем необходимо для прохождения текущего отказного теста.
F.I.R.S.T. Качественные тесты должны обладать пятью характеристиками, первые буквы которых образуют указанный акроним:
- Fast. Тесты должны выполняться быстро.
- Independent. Тесты не должны зависеть друг от друга и выполняться в любом порядке.
- Repeatable. Тесты должны давать воспроизводимые в любой среде результаты.
- Self-validating. Результат выполнения теста – логический признак: тест пройден или нет. Иначе результаты приобретают субъективный характер.
- Timely. Тест должен создаваться своевременно. Тесты нужно писать непосредственно перед написанием кода.
Повышение уровня абстракции и устранение дубликатов. Все программы состоят из очень похожих элементов, а все задачи программирования сводятся к работе с ограниченным набором действий. Многие из этих действий могут быть описаны в одних и тех же терминах, например, извлечение элемента из коллекции. В подобных ситуациях правильно инкапсулировать реализацию в более абстрактном классе. Повышение уровня абстракции позволяет избежать дублирования и многократно применения одного и того же кода, лучше понять, что действительно происходит в программе, уйдя от частностей.
Если что-то в программе делается снова и снова, значит, какая-то важная концепция не нашла своего отражения в коде. Нужно попытаться понять, что это такое, и выразить идею в виде кода. Избегайте дубликатов, это всегда лишняя работа, лишний риск, лишняя сложность.
Несколько языков в одном исходном файле. Современные среды программирования позволяют объединять в одном файле код, написанный на разных языках. Результат получается запутанным, неаккуратным и ненадежным. Чтобы четко разграничить ответственность, в файле должен преобладать один язык. Сведите к минимуму количество и объем кода на дополнительных языках.
Не нужно бездумно следовать догмам. Не переусердствуйте с сокращением кода функций и классов. Всегда руководствуйтесь здравым смыслом.
Заключение
Чистый код выглядит так, как если его автор над ним тщательно потрудился. Вы не можете найти очевидных возможностей для его улучшения. Попытавшись его улучшить, вы вновь вернетесь к тому же коду.
Чтобы писать чистый код, который бы никого не удивлял, необходимо раз за разом сознательно применять описанные приемы. При чтении чистого кода вы улыбаетесь, как при виде искусно сделанной музыкальной шкатулки. Код можно назвать красивым, если у вас создается впечатление, что язык был создан специально для этой задачи.
Расскажите нам о правилах, которые вы применяете для написания своего программного кода. Какие open source программы, на ваш взгляд, имеют лучшее качество кода?
1. Вступление
Сталкиваясь с необходимостью контролировать работу других программистов, начинаешь понимать, что, помимо вещей, которым люди учатся достаточно легко и быстро, находятся проблемы, для устранения которых требуется существенное время.
Сравнительно быстро можно обучить человека пользоваться необходимым инструментарием и документацией, правильной коммуникации с заказчиком и внутри команды, правильному целеполаганию и расстановке приоритетов (ну, конечно, в той мере, в которой сам всем этим владеешь).
Но когда дело доходит собственно до кода, все становится гораздо менее однозначно. Да, можно указать на слабые места, можно даже объяснить, что с ними не так. И в следующий раз получить ревью с абсолютно новым набором проблем.
Профессии программиста, как и большинству других профессий, приходится учиться каждый день в течение нескольких лет, а, по большому счету, и всю жизнь. Вначале ты осваиваешь набор базовых знаний в объеме N семестровых курсов, потом долго топчешься по различным граблям, перенимаешь опыт старших товарищей, изучаешь хорошие и плохие примеры (плохие почему-то чаще).
Говоря о базовых знаниях, надо отметить, что умение писать красивый профессиональный код — это то, что по тем или иным причинам, в эти базовые знания категорически не входит. Вместо этого, в соответствующих заведениях, а также в книжках, нам рассказывают про алгоритмы, языки, принципы ООП, паттерны дизайна…
Да, все это необходимо знать. Но при этом, понимание того, как должен выглядеть достойный код, обычно появляется уже при наличии практического (чаще в той или иной степени негативного) опыта за плечами. И при условии, что жизнь “потыкала” тебя не только в сочные образцы плохого кода, но и в примеры всерьез достойные подражания.
В этом-то и заключается вся сложность: твое представление о “достойном” и “красивом” коде полностью основано на личном многолетнем опыте. Попробуй теперь передать это представление в сжатые сроки человеку с совсем другим опытом или даже вовсе без него.
Но если для нас действительно важно качество кода, который пишут люди, работающие вместе с нами, то попробовать все же стоит!
2. Зачем нам нужен красивый код?
Обычно, когда мы работаем над конкретным программным продуктом, эстетические качества кода заботят нас далеко не в первую очередь.
Нам гораздо важнее наша производительность, качество реализации функционала, стабильность его работы, возможность модификации и расширения и т.д.
Но являются ли эстетические качества кода фактором, положительно влияющим на вышеперечисленные показатели?
Мой ответ: да, и при этом, одним из самых важных!
Это так, потому что красивый код, вне зависимости от субъективной трактовки понятия о красоте, обладает следующими (в той или иной степени сводимыми друг к другу) важнейшими качествами:
- Читаемость. Т.е. возможность, глядя на код, быстро понять реализованный алгоритм, и оценить, как будет вести себя программа в том или ином частном случае.
- Управляемость. Т.е. возможность в минимальные сроки внести в код требуемые поправки, избежав при этом различных неприятных предсказуемых и непредсказуемых последствий.
Почему эти качества действительно являются важнейшими, и как они способствуют повышению показателей, указанных в начале параграфа, уверен, очевидно любому, кто занимается программированием.
А теперь, чтобы от общих слов перейти к конкретике, давайте сделаем обратный ход и скажем, что именно читаемый и управляемый код обычно воспринимается нами как красивый и профессионально написанный. Соответственно, на обсуждении того, как добиться этих качеств, мы далее и сосредоточимся.
3. Три базовых принципа.
Переходя к изложению собственного опыта, отмечу, что, работая над читаемостью и управляемостью своего и чужого кода, я постепенно пришел к следующему пониманию.
Вне зависимости от конкретного языка программирования и решаемых задач, для того, чтобы фрагмент кода в достаточной степени обладал этими двумя качествами необходимо, чтобы он был:
- максимально линейным;
- коротким;
- самодокументированным.
Можно бесконечно перечислять различные хинты и техники, с помощью которых можно сделать код красивее. Но я утверждаю, что наилучших, или, во всяком случае, достаточно хороших результатов можно достигнуть, ориентируясь именно на эти три принципа.
Поэтому дальше я постараюсь подробно пояснить их суть, а также описать набор основных техник, с помощью которых можно привести свой код в соответствие с этими принципами.
4. Линеаризация кода.
Мне кажется, что из трех базовых принципов, именно линейность является самым неочевидным, и именно ей чаще всего пренебрегают.
Наверное, потому что за годы учебы (и, возможно, научной деятельности) мы привыкли обсуждать от природы нелинейные алгоритмы с оценками типа O(n3), O(nlogn) и т.д.
Это все, конечно, хорошо и важно, но, говоря о реализации бизнес-логики в реальных проектах, обычно приходится иметь дело с алгоритмами совсем другого свойства, больше напоминающими иллюстрации к детским книжкам по программированию. Что-то типа такого (взято из гугла):
Таким образом, с линейностью я связываю не столько асимптотическую сложность алгоритма, сколько максимальное количество вложенных друг в друга блоков кода, либо же уровень вложенности максимально длинного подучастка кода.
Например, идеально линейный фрагмент:
do_a();
do_b();
do_c();
И совсем не линейный:
do_a();
if (check) {
something();
} else {
anything();
if (whatever()) {
for (a in b) {
if (good(a)) {
something();
}
}
}
}
Именно “куски” второго типа мы и будем пытаться переписать при помощи определенных техник.
Примечание: поскольку здесь и далее нам потребуется примеры кода для иллюстрации тех или иных идей, сразу условимся, что они будут написаны на абстрактном обобщенном C-like языке, кроме тех случаев, когда потребуются особенности конкретного существующего языка. Для таких случаев будет явно указано, на каком языке написан пример (конкретно будут встречаться примеры на Java и Javascript).
4.1. Техника 1. Выделяем основную ветку алгоритма.
В подавляющем большинстве случаев в качестве основной ветки имеет смысл взять максимально длинный успешный линейный сценарий алгоритма.
Попробуем сделать это на основе “алгоритма авторемонта” на диаграмме выше:
- Клиент сообщает пожелания.
- Мастер осматривает и говорит стоимость.
- Поиск дефектов.
- Составляем заказ на запчасти.
- Берем предоплату, обозначаем срок.
- Клиент уезжает.
Именно эта основная ветка у нас в идеале должна быть на нулевом уровне вложенности.
listen_client();
if (!is_clean()) {
...
}
check_cost();
if (!client_agree()) {
...
}
find_defects();
if (defects_found()) {
...
}
create_request();
take_money();
bye();
Давайте для сравнения рассмотрим вариант, где на нулевом уровне находится альтернативная ветка, вместо основной:
listen_client();
if (!is_clean()) {
...
}
check_cost();
if (client_agree()) {
find_defects();
if (defects_found()) {
...
}
create_request();
take_money();
} else {
...
}
bye();
Как видно, уровень вложенности значительной части кода вырос, и смотреть на код в целом уже стало менее приятно.
4.2. Техника 2. Используем break, continue, return или throw, чтобы избавиться от блока else.
Плохо:
...
if (!client_agree()) {
...
} else {
find_defects();
if (defects_found()) {
...
}
create_request();
take_money();
bye();
}
Лучше:
...
if (!client_agree()) {
...
return;
}
find_defects();
if (defects_found()) {
...
}
create_request();
take_money();
bye();
Разумеется, неверным был бы вывод, что вообще никогда не нужно использовать оператор else. Во-первых, не всегда контекст позволяет поставить break, continue, return или throw (хотя часто как раз таки позволяет). Во-вторых, выигрыш от этого может быть не столь очевиден, как в примере выше, и простой else будет выглядеть гораздо проще и понятней, чем что-либо еще.
Ну и в-третьих, существуют определенные издержки при использовании множественных return в процедурах и функциях, из-за которых многие вообще расценивают данный подход как антипаттерн (мое личное мнение: обычно преимущества все-таки покрывают эти издержки).
Поэтому эта (и любая другая) техника должна восприниматься как подсказка, а не как безусловная инструкция к действию.
4.3. Техника 3. Выносим сложные подсценарии в отдельные процедуры.
Т.к. в случае “алгоритма ремонта” мы довольно удачно выбрали основную ветку, то альтернативные ветки у нас все остались весьма короткими.
Поэтому продемонстрируем технику на основе “плохого” примера из начала главы:
Плохо:
do_a()
if (check) {
something();
} else {
anything();
if (whatever()) {
for (a in b) {
if (good(a)) {
something();
}
}
}
}
Лучше:
procedure do_on_whatever() {
for (a in b) {
if (good(a)) {
something();
}
}
}
do_a();
if (check) {
something();
} else {
anything();
if (whatever()) {
do_on_whatever();
}
}
Обратите внимание, что правильно выбрав имя выделенной процедуре, мы, кроме того, сразу же повышаем самодокументированность кода. Теперь для данного фрагмента в общих чертах должно быть понятно, что он делает и зачем нужен.
Однако следует иметь в виду, что та же самодокументированность сильно пострадает, если у вынесенной части кода нет общей законченной задачи, которую этот код выполняет. Затруднения в выборе правильного имени для процедуры могут быть индикатором именно такого случая (см. п. 6.1).
4.4. Техника 4. Выносим все, что возможно, во внешний блок, оставляем во внутреннем только то, что необходимо.
Плохо:
if (check) {
do_a();
something();
if (whatever()) {
for (a in b) {
if (good(a)) {
something();
}
}
}
} else {
do_a();
anything();
if (whatever()) {
for (a in b) {
if (good(a)) {
something();
}
}
}
}
Лучше:
do_a();
if (check) {
something();
} else {
anything();
}
if (whatever()) {
for (a in b) {
if (good(a)) {
something();
}
}
}
4.5. Техника 5 (частный случай предыдущей). Помещаем в try…catch только то, что необходимо.
Надо отметить, что блоки try…catch вообще являются болью, когда речь идет о читаемости кода, т.к. часто, накладываясь друг на друга, они сильно повышают общий уровень вложенности даже для простых алгоритмов.
Бороться с этим лучше всего, минимизируя размер участка внутри блока. Т.е. все строки, не предполагающие появление исключения, должны быть вынесены за пределы блока. Хотя в некоторых случаях с точки зрения читаемости более выигрышным может оказаться и строго противоположный подход: вместо того, чтобы писать множество мелких блоков try..catch, лучше объединить их в один большой.
Кроме того, если ситуация позволяет не обрабатывать исключение здесь и сейчас, а выкинуть вниз по стеку, обычно лучше именно так и поступить. Но надо иметь в виду, что вариативность тут возможна только в том случае, если вы сами можете задавать или менять контракт редактируемой вами процедуры.
4.6. Техника 6. Объединяем вложенные if-ы.
Тут все очевидно. Вместо:
if (a) {
if (b) {
do_something();
}
}
пишем:
if (a && b) {
do_something();
}
4.7. Техника 7. Используем тернарный оператор (a? b: c) вместо if.
Вместо:
if (a) {
var1 = b;
} else {
var1 = c;
}
пишем:
var1 = a ? b : c;
Иногда имеет смысл даже написать вложенные тернарные операторы, хотя это предполагает от читателя знания приоритета, с которым вычисляются подвыражения тернарного оператора.
Вместо:
if (a) {
var1 = b;
} else if (aa) {
var1 = c;
} else {
var1 = d;
}
пишем:
var1 = a ? b :
aa ? c : d;
Но злоупотреблять этим, пожалуй, не стоит.
Заметим, что инициализация переменной var1 теперь осуществляется одной единственной операцией, что опять же сильно способствует самодокументированности (см п. 6.8).
4.8. Суммируя вышесказанное, попробуем написать полную реализацию алгоритма ремонта максимально линейно.
listen_client();
if (!is_clean()) {
wash();
}
check_cost();
if (!client_agree()) {
pay_for_wash();
bye();
return;
}
find_defects();
if (defects_found()) {
say_about_defects();
if (!client_agree()) {
pay_for_wash_and_dyagnosis();
bye();
return;
}
}
create_request();
take_money();
bye();
На этом можно было бы и остановиться, но не совсем здорово выглядит то, что нам приходится 3 раза вызывать bye() и, соответственно, помнить, что при добавлении новой ветки, его придется каждый раз писать перед return (собственно, издержки множественных return).
Можно было бы решить проблему через try…finally, но это не совсем правильно, т.к. в данном случае не идет речи об обработке исключений. Да и делу линеаризации такой подход бы сильно помешал.
Давайте сделаем так (на самом деле, я тут применил п. 5.1 еще до того, как его написал):
procedure negotiate_with_client() {
check_cost();
if (!client_agree()) {
pay_for_wash();
return;
}
find_defects();
if (defects_found()) {
say_about_defects();
if (!client_agree()) {
pay_for_wash_and_dyagnosis();
return;
}
}
create_request();
take_money();
}
listen_client();
if (!is_clean()) {
wash();
}
negotiate_with_client();
bye();
Если вы думаете, что мы сейчас записали что-то тривиальное, то, в принципе, так и есть. Однако уверяю, что во многих живых проектах вы увидели бы совсем другую реализацию этого алгоритма…
5. Минимизация кода.
Думаю, было бы лишним пояснять, что уменьшая количество кода, используемого для реализации заданного функционала, мы делаем код гораздо более читаемым и надежным.
В этом смысле, идеальное инженерное решение — это, когда ничего не сделано, но все работает, как требуется. Разумеется, в реальном мире крайне редко доступны идеальные решения, и поэтому у нас, программистов, пока еще есть работа. Но стремиться стоит именно к такому идеалу.
5.1. Техника 1. Устраняем дублирование кода.
О копипасте и вреде, от него исходящем, сказано уже так много, что добавить что-то новое было бы сложно. Тем не менее, программисты, поколение за поколением, интенсивно используют этот метод для реализации программного функционала.
Разумеется, самым очевидным методом борьбы с проблемой является вынесение переиспользуемого кода в отдельные процедуры и классы.
При этом всегда возникает проблема выделения общего из частного. Зачастую даже не всегда понятно, чего больше у похожих кусков кода: сходства или различий. Выбор тут делается исключительно по ситуации. Тем не менее, наличие одинаковых участков размером в пол-экрана сразу говорит о том, что данный код можно и нужно записать существенно короче.
Стоит также упомянуть весьма полезную технику устранения дублирования, описанную в п. 4.3.
Распространить ее можно дальше одних лишь операторов if. Например, вместо:
procedure proc1() {
init();
do1();
}
procedure proc2() {
init();
do2();
}
proc1();
proc2();
запишем:
init();
do1();
do2();
Или обратный вариант. Вместо:
a = new Object();
init(a);
do(a);
b = new Object();
init(b);
do(b);
запишем:
procedure proc(a) {
init(a);
do(a);
}
proc(new Object());
proc(new Object());
5.2. Техника 2. Избегаем лишних условий и проверок.
Лишние проверки — зло, которое можно встретить практически в любой программе. Сложно описать, насколько самые тривиальные процедуры и алгоритмы могут быть засорены откровенно ненужными, дублирующимися и бессмысленными проверками.
Особенно это касается, конечно же, проверок на null. Как правило, вызвано подобное извечным страхом программистов перед вездесущими NPE и желанием лишний раз от них перестраховаться.
Далеко не редким видом ненужной проверки является следующий пример:
obj = new Object();
...
if (obj != null) {
obj.call();
}
Не смотря на свою очевидную абсурдность, встречаются такие проверки с впечатляющей регулярностью. (Cразу же оговорюсь, что пример не касается тех редких языков и сред, где оператор new() может вернуть null. В большинстве случаев (в т.ч. в Java) подобное в принципе невозможно).
Сюда же можно включить десятки других видов проверок на заведомо выполненное (или заведомо не выполненное) условие.
Вот, например, такой случай:
obj = factory.getObject();
obj.call1();
// если бы obj был null, мы бы уже умерли )
if (obj != null) {
obj.call2();
}
Встречается в разы чаще, чем предыдущий тип проверок.
Третий пример чуть менее очевиден, чем первые два, но распространен просто повсеместно:
procedure proc1(obj) {
if (!is_valid(obj)) return;
obj.call1();
}
procedure proc2(obj) {
if (!is_valid(obj)) return;
obj.call2();
}
obj = factory.getObject();
if (is_valid(obj) {
proc1(obj);
proc2(obj);
}
Как видно, автор данного отрезка панически боится нарваться на невалидный объект, поэтому проверяет его перед каждым чихом. Не смотря на то, что иногда такая стратегия может быть оправдана (особенно, если proc1() и proc2() экспортируются в качестве API), во многих случаях это просто засорение кода.
Варианты тут могут быть разными:
- Для каждой процедуры обозначить входной контракт, требующий от вызывающего соблюдения определенных условий валидности. После этого удалить страхующие проверки и целиком перенести ответственность за валидность входных данных на вызывающий код. Для Java, например, хорошей практикой может быть пометка “опасных” методов и полей аннотацией @Nullable. Таким образом, мы неявно обозначим, что остальные поля и методы могут принимать/возвращать значение null.
- Вообще не создавать невалидных объектов! Этот подход предполагает выполнение всех процедур валидации на этапе создания объекта, чтобы сам факт успешного создания уже гарантировал валидность. Разумеется, для этого как минимум требуется, чтобы мы контролировали класс фабрики или иные механизмы создания объектов. Либо, чтобы имеющаяся реализация давала нам такую гарантию.
Может быть, на первый взгляд, эти варианты кажутся чем-то невозможным или даже страшным, тем не менее, в реальности с их помощью можно существенно повысить читаемость и надежность кода.
Еще одним полезным подходом является применение паттерна NullObject, предполагающего использование объекта с ничего не делающими, но и не вызывающими ошибок, методами вместо “опасного” null. Частным случаем такого подхода можно считать отказ от использования null для переменных-коллекций в пользу пустых коллекций.
Сюда же относятся специальные null-safe библиотеки, среди которых хотелось бы выделить набор библиотек apache-commons для Java. Он позволяет сэкономить огромное количество места и времени, избавив от необходимости писать бесконечные рутинные проверки на null.
5.3. Техника 3. Избегаем написания “велосипедов”. Используем по максимуму готовые решения.
Велосипеды — это почти как копипаст: все знают, что это плохо, и все регулярно их пишут. Можно лишь посоветовать хотя бы пытаться бороться с этим злом.
Большую часть времени перед нами встают задачи или подзадачи, которые уже множество раз были решены, будь то сортировка или поиск по массиву, работа с форматами 2D графики или long-polling сервера на Javascript. Общее правило заключается в том, что стандартные задачи имеют стандартное решение, и это решение дает нам возможность получить нужный результат, написав минимум своего кода.
Порой есть соблазн, вместо того, чтобы что-то искать, пробовать и подгонять, быстренько набросать на коленке свой велосипед. Иногда это может быть оправдано, но если речь идет о поддерживаемом в долгосрочной перспективе коде, минутная “экономия” может обернуться часами отладки и исправления ошибок на пропущенных корнер-кейсах.
С другой стороны, лишь слегка затрагивая достаточно обширную тему, хотелось бы сказать, что иногда “велосипедная” реализация может или даже должна быть предпочтена использованию готового решения. Обычно это верно в случае, когда доверие к качеству готового решения не выше, чем к собственному коду, либо в случае, когда издержки от внедрения новой внешней зависимости оказываются технически неприемлемы.
Тем не менее, возвращаясь к вопросу о краткости кода, безусловно, использование стандартных (например, apache-commons и guava для Java) и нестандартных библиотек является одним из наиболее действенных способов уменьшить размеры собственного кода.
5.4. Техника 4. Оставляем в коде только то, что действительно используется.
“Висящие” функции, которые никем нигде не вызываются; участки кода, которые никогда не выполняются; целые классы, которые нигде не используются, но их забыли удалить — уверен, каждый мог наблюдать такие вещи в своем проекте, и может быть, даже воспринимал их, как должное.
На деле же, любой код, в том числе неиспользуемый, требует плату за свое содержание в виде потраченного внимания и времени.
Поскольку неиспользуемый код реально не выполняется и не тестируется, в нем могут содержатся некорректные вызовы тех или иных процедур, ненужные или неправильные проверки, обращения к процедурам и внешним библиотекам, которые больше ни для чего не нужны, и множество других сбивающих с толку или просто вредных вещей.
Таким образом, удаляя ненужные и неиспользуемые участки, мы не только уменьшаем размеры кода, но и, что не менее важно, способствуем его самодокументированности.
5.5. Техника 5. Используем свои знания о языке и полагаемся на наличие этих знаний у читателя.
Одним из эффективных способов сделать свой код проще, короче и понятней является умелое использование особенностей конкретного языка: различных умолчаний, приоритетов операций, кратких форм записи и т.д.
В качестве иллюстрации приведу наиболее, с моей точки зрения, яркий пример для языка Javascript.
Очень часто при разборе строковых выражений можно увидеть такие нагромождения:
if (obj != null && obj != undefined && obj.s != null && obj.s != undefined && obj.s != '') {
// do something
}
Выглядит пугающе, в том числе и с точки зрения “а не забыл ли автор еще какую-нибудь проверку”. На самом деле, зная особенность языка Javascript, в большинстве подобных случаев всю проверку можно свести до тривиальной:
if (obj && obj.s) {
// do something
}
Дело в том, что благодаря неявному приведению к boolean, проверка if (obj) {} отсеет:
- false
- null
- undefined
- 0
- пустую строку
В общем, она отсеет большинство тривиальных значений, которые мы можем себе представить. К сожалению, повторюсь, программисты используют эту особенность достаточно редко, из-за чего их “перестраховочные” проверки выглядят весьма громоздко.
Аналогично, сравните следующие формы записи:
if (!a) {
a = defaultValue;
}
и
a = a || defaultValue;
Второй вариант выглядит проще, благодаря использованию специфичной семантики логических операций в скриптовых языках.
6. Самодокументированный код.
Термин “самодокументированность” наиболее часто употребляется при описании свойств таких форматов, как XML или JSON. В этом контексте подразумевается наличие в файле не только набора данных, но и сведений об их структуре, о названиях сущностей и полей, задаваемых этими данными.
Читая XML файл мы, даже ничего не зная о контексте, почти всегда можем составить представление о том, что описывает данный файл, что за данные в нем представлены и даже, возможно, как они будут использованы.
Распространяя эту идею на программный код, под термином “самодокументированность” хотелось бы объединить множество свойств кода, позволяющих быстро, без детального разбора и глубокого вникания в контекст понять, что делает данный код.
Хотелось бы противопоставить такой подход “внешней” документированности, которая может выражаться в наличии комментариев или отдельной документации. Не отрицая необходимости в определенных случаях того и другого, отмечу, что, когда речь идет о читаемости кода, методы самодокументирования оказываются значительно более эффективными.
6.1. Техника 1. Тщательно выбираем названия функций, переменных и классов. Не стоит обманывать людей, которые будут читать наш код.
Самое главное правило, которое следует взять за основу при написании самодокументированного кода — никогда не обманывайте своего читателя.
Если поле называется name, там должно храниться именно название объекта, а не дата его создания, порядковый номер в массиве или имя файла, в который он сериализуется. Если метод называется compare(), он должен именно сравнивать объекты, а не складывать их в хэш таблицу, обращение к которой можно будет найти где-нибудь на 1000 строк ниже по коду. Если класс называется NetworkDevice, то в его публичных методах должны быть операции, применимые к устройству, а не реализация быстрой сортировки в общем виде.
Сложно выразить словами, насколько часто, несмотря на очевидность этого правила, программисты его нарушают. Думаю, не стоит пояснять, каким образом это сказывается на читаемости их кода.
Чтобы избежать таких проблем, необходимо максимально тщательно продумывать название каждой переменной, каждого метода и класса. При этом надо стараться не только корректно, но и, по возможности, максимально полно охарактеризовать назначение каждой конструкции.
Если этого сделать не получается, причиной обычно являются мелкие или грубые ошибки дизайна, поэтому воспринять такое надо минимум как тревожный “звоночек”.
Очевидно, так же стоит минимизировать использование переменных с названиями i, j, k, s. Переменные с такими названиями могут быть только локальными и иметь только самую общепринятую семантику. В случае i, j, это могут счетчики циклов или индексы в массиве. Хотя, по возможности, и от таких счетчиков стоит избавляться в пользу циклов foreach и функциональных выражений.
Переменные же с названиями ii, i1, ijk42, asdsa и т.д, не стоит использовать никогда. Ну разве что, если вы работаете с математическими алгоритмами… Нет, лучше все-таки никогда.
6.2. Техника 2. Стараемся называть одинаковые вещи одинаково, а разные — по-разному.
Одна из самых обескураживающих трудностей, возникающих при чтении кода — это употребляемые в нем синонимы и омонимы. Иногда в ситуации, когда две разных сущности названы одинаково, приходится тратить по несколько часов, чтобы разделить все случаи их использования и понять, какая именно из сущностей подразумевается в каждом конкретном случае. Без такого разделения нормальный анализ, а следовательно и осмысленная модификация кода, невозможны в принципе. А встречаются подобные ситуации намного чаще, чем можно было бы предположить.
Примерно то же самое можно сказать и об “обратной” проблеме — когда для одной и той же сущности/операции/алгоритма используется несколько разных имен. Время на анализ такого кода может возрасти по сравнению с ожидаемым в разы.
Вывод простой: в своих программах к возникновению синонимов и омонимов надо относиться крайне внимательно и всеми силами стараться подобного избегать.
6.3. Техника 3. “Бритва Оккама”. Не создаем сущностей, без которых можно обойтись.
Как уже говорилось в п. 5.4., любой участок кода, любой объект, функция или переменная, которые вы создаете, в дальнейшем, в процессе поддержки, потребует себе платы в виде вашего (или чужого) времени и внимания.
Из этого следует самый прямой вывод: чем меньше сущностей вы введете, тем проще и лучше в итоге окажется ваш код.
Типичный пример “лишней” переменной:
...
int sum = counSum();
int increasedSum = sum + 1;
operate(increasedSum);
...
Очевидно, переменная increasedSum является лишней сущностью, т.к. описание объекта, который она хранит (sum + 1) характеризует данный объект гораздо лучше и точнее, чем название переменной. Таким образом код стоит переписать следующим образом (“заинлайнить” переменную):
...
int sum = counSum();
operate(sum + 1);
...
Если далее по коду сумма нигде не используется, можно пойти и дальше:
...
operate(countSum() + 1);
...
Инлайн ненужных переменных — это один из способов сделать ваш код короче, проще и понятней.
Однако применять его стоит лишь в случае, если этот прием способствует самодокументированности, а не противоречит ей. Например:
double descr = b * b - 4 * a *c;
double x1 = -b + sqrt(descr) / (2 * a);
В этом случае инлайн переменной descr вряд ли пойдет на пользу читаемости, т.к. данная переменная используется для представления определенной сущности из предметной области, а, следовательно, наличие переменной способствует самодокументированности кода, и сама переменная под “бритву Оккама” не попадает.
Обобщая принцип, продемонстрированный на данном примере, можно заключить следующее: желательно в своей программе создавать переменные/функции/объекты, только если они имеют прототипы в предметной области. При этом, в соответствии с п. 6.1, надо стараться, чтобы название этих объектов максимально ясно выражало это соответствие. Если же такого соответствия нет, вполне возможно, что использование переменной/функции/объекта лишь перегружает ваш код, и их удаление пойдет программе только на пользу.
6.4. Техника 4. Всегда стараемся предпочитать явное неявному, а прямое — косвенному.
Общий принцип прост: любые явно выписанные алгоритмы или условия уже являются самодокументированными, т. к. их назначение и принцип действия уже описаны ими же самими. Наоборот, любые косвенные условия и операции с сайд-эффектами сильно затрудняют понимание сути происходящего.
Можно привести грубый пример, подразумевая, что жизнь подкидывает подобные примеры совсем не редко.
Косвенное условие:
if (i == abs(i)) {
}
Прямое условие:
if (i >= 0) {
}
Оценить разницу в читаемости, думаю, несложно.
6.5. Техника 5. Все, что можно спрятать в private (protected), должно быть туда спрятано. Инкапсуляция — наше все.
Говорить о пользе и необходимости следования принципу инкапсуляции при написании программ в данной статье я считаю излишним.
Хотелось бы только подчеркнуть роль инкапсуляции, как механизма самодокументирования кода. В первую очередь, инкапсуляция позволяет четко выделить внешний интерфейс класса и обозначить его “точки входа”, т. е. методы, в которых может быть начато выполнение кода, содержащегося в классе. Это позволяет человеку, изучающему ваш код, сохранить огромное количество времени, сфокусировавшись на функциональном назначении класса и абстрагировавшись от деталей реализации.
6.6. Техника 6. (Обобщение предыдущего) Все объекты объявляем в максимально узкой области видимости.
Принцип максимального ограничения области видимости каждого объекта можно распространить шире, чем привычная нам инкапсуляция из ООП.
Простой пример:
Object someobj = createSomeObj();
if (some_check()) {
// Don't need someobj here
} else {
someobj.call();
}
Очевидно, такое объявление переменной someobj затрудняет понимание ее назначения, т. к. читающий ваш код будет искать обращения к ней в значительно более широкой области, чем она используется и реально нужна.
Нетрудно понять, как сделать этот код чуть-чуть лучше:
if (some_check()) {
// Don't need someobj here
} else {
Object someobj = createSomeObj();
someobj.call();
}
Ну или, если переменная нужна для единственного вызова, можно воспользоваться еще и п. 6.3:
if (some_check()) {
// Don't need someobj here
} else {
createSomeObj().call();
}
Отдельно хотелось бы оговорить случай, когда данная техника может не работать или работать во вред. Это переменные, инициализируемые вне циклов. Например:
Object someobj = createSomeObj();
for (int i = 0; i < 10; i++) {
someobj.call();
}
Если создание объекта через createSomeObj() — дорогая операция, внесение ее в цикл может неприятно сказаться на производительности программы, даже если читаемость от этого и улучшится.
Такие случаи не отменяют общего принципа, а просто служат иллюстрацией того, что каждая техника имеет свою область применения и должна быть использована вдумчиво.
6.7. Техника 7. Четко разделяем статический и динамический контекст. Методы, не зависящие от состояния объекта, должны быть статическими.
Смешение или вообще отсутствие разделения между статическим и динамическим контекстом — не самое страшное, но весьма распространенное зло.
Приводит оно к появлению ненужных зависимостей от состояния объекта в потенциально статических методах, либо вообще к неправильному управлению состоянием объекта. Как следствие — к затруднению анализа кода.
Поэтому необходимо стараться обращать внимание и на данный аспект, явно выделяя методы, не зависящие от состояния объекта.
6.8. Техника 8. Стараемся не разделять объявление и инициализацию объекта.
Данный прием позволяет совместить декларацию имени объекта с немедленным описанием того, что объект из себя представляет. Именно это и является ярким примером следования принципу самодокументированности.
Если данной техникой пренебречь, читателю придется для каждого объекта искать по коду, где он был объявлен, нет ли у него где-нибудь инициализации другим значением и т.д. Все это затрудняет анализ кода и увеличивает время, необходимое для того, чтобы в коде разобраться.
Именно поэтому, например, функциональные операции из apache CollectionUtils и guava Collections2 часто предпочтительней встроенных в Java foreach циклов — они позволяют совместить объявление и инициализацию коллекции.
Сравним:
// getting “somestrings” collection somehow
...
Collection<String> lowercaseStrings = new ArrayList<String>();
/* collection is uninitialized here, need to investigate the initializion */
for (String s : somestrings) {
lowercaseStrings.add(StringUtils.lowerCase(s));
}
c:
// getting “somestrings” collection somehow
...
Collection<String> lowercaseStrings = Collections2.transform(somestrings, new Function<String, String>() {
@Override
public String apply(String s) {
return StringUtils.lowerCase(s);
}
}));
Если мы используем Java 8, можно записать чуть короче:
...
Collection<String> lowercaseStrings = somestrings.stream()
.map( StringUtils::lowerCase ).collect(Collectors.toList());
Ну и стоит упомянуть случай, когда разделять объявление и инициализацию переменных так или иначе приходится. Это случай использования переменной в блоках finally и catch (например, для освобождения какого-нибудь ресурса). Тут уже ничего не остается, кроме как объявить переменную перед try, а инициализировать внутри блока try.
6.9. Техника 9. Используем декларативный подход и средства функционального программирования для обозначения, а не сокрытия сути происходящего.
Данный принцип может быть проиллюстрирован примером из предыдущего пункта, в котором мы использовали эмуляцию функционального подхода в Java с целью сделать наш код понятнее.
Для большей наглядности рассмотрим еще и пример на Javascript (взято отсюда: http://habrahabr.ru/post/154105).
Сравним:
var str = "mentioned by";
for(var i =0; l= tweeps.length; i < l; ++i){
str += tweeps[i].name;
if(i< tweeps.length-1) {str += ", "}
}
c:
var str = "mentioned by " + tweeps.map(function(t){
return t.name;
}).join(", ");
Ну а примеры использования функционального подхода, убивающие читаемость… Давайте на этот раз сэкономим свои нервы и обойдемся без них.
6.10. Техника 10. Пишем комментарии только, если без них вообще не понятно, что происходит.
Как уже говорилось выше, принцип самодокументирования кода противопоставляется документированию с помощью комментариев. Хотелось бы коротко пояснить, чем же так плохи комментарии:
- В хорошо написанном коде они не нужны.
- Если поддержка кода часто становится трудоемкой задачей, поддержкой комментариев обычно просто никто не занимается и через некоторое время комментарии устаревают и начинают обманывать людей (“comments lie”).
- Они засоряют собой пространство, тем самым ухудшая внешний вид кода и затрудняя читаемость.
- В подавляющем большинстве случаев они пишутся лично Капитаном Очевидностью.
Ну а нужны комментарии в случаях, когда написать код хорошо по тем или иным причинам не представляется возможным. Т.е. писать их надо для пояснения участков кода, без них трудночитаемых. К сожалению, в реальной жизни такое встречается регулярно, хотя расценивать это следует лишь как неизбежный компромисс.
Также как разновидность подобного компромисса следует расценивать необходимость спецификации контракта метода/класса/процедуры при помощи комментариев, если его не получается явно выразить другими языковыми средствами (см п. 5.2).
7. Философское заключение.
Закончив изложение основных техник и приемов, с помощью которых можно сделать код чуть красивее, следует еще раз явно сформулировать: ни одна из техник не является безусловным руководством к действию. Любая техника — лишь возможное средство достижения той или иной цели.
В нашем случае целью является получение максимально читаемого и управляемого кода. Который одновременно будет приятен с эстетической точки зрения.
Изучая примеры, приведенные в текущей статье, можно легко заметить, что, как сами исходные принципы (линейность, минимальность, самодокументированность), так и конкретные техники не являются независимыми друг от друга. Применяя одну технику мы можем также косвенно следовать и совсем другой. Улучшая один из целевых показателей, мы можем способствовать улучшению других.
Однако, у данного явления есть и обратная сторона: нередки ситуации, когда принципы вступают в прямое противоречие друг с другом, а также со множеством других возможных правил и догм программирования (например с принципами ООП). Воспринимать это надо совершенно спокойно.
Встречаются и такие случаи, когда однозначно хорошего решения той или иной проблемы вообще не существует, или это решение по ряду причин нереализуемо (например, заказчик не хочет принимать потенциально опасные изменения в коде, даже если они способствуют общему улучшению качества).
В программировании, как и в большинстве других областей человеческой деятельности, не может существовать универсальных инструкций к исполнению. Каждая ситуация требует отдельного рассмотрения и анализа, а решение должно приниматься исходя из понимания особенностей ситуации.
В целом, этот факт нисколько не отменяет полезности как вывода и понимания общих принципов, так и владения конкретными способами их воплощения в жизнь. Именно поэтому я надеюсь, что все изложенное в статье может оказаться действительно полезно для многих программистов.
Ну и еще пара слов о том, с чего мы начинали — о красоте нашего кода. Даже зная о “продвинутых” техниках написания красивого кода, не стоит пренебрегать самыми простыми вещами, такими, как элементарное автоформатирование и следование установленному в проекте стилю кодирования. Зачастую одно лишь форматирование способно сотворить с уродливым куском кода настоящие чудеса. Как и разумное группирование участков кода с помощью пустых строк и переносов.
Не стоит забывать и о таком средстве, как статические анализаторы кода, которые позволяют выявить множество проблем (в том числе, описанных в данной статье) автоматически, благо сейчас они встроены в большинство популярных IDE.
И самое последнее. Всегда стоит помнить о субъективности эстетического восприятия вещей. Поэтому уделяя внимание этому вопросу, следует с пониманием относиться к чужим вкусам и привычкам.
Приятного вам программирования!
Выполнение любой задачи в программировании включает в себя нескольких общих этапов:
- понимание;
- планирование;
- написание кода;
- тестирование/отладка;
- завершение работы.
Программисты тратят большую часть времени на написание кода и отладку, потому что обычно занимаются планированием во время создания кода. Поэтому каждый разработчик стремится воспользоваться специальными советами по увеличению скорости кодирования. Так, некоторые используют редакторы кода, автоматически генерирующие часто используемые фрагменты.
Взяв на вооружение решения по увеличению продуктивности, вы можете ускорить выполнение повторяющихся действий. Как правило, написание кода — это динамичная работа. Ведь, скажем, вы же не пишете сегодня снова тот же код, который писали вчера. А если вы так делаете, то вам, вероятно, следует изучить принципы шаблонов проектирования.
Чтобы увеличить скорость написания кода, нужно знать факты, влияющие на этот процесс. Один программист может написать код для ПО за час, в то время как его коллеге удастся выполнить ту же работу всего за пять минут и сохранить то же качество на выходе.
В этой статье мы дадим несколько советов, которые помогут увеличить скорость написания кода и сохранить его качество. Вы сможете применять эти советы на практике с любым языком программирования, фреймворком и библиотекой.
По возможности применяйте метапрограммирование
Обычно для хранения наборов данных используются сложные типы данных. Например, если нужно хранить и обрабатывать данные о человеке, мы не передаем и не обрабатываем отдельные переменные, такие как имя, возраст, страна проживания и т. д. Вместо этого мы создаем один класс — объект “ключ-значение” — или структуру в соответствии с используемым языком.
Предположим, что вам нужно сгенерировать SQL-запрос с объектом под названием person
. Некоторые выбирают долгий путь, обращаясь к каждой базовой переменной по имени ключа:
person.name, person.age, person.country
Если вам нужно добавить еще одно поле SQL, придется изменить функцию генератора SQL-запросов. По мере увеличения количества полей может потребоваться много времени для явного ввода имен полей.
Но если относиться к person
как к данным и извлекать всю информацию (поля и значения), необходимую для генерации SQL-запроса во время выполнения программы, можно быстро написать высокодинамичный код. Эта техника называется “метапрограммирование”, и при работе с большинством популярных языков вы можете применять ее с помощью Reflection API.
Прежде чем использовать имена полей конкретной структуры в явном виде, попробуйте мыслить как специалист в сфере метапрограммирования. Эта техника сэкономит много времени при работе с сериализацией и преобразованием данных. Однако чрезмерное использование метапрограммирования может снизить читаемость кода, а также замедлить работу программы. Так что используйте эту технику с осторожностью.
Пишите готовый к коммитам код с первой попытки
Большинство программистов стремятся прежде всего реализовать быстрое решение. После этого они часто проводят перепроектирование кода для его очистки. Кроме того, иногда требуется быстрый рефакторинг, чтобы подготовить код для повторного использования.
Между тем, есть разработчики, которые всегда стремятся реализовать чистый и многократно используемый код с первой попытки. Если вы сначала реализуете быстрое решение, вы можете оперативно проверить, работает код целиком или нет.
Однако если вы начнете терпеливо писать многократно используемый и готовый к коммитам код, вы сэкономите массу времени, которое могли бы потратить на рефакторинг. Привыкнуть к такой практике будет нелегко. К тому же иногда быстрая реализация помогает проверить техническую осуществимость проекта.
Тем не менее, если вы начнете практиковать внедрение многократно используемого, чистого и готового к коммитам кода с первой попытки, то сможете быстрее выполнять задачи, сохраняя при этом качество работы.
Изучайте новейшие возможности языка, но используйте их с осторожностью
Каждый популярный язык программирования периодически обновляется. Вместе с новыми версиями появляется новый инструментарий, улучшается структура языка и совершенствуется стандартный API. Помимо этого, из языка могут исключаться некоторые существующие функции.
Подобные изменения часто затрагивают функции, ускоряющие написание кода. Так, работа с большими многострочными строками в JavaScript с обычным строковым синтаксисом (с одинарными или двойными кавычками) занимает много времени, поскольку необходимо добавлять множество операторов конкатенации строк. Но с новыми шаблонными литералами ES6 вы можете выполнять манипулирования строками любой сложности.
Точно так же и в любом языке программирования могут появиться функции, способные повысить скорость работы. Чтобы быстрее узнавать о новых возможностях, просматривайте историю версий языка, а также читайте сообщения в блогах и репозиториях с открытым исходным кодом. Тажк учтите, что в стандартной библиотеке языка уже может быть заложена часть кода, который вы пытались реализовать с нуля.
Старайтесь не переусердствовать при разработке
Ведущие инженеры мотивируют команду писать чистый код, ориентируясь на лучшие шаблоны проектирования. Но если добавлять все шаблоны в каждый фрагмент, то код усложнится настолько, что к нему никто не захочет прикасаться.
Есть два способа писать код:
- Одни пишут его только для компилятора/интерпретатора без использования каких-либо шаблонов.
- Другие пишут слишком сложный код, чтобы показать, что они знают все шаблоны проектирования.
Разумеется, программисту нужно уметь находить баланс между этими двумя крайностями, чтобы создавать качественный и читабельный код.
Рефакторить код с малым количеством шаблонов проектирования легко. Гораздо труднее производить рефакторинг огромной перегруженной кодовой базы. Поэтому старайтесь реализовать чистый код, используя только релевантные шаблоны проектирования. Это сэкономит время вам и вашим коллегам.
Старайтесь также не переусердствовать с интеграцией библиотек, которые вам не нужны. Например, нет необходимости интегрировать сторонние решения по управлению состояниями для простого приложения на React.
Учитесь быстро печатать на клавиатуре
До этого мы приводили советы, касающиеся перестройки вашего мышления как программиста. Теперь поговорим о физических способностях, которые могут прямо повлиять на скорость. Любой код, помимо присущего ему логического значения, представляет собой текстовый документ, набранный с помощью клавиатуры. Следовательно, скорость набора текста сильно влияет на скорость написания код. Но для того, чтобы быстро писать код, не обязательно быть сверходаренным наборщиком, печатающим 100 слов в минуту.
Программисты используют клавиатуру по-разному. Некоторые набирают исходный код в виде последовательности символов. Другими словами, они вводят слова и специальные символы один за другим в правильном порядке. Между тем, другие практикуют собственные способы ввода кода, оказывающиеся на деле довольно продуктивными.
Есть программисты, использующие в редакторах расширения для автоматического закрытия скобок и расширения для автозаполнения с поддержкой искусственного интеллекта. С другой стороны, некоторые разработчики любят работать по старинке, без всяких расширений для автозаполнения. Вы должны найти свой способ, который окажется для вас максимально комфортным.
Если программист вместо указателя мыши использует клавиши со стрелками для перемещения между строками кода, у него меньше времени уходит на его написание. Вам тоже следует изобрести собственные “лайфхаки” для быстрого управления клавиатурой при наборе кода.
Читайте также:
- 8 советов по улучшению качества кода
- Всё, что должен знать разработчик ПО о качестве кода
- 6 ответов на вопрос: «почему читать код важнее, чем писать?»
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Shalitha Suranga: 5 Tips to Write Code Faster In Any Programming Language
Набор практик хорошего кода, не зависящих от языка программирования, опубликовал сайт proglib.io. Примените их, и ваш код будет не только работать, но и читаться.
Программисты в первую очередь работают с языком. Поэтому написание программ похоже на любой другой вид письменной работы. Сначала вы излагаете свои мысли как есть, а затем «причесываете» до тех пор, пока текст не будет легко читаться. Качество кода – результат проявления небезразличного отношения к делу и показатель профессионализма.
Почему важна читаемость кода
Чтение кода происходит чаще, чем написание. Есть большая разница между обучением программированию и реальной работой в компании. Вначале мы и пишем, и читаем собственные программы. Но чем дальше мы продвигаемся, тем чаще нам приходится не писать, а читать код. Чем легче код читается, тем проще с ним работать другим людям.
«Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете», — Джон Вуд в доске обсуждений по C++, 1991 г.
Чем проще читать код, тем проще его сопровождать. Понятный, читаемый код легче тестировать, в нем легче отлавливать ошибки – они не скрываются в его запутанной структуре. Плохо оформленный код неприятно изучать, читать, тестировать, сложно дополнять. Рано или поздно плохой код становится проще переписать.
Эстетическое восприятие кода влияет на удобство работы. Казалось бы, гораздо важнее производительность, возможность модификации, расширения… Но все эти показатели улучшаются, если код соответствует нашему чувству прекрасного. Глядя на качественно написанный код, можно быстро понять алгоритм и то, как работает программа для разных входных данных. Чистый код читается как хорошо написанная проза: слова превращатся в зрительные образы.
Стиль кода определяет его будущее. Стиль и дисциплина продолжают жить в коде, даже если в нем не осталось ни одной исходной строки.
С чего начать: документация по стилю оформления кода
Все дороги программиста ведут к документации. В каждом языке существует свой стандарт оформления кода. Для Python используется документ PEP-8, для PHP – стандартные рекомендации PSR-1 и PSR-2, для Java – Java Coding Conventions, для JavaScript – Airbnb JavaScript Style Guide или Google JavaScript Style Guide. Документ для вашего языка вы найдете по поисковому запросу <Название языка> Code Style.
Когда вы работаете в группе разработчиков, нужно использовать принятые в команде правила. Стиль должен быть единым, как будто код был написан одним здравомысленным человеком.
В популярных IDE заложена возможность автоматической настройки стиля кода под стандарты – общие или предложенные командой. Разберитесь, как настроить среду под необходимое оформление. Потраченное время сэкономит многие часы рутинной работы.
Применение стандартов – лучший подход для новичка. Читающий не будет отвлекаться на оформление и сразу погрузится в тонкости выбранных подходов, а не расстановок переносов. Изложенные ниже правила понадобятся для того, чтобы понять, как действовать в тех случаях, когда стандарт не дает никаких рекомендаций.
Роберт Мартин «Чистый код. Создание, анализ и рефакторинг»
Конечно, мы не могли обойтись без упоминания замечательной книги Роберта Мартина о чистом коде и анализе программ. В ней приводятся примеры для языка Java, но большинство идей справедливы для любых языков.
Всё что изложено ниже, в значительной мере представляет сжатый конспект этой книги с дополнениями из нашего опыта в проектировании программ. Итак, приступим к практикам.
Главное правило чистого кода: выразительные имена
Содержательность. К выбору названий любых объектов нужно подходить со всей ответственностью. Выразительные имена позволяют писать код, не требующий комментариев.
Полезно не только исходно выбирать ясные имена, но и заменять названия на более удачные, если они нашлись позже. Современные среды программирования позволяют легко заменить название переменной во всём коде, так что это не должно быть проблемой.
Сравните. До:
public List < int[] > getThem() { List < int[] > list1 = new ArrayList < int[] > (); for (int[] x: theList) if (x[0] == 4) list1.add(x); return list1; }
После:
public List < int[] > getFlaggedCells() { List < int[] > flaggedCells = new ArrayList < int[] > (); for (int[] cell: gameBoard) if (cell[STATUS_VALUE] == FLAGGED) flaggedCells.add(cell); return flaggedCells; }
В первом примере непонятно, что вообще происходит, хотя в этом коде нет ни сложных выражений, ни каких-либо странностей. В результате правок сам код никак не изменился. Если знать, что это часть игры «Сапер», то теперь из кода понятно: здесь обрабатывается список ячеек игрового поля. Этот код можно улучшать и далее, но уже в результате простого переименования стало понятно, что происходит.
Избегайте любых двусмысленностей и ложных ассоциаций. Если в объекте перечисляется список, но сам объект не является списком, нельзя в составе его названия употреблять слово list
– это запутывает читающего.
Остерегайтесь малозаметных различий – имена объектов должны существенно отличаться друг от друга. По этой причине плохи длинные имена с повторяющимся элементами – чтобы сличить их друг с другом, тратятся лишние силы и время. Избегайте использования в именах переменных строчной буквы L и прописных I, O – они часто путаются с единицей и нулем.
Путаница также возникает, если несколько синонимичных слов и выражений используются для обозначениях разных сущностей, например, controller
, manager
и driver
.
Имя должно легко произноситься. Используйте для названий слова. Если названия состоят из сокращений, каждый начинает произносить их по-своему, что затрудняет взаимопонимание. А при чтении кода каждый раз «спотыкаешься» о такое название.
Имя должно быть удобным для поиска. Слишком короткие имена трудно искать в большом объеме текста. Однобуквенные имена можно использовать только для локальных переменных в коротких методах и счетчиков циклов (i, j, k
). Обычно называя объект одной буквой, вы всего лишь создаете временный заменитель. Но не бывает ничего более постоянного, чем что-то «временное». Проверяйте грамотность написания выбранных слов.
Правильно выбирайте часть речи. Классы и объекты желательно называть существительными и их комбинациями: Account
, WikiPage
, HTMLParser
. Имена функций и методов лучше представлять глаголами или глагольными словосочетаниями: delete_page
, writeField(name)
. Для методов чтения/записи и предикатов используйте стандартные префиксы get
, set
, is
.
Заменяйте «магические» числа именованными константами. Одно из самых древних правил разработки. Магическими называют числа, о которых сходу нельзя сказать, что они означают. Например: 100
, 1.1
, 42
, 1000000
. Выделяйте такие числа в соответствующие константы с конкретным названиями. Например, вместо числа 86400
в теле кода приятнее встретить константу SECONDS_PER_DAY
.
Не стоит следовать этому правилу, как и любому другому, безоговорочно. В формулах некоторые константы лучше воспринимаются в числовой записи.
Одно слово для каждой концепции. Для одной и той же идеи, реализующей одну механику, используйте одно слово. Например, для добавления элементов одинаковым образом – метод add
. Однако, если механика и семантика изменились, потребуется и другое слово (например, insert
, append
), описывающее новую концепцию.
Ваш код будут читать программисты. Не стесняйтесь использовать термины из области информатики, общепринятые названия алгоритмов и паттернов. Такие имена сообщают информацию быстрее, чем сам код.
Помещайте имена в соответствующий контекст. Например, имена street
, house_number
, city
понятнее смотрятся внутри класса Address
.
Избегайте остроумия и каламбуров в названиях. Шутки имеют свойство быть понятными лишь ограниченное время и для конкретной аудитории, знакомой с первоисточником. Отдавайте предпочтение ясности перед развлекательностью. Шутки можно приберечь для презентации, которая происходит «здесь и сейчас». Хороший код способен выйти далеко за границы вашей культуры.
Рабочие среды продолжают развиваться. Уже нет никакой необходимости кодировать типы в именах, создавать префиксы для членов классов. Всю нужную информацию можно получить из цветового выделения или контекстно-зависимых подсказок сред разработки. Добавление префиксов убивает удобство поиска по автовыполнению – выпадает слишком много имен, начинающихся с одинаковых символов.
Функции
Компактность. Уже в 80-е годы считалось, что функция должна занимать не более одного экрана. Экраны VT100 тогда состояли из 24 строк и 80 столбцов. В наши дни на экране можно разместить гораздо больше инорфмации, но лучше ограничиться тем же объемом. Самоограничение позволяет видеть точку объявления каждой используемой переменной и держать в уме всю «историю», которую рассказывает функция.
Вполне вероятно, что тот, кто будет сопровождать ваш код, не будет иметь возможности работать на большом мониторе. Например, ему необходимо одновременно разместить на одном рабочем столе экрана ноутбука несколько окон. Среды разработки позволяют установить ограничение, «верхнюю планку» (то есть правую 😉 ).
Блоки if, else, while должны иметь минимальный размер, чтобы информацию о них можно было держать в уме. Старайтесь избегать отрицательных условий – на их восприятие обычно уходит чуть больше времени, чем на положительные. То есть запись if (buffer.shouldCompact())
предпочтительнее записи if (!buffer.shouldNotCompact()
.
Правило одной операции. Плохой код пытается сделать слишком много всего, намерения программиста расплываются для читателя. Поэтому стоит ввести важное правило:
Функция должна выполнять только одну операцию, выполнять ее хорошо, и ничего другого она делать не должна.
Каждая функция должна делать то, что вы от нее ожидали из названия. Если функция действует не так, как она названа, читатель кода перестает доверять автору программы, ему приходится самостоятельно разбираться во всех подробностях реализации.
«Я люблю, чтобы мой код был элегантным и эффективным. Логика должны быть достаточно прямолинейной, чтобы ошибкам было трудно спрятаться; зависимости — минимальными, чтобы упростить сопровождение; обработка ошибок — полной в соответствии с выработанной стратегией; а производительность — близкой к оптимальной, чтобы не искушать людей загрязнять код беспринципными оптимизациями. Чистый код хорошо решает одну задачу», — Бьёрн Страуструп, создатель языка С++
Исключения вместо кодов ошибок. Используйте исключения (try-catch
, try-except
) вместо возвращения кодов ошибок. Возвращение кодов приводит к слишком глубокой вложенности.
К тому же при использовании исключений код обработки ошибок изолируются от ветви нормального выполнения. Сами блоки лучше выделять в отдельные функции. Вместе с исключением нужно передавать контекст ошибки – сообщение, содержащее сведения об операции и типе сбоя.
Соблюдайте уровни абстракции. Одна функция – один уровень абстракции. Смешение уровней абстракции создает путаницу, функция обрастает слишком большим количеством второстепенных подробностей. Старайтесь соблюдать ясную иерархию.
Код читается сверху вниз. По мере чтения уровни абстракции должны меняться равномерно. Каждая функция должна быть окружена функциями единого уровня абстракции.
Ограничивайте число аргументов. Чем больше аргументов у функции, тем сложнее с ней работать. Необходимость функций с количеством аргументов большим двух должна быть подкреплена очень вескими доводами. Каждый новый аргумент критически усложняет процедуру тестирования. Если функция должна получать более двух аргументов, скорее всего, эти аргументы образуют концепцию, заслуживающую собственного имени.
Комментарии
Это непопулярное мнение, но в большинстве случаев комментарии – зло. Код должен быть самодокументированным. Комментарий – всегда признак неудачи: мы не смогли написать код так, что он понятен без комментариев. Проверьте, можно ли выразить свое намерение в самом коде.
В чём проблема? Программисты умеют сопровождать код, но не комментарии. В растущем коде комментарии быстро устаревают, частично или полностью переставая соответствовать ситуации. Только код правдиво сообщает своим содержанием, что он в действительности делает. Лучше потратить время на исправление запутанного кода, чем добавлять к плохому коду комментарии.
Однако есть несколько видов комментариев, которые выглядят достаточно оправданными.
TODO-комментарии. Бывает так: нужно было успеть к дедлайну, пришлось писать код быстро, поэтому в нем остались дыры. То есть всё работает, но реализация ущербная. Укажите все недоработки и создайте под них задачи. Каждый комментарий указывает на недоработку или потенциальную уязвимость.
Юридические комментарии. Корпоративные стандарты могут принуждать вставлять комментарии по юридическим соображениям. Ограничьтесь в таком комментарии описанием лицензии и ссылкой на внешний документ.
Предупреждения о последствиях. Иногда бывает полезно предупредить других программистов о нежелательных последствиях:
// Не запускайте, если только не располагаете // излишками свободного времени.
Комментарий также может подчеркивать важность обстоятельства, которое на первый взгляд кажется несущественным.
По-настоящему плохие комментарии
Бывают такие типы комментариев, которые лучше никогда не делать.
Закомментированный программный код. «Когда-нибудь в будущем расскомментирую этот код, приведу всё в порядок. Или вдруг эта идея кому-то поможет». Любой закомментированный код только ухудшает ситуацию. Все изменения хранятся в контроле версий – удаляйте такой код на корню. Это просто мусор: «потом» равносильно «никогда». Если что-то действительно нужно сделать, создайте краткий TODO-комментарий и задачу.
Мертвые функции – идентичные по смыслу предыдущему пункту функции и методы, которые не вызываются в программе. Пользуйтесь системой контроля версий и без зазрений совести удаляйте любой код, который не используется во время выполнения программы.
Избыточные комментарии. Задайте себе вопрос: стал ли код понятнее после прочтения комментария? Часто комментарии просто загромождают код и скрывают его смысл, излагая очевидные вещи. Иногда в комментарии включаются описания не относящихся к делу подробностей. Но профессионал бережет не только свое, но и чужое время, и не отвлекает читающего без повода.
Журнальные комментарии и ссылки на авторов. Некоторые программисты добавляют комментарий в начало файла при редактировании. Или указывают, кто и когда внес исправления. Когда-то это было оправдано, но теперь у нас есть системы контроля версий – это гораздо лучший способ обозначить границы зоны ответственности каждого.
Позиционные маркеры. Иногда любят отмечать определенные группы и позиции в исходных файлах:
// Классы //////////////////////////////////
Качественно организованный код способен внятно рассказать историю без балластных заголовков.
Уровень файлов программ
Минималистичность. Чем меньше кода, тем лучше. Имя файла должно быть простым, но содержательным. Маленькие файлы обычно более понятны, чем большие. Но размер файла, конечно, не должен быть самоцелью.
Код должен быть максимально линейным. Чем больше вложенность кода, тем сложнее его читать. Следите за тем, как двигаются ваши глаза. В хорошем коде вы двигаетесь строка за строкой, лишь изредка возвращаясь к предыдущим строчкам. Вложенность более трех уровней указывает на то, что с кодом нужно поработать: переписать условия проверок и циклов (использовать return и функциональное программирование), разбить код на меньшие методы.
Отдельные «мысли» следует отделять друг от друга пустыми строками. Каждая пустая строка – зрительная подсказка: описание одной концепции закончилось, далее следует новая. При просмотре кода взгляд концентрируется на первых строчках – в них больше всего информации, как в началах абзацев этого текста.
Тесно связанные концепции, должны располагаться вблизи друг друга. Не заставляйте читателя прыгать между файлами или постоянно скроллить файл. По той же причине переменные нужно объявлять как можно ближе к месту использования. Однако переменные экземпляров лучше объявлять в одном месте, обычно в начале класса (то есть в одном месте), так как в хорошо спроектированном классе переменные используются большинством методов класса.
Пробелы для группировки взаимосвязанных элементов. Пробелы улучшают читаемость кода, если они стоят вокруг операторов присваивания, после запятых при перечислении переменных. В формулах пробелы используются для подчеркивания приоритета: не ставятся между множителями, но отбивают знаки сложения и вычитания.
Отступы. Размер отступов должен соответствовать позиции кода в иерархии. Это общая практика, которая позволяет быстро пропускать области видимости, не относящиеся к текущей ситуации. Не поддавайтесь искушению нарушить правила расстановки отступов для коротких команд.
Некоторые замечания по поводу архитектуры и тестов
В системе должны выполняться все тесты. Тесты – главный способ, с помощью которого можно понять, что система контролируема. А только контролируемую систему можно проверить.
Три закона тестирования по методологии TDD. Тестовый код не менее важен, чем код продукта. Соблюдение следующих трех правил позволяет организовать работу так, чтобы тесты охватывали все аспекты кода продукта:
- Не пишете код продукта, пока не напишете отказной модульный тест.
- Не пишите модульный тест в объеме большем, чем необходимо для отказа.
- Не пишите код продукта в объеме большем, чем необходимо для прохождения текущего отказного теста.
F.I.R.S.T. Качественные тесты должны обладать пятью характеристиками, первые буквы которых образуют указанный акроним:
- Fast. Тесты должны выполняться быстро.
- Independent. Тесты не должны зависеть друг от друга и выполняться в любом порядке.
- Repeatable. Тесты должны давать воспроизводимые в любой среде результаты.
- Self-validating. Результат выполнения теста – логический признак: тест пройден или нет. Иначе результаты приобретают субъективный характер.
- Timely. Тест должен создаваться своевременно. Тесты нужно писать непосредственно перед написанием кода.
Повышение уровня абстракции и устранение дубликатов. Все программы состоят из очень похожих элементов, а все задачи программирования сводятся к работе с ограниченным набором действий. Многие из этих действий могут быть описаны в одних и тех же терминах, например, извлечение элемента из коллекции. В подобных ситуациях правильно инкапсулировать реализацию в более абстрактном классе. Повышение уровня абстракции позволяет избежать дублирования и многократно применения одного и того же кода, лучше понять, что действительно происходит в программе, уйдя от частностей.
Если что-то в программе делается снова и снова, значит, какая-то важная концепция не нашла своего отражения в коде. Нужно попытаться понять, что это такое, и выразить идею в виде кода. Избегайте дубликатов, это всегда лишняя работа, лишний риск, лишняя сложность.
Несколько языков в одном исходном файле. Современные среды программирования позволяют объединять в одном файле код, написанный на разных языках. Результат получается запутанным, неаккуратным и ненадежным. Чтобы четко разграничить ответственность, в файле должен преобладать один язык. Сведите к минимуму количество и объем кода на дополнительных языках.
Не нужно бездумно следовать догмам. Не переусердствуйте с сокращением кода функций и классов. Всегда руководствуйтесь здравым смыслом.
Заключение
Чистый код выглядит так, как если его автор над ним тщательно потрудился. Вы не можете найти очевидных возможностей для его улучшения. Попытавшись его улучшить, вы вновь вернетесь к тому же коду.
Чтобы писать чистый код, который бы никого не удивлял, необходимо раз за разом сознательно применять описанные приемы. При чтении чистого кода вы улыбаетесь, как при виде искусно сделанной музыкальной шкатулки. Код можно назвать красивым, если у вас создается впечатление, что язык был создан специально для этой задачи.
Download Article
Download Article
As technology becomes more and more accessible, the need for programmers is always increasing. Coding is a skill learned and perfected over time, but everyone has to start somewhere. There are a variety of languages that are perfect for beginners regardless of the field that you’re interested in (ex. JavaScript, etc. JavaScript is quite advanced, so start with HTML or CSS). See Step 1 below to learn how.
-
1
Don’t worry too much about what language you pick. Many beginning coders struggle with what language to pick when they first start learning. (Because they don’t know where to start with their first code). The actual language that you start learning on doesn’t make a big difference when it comes to learning about data structures and logic. These are the skills that matter most, and they can be honed with any language.[1]
- When picking a language, just focus on what kind of development you want to start with and pick an introductory language from there. For example, if you want to learn web development, start with HTML5, supplemented with CSS, JavaScript, and PHP. If you want to want to do desktop applications, start with C++ or another basic programming language.
- If you make coding a career, you will find that you may never use the language that you first learned to code. Instead, you will be learning languages as you go through documentation and experimentation.
-
2
Find free resources online for the language you choose. The internet is a treasure trove of free tutorials, classes, and videos, all tailored towards the language of your choice. You can start getting a basic grasp of just about any introductory language in a day.
- Popular sites include Bento, CodeAcademy, Code.org, html.net, Khan Academy, Udacity, W3Schools, Code School and many more.
- There are a variety of language-specific starter guides available right here on wikiHow.
- You can find how-to guides for almost any programming scenario on YouTube.
- Stack Exchange is one of the more popular Q&A sites for any programming questions you may have.
EXPERT TIP
Archana Ramamoorthy is the Chief Technology Officer, North America at Workday She is a product ninja, security advocate, and on a quest to enable more inclusion in the tech industry. Archana received her BS from SRM University and MS from Duke University and has been working in product management for over 8 years.
Our Expert’s Story: «I came to coding with zero background in either computer design or programming. When I wanted to learn to code, I started by reading Java books and using online information. In today’s world, there are so many resources available, so it’s very easy to learn new skills!»
Advertisement
-
3
Download a good text editor. Many programming languages allow you to use external text editors when you write your code. Find a text editor that will allow you to see indentations and code markup.
- Popular programs include Notepad++ (Windows), TextWrangler (OS X), JEdit, or Visual Studio Code.
-
4
Download any necessary compilers. Some programming languages require a compiler in order to run the code that you create. Compilers translate the code you write into a lower-level language that the machine can process. Many compilers are open-source and free to use. Languages that require compilers include:
- C
- C++
- C#
- Java
- BASIC
- Fortran
-
5
Start your first project. Pick a good introductory project that will help you get your feet wet. There are a variety of suggestions and tutorials online, but some places to start are basic websites for HTML, basic database and form functions with PHP, or simple programs with any of the compiler languages.
-
6
Comment all of your code. All programming languages have a comment feature that allows you to enter text that is ignored by the compiler. This allows you to add comments to your code. These comments are crucial, both for letting others know how your code works and for reminding yourself what your code does.
- You can also use the comment function to quickly remove code from your program for testing purposes. Put comment tags around the code you want to exclude and then remove the comment tags to return the code.
-
7
Pick apart other programs or web projects. When you’re learning as you go, don’t be ashamed to look things up and see how others have tackled the same tasks. Take time to understand why the code is doing what it does.
- See this guide for details on how to view the source code of websites.
Advertisement
-
1
Take classes. Community colleges, tech schools and online programs offer certifications and classes that will help you get a job as well as teaching you programming. An advanced degree like Computer Science is not always necessary, but could help to get a full time programming job.[2]
- (A lot can be said for having one-on-one time with a teacher or programming expert, something that isn’t always available with online sources.)
- Classes can get expensive, so weigh the benefits. If programming is just a hobby, classes may not be worth the time and money. If you want to turn programming into a career, classes can give you a big boost (but again, it’s far from necessary if you have the talent).
-
2
Expand your knowledge. Beyond simply learning programming languages, you will benefit a lot from logic and mathematics classes, as these are often required for advanced programming. You don’t necessarily need to learn this at school, but classroom settings can help.
- Programming involving physics calculations and other simulations requires a strong understanding of algorithms and models.
- Logic is the fundamental basis of programming, so understanding logic and processes will help you problem-solve when you are coding.
- Knowing advanced math isn’t required for most programming, but the knowledge can lead to benefits and optimizations.
-
3
Learn more languages. Once you’ve got a good grasp on your initial language, you can start to branch out. Find another language that complements the one you know, or pick a language for a specific job that interests you. Complementary languages, such as HTML and CSS, are often the easiest to learn.
- Java is one of the most popular languages, and there are often lots of opportunities for Java developers. Java can run on a huge variety of systems and has innumerable applications. Java is used for Android applications, one of the fastest growing markets.
- C++ is highly recommended for developing video games. Learning how to code in Unity (a widely used, low-cost game engine) and UDK (the code for the popular Unreal engine) can help open some doors, though they aren’t as useful outside of the video game industry.
- If you want to make iPhone apps, Xcode and Objective-C are going to be your primary tools. You’ll also need a Mac, since Xcode can only compile on a Mac.
- Python is a server scripting language that is one of the easier languages to learn. Python is used for web services such as Pinterest and Instagram, and is simple enough to learn the basics in just a few days.
-
4
Be patient. You will often be faced with challenges while programming, especially when it comes to hunting for bugs or implementing a new idea. You’ll have to learn to be satisfied with achieving small results rather than solving a whole puzzle at once. Patience leads to more effective code, which will lead to better-performing programs and happier colleagues.
-
5
Learn to work with others. If you have multiple people working on a project, you get multiple viewpoints on how to go about it. Working on teams is virtually unavoidable in the business world, so unless you’re planning to develop everything independently, be prepared to work with others.
-
6
Get a job where you can practice your coding skills. Work as a volunteer designing websites or writing desktop applications. A part time job with a small company can lead to opportunities to write code for websites or simple applications.
-
7
Connect with other programmers. There are countless communities and gatherings of developers that can help support and inspire you. Look up local programming conventions, participate in a hack-a-thon or game jam (timed events with a common theme), and sign up on some programming forums to start expanding your exposure and network.
-
8
Practice, practice, practice. It is estimated that it takes about 15,000 hours before you can be considered an expert in computer programming.[3]
This is years of applied practice. True mastery of programming will come only after you put the time in to practice and become proficient.- Try to spend time programming every day, even when you’re not working. Programming in your free time can lead to breakthroughs and new ideas.
Advertisement
Add New Question
-
Question
How do I make a website after I learn to code?
If you can write HTML, CSS, and Javascript, you can write a coded webpage using a code writer and then you can have your website validated and exported onto the web. There are quite a few websites to do this with online, so look around to find the best choice.
-
Question
Can I use a moble phone to learn how to creat a code?
Actually, yes you can. All you will need to make it possible is to have a mobile text editor.
-
Question
I don’t know a lot about coding, but all of my friends do. I get embarrassed. There are no classes to take in my area, what should I do?
If all of your friends know how to code, you should ask them to teach you! There’s nothing to be embarrassed about, they all had to start somewhere too. If you really want to do it on your own, codecademy.com and code.org are pretty good resources for beginners.
See more answers
Ask a Question
200 characters left
Include your email address to get a message when this question is answered.
Submit
Advertisement
Video
References
About This Article
Article SummaryX
To code, start by choosing a programming language that you want to learn, like HTML5 or C++, which are basic languages for beginners. Use free resources online, like video tutorials and blogs. Download a text editor like Notepad++, TextWrangler, or JEdit, and a compiler if necessary. In the text editor, start your first project and use online resources and other websites and apps for inspiration. For more experience, try taking an online class, or learning a different language. For tips on completing your first project, read on!
Did this summary help you?
Thanks to all authors for creating a page that has been read 393,325 times.
Reader Success Stories
-
«I am 14 years old. I always wanted to be a programmer. I searched on Google about programming all by myself and…» more
Is this article up to date?
Download Article
Download Article
As technology becomes more and more accessible, the need for programmers is always increasing. Coding is a skill learned and perfected over time, but everyone has to start somewhere. There are a variety of languages that are perfect for beginners regardless of the field that you’re interested in (ex. JavaScript, etc. JavaScript is quite advanced, so start with HTML or CSS). See Step 1 below to learn how.
-
1
Don’t worry too much about what language you pick. Many beginning coders struggle with what language to pick when they first start learning. (Because they don’t know where to start with their first code). The actual language that you start learning on doesn’t make a big difference when it comes to learning about data structures and logic. These are the skills that matter most, and they can be honed with any language.[1]
- When picking a language, just focus on what kind of development you want to start with and pick an introductory language from there. For example, if you want to learn web development, start with HTML5, supplemented with CSS, JavaScript, and PHP. If you want to want to do desktop applications, start with C++ or another basic programming language.
- If you make coding a career, you will find that you may never use the language that you first learned to code. Instead, you will be learning languages as you go through documentation and experimentation.
-
2
Find free resources online for the language you choose. The internet is a treasure trove of free tutorials, classes, and videos, all tailored towards the language of your choice. You can start getting a basic grasp of just about any introductory language in a day.
- Popular sites include Bento, CodeAcademy, Code.org, html.net, Khan Academy, Udacity, W3Schools, Code School and many more.
- There are a variety of language-specific starter guides available right here on wikiHow.
- You can find how-to guides for almost any programming scenario on YouTube.
- Stack Exchange is one of the more popular Q&A sites for any programming questions you may have.
EXPERT TIP
Archana Ramamoorthy is the Chief Technology Officer, North America at Workday She is a product ninja, security advocate, and on a quest to enable more inclusion in the tech industry. Archana received her BS from SRM University and MS from Duke University and has been working in product management for over 8 years.
Our Expert’s Story: «I came to coding with zero background in either computer design or programming. When I wanted to learn to code, I started by reading Java books and using online information. In today’s world, there are so many resources available, so it’s very easy to learn new skills!»
Advertisement
-
3
Download a good text editor. Many programming languages allow you to use external text editors when you write your code. Find a text editor that will allow you to see indentations and code markup.
- Popular programs include Notepad++ (Windows), TextWrangler (OS X), JEdit, or Visual Studio Code.
-
4
Download any necessary compilers. Some programming languages require a compiler in order to run the code that you create. Compilers translate the code you write into a lower-level language that the machine can process. Many compilers are open-source and free to use. Languages that require compilers include:
- C
- C++
- C#
- Java
- BASIC
- Fortran
-
5
Start your first project. Pick a good introductory project that will help you get your feet wet. There are a variety of suggestions and tutorials online, but some places to start are basic websites for HTML, basic database and form functions with PHP, or simple programs with any of the compiler languages.
-
6
Comment all of your code. All programming languages have a comment feature that allows you to enter text that is ignored by the compiler. This allows you to add comments to your code. These comments are crucial, both for letting others know how your code works and for reminding yourself what your code does.
- You can also use the comment function to quickly remove code from your program for testing purposes. Put comment tags around the code you want to exclude and then remove the comment tags to return the code.
-
7
Pick apart other programs or web projects. When you’re learning as you go, don’t be ashamed to look things up and see how others have tackled the same tasks. Take time to understand why the code is doing what it does.
- See this guide for details on how to view the source code of websites.
Advertisement
-
1
Take classes. Community colleges, tech schools and online programs offer certifications and classes that will help you get a job as well as teaching you programming. An advanced degree like Computer Science is not always necessary, but could help to get a full time programming job.[2]
- (A lot can be said for having one-on-one time with a teacher or programming expert, something that isn’t always available with online sources.)
- Classes can get expensive, so weigh the benefits. If programming is just a hobby, classes may not be worth the time and money. If you want to turn programming into a career, classes can give you a big boost (but again, it’s far from necessary if you have the talent).
-
2
Expand your knowledge. Beyond simply learning programming languages, you will benefit a lot from logic and mathematics classes, as these are often required for advanced programming. You don’t necessarily need to learn this at school, but classroom settings can help.
- Programming involving physics calculations and other simulations requires a strong understanding of algorithms and models.
- Logic is the fundamental basis of programming, so understanding logic and processes will help you problem-solve when you are coding.
- Knowing advanced math isn’t required for most programming, but the knowledge can lead to benefits and optimizations.
-
3
Learn more languages. Once you’ve got a good grasp on your initial language, you can start to branch out. Find another language that complements the one you know, or pick a language for a specific job that interests you. Complementary languages, such as HTML and CSS, are often the easiest to learn.
- Java is one of the most popular languages, and there are often lots of opportunities for Java developers. Java can run on a huge variety of systems and has innumerable applications. Java is used for Android applications, one of the fastest growing markets.
- C++ is highly recommended for developing video games. Learning how to code in Unity (a widely used, low-cost game engine) and UDK (the code for the popular Unreal engine) can help open some doors, though they aren’t as useful outside of the video game industry.
- If you want to make iPhone apps, Xcode and Objective-C are going to be your primary tools. You’ll also need a Mac, since Xcode can only compile on a Mac.
- Python is a server scripting language that is one of the easier languages to learn. Python is used for web services such as Pinterest and Instagram, and is simple enough to learn the basics in just a few days.
-
4
Be patient. You will often be faced with challenges while programming, especially when it comes to hunting for bugs or implementing a new idea. You’ll have to learn to be satisfied with achieving small results rather than solving a whole puzzle at once. Patience leads to more effective code, which will lead to better-performing programs and happier colleagues.
-
5
Learn to work with others. If you have multiple people working on a project, you get multiple viewpoints on how to go about it. Working on teams is virtually unavoidable in the business world, so unless you’re planning to develop everything independently, be prepared to work with others.
-
6
Get a job where you can practice your coding skills. Work as a volunteer designing websites or writing desktop applications. A part time job with a small company can lead to opportunities to write code for websites or simple applications.
-
7
Connect with other programmers. There are countless communities and gatherings of developers that can help support and inspire you. Look up local programming conventions, participate in a hack-a-thon or game jam (timed events with a common theme), and sign up on some programming forums to start expanding your exposure and network.
-
8
Practice, practice, practice. It is estimated that it takes about 15,000 hours before you can be considered an expert in computer programming.[3]
This is years of applied practice. True mastery of programming will come only after you put the time in to practice and become proficient.- Try to spend time programming every day, even when you’re not working. Programming in your free time can lead to breakthroughs and new ideas.
Advertisement
Add New Question
-
Question
How do I make a website after I learn to code?
If you can write HTML, CSS, and Javascript, you can write a coded webpage using a code writer and then you can have your website validated and exported onto the web. There are quite a few websites to do this with online, so look around to find the best choice.
-
Question
Can I use a moble phone to learn how to creat a code?
Actually, yes you can. All you will need to make it possible is to have a mobile text editor.
-
Question
I don’t know a lot about coding, but all of my friends do. I get embarrassed. There are no classes to take in my area, what should I do?
If all of your friends know how to code, you should ask them to teach you! There’s nothing to be embarrassed about, they all had to start somewhere too. If you really want to do it on your own, codecademy.com and code.org are pretty good resources for beginners.
See more answers
Ask a Question
200 characters left
Include your email address to get a message when this question is answered.
Submit
Advertisement
Video
References
About This Article
Article SummaryX
To code, start by choosing a programming language that you want to learn, like HTML5 or C++, which are basic languages for beginners. Use free resources online, like video tutorials and blogs. Download a text editor like Notepad++, TextWrangler, or JEdit, and a compiler if necessary. In the text editor, start your first project and use online resources and other websites and apps for inspiration. For more experience, try taking an online class, or learning a different language. For tips on completing your first project, read on!
Did this summary help you?
Thanks to all authors for creating a page that has been read 393,325 times.
Reader Success Stories
-
«I am 14 years old. I always wanted to be a programmer. I searched on Google about programming all by myself and…» more
Is this article up to date?
Автор курса по С++ в Яндекс.Практикуме Маша Гутовская рассказала, что такое красивый код, и объяснила, почему начинающим разработчикам важно сразу учиться писать код красиво. В конце текста вас ждет чеклист с советами и полезными материалами.
Зачем писать красивый код
Красивый код — это работающий, понятный и читаемый код, который написан по определённым стандартам. Такой код экономит время самого разработчика и его коллег, потому что он структурирован и понятно оформлен, его легко читать и использовать, а написанные на нём программы легче поддерживать, «дебажить» и тестировать.
Если красивый код станет привычкой, принципом работы, с которым вы входите в профессию, то вам будет гораздо проще в дальнейшем. Попав в компанию, которая работает по общепринятым стандартам, вам не придётся переучиваться и менять собственные подходы к разработке.
Это также может сослужить добрую службу на собеседовании. С большей вероятностью рекрутеры отдадут предпочтение тому разработчику, который умеет писать красивый код.
Три уровня красоты кода
Я занимаюсь проектами для интернета вещей, умного дома и медицинского оборудования. Обсуждать красоту кода я буду на примере С++, так как работаю на нём. Но думаю, что мой подход актуален для любого языка. Я бы выделила три уровня красоты кода.
Визуальный уровень
Вы работаете не в вакууме, у вас есть коллеги, начальники, подчинённые. Даже если код вы пишете один, его кто-то использует. Чтобы избежать вкусовщины и разночтений, в IT-компаниях создают так называемые coding conventions — стандарты оформления кода. Это фиксированный набор правил, который определяет, как должен выглядеть код в компании. Многие разработчики создают собственные руководства. Некоторые из них выложены в открытых источниках, к примеру, руководство по стилю Google лежит на GitHub. Если вы разделяете эти положения, используйте их для написания своего кода, а опытный наставник поможет вам исправить шероховатости.
Уровень восприятия
Код должен быть красиво организован. Важны названия переменных, размеры классов, функций и методов, а также частота использования условных выражений. Обилие if, else и switch case создаёт нагромождение условий и частных случаев. И это не только визуальная проблема: такой код сложнее понимать, дорабатывать и поддерживать.
Структурный уровень
Код должен быть продуман с точки зрения архитектуры. Бывает, что он написан по всем правилам, но если вы попытаетесь внести изменения, поправить класс или фичу, то всё рассыплется. Просто потому, что архитектура кода плохо продумана.Представьте, что новому разработчику в команде нужно найти баг в программе. Для этого он должен суметь быстро и без посторонней помощи понять, как устроен ваш код: все элементы должны стоять на привычных местах и не должно быть неявных зависимостей.Чтобы избежать плохой архитектуры, рекомендую освоить принципы SOLID и писать код в соответствии с этими принципами. SOLID (single responsibility, open-closed, Liskov substitution, interface segregation, dependency inversion) — это пять принципов объектно-ориентированного программирования. Это набор правил, которым нужно следовать при создании структуры классов. Я советую подробнее узнать об этих принципах и других важных аспектах красоты кода, прочитав книгу Роберта Мартина «Чистый код».
Code smells и рефакторинг
Отчасти из-за отсутствия чёткой регламентации в отрасли возник термин code smells. Так говорят, когда код вроде работает и даже соответствует всем правилам компании, но что-то в нём не так. В таком случае полезно провести рефакторинг — переработку кода, которая не затрагивает его работу, но облегчает понимание. Подробнее об этом понятии можно почитать в книге Мартина Фаулера «Рефакторинг. Улучшение существующего кода».
Допустим, вы написали код. Прошло полгода, вы успели поучаствовать в разных проектах и написать не одну сотню строк. Стоит ли возвращаться к нему и делать рефакторинг? Обязательно. Во-первых, это повысит качество самого кода, а вы научитесь замечать детали, которые надо исправлять и которых стоит избегать в будущем. Во-вторых, вероятно, вам будет нужно время от времени рефакторить и чужой код — сможете набить руку на своём же старом. В блоке полезных ссылок в конце статьи вы найдете ссылку на отличный ресурс о том, как делать правильный рефакторинг.
Разберём код, в котором можно найти много проблем:
Этот код вполне соответствует правилам оформления. Но что мешает понять его легко и быстро?
Запутанные наименования, которые только усложняют понимание
— Название функции не соответствует тому, что она делает.
— Переменная типа int названа словом «condition», что выглядит сомнительно с точки зрения логики. Обычно условием можно называть какое-то выражение, проверку чего-то. Тип int — это число. Сказать, что число четыре является условием чего-то, странно.
— В названии функции заявлено, что она должна считать элементы. Вряд ли пользователь ожидает, что с элементами вектора что-то произойдет, тем более, что контейнер передаётся как константная ссылка. Но внутри функции константность убирается, и элементы контейнера меняются! Поэтому вызов такой функции может быть небезопасен.
— Непонятно, что скрывается за числом 10. Важно это обозначить, чтобы после возможных изменений в программе не появился баг. Ведь если никто не вспомнит о значении, никто не проверит код на ошибки.
Визуальный мусор
— Мёртвый код (закомментированный вызов ProcceedElementsCheck). Довольно сложно, на мой взгляд, понять, какой логики придерживался автор на этом участке. Причина — закомментированный код.
Проблемы с документированием
— Комментарий над функцией явно устарел и сбивает с толку.
Вероятно, опытный глаз может заметить ещё множество недочётов. Больше примеров плохого кода также можно посмотреть в книге «Чистый код».
Как написать красивый код
1. Следуйте правилам оформления кода (или coding conventions). Даже если вы фрилансите или учитесь, приобретите привычку всегда писать красиво.
2. Используйте специальные инструменты. Для корректировки кода под конкретный стайлгайд существует множество различных плагинов, скриптов и шаблонов для разных сред программирования. IDE (Integrated Development Environment) успешно автоматизирует этот процесс, а вы сможете и время сэкономить, и новый навык освоить.
Вот несколько полезных инструментов для автоматизации.
Plug-in Beautifier для вашей IDE — плагин, который сделает код единообразным, отформатирует, уберёт всё лишнее.
Clang-format — инструмент для автоматического форматирования кода без запуска IDE.
Google C++ Style Guide — руководство по стилю Google, которое можно взять за основу и использовать для создания собственного стайлгайда.
3. Сделайте код понятным. Код должен быть написан так, чтобы без ваших комментариев всё было ясно. Используйте простые названия переменных, классов и методов.
4. Освойте принципы SOLID. Нужно, чтобы ваш код не рассыпался при попытках коллег внести в него изменения.
5. Не забывайте про ревью. Код-ревью — это процесс, во время которого ваш код просматривают и комментируют члены команды. Хороший ревьюер укажет не только на баги в коде, но и на архитектурные недочёты и плохой стиль написания. Кроме этого, он объяснит, как можно было бы сделать работу быстрее и проще. Чтобы писать красивый и правильный код, внимательно относитесь к комментариям ревьюера, и тогда с каждой итерацией недочётов будет всё меньше.
6. Оставляйте время для рефакторинга. После рефакторинга ещё раз протестируйте код. Будет лучше, если ошибки заметите вы, а не ребята из вашей команды или пользователи.
7. Изучайте код новых проектов в open source. Это поможет вам быть в курсе новых практик и подходов. Следите за проектами, которые вам нравятся, смотрите, как и на чём они написаны, задавайте вопросы разработчикам.
8. Читайте книги и ресурсы по теме. Это просто, но работает. Я рекомендую «Чистый код» Роберта Мартина, «Рефакторинг. Улучшение существующего кода» Мартина Фаулера и отличный ресурс о рефакторинге Refactoring.guru.