Домой / Скайп / Критерии хорошего кода в программировании. Правила программирования на PHP (14 правил) Правила программирования

Критерии хорошего кода в программировании. Правила программирования на PHP (14 правил) Правила программирования

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

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

Существует множество известных подходов к критериям качества кода, о которых рано или поздно узнаёт практически любой разработчик. Например, есть программисты, которые придерживаются принципа проектирования KISS (Keep It Simple, Stupid! - Делай это проще, тупица!). Этот метод разработки вполне справедлив и заслуживает уважения, к тому же отражает универсальное правило хорошего кода - простоту и ясность. Однако простота должна иметь границы - порядок в программе и читабельность кода не должны быть результатом упрощения. Кроме простоты, существует ещё несколько несложных правил. И они решают ряд задач.

  • Обеспечивать лёгкое покрытие кода тестами и отладку. Unit тестирование - это процесс тестирования модулей, то есть функций и классов, являющихся частью программы. Создавая программу, разработчик должен учитывать возможности тестирования с самого начала работы над написанием кода.
  • Облегчать восприятие кода и использование программы. Этому способствуют логичное именование и хороший стиль интерфейса и реализации.
  • Гарантировать лёгкость сопровождения. Продуманная и реализованная структура программы позволяет решать вопросы, связанные с работой программы на новом аппаратном обеспечении или новой платформе.
  • Упрощать процесс внесения дальнейших изменений. Чем лучше оптимизирована структура, тем проще изменять код, добавлять новые свойства, повышать быстродействие и изменять архитектуру.
  • Обеспечивать устойчивость программы. При внесении изменений или возможных неполадках можно легко внести исправления. А правильная обработка ошибок значительно облегчает эксплуатацию программного продукта.
  • Обеспечивать возможность поддержки проекта несколькими разработчиками или целыми сообществами (особенно важно для проектов с открытым исходным кодом).

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

Критерии хорошего кода в программировании – 8 правил от GeekBrains

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

Вот несколько общих правил, с которыми вы можете столкнуться:

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

if (typeof a ! == "undefined" &&
typeof b ! == "undefined" &&
typeof c === "string") {
//your stuff
}

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

for (var i = 0; i < 100; i++) {
}

В некоторых средах разработки можно изначально задать правила форматирования кода, загрузив настройки отдельным файлом (доступно в Visual Studio). Таким образом, у всех программистов проекта автоматически получается однотипный код, что значительно улучшает восприятие. Известно, что достаточно трудно переучиваться после долгих лет практики и привыкать к новым правилам. Однако в любой компании code style - это закон, которому нужно следовать неукоснительно.

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

Например, в коде есть строка:
DrawWindow(50, 70, 1000, 500);

Очевидно, она не вызовет ошибок в работе кода, но и её смысл не всем понятен. Гораздо лучше не полениться и сразу написать таким образом:

int left = 50;
int top = 70;
int width = 1000;
int height = 500;
DrawWindow(left, top, width, height);

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

SquareCircle = 3.14*rad*rad

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

const float pi = 3.14;
SquareCircle = pirad rad

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

Используйте осмысленные имена для переменных, функций, классов. Всем программистам известен термин “обфускация кода” - сознательное запутывание программного года с помощью приложения-обфускатора. Она делается с целью скрыть реализацию и превращает код в невнятный набор символов, переименовывает переменные, меняет имена методов, функций и проч… К сожалению, случается так, что код и без обфускации выглядит запутанно - именно за счёт бессмысленных имён переменных и функций: var_3698, myBestClass, NewMethodFinal и т.д… Это не только мешает разработчикам, которые участвуют в проекте, но и приводит к бесконечному количеству комментариев. Между тем, переименовав функцию, можно избавиться от комментариев - её имя будет само говорить о том, что она делает.

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

Например, возьмём код такого вида:

// находим лист, записываем в r
if (x != null) {
while (x.a != null) {
x = x.a;
r = x.n;
}
}
else {
r = ””;
}

Из комментария должно быть понятно, что именно делает код. Но совершенно неясно, что обозначают x.a и x.n. Попробуем внести изменения таким образом:
// находим лист, записываем имя в leafName
if (node != null) {
while (node.next != null) {
node = node.next;
leafName = node.name;
}
}
else {
leafName = ””;
}

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

