понедельник, 6 сентября 2010 г.

Заметки программиста

Статус. Не завершен.
Хорошая книга "Как программировать на С". Харви Дейтел, Пол Дейтел.
Было бы глупо не воспользоваться заметками, которые они составили для читателя.
Много, но полезно.


1. Хороший стиль программирования


  • Пишите программы в простой, четкой манере. Подобный стиль иногда называют KIS ("keep it simple" - будьте проще).
  • Читайте руководства пользователя по версиям языка программирования, с которыми вы работаете. Частое обращение к ним позволит вам узнать много полезного об особенностях языка и поможет их корректно использовать.
  • Хорошими учителями являются компьютер и компилятор. Если вы не уверены в том, как работает та или иная конструкция языка программирования, то следует написать пробную программу, откомпилировать, запустить ее и посмотреть, что получится.
  • Каждая функция должна быть предварена комментарием, объясняющим ее назначение.
  • Для функции, производящей какую-либо печать, последний выводимый символ должен быть символом новой строки (\n). Это гарантирует, что функция оставит экранный курсор расположенным в начале новой строки. Соглашения такого рода облегчают возможность повторного использования программного кода - основной задачи в процессе усовершенствования программного обеспечения.
  • Сдвигайте тело каждой функции на один абзацный отступ (три пробела) внутрь относительно скобок, ограничивающих тело функций. Это придает выразительность функциональной структуре программ и облегчает восприятие при чтении.
  • Установив соглашение на величину отступа, вы предпочтете и в дальнейшем придерживаться такого стиля написания программ. Для создания отступов можно воспользоваться клавишей табуляции, но позиции табуляции могут изменяться, и мы рекомендуем или использовать позиции табуляции в полсантиметра, или вручную вводить три пробела на каждый уровень отступа.
  • Хотя включение в текст программы <stdio.h> не является обязательным, это необходимо делать для каждой программы C, использующей любую из стандартных функций ввода/вывода. При этом компилятор поможет вам обнаружить ошибки еще на стадии компиляции, а не на стадии исполнения (когда исправить ошибки значительно труднее).
  • Вставляйте пробел после каждой запятой ( , ), это облегчит восприятие текста программы.
  • Осмысленный выбор имен переменных поможет сделать программу самодокументированной, то есть уменьшить количество необходимых комментариев.
  • Первая буква индентификатора, представляющего простую переменную, должна быть набрана в нижнем регистре. Кроме того, в книге будет придаваться особый смысл идентификаторам, в которых используются исключительно заглавные буквы.
  • Имена переменных, состоящие из нескольких слов, помогут сделать программу более легкой для восприятия. Однако следует избегать слитного написания отдельных слов, как например totalcommissions. Лучше разделять слова при помощи символа подчеркивания total_commissions, или, если вы все же хотите писать слова слитно, начинайте каждое слово после первого с заглавной буквы, например, totalCommisions.
  • Разделяйте объявления и исполняемые операторы одной пустой строкой, отмечая, таким образом, где кончаются объявления, а где начинаются исполняемые операторы.
  • Оставлять пробелы с каждой стороны двухместной операции. Это выделит операции и придаст программе более ясный вид.
  • Смещать вправо операторы в теле if.
  • Помещать в программе пустую строку над и под каждым оператором  if для облегчения восприятия программы.
  • В каждой строке программы должно быть не более одного оператора.
  • Длинные операторы можно записывать в несколько строк. Если оператор необходимо разбить на несколько строк, старайтесь делать это осмысленно (скажем, после запятой в разделенном запятыми списке). Если оператор разбит на две или большее число строк, смещайте вправо все следующие за первой строки.
  • При написании выражений, содержащих много операций, пользуйтесь таблицей старшинства операций. Следите за тем, чтобы операции в выражении выполнялись в правильном порядке. Если вы не уверены в порядке выполнения операций в сложном выражении, используйте скобки для принудительного задания нужного порядка, так же как делаете это в алгебраических выражениях. Не следует также забывать, что некоторые операции в C, например операция присваивания (=), ассоциативны справа налево, а не наоборот.
  • Единообразное применение разумных соглашений об отступах значительно повышает удобочитаемость программы. Мы предлагаем использовать для отступа табуляцию фиксированного размера приблизительно в 1/4 дюйма или три пробела.
  • Псевдокод часто используется для "продумывания" программы в процессе ее проектирования. Затем программа на псевдокоде преобразуется в программу на С.
  • Делайте отступы для обеих групп операторов структуры if/else.
  • При наличии нескольких уровней отступов отступы для каждого уровня должны содержать одно и тоже количество дополнительных пробелов.
  • Некоторые программисты предпочитают вводить начальную и завершающую фигурные скобки в составных операторах до начала ввода операторов внутри фигурных скобок. Это помогает избежать пропусков одной или обеих фигурных скобок.
  • Инициализируйте счетчики и итоговые суммы.
  • При выполнении деления на выражение, значение которого может быть нулем, явно осуществляйте проверку этого случая и обрабатывайте его соответствующим образом (например, выводите сообщение об ошибке), и не допускайте возникновения фатальной ошибки.
  • В цикле, управляемом контрольным значением, подсказки для запроса ввода данных должны явно напоминать пользователю, чему равно это значение.
  • Не проверяйте на равенство значения с плавающей точкой.
  • Унарные операции следует помещать непосредственно рядом с их операндами без разделительных пробелов.
  • Стандартом ANSI в общем случае не специфицируется порядок, в котором будут оцениваться операнды той или иной операции (хотя в главе 4 мы увидим, что для некоторых операций из этого правила существуют исключения). Поэтому программист должен избегать команд с операциями инкремента или декремента, в которых некоторая переменная подвергается их воздействию более одного раза.
  • Управляйте циклами со счетчиком с помощью целочисленных значений.
  • Делайте отступы для операторов в теле каждой управляющей структуры.
  • Помещайте пустую строку до и после каждой управляющей структуры большого размера для выделения ее в тексте программы.
  • Слишком большое число уровней вложенности может сделать программу трудной для понимания. Пытайтесь избегать использования более трех уровней вложенности.
  • Сочетание междустрочного интервала до и после управляющих структур и отступов в теле управляющих структур в пределах их заголовков придает программам двумерный вид, который значительно повышает их удобочитаемость.
  • Использование конечного значения в условии структур while или for и операции отношения <= помогает избежать ошибок сдвига счетчика. Например, для цикла, используемого для вывода значений от 1 до 10, условием продолжения цикла должно быть counter <=10, а не counter <11 или соunter <10.
  • Помещайте в разделы инициализации и приращения структуры for только выражения, включающие управляющие переменные. Манипуляции с другими переменными должны производиться либо перед циклом (если они выполняются только один раз, как операторы инициализации), либо в теле цикла (если они выполняются по одному разу за повторение, как операторы инкремента или декремента).
  • Хотя значение управляющей переменной может быть изменено в теле цикла for, это может привести к труднообнаружимым ошибкам; лучше его не изменять.
  • Хотя операторы, предшествующие структуре for,  и операторы, содержащиеся в ее теле, часто могут быть объединены в заголовке структуры for, избегайте этого, поскольку это делает программу более трудной для чтения.
  • Ограничивайте, если это возможно, размер заголовков управляющих структур одной строкой.
  • Не используйте переменных типа double или float для проведения вычислений, связанных с денежными суммами. Неточность чисел с плавающей точкой может вызвать ошибки, которые приведут к неправильным значениям денежных сумм.
  • Не забывайте о блоке default в операторах switch. Случаи, явно не проверяемые в структуре switch, игнорируются. Блок default помогает избежать этого, фокусируя внимание программиста на необходимости обрабатывать необычные условия. Существуют ситуации, когда никакой обработки по умолчанию (в блоке default) не требуется.
  • Хотя предложение case и предложение default в структуре switch могут появляться в произвольном порядке, хорошим стилем программирования считается размещение предложения default в конце структуры.
  • В том случае, когда в структуре switch предложение default указано последним, оператора break для него не требуется. Однако некоторые программисты вводят этот break для ясности и единообразия с метками case.
  • Не забывайте о возможной необходимости обработки символов новой строки во входном потоке при считывании символов по одному.
  • Некоторые программисты всегда включают фигурные скобки в структуру do/while, даже если они не являются необходимыми. Это помогает отличить структуру do/while, содержащую один оператор, от структуры while.
  • Некоторым программистам кажется, что операторы break и  continue нарушают нормы структурного программирования. Поскольку результаты действия этих операторов могут быть достигнуты структурными средствами.
  • Когда в выражение равенства входят переменная и константа, как в случае x==1, некоторые программисты предпочитают записывать в этом выражении константу слева, а имя переменной справа от знака = в качестве защитной меры от логической ошибки, возникающей при случайной замене программистом операции == операцией =.
  • Освойтесь с широким набором функций стандартной библиотеки ANSI C.
  • При использовании функций математической библиотеки включите в программу заголовочный файл математики с помощью директивы препроцессора #include <math.h>
  • Вставляйте пустую строку между определениями функций, чтобы отделить функции и улучшить тем самым читаемость программы.
  • Хотя опущенный возвращаемый тип по умолчанию рассматривается как int, всегда задавайте возвращаемый тип явным образом. Однако для функций main возвращаемый тип обычно опускается.
  • Включайте тип каждого параметра в список параметров функции даже в том случае, если данный параметр относится к типу int, принятому по умолчанию.
  • Хотя это и не является ошибкой, не используйте одни и те же имена для аргументов, переданных функции, и соответствующих параметров в определении функции. Это поможет избежать неоднозначности.
  • Выбор осмысленных имен функций и параметров делает программы более удобочитаемыми и помогает избежать чрезмерного использования комментариев.
  • Включите в программу прототипы функций для всех функций, чтобы воспользоваться преимуществами проверки типов C. Используйте директивы препроцессора #include, чтобы получить прототипы функций или прототипы, используемые членами вашей рабочей группы.
  • Имена параметров иногда включаются в прототип функции с целью документирования. Компилятор игнорирует эти имена.
  • Переменные, использующиеся только в определенной функции, должны быть объявлены локальными переменными этой функции, а не внешними переменными.
  • Не объявляйте имен переменных, которые скрывают имена во внешних областях действия. Можно просто избегать любого дублирования имен идентификаторов в программе.
