Стилевое единство и идиомы Чем логичнее и последовательнее оформлена программа, тем она лучше. Если форматирование кода меняется непредсказуемым образом: цикл просматривает массив то по возрастанию, то по убыванию, строки копируются то с помощью strcpy, то с помощью цикла for — все перечисленные вариации сбивают читателя с толку, и ему трудно понять, что в действительности происходит. Но если одни и те же вычисления всегда выполняются одинаково, то каждое отклонение от обычного стиля будет указывать на действительное различие, заслуживающее быть отмеченным. Будьте последовательны в применении
отступов и фигурных скобок. Стоит ли вставлять фигурные скобки, когда необходимости в них нет? Как и обычные скобки, дополнительные фигурные скобки могут разрешить неясные моменты и облегчить понимание структуры кода. Многие опытные программисты во имя все того же постоянства всегда заключают в фигурные скобки тела циклов и условных операторов. Однако если тело состоит лишь из одной строки, то скобки явно не нужны, и мы бы посоветовали их опустить. Впрочем, не забудьте вставить их в тех случаях, когда они помогут разрешить неясности с "висящим else", — подобная неопределенность хорошо иллюстрируется следующим примером:
if (day > 29) и весь код работает неверно. Когда один if следует сразу за другим всегда используйте фигурные скобки:
В рассматриваемом примере даже после исправления отмеченной ошибки код трудно понять. В нем будет проще разобраться, если мы заведем переменную для хранения количества дней в феврале:
Кстати, если вы работаете над программой, которую писал кто-то другой, сохраняйте ее оригинальный стиль. При внесении изменений не применяйте свой стиль, даже если он вам больше нравится. Постоянство в форматировании программы гораздо важнее, чем ваше собственное, поскольку только оно сможет облегчить жизнь тем, кому придется вникать в способы функционирования этой программы. Используйте идиомы для единства стиля. В языках программирования, как и в обычных языках, существуют свои идиомы — это устоявшиеся приемы, используемые опытными программистами для написания типичных фрагментов кода. Одна из центральных проблем, возникающих при изучении языка программирования, — необходимость привыкнуть к его идиомам. Одной из наиболее типичных идиом является форма написания цикла. Рассмотрим код на С, C++ или Java для просмотра п элементов массива, например для их инициализации. Можно написать этот код так: ? 1 = 0; или так: ? for (1=0; 1 < п; ) или даже так: for (i = n; --1 >~ 0; ) Все три способа, в принципе, правильны, однако устоявшаяся, идиоматическая форма выглядит так: for (1 = 0; i < n; i++) Выбор именно этой формы не случаен. При такой записи обходятся все п элементов массива, пронумерованные от 0 до n-1. Все управление циклом находится непосредственно в его заголовке; обход происходит в возрастающем порядке; для обновления переменной счетчика цикла используется типичная операция инкремента (++). После выхода индекс цикла имеет известное нам значение как раз за последним элементом массива. Те, для кого этот язык как родной, все понимают без пояснений и воспроизводят конструкцию не задумываясь. В C++ и Java стандартный вариант включает еще и объявление переменной цикла: for (int 1=0; i < n; i++) Вот как выглядит стандартный цикл для обхода списка в С: for (р = list; р != NULL; р = p->next) ....... И опять все управляющие элементы цикла находятся непосредственно в выражении for. Для бесконечного цикла мы предпочитаем конструкцию for (;;) однако популярна и конструкция while (1) Не используйте других форм, кроме двух приведенных. Использование отступов также должно быть стандартным, можно сказать — идиоматичным. Вот такое необычное вертикальное расположение управляющих элементов цикла ухудшает читабельность кода, поскольку выглядит как три отдельных выражения, а не как единый заголовок цикла:
for (ар = агг; ар < агг+128; ар++) Небрежно выбранное расположение отступов растянет код на несколько экранов или страниц, что также не улучшает его восприятие. Еще.один стандартный прием — вставлять присваивание внутрь условия цикла: while ((с = getchar()) != EOF) Выражение do-while используется гораздо реже, чем for и while, поскольку при его применении тело цикла исполняется как минимум один раз вне зависимости от условий, которые проверяются не в начале цикла, а в конце. Во многих случаях это становится причиной появления ошибки, ждущей своего часа, как, например, в следующем варианте цикла для getchar:
Одно из преимуществ последовательного применения идиом состоит в том, что любой нестандартный цикл, потенциальный источник ошибок, сразу же привлечет внимание. Например:
В С и C++ есть также идиомы для выделения места под строки и для работы с ними, а код, который их не использует, зачастую таит в себе ошибку:
р = malloc(strlen(buf)+1); или р = new char[strlen(buf)+1]; в C++. Таким образом, если вы не видите +1, то стоит проверить все еще раз. В Java такой проблемы нет, поскольку строки там не представляются в виде массивов, оканчивающихся на 0. Кроме того, индексы массивов проверяются, так что в Java выйти за границы массива невозможно. В большинстве сред С и C++ предусмотрена библиотечная функция strdup, которая создает копию строки, используя для этого malloc и st rcpy, что гарантирует от обсуждавшейся чуть выше ошибки. К сожалению, st rdup не входит в стандарт ANSI С. Кстати говоря, ни в первой, ни в окончательной версии не проверяется значение, возвращаемое функцией malloc. Мы опустили эту проверку для того, чтобы сфокусировать ваше внимание на теме данного раздела, однако в настоящей программе значения, возвращаемые функциями malloc, realloc, strdup, а также другими функциями, выделяющими память, должны в обязательном порядке проверяться. Используйте цепочки else-if для многовариантных ветвлений. Стандартной формой записи многовариантного ветвления является последовательность операторов if. . .else if...else:
Все операторы else стоит выровнять по вертикали, вместо того чтобы устанавливать каждое else на одном уровне с соответствующим ему if. Вертикальное выравнивание означает, что проверки производятся последовательно; кроме того, предотвращается выползание кода за правый край страницы. Последовательность вложенных выражений if —. предвестник трудно читаемого кода, если не заведомых ошибок:
Попытки повторного использования уже написанных блоков кода ведут к появлению программ, похожих на затянутые узлы:
Использование перескоков оправдано в случае, когда несколько условий имеют одинаковый код. Тогда их размещают таким образом:
Упражнение 1-7 Перепишите выражения C/C++ более понятным образом:
Найдите ошибки в этом фрагменте кода на языке Java и исправьте их, переписав цикл в стандартной форме:
|