Объединим в одно разъяснение ещё два важных правила. Создавайте методы как новый уровень абстракции с осмысленными именами и делайте методы компактными. Вообще, сегодня модульность кода доступна каждому программисту, а это значит, что нужно стремиться создавать абстракции там, где это возможно. Абстракция - это один из способов сокрытия деталей реализации функциональности. Создавая отдельные небольшие методы, программист получает хороший код, разделённый на блоки, в которых содержится реализация каждой из функций. При таком подходе нередко увеличивается количество строк кода. Есть даже определённые рекомендации, которые указывают длину метода не более 10 строк. Конечно, размер каждого метода остаётся целиком на усмотрении разработчика и зависит от многих факторов. Наш совет: всё просто, делайте метод компактным так, чтобы один метод выполнял одну задачу. Отдельные вынесенные сущности проще улучшить, например, вставить проверку входных данных прямо в начале метода.

Для иллюстрации этих правил возьмём пример из предыдущего пункта и создадим метод, код которого не требует комментариев:
string GetLeafName (Node node) {
if (node != null) {
while (node.next != null) {
node = node.next;
}
return node.name;
}
return ””;
}

И, наконец, скроем реализацию метода:

LeafName = GetLeafName(node); …

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

В примере ниже вставляем проверку на то, чтобы на входе не получить null.
List GetEvenItems(List items) {
Assert(items != null);

List result = new List();
foreach (int i in items) {
if (i % 2 == 0) {
result.add(i);
}
}
return result;
}

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

Соответственно, программист начинает плодить наследование. А ведь даже с точки зрения обывательской логики класс Автомобиль - это композиция элементов. Допустим, есть такой код, когда новый класс создаётся с использованием наследования (класс ScreenElement наследует поля и методы класса Coordinate и расширяет этот класс):

сlass Coordinate {
public int x;
public int y;
}
class ScreenElement: Coordinate {
public char symbol;
}

Используем композицию:
сlass Coordinate {
public int x;
public int y;
}
class ScreenElement {
public Coordinate coordinate;
public char symbol;
}

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

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

Возьмём класс:

class Square { public float edge; public float area; }

Ещё один класс:

class Square { public float GetEdge () ; public float GetArea () ; public void SetEdge ( float e ) ; public void SetArea ( float a ) ; private float edge; private float area; }

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

В ходе вебинара был задан вопрос: «А можно писать сразу хорошо и не рефакторить?» Вероятно, через несколько лет или даже десятков лет программирования это будет возможно, особенно если есть изначальное видение архитектуры программы. Но никогда нельзя предвидеть конечное состояние проекта через несколько релизов и итераций доработки. Именно поэтому важно всегда помнить о перечисленных правилах, которые гарантируют устойчивость и способность вашей программы к развитию.

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

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

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

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

«Самых лучших?» Но это же я! Я «самый лучший». Разумеется, я должен нанимать людей, таких же одаренных, таких умных и симпатичных, как я сам. Да и зачем засорять мою прекрасную команду всяким сбродом?

Кто они, самые лучшие программисты?

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

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

Начальник

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

Наверное, в крупной компании с таким подходом можно прожить. Я сильно подозреваю, что Лохматый Начальник в комиксах про Дилберта был срисован с натуры.

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

Кадры программистов

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

Ищите людей, склонных к самоанализу

«Самые лучшие» работники никогда не перестают учиться.

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

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

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

Как найти такого человека?

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

На собеседованиях кандидату часто предлагается описать свою основную слабость. Хотя этот вопрос до ужаса традиционен, он мне нравится.

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

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

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

Нанимайте разработчиков, а не программистов

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

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

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

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

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

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

  1. Способен ли этот кандидат сделать для группы то, что не сможет никто другой?
  2. Находится ли он в процессе постоянного обучения?
  3. Знает ли этот кандидат о своих слабостях и может ли спокойно обсуждать их?
  4. Насколько этот кандидат универсален и способен сделать «все, что потребуется», чтобы обеспечить коммерческий успех продукта?
  5. Принадлежит ли кандидат к числу «десятикратных» программистов?
  6. Обладает ли он степенью бакалавра, полученной в одном из уважаемых университетов?
  7. Если кандидат обладает докторской степенью, имеются ли другие признаки, свидетельствующие о его способностях к разработке коммерческих продуктов?
  8. Имеется ли у кандидата опыт работы в группах, занимавшихся разработкой коммерческих продуктов?
  9. Может ли кандидат представить примеры хорошего кода?
  10. Любит ли кандидат программирование настолько, чтобы писать код в свободное время?

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

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

Так что теперь у вас есть проблема, если вы пишете библиотеку, которая будет использоваться как кодом старой школы, написанным с wchar_t , определённым как псевдоним для unsigned short , так и кодом новой школы, написанным с wchar_t как отдельным внутренним типом. Какой тип данных вам нужно использовать для строковых параметров?