2. Советы по переносимости программ
  • Благодаря тому, что С является машинно-независимым и широко доступным языком, прикладные программы, написанные на С, могут работать практически без изменений на большинстве компьютерных систем.
  • Использование функций стандартной библиотеки ANSI вместо написания собственных аналогичных функций может улучшить переносимость программы, т.к. стандартные функции реализованы фактически во всех существующих вариантах С.
  • Рекомендуется использовать идентификаторы с количеством символов, равным или меньшим 31. Это гарантирует переносимость и даст возможность избежать некоторых трудно обнаружимых ошибок в процессе программирования.
  • Комбинации клавиш для ввода EOF(конца файла) являются системно-зависимыми.
  • Проверка значения на равенство символической константе EOF, а не числу -1 делает программы более переносимыми. Стандарт ANSI требует, чтобы EOF являлась отрицательным целым числом (но не обязательно -1). Таким образом, на различных вычислительных системах EOF может иметь различные значения.
  • Поскольку тип int различается по размеру от системы к системе, пользуйтесь целыми типа long, если вы рассчитываете обрабатывать целые числа вне диапазона +-32767 и хотели бы иметь возможность выполнять программу на нескольких различных компьютерных системах.
  • Использование функций стандартной библиотеки ANSI С делает программы более переносимыми.
  • Программы, которые зависят от порядка вычисления значений операндов операций, не являющихся &&, ||, ?: и операцией-запятой (,), могут функционировать по-разному на системах с различными компиляторами.
