Правила программирования на Си и Си++ Ален И. Голуб




Название Правила программирования на Си и Си++ Ален И. Голуб
страница 5/19
Дата публикации 19.06.2014
Размер 3 Mb.
Тип Реферат
literature-edu.ru > Авто-ремонт > Реферат
1   2   3   4   5   6   7   8   9   ...   19

#define begin {

#define end }

while ( ... )

begin

// ...

end

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

Родственная проблема связана с использованием макросов препроцессора для скрытия синтаксиса объявлений Си. Например, не делайте следующего:

typedef const char *LPCSTR;

LPCSTR str;

Подобные вещи вызывают проблемы с сопровождением, потому что кто-то, не знакомый с вашими соглашениями, будет должен просматривать typedef, чтобы разобраться, что происходит на самом деле. Дополнительная путаница возникает в Си++, потому что читатель может интерпретировать происходящее, как определение объекта Си++ из класса LPCSTR. Большинству программистов на Си++ не придет в голову, что LPCSTR является указателем. Объявления Си очень легко читаются программистами на Си. (Кстати, не путайте вышеупомянутое с разумной практикой определения типа word в виде 16-битового числа со знаком для преодоления проблемы переносимости, присущей типу int, размер которого не определен ни в ANSI Си, ни в ANSI Си++).

К тому же, многие программисты избегают условной операции (?:) просто потому, что она им кажется непонятной. Тем не менее, это условное выражение может существенно упростить ваш код и, следственно, сделать его лучше читаемым. Я думаю, что:

printf("%s", str ? str : "<�пусто>");

гораздо элегантнее, чем:

if ( str == NULL )

printf( "<�пусто>" );

else

printf( "%s", str );

Вы к тому же экономите на двух вызовах printf(). Мне также часто приходится видеть неправильное использование операций ++ и --. Весь смысл автоинкремента или автодекремента заключается в соединении этих операций с другими. Вместо:

while ( *p )

{

putchar ( *p );

++p;

}

или:

for( ; *p ; ++p )

putchar ( *p );

используйте:

while( *p )

putchar ( *p++ );

Этот код вполне читаем для компетентного программиста на языке Си, даже если ему нет эквивалентной операции в ФОРТРАНе или Паскале.

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

struct tree_node

{

struct tree_node *lftchld;

};

#define left_child(x) ((x)->lftchld)

//...

traverse( tree_node *root )

{

if( left_child(root) )

traverse( left_child( root ) );

// ...

}

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

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

51. Функция должна делать только одно дело

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

UpdateAllViews( CView *sender, long lhint, CObject *phint )

{

// sender lhint phint

// NULL xx xx Начальное обновление, вызываемое из

// обрамляющего окна

// Cview* 0 Crect* Вызывается, когда встроенный объект

// становится действительным. phint

// указывает на прямоугольник документа,

// сохраняя положение недействительного

// объекта

// Cview* 1 Crect* Сообщение, посылаемое объектом CView*

// ("sender" - передатчик). phint сохраняет

// для CView* обрамляющее окно его клиента.

}

Вам нужны вместо этого три функции: initial_update(), update_embedded_object() и update_view(). Верным ключом для понимания того, что здесь что-то не так, является туманная природа имен аргументов. Функциям не должны передаваться "намеки". Им должны даваться указания.

52. Иметь слишком много уровней абстракции или инкапсуляции так же плохо, как и слишком мало

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

struct tree_node;

struct child_ptr

{

unsigned is_thread;

struct tree_node *child;

};

struct tree_node

{

struct child_ptr left,

right;

};

tree_node *p;

if( !p->left.am_a_thread )

p = p->left.child;

Следующий код лучше читается, потому что в нем меньше точек, и легче сопровождается, так как в нем нужно отлаживать на одно определение меньше:

struct tree_node

{

struct tree_node *left_child;

unsigned left_is_thread : 1;

struct tree_node *right_child;

unsigned right_is_thread : 1;

};

if( !p->left_is_thread )

p = p->left_child;

53. Функция должна вызываться более одного раза, но…

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

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

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

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

54. Функция должна иметь лишь одну точку выхода

Это правило применимо лишь к программам на Си. Вообще, множество переходов goto к одной точке выхода лучше, чем много операторов return. Этим способом вы можете поместить точку прерывания у единственной точки выхода, вместо того, чтобы возиться с несколькими прерываниями. Например§:

f()

{

int возвращаемое_значение = ОШИБКА;

if( некое_условие )

{

// ...

возвращаемое_значение = НЕЧТО;

goto выход;

}

else

{

// ...

возвращаемое_значение = НЕЧТО_ЕЩЕ;

goto выход;

}

выход:

return возвращаемое_значение;

}

Этот метод не срабатывает в Си++, потому что функции конструктора вызываются неявно в качестве части объявления; объявление часто скрывает вызов функции. Если вы пропускаете объявление, то вы пропускаете и вызов конструктора. Например, в следующей программе деструктор для x вызовется, а конструктор — нет:

foo()