Это перевод The sad history of Unicode printf-style format specifiers in Visual C++ .

Windows реализовала Unicode раньше, чем большинство других операционных систем. В результате решения Windows для многих проблем отличаются от решений, принятых теми, кто подождал, когда пыль осядет¹. Самым ярким примером этого является использование Windows UCS-2 в качестве кодировки Unicode. Тогда это была кодировка, рекомендованная консорциумом Unicode, потому что Unicode 1.0 поддерживал только 65"536 символов². Консорциум Unicode передумал пять лет спустя, но к тому времени было уже слишком поздно для Windows, которая уже выпустила Win32s, Windows NT 3.1, Windows NT 3.5, Windows NT 3.51 и Windows 95 - все из которых использовали UCS-2³.

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

Это перевод If FlushInstructionCache doesn’t do anything, why do you have to call it, revisited .

Предполагается, что вы будете вызывать функцию FlushInstructionCache , когда вы генерируете или модифицируете исполняемый код в run-time - чтобы процессор при выполнении вашего сгенерированного/модифицированного кода читал бы написанные вами инструкции, а не старые инструкции, которые могут остаться в кеше команд процессора.

Ранее мы узнали, что . Это потому, что простого вызова функции было достаточно, чтобы очистить кэш команд.

Но в Windows NT функция FlushInstructionCache выполняет реальную работу, поскольку ей необходимо уведомить все остальные процессоры о необходимости очищать их кэши.

Однако если вы посмотрите на Windows 10, то вы обнаружите, что функция FlushInstructionCache выглядит как версия для Windows 95: она ничего не делает .

В чём тут дело?

В последнее время я видел мало действительно хорошего кода, много посредственного и очень много - плохого. (Много того, что я писал раньше - особенно, когда я только начинал - относится к последним, увы.) Читая случайные статьи в интернете и профессиональные книги, я пришел к выводу, что писать хороший код - легко. Невероятно трудно, но в то же время легко. На самом деле, это настолько просто, что сводится к трем правилам.

  1. Пишите код для людей, а не машины.
  2. Каждый фрагмент кода должен выполнять одну задачу.
Соблюдая их постоянно, вы будете писать хороший код. На любом языке программирования и в любой парадигме. Проблема в том, что это очень трудно. Все они требуют дисциплины, а для двух последних в большинстве случаев нужны еще и продолжительные размышления.
Пишите код для людей, а не машины
Это самое важное из трех правил, лежащее в основе двух других. Пишите код, который прост для человека; оставьте тяжелую работу компьютеру. Используйте осмысленные имена переменных и методов. Не создавайте запутанных логических цепочек там, где можно применить и простые. Не пытайтесь уместить в одной строке как можно больше. Соблюдайте единый стиль написания кода, в котором есть осмысленные отступы. Если ваши файлы настолько громоздкие, что их становится сложно прокручивать, разбейте их на несколько небольших.

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

Ваш компилятор (или интерпретатор) может обрабатывать абсолютно разные стили кода. Для него n и numberOfObjects - это одно и тоже. Для людей - нет. Они будут долго вчитываться в код, даже если предназначение переменной кажется вам очевидным.

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

Если вы делаете что-то неочевидное, чтобы оптимизировать часть кода, опишите в комментарии, что именно она делает. Но не забывайте, что в большинстве случаев вы не сможете оптимизировать программу лучше, чем компилятор. Реальность такова, что он умнее вас. Это факт: компиляторы улучшались в течение десятилетий тяжелой работы профессионалов. Бывают и исключения, но они лишь подтверждают правило.

Пишите код, понятный людям.

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

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

Подумайте, что будет легче понять при знакомстве с кодом: фрагмент длиной в 30 строк, освобождающий блок памяти, или вызов функции clearMapVariableMemory() ?
Возможно, вам понадобится изучить фрагмент позже, но даже в этом случае работать с отдельной функцией будет легче.

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

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

Не повторяйте себя.

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

Несколько лет назад один разработчик показал мне неработающий фрагмент кода. Для того, чтобы разобраться в нем, программисту потребовалось несколько часов. В итоге проблема обнаружилась в пост-инкрементальных операторах C (в особых случаях их поведение разнится от компилятора к компилятору). Впоследствии, читая одну книгу по разработке, я отметил, что истинная проблема заключается не в операторах, а в том, что один фрагмент отвечал за выполнение множества разных задач.

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

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

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

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

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

Перевод статьи

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

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

Правило 1. Следуйте стандартам оформления кода.

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

Например, в этом куске кода в соответствии со стандартом есть 12 ошибок:

For(i=0 ;i

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

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

Правило 2. Давайте наглядные имена.

Ограниченные медленными, неуклюжими телетайпами, программисты в древности использовали контракты для имён переменных и процедур, чтобы сэкономить время, стуки по клавишам, чернила и бумагу. Эта культура присутствует в некоторых сообществах ради сохранения обратной совместимости. Возьмите, например, ломающую язык функцию C wcscspn (wide character string complement span). Но такой подход неприменим в современном коде.

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

Гораздо важнее, чтобы вы долго и хорошо думали перед тем, как что-то назвать. Является ли имя точным? Имели ли вы в виду highestPrice или bestPrice? Достаточно ли специфично имя, дабы избежать его использования в других контекстах для схожих по смыслу объектов? Не лучше ли назвать метод getBestPrice заместо getBest? Подходит ли оно лучше других схожих имён? Если у вас есть метод ReadEventLog, вам не стоит называть другой NetErrorLogRead. Если вы называете функцию, описывает ли её название возвращаемое значение?

В заключение, несколько простых правил именования. Имена классов и типов должны быть существительными. Название метода должно содержать глагол. Если метод определяет, является ли какая-то информация об объекте истинной или ложной, его имя должно начинаться с «is». Методы, которые возвращают свойства объектов, должны начинаться с «get», а устанавливающие значения свойств - «set».

Правило 3. Комментируйте и документируйте.

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

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

Правило 4. Не повторяйтесь.

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

  • Создание справочников API из комментариев, используя Javadoc и Doxygen.
  • Автоматическая генерация Unit-тестов на основе аннотаций или соглашений об именовании.
  • Генерация PDF и HTML из одного размеченного источника.
  • Получение структуры классов из базы данных (или наоборот).

Правило 5. Проверяйте на ошибки и реагируйте на них.

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

Правило 6. Разделяйте код на короткие, обособленные части.

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

Более того, каждый класс, модуль, файл или процесс должен выполнять определённый род задач. Если часть кода выполняет совершенно разнородные задачи, то разделите его соответственно.

Правило 7. Используйте API фреймворков и сторонние библиотеки.

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

Правило 8. Не переусердствуйте с проектированием.

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

Правило 9. Будьте последовательны.

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

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

Правило 10. Избегайте проблем с безопасностью.

Современный код редко работает изолированно. У него есть неизбежный риск стать мишенью атак. Они необязательно должны приходить из интернета; атака может происходить через входные данные вашего приложения. В зависимости от вашего языка программирования и предметной области, вам возможно стоит побеспокоиться о переполнении буфера, кросс-сайтовых сценариях, SQL-инъекциях и прочих подобных проблемах. Изучите эти проблемы, и избегайте их в коде. Это не сложно.

Правило 11. Используйте эффективные структуры данных и алгоритмы.

Простой код часто легче сопровождать, чем такой же, но изменённый ради эффективности. К счастью, вы можете совмещать сопровождаемость и эффективность, используя структуры данных и алгоритмы, которые даёт ваш фреймворк. Используйте map, set, vector и алгоритмы, которые работают с ними. Благодаря этому ваш код станет чище, быстрее, более масштабируемым и более экономным с памятью. Например, если вы сохраните тысячу значений в отсортированном множестве, то операция пересечения найдёт общие элементы с другим множеством за такое же число операций, а не за миллион сравнений.

Правило 12. Используйте Unit-тесты.

Сложность современного ПО делает его установку дороже, а тестирование труднее. Продуктивным подходом будет сопровождение каждого куска кода тестами, которые проверяют корректность его работы. Этот подход упрощает отладку, т.к. он позволяет обнаружить ошибки раньше. Unit-тестирование необходимо, когда вы программируете на языках с динамической типизацией, как Python и JavaScript, потому что они отлавливают любые ошибки только на этапе исполнения, в то время как языки со статической типизацией наподобие Java, C# и C++ могут поймать часть из них во время компиляции. Unit-тестирование также позволяет рефакторить код уверенно. Вы можете использовать XUnit для упрощения написания тестов и автоматизации их запуска.

Правило 13. Сохраняйте код портируемым.

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

Правило 14. Делайте свой код собираемым.

Простая команда должна собирать ваш код в форму, готовую к распространению. Команда должна позволять вам быстро выполнять сборку и запускать необходимые тесты. Для достижения этой цели используйте средства автоматической сборки наподобие Make , Apache Maven , или Ant . В идеале, вы должны установить интеграционную систему, которая будет проверять, собирать и тестировать ваш код при любом изменении.

Правило 15. Размещайте всё в системе контроля версий.

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

Заключение.

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