3. Советы по повышению эффективности
  • Использование функций стандартной библиотеки ANSI вместо написания собственных аналогичных функций может улучшить характеристики программы, т.к. стандартные функции написаны весьма тщательно и работают очень эффективно.
  • Инициализация переменных при их объявлении уменьшает время выполнения программы.
  • Выражение с операцией присваивания (например, с+=3) компилируется быстрее, чем эквивалентное раскрытое выражение (с=с+3), поскольку переменная с в первом выражении оценивается только один раз, в то время как во втором выражении оценивается дважды.
  • Многие из советов по повышению эффективности приводят к незначительным на первый взгляд улучшениям, поэтому у читателя может возникнуть искушение игнорировать их. Дело в том, что заставить программу выполняться значительно быстрее может только совокупный эффект от всех этих мер по повышению эффективности. Кроме того, значительное улучшение достигается при внесении вроде бы незначительного усовершенствования в цикл, который может повторяться большое количество раз.
  • В случаях, ориентированных на эффективность выполнения, когда нужно экономить память или необходимо быстродействие, может оказаться предпочтительным использование целых чисел меньших размеров.
  • Операторы break и continue, при условии их надлежащего использования, выполняются быстрее соответствующих структурных методов.
  • В выражениях, включающих операцию &&(И), делайте условие, ложность которого наиболее вероятна, крайним слева. В выражениях, включающих операцию || (ИЛИ),  делайте условие крайним слева, истинность которого наиболее вероятна. Это может уменьшить время выполнения программы.
  • Автоматическое хранение способствует экономии памяти, поскольку автоматические переменные существуют только тогда, когда они необходимы. Они создаются при запуске функции, в которой они объявлены, и уничтожаются, когда происходит выход из функции.
  • Перед объявлением автоматической переменной может быть помещен спецификатор класса памяти register, чтобы рекомендовать компилятору разместить ее в одном из быстродействующих аппаратных регистров компьютера. Если интенсивно используемые переменные типа счетчиков или сумм будут реализованы в аппаратных регистрах, то можно исключить непроизводительные затраты на неоднократную загрузку переменных из памяти в регистры и обратно.
  • Объявления register часто бывают не нужны. Современные оптимизирующие компиляторы способны распознать часто используемые переменные и могут размещать их в регистрах самостоятельно, не требуя от программиста объявления register.
  • Избегайте рекурсивных программ, подобных вычислению чисел Фибоначчи, которые приводят к экспоненциальному "взрыву" рекурсивных вызовов.
  • Избегайте использовать рекурсию в ситуациях, требующих производительности программы. Рекурсивные обращения требуют времени и расходуют дополнительную память.
  • Программа, интенсивно использующая функции, по сравнению с монолитной (т.е. состоящей из одной части) программой без функций имеет потенциально большое число вызовов, а это увеличивает процессорное время программы. Но монолитные программы трудны для написания, тестирования, отладки, сопровождения и развития.
