|
|
|||||||||||||||||||||||||||||
|
Лучшие приемы программирования на CИсточник: ibm
Эта статья написана с учетом требований программистов. Здесь собран ряд рекомендаций, которым следуют авторы, работая программистами и консультантами в течение нескольких лет, и теперь эти рекомендации предлагаются читателям, чтобы помочь в их работе. Возможно, не все читатели согласятся со всеми предложенными здесь рекомендациями, но тем не менее некоторые из советов могут помочь в разработке и портировании приложений. Стили и нормы программирования
int i;main(){for(;i["]<i;++i){--i;}"];read('-'-'-',i+++"hell\
Отступы Необходимо использовать вертикальные и горизонтальные отступы. Количество и расположение отступов и пробелов должно отражать структуру кода. Длинная строка условного оператора должна быть разбита на несколько строк. Например: if (foo->next==NULL && number < limit && limit <=SIZE будет лучше выглядеть как: if (foo->next == NULL Точно так же сложные циклы for (curr = *varp, trail = varp; Другие сложные выражения, такие как использующие оператор z = (x == y) Комментарии Комментарии должны описывать то, что происходит, каким образом это происходит, что означает тот или иной параметр, какие глобальные переменные используются, а также любые ограничения и возможные ошибки. Однако необходимо избегать необязательных комментариев. Если код понятен и используются хорошие имена переменных, то, возможно, не потребуется дополнительных пояснений. Так как комментарии не проверяются компилятором, то не гарантируется, что они правильные. Комментарии, которые не согласуются с кодом, вредны. Слишком большое число комментариев приводит к беспорядку в коде. Такой стиль комментирования является избыточным: i=i+1; /* добавляем 1 к i */ Хорошо видно, что переменная /************************************ i=i+1; Правила наименования Имена с ведущими или завершающими знаками подчеркивания предназначены только для системы целей и не должны использоваться для каких-либо пользовательских имен переменных. Правила определяют следующие требования:
Для понятности необходимо избегать имен, различающихся только регистром, например, Имена переменных При выборе имени переменной не так важна длина имени, как понятность. Длинные имена могут назначаться глобальным переменным, которые редко используются, но индексы массивов, появляющиеся в каждой строке цикла, не должны быть значительно сложнее, чем for(i=0 to 100) и for(elementnumber=0 to 100) Имена функций Имена должны отражать то, что делают функции и что они возвращают. Функции используются в выражениях, часто в условных операторах if (checksize(x)) непонятно, так как не говорит о том, возвращает ли функция if (validsize(x)) делает все понятным. Объявление переменных Все объявления внешних переменных должны предваряться ключевым словом Обозначение указателя, char *s, *t, *u; вместо char* s, t, u; Второй пример объявления переменных не является неправильным, но могут возникнуть сомнения из-за того, что Заголовочные файлы Заголовочные файлы должны быть функционально организованы, т. е. объявления для различных подсистем должны содержаться в различных заголовочных файлах. Также объявления, которые являются платформозависимыми, должны быть вынесены в отдельный заголовочный файл. Следует избегать имен заголовочных файлов, совпадающих с именами стандартных библиотек. Строка Наконец, использование абсолютных путей для заголовочных файлов - не самая лучшая идея. Опция компилятора C
Не следует использовать #include <stdio.h> int main(void) printf("Enter an integer and a float: "); printf("I read %d and %f\n", i, f); Запустим тест: Enter an integer and a float: 182 52.38 I read 182 and 52.380001 Теперь другой тест: Enter an integer and a float: 6713247896 4.4 I read -1876686696 and 4.400000
При применении операций инкремента или декремента к переменной эта переменная не должна появляться в выражении более одного раза, так как итог в этом случае зависит от компилятора. Не следует писать код, который полагается на порядок обработки или особенности компилятора: int i = 0, a[5]; a[i] = i++; /* присваивание значения a[0]? или a[1]? */ Нельзя позволять себе видеть то, чего на самом деле нет Рассмотрим следующий пример: while (c == '\t' // c = ' ' // c == '\n') На первый взгляд такой оператор while ((c == '\t' // c) = (' ' // c == '\n')) Левая часть оператора присваивания: (c == '\t' // c) не приводит к появлению корректного значения. Если переменная c содержит символ табуляции, то результат Явно выраженные намерения При написании кода, который может быть интерпретирован как что-то другое, необходимо заключать этот код в скобки, чтобы быть уверенным, что намерения выражены явно. Это поможет понять намерения разработчика при последующих обращениях к коду, а также помогает в сопровождении кода. Иногда можно разрабатывать код, который предупреждает возможные ошибки. Например, можно ставить константы в левую часть оператора сравнения, т. е. вместо: while (c == '\t' // c == ' ' // c == '\n') можно написать так: while ('\t' == c // ' ' == c // '\n' == c) В этом случае компилятор выдаст предупреждение: while ('\t' = c // ' ' == c // '\n' == c) Такой стиль программирования позволяет компилятору находить потенциальные проблемы; пример кода выше неправилен, так как пытается присвоить значение для Ошибки из-за специфики реализации языка программирования Реализации языка C могут отличаться в некоторых аспектах. Необходимо иметь представление о той части языка, которая совпадает во всех реализациях. Зная это, значительно проще портировать программу на другую систему или другой компилятор, что уменьшает шансы столкнуться со спецификой компилятора. Для примера рассмотрим следующую строку: /*/*/2*/**/1 Выражение будет интерпретироваться по правилу максимального оператора. Если комментарии могут быть вложенными, то интерпретация будет следующей: /* /* /2 */ * */ 1 Паре символов /* / */ 2 * /* */ 1
Сбрасывание буфера на диск Когда приложение завершается некорректно, окончание его вывода обычно теряется. Приложение может не успеть полностью завершить вывод. Часть информации может оставаться в памяти и уже не будет записана в вывод. В некоторых системах такой незавершенный вывод может достигать нескольких страниц памяти. Потеря вывода может также привести к мысли, что программа завершилась ошибочно гораздо раньше, чем это произошло на самом деле. Способ решения такой проблемы состоит в организации принудительного вывода, особенно при отладке. Конкретная реализация этого отличается для различных систем, но обычно выглядит так: setbuf(stdout, (char *) 0); Это выражение должно быть выполнено перед записью в
Следующая программа выводит свои входные данные: #include <stdio.h> int main(void) while ((a = getchar()) != EOF) Если удалить включение заголовочного файла Программу можно переписать следующим образом: #define EOF -1 int main(void) while ((a = getchar()) != EOF) Это будет работать на большинстве систем, но на некоторых может быть значительно медленнее. Так как вызов функции обычно занимает довольно много времени, В действительности многие реализации компиляторов языка C имеют свои стандартные функции Пустой указатель Пустой указатель не указывает ни на какой объект. Неправильно использовать пустой указатель для любых целей, кроме присваивания и сравнения. Никогда не следует переопределять значение Переход по пустому указателю может вызвать странные эффекты. Что означает Единственный правильный способ интерпретации этого выражения такой: a ++ + ++ b Однако правило длинного оператора предписывает разбить выражение следующим образом: a ++ ++ + b Это синтаксически неверно, такой код эквивалентен строке: ((a++)++) + b Но результат ++b; Осторожное обращение с функциями Функции обеспечивают наиболее общее структурирование кода на C. Они должны использоваться для решения проблемы "сверху вниз" - для разбиения задачи на ряд более мелких подзадач до тех пор, пока подзадача не будет легко решаться. Это помогает реализовать модульность и упростить документирование программы. Кроме того, программы, составленные из большого числа маленьких функций, значительно легче для отладки. Необходимо приводить все аргументы функций к нужному типу, если это не было сделано раньше, даже если точно известно, что компилятор осуществляет необходимое приведение типов. Делая приведение типа вручную, программист явно обозначает свои намерения и получит правильный результат при портировании приложения на другую платформу. Если заголовочные файлы не объявляют тип возвращаемого значения библиотечных функций, необходимо сделать это самостоятельно. Окружив объявления конструкцией Прототипы функций используются для того, чтобы сделать код более устойчивым, а приложение - быстрым. "Висячий" Нужно опасаться проблемы "висячего" if (a == 1) Правило гласит, что Границы массива Необходимо проверять границы всех массивов, включая строки, так как сегодняшнее Тот факт, что в C элементы нумеруются с нуля, делает более вероятными ошибки подсчета. Однако требуются некоторые усилия на изучение того, как уберечься от них. Пустой оператор Пустой оператор цикла while (*dest++ = *src++) Проверка выражений на истинность Не нужно оставлять по умолчанию проверку на ненулевое значение, т. е.: if (f() != FAIL) if (f()) даже если Частые затруднения вызывает функция проверки равенства строк #define STREQ(str1, str2) (strcmp((str1), (str2)) == 0) If ( STREQ( inputstring, somestring ) ) ... Таким образом, функция получает желаемое поведение (не требуется переписывать или переопределять стандартные библиотечные функции типа Не следует сравнивать логические выражения с if (func() == TRUE) {... лучше переписать так: if (func() != FALSE) Вложенные операторы А сейчас - время для разговора о вложенном операторе присваивания. В некоторых конструкциях нет лучшего способа присваивания, хотя он и влечет увеличение кода в операторе и ухудшение читабельности: while ((c = getchar()) != EOF) { Использование вложенного оператора присваивания для улучшения производительности возможно. Однако необходимо искать компромисс между увеличением скорости и усложнением сопровождения кода, которое возникает при использовании вложенных присваиваний в неподходящем месте. Например: x = y + z; не должно заменяться на: d = (x = y + z) + r; даже если последний вариант сможет сберечь один цикл. В долговременной перспективе разница во времени между двумя этими вариантами будет уменьшаться из-за использования компьютерной оптимизации, в то время как разница во времени, необходимом для сопровождения кода, будет увеличиваться. Оператор
for (...) { Когда необходимо применять оператор "Проваливание" через Когда блок кода имеет несколько меток, каждую из них нужно размещать на отдельной строке. Этот элемент стиля программирования согласуется с правилом установки вертикальных отступов и делает перекомпоновку (если она понадобится) сравнений case простой задачей. Использование предоставляемой языком С возможности "проваливания" в операторе switch (expr) { Хотя последний оператор Константы Символические константы делают код более простым для чтения. Числовых констант, как правило, следует избегать; лучше использовать Константы необходимо объявлять соответственно их использованию, т. е. необходимо указывать for (i = 0; i < arraysub; i++) оправдан, а код: gate_t *front_gate = opens(gate[i], 7); - нет. Во втором примере Не нужно использовать переменные с плавающей точкой там, где нужны дискретные значения. Это связано с не совсем корректным представлением чисел с плавающей точкой (можно вспомнить второй пример из раздела Символьные константы должны быть объявлены как символы, а не как числа. Нетекстовые символы являются более трудными для портирования. Если нетекстовые символы необходимы, в частности, при использовании в строках, они должны быть записаны в виде управляющих последовательностей из трех восьмеричных цифр, а не одной (например, Условная компиляция Условная компиляция полезна в случаях, когда требуется реализовать машинозависимый код, при отладке и для установок значений во время компиляции. Различные варианты управления могут легко привести к непредвиденным ситуациям. При использовании
Общеизвестно, что сопровождение приложения отнимает значительную часть времени программиста. Частично это происходит из-за использования платформозависимых и нестандартных особенностей, но в большей степени - из-за плохого стиля программирования. В этой статье дается несколько советов, которые помогают сберечь время, требуемое для сопровождения кода. Следование этим советам сделает сопровождение приложений командой разработчиков более простым.
Ссылки по теме
|
|