{

if( некое_условие )

goto выход;

некий_класс x; // Конструктор не вызывается. (Оператор

// goto перескакивает через него.)

// ...

выход:

// Здесь вызывается деструктор для x

// при выходе x из области видимости.

}

Вследствие этой проблемы лучше всего совсем избегать переходов goto в программах на Си++.

54.1. Всегда предусматривайте возврат значения из блока внешнего уровня

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

if( a )

{

// ...

return делай_что_нужно();

}

else

{

// ...

return ОШИБКА;

}

а так:

if( a )

{

// ...

return делай_что_нужно();

}

// ...

return ОШИБКА;

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

55. Избегайте дублирования усилий

Следующий фрагмент демонстрирует эту проблему:

if( strcmp(a, b) < 0 )

{

}

else if( strcmp(a, b) > 0 )

{

}

else if( strcmp(a, b) == 0 )

{

}

Вызов strcmp() в Си связан с немалыми накладными расходами (как в Паскале и других языках программирования), значительно лучше сделать так:

int cmp = strcmp(a, b);

if( cmp < 0 )

{

}

else if( cmp > 0 )

{

}

else // остается случай cmp == 0

{

}

56. Не захламляйте область глобальных имен

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

  • Локальная переменная всегда более предпочтительна, чем член класса.

  • Член класса всегда более предпочтителен, чем статическая глобальная переменная.

  • Статическая глобальная переменная всегда более предпочтительна, чем настоящая глобальная переменная.

    Статический глобальный идентификатор не экспортируется из файла .c, поэтому он невидим из других модулей. Применяйте модификатор static к как можно большему числу глобальных идентификаторов (переменным и функциям). Ключ доступа private в определении класса еще лучше. Идентификатор, определенный локально внутри подпрограммы, лучше всех, потому что он изолирован от всех других функций в программе.

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

    56.1. Избегайте глобальных идентификаторов

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

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

    56.2. Никогда не требуйте инициализации глобальной переменной при вызове функции

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

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

    static int glob;

    get_glob( x )

    {

    return glob;

    }

    set_glob( x )

    {

    glob = x;

    }

    void recursive_function( void )

    {

    int y = glob;

    // ...

    recursive_function();

    }

    а вот файл 2:

    set_glob( 10 );

    recursive_function();

    x = get_glob();

    Вы при этом немногого достигли с точки зрения связывания; на самом деле, с простой глобальной переменной было бы проще управляться. Кроме того, вы подготовили себе потенциальную проблему: возможность забыть вызвать set_glob(). Вот как сделать это правильно:
1   2   3   4   5   6   7   8   9   ...   19

Похожие:

Правила программирования на Си и Си++ Ален И. Голуб icon Основы информатики и вычислительной техники системы программирования
Рассматриваются основные понятия языков программирования. Излагаются процедурный и объектный подходы в программировании. Более подробно...
Правила программирования на Си и Си++ Ален И. Голуб icon Рабочая программа по курсу «основы Программирования на языке ассемблер»
Программа предназначена для обучения основам программирования на языке низкого уровня Ассемблере учащихся средних школ, учреждений...
Правила программирования на Си и Си++ Ален И. Голуб icon Конспект лекций доцента и. А. Волковой по курсу «системы программирования»
Система программирования – комплекс программных инструментов и библиотек, который поддерживает создание и существование программного...
Правила программирования на Си и Си++ Ален И. Голуб icon Практикум на ЭВМ технология программирования в среде С++
Трунов К. В., Рыков В. И. Методы и технологии С++. Технология программирования в среде С++. /Издание Башкирского ун-та. Уфа 2007....
Правила программирования на Си и Си++ Ален И. Голуб icon Гигиенические требования к условиям обучения в общеобразовательных...
Настоящие Санитарно-эпидемиологические правила(далее — Санитарные правила) направлены на предотвращение неблагоприятного воздействия...
Правила программирования на Си и Си++ Ален И. Голуб icon Среда программирования Visual C++ 0 Общий вид окна
Совокупность средств и правил для представления алгоритма в виде пригодном для выполнения вычислительной машиной называется языком...
Правила программирования на Си и Си++ Ален И. Голуб icon Обоснование выбора средств и методов разработки
На данный момент существует огромное множество языков программирования с помощью которых можно написать данную дипломную работу....
Правила программирования на Си и Си++ Ален И. Голуб icon Учебно-методический комплекс по дисциплине «Технологии программирования»
Техническое задание по разработке дизайнерского проекта приложения «умк – учебно-методический комплекс по дисциплине «Технологии...
Правила программирования на Си и Си++ Ален И. Голуб icon Создание диалектов языков программирования с использованием грамматических аспектов
Такие языки называют диалектами. Диалекты языков программирования встречаются довольно часто: практически каждая субд использует...
Правила программирования на Си и Си++ Ален И. Голуб icon Календарно тематическое планирование по литературному чтению 2 класс
...
Литература


При копировании материала укажите ссылку © 2015
контакты
literature-edu.ru
Поиск на сайте

Главная страница  Литература  Доклады  Рефераты  Курсовая работа  Лекции