4. Общие методические замечания
  • Составной оператор может быть помещен в любое место программы, где может стоять простой оператор.
  • Подобно тому, как составной оператор можно помещать всюду, где может быть помещен простой оператор, так же возможно не помещать там никакого оператора вообще, то есть использовать пустой оператор. Пустой оператор представляется помещением точки с запятой (;) на место, обычно занимаемое некоторым оператором.
  • Каждое уточнение, так же как и сам верхний уровень, представляет собой полную спецификацию алгоритма; меняется только уровень детализации.
  • Многие программы логически могут быть разделены на три этапа: этап инициализации, на котором происходит инициализация переменных программ; этап обработки, на котором происходит ввод данных и соответствующая настройка переменных программы; и этап завершения работы программы, на котором происходит вычисление и вывод окончательных результатов.
  • Программист завершает процесс нисходящего пошагового уточнения в тот момент, когда алгоритм на псевдокоде специфицирован достаточно подробно для того, чтобы программист мог преобразовать псевдокод в программу на С. Реализация программы на С в этом случае обычно не вызывает затруднений.
  • Опыт показывает, что наиболее трудной частью решения задачи на компьютере является разработка алгоритма ее решения. Как только соответствующий алгоритм построен, процесс написания работающей программы на С обычно не вызывает затруднений.
  • Многие программисты пишут программы, никогда не пользуясь никакими инструментами разработки программ типа псевдокода. Они полагают, что поскольку их конечной целью является решение задачи на компьютере, написание псевдокода только задерживает производство конечного продукта.
  • Существует определенное противоречие между разработкой качественного программного обеспечения и разработкой программного обеспечения, работающего наиболее эффективно. Часто одна из этих целей достигается за счет другой.
  • Не изобретайте колесо. Если есть возможность, используйте функции стандартной библиотеки ANSI C вместо того, чтобы создавать новые функции. Это уменьшает время разработки программы.
  • В программах, содержащих большое число функций, main должна быть реализована как группа обращений к функциям, выполняющим основную часть работы.
  • Каждая функция должна ограничиваться выполнением одной, точно определенной задачи, а имя функции должно отражать смысл данной задачи. Это облегчает абстракцию и поддерживает способствует многократному использованию программного кода.
  • Если вы не можете выбрать краткое имя, которое выражает назначение функции, возможно, что ваша функция пытается выполнить слишком много задач. Лучше разбить такую функцию на несколько меньших функций.
  • Длина функции не должна превышать одной страницы. Еще лучше, чтобы функция не превышала по длине половины страницы. Небольшие функции способствуют повторному использованию программного кода.
  • Программы должны составляться в виде совокупности небольших функций. Это упрощает создание программ, их отладку, поддержку и модификацию.
  • Функция, требующая большого количества параметров, может выполнять слишком много задач. Рассмотрите возможность деления функции на меньшие функции, которые выполняют выделенные задачи. Заголовок функции должен, по возможности, умещаться на одной строке.
  • Прототип функции, заголовок функции и вызов функции должны иметь взаимное соответствие по числу, типу и порядку следования аргументов и параметров, а также по типу возвращаемого значения.
  • Прототип функции, размещенный вне любого определения функции, применяется ко всем вызовам, появляющимся в файле после прототипа функции. Прототип функции, помещенный в функцию, применяется только к тем вызовам, которые делаются из этой функции.
  • Автоматическое хранение является примером реализации принципа минимальных привилегий. Почему переменные должны храниться в памяти и быть доступными, когда в данный момент в них нет необходимости?
  • Объявление переменной как глобальной, а не локальной, может приводить к случайным побочным эффектам, когда функция, которой не нужен доступ к этой переменной, случайно или преднамеренно ее изменяет. В целом нужно избегать использования глобальных переменных, за исключением некоторых ситуаций с уникальными требованиями к производительности.
  • Любая проблема, которая может быть решена рекурсивно, может также быть решена итеративно (не рекурсивно). Рекурсивный подход обычно предпочитается итеративному в тех случаях, когда рекурсия более естественно отражает математическую сторону задачи и приводит к программе, которая проще для понимания и отладки. Другой причиной для выбора рекурсивного решения является то, что итеративное решение может не быть очевидным.
  • Разбиение программы на функции аккуратным, иерархическим образом, способствует систематизации конструирования программного обеспечения. Но за все приходится платить.

Комментариев нет:

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.