Студопедия

Главная страница Случайная страница

КАТЕГОРИИ:

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Кодирование и документирование программы






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

Главная цель, к которой нужно стремиться, — получить легко читаемую программу возможно более простой структуры. Широко известен верный в большинстве случаев девиз — «Better simpler than clever» (лучше по-простому, чем по-умному). В конечном счете все технологии программирования направлены на достижение именно этой цели, поскольку только так можно добиться надежности и простоты модификации программы. Смысл девиза заключается в том, что если какое-либо действие можно запрограммировать разными способами, то предпочтение должно отдаваться не наиболее компактному и даже не наиболее эффективному, а такому, который легче для понимания. Особенно это важно в том случае, когда пишут программу одни, а сопровождают другие, что является широко распространенной практикой.

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

#include < stdio.h>

main (int t, int _, char *a){return! 0< t? t< 3? main(-79, -13, a+main(-87.1-_,

main(-86, 0, a+l)+a)): 1, t< _? main(t+1, _, a): 3, main(-94, -27+t, a)& & t==2\

? _< 13? main(2, _+1, " %s %d %d\n"): 9: 16: t< 0? t< -72?

main(_, t, " @n' +, #'/*s{}w+/w#cdnr/+, {}r/*de}+, /*{*+, /w{%+, /w#q#n+, /#{l+, /n\

{n+, /+#n+, /#; #q#n+, /+k#; *+, /'r: 'd*'3, }{w+K w'K: '+}e#'; dq#'l q#'+d'K#! \

/+k#; q#'r}eKK#}w'r}eKK{nl]'/#; #q#n'){)#}w'){){nl]'/+#n'; d}rw' i; #){nl]! \

/n{n#'; r{#w'r nc{nl]'/#{l, +'K {rw' iK{: [{nl]'/w#q#n'wk nw' iwk{KK{nl]! /\

w{%'l##w#' i;: {nl]'/*{q#'ld; r'}{nlwb! /*de}'c;; {nl'-{}rw]'/+, }##'*}\

#nc, ', #nw]'/+kd'+e}+; #'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!! /")

: t< -50? _==*a? putchar(31[a]):

main(-65, _, a+l):

main((*a=='/')+t, _, a+l):

0< t? main(2, 2, " %s")

: *a=='/' ||main(0, main(-61, *a,

"! ek; dc i@bK'(q)-[w]*%n+r3#l, {}: \nuw1oca-O; m.vpbks, fxntdCeghiry"

), a+l);

}

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

On the first day of Christmas my true love gave to me

a partridge in a pear tree.

 

On the second day of Christmas my true love gave to me

two turtle doves

and a partridge in a pear tree.

 

On the third day of Christmas my true love gave to me

three french hens, two turtle doves

and a partridge in a pear tree.

On the twelfth day of Christmas my true love gave to me

twelve drummers drumming, eleven pipers piping, ten lords a-leaping,

nine ladies dancing, eight maids a-milking, seven swans a-swimming,

six geese a-laying, five gold rings;

four calling birds, three french hens, two turtle doves

and a partridge in a pear tree.

Первый шаг в написании программы — записать на естественном языке (возможно, с применением обобщенных блок-схем), что именно и как она должна делать. Если вы не можете сформулировать алгоритм по-русски, велика вероятность того, что он плохо продуман (естественно, я не имею в виду, что надо «проговаривать» все на уровне отдельных операторов, например, «изменяя индекс от 1 до 100 с шагом 1...»). Такое описание алгоритма полезно по нескольким причинам: оно помогает в деталях продумать алгоритм, найти на самой ранней стадии некоторые ошибки, разбить программу на логическую последовательность блоков, а также обеспечить комментарии к программе.

Если алгоритм можно разбить на последовательность законченных действий (а к этому надо стремиться!), каждое законченное действие оформляется в виде функции. Каждая функция должна решать только одну задачу (не надо объединять два коротких независимых фрагмента в одну функцию). Размер функции может варьироваться в широких пределах, все зависит от того, какой размер имеет законченный фрагмент кода, выделяемый в функцию. Желательно, чтобы тело функции помещалось на 1-2 экрана: одинаково сложно разбираться в программе, содержащей несколько необъятных функций, и в россыпи из сотен подпрограмм по несколько строк каждая.

Если некоторые действия встречаются в теле программы хотя бы дважды, их также нужно оформить в виде функции. Однотипные действия оформляются в виде перегруженных функций или функций с параметрами. Короткие функции имеет смысл объявить с директивой inline.

Необходимо тщательно выбирать имена переменных. Правильно выбранные имена могут сделать программу в некоторой степени самодокументированной. Неудачные имена, наоборот, служат источником проблем. Не увлекайтесь сокращениями, они ухудшают читаемость, и часто можно забыть, как именно было сокращено то или иное слово. Общая тенденция состоит в том, что чем больше область видимости переменной, тем более длинное у нее имя. Перед таким именем часто ставится префикс типа (одна или несколько букв, по которым можно определить тип переменной). Для счетчиков коротких циклов, напротив, лучше обойтись однобуквенными именами типа i, j или k. Имена макросов предпочтительнее записывать заглавными буквами, чтобы отличать их от других объектов программы. Не рекомендуется использовать имена, начинающиеся с символа подчеркивания, имена типов, оканчивающиеся на _t, а также идентификаторы, совпадающие с именами ресурсов стандартной библиотеки C++.

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

Локальные переменные предпочтительнее глобальных. Если глобальная переменная все же необходима, лучше объявить ее статической, что ограничит область ее действия одним исходным файлом.

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

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

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

Нельзя возвращать из функции ссылку на локальную переменную, потому что она автоматически уничтожается при выходе из функции, которая является ее областью действия. Не рекомендуется возвращать ссылку на переменную, созданную внутри функции в динамической области памяти с помощью функции malloc или операции new, так как это приводит к трудно контролируемым утечкам памяти.

Следует избегать использования в программе чисел в явном виде. Константы должны иметь осмысленные имена, заданные через const или enum (последнее предпочтительнее, так как память под перечисление не выделяется). Символическое имя делает программу более понятной, а кроме того, при необходимости изменить значение константы это можно сделать всего лишь в одном месте программы.

Для записи каждого фрагмента алгоритма необходимо использовать наиболее подходящие средства языка. Любой цикл можно, в принципе, реализовать с помощью операторов goto и if, но это было бы нелепо, поскольку с помощью операторов цикла те же действия легче читаются, а компилятор генерирует более эффективный код. Ветвление на несколько направлений предпочтительнее реализовывать с помощью оператора switch, а не нескольких if, красивый способ передачи управления одной функции из группы — массив указателей на функции.

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

if (strstr(a, b) > 0){...}

else if (strstr(a. b) < 0){...}

else if (strstr(a, b) == 0) {... }

лучше написать

int is_equal = strstr(a, b);

if (is_equal > 0) {... }

else if (is_equal < 0) {... }

else {... } // здесь is_equal == 0

Если первая ветвь оператора if содержит передачу управления, использовать else нет необходимости:

if (is_equal > 0) {... break; }

if (is_equal < 0) {... return; }

{... } // здесь is_equal == 0

Бессмысленно использовать проверку на неравенство нулю (или, что еще хуже, на равенство true или false):

bool is_busy;

if (is_busy == true) {... } // плохо! Лучше if (is_busy)

if (is_busy == false) {... } // плохо! Лучше if (! is_busy)

char s[80];

while (fgets(s)! = NULL) {... } // плохо! Лучше while (fgets(s))

while(a == 0) {... } // можно while(! a)

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

В некоторых случаях условная операция лучше условного оператора:

if (z) i = j; else i = k; // лучше так: i = z? j: k;

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

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

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

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

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

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

Дальнейшие советы касаются комментариев и форматирования текста программы. Программы на C++ весьма плохо читаемы (лучше, чем программы на Perl, но хуже, чем на Pascal), и в них особенно важно придерживаться хорошего стиля при форматировании и документации.

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

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

// Комментарий, описывающий,

// что происходит в следующем ниже

// блоке программы.

{ /* Непонятный

блок

программы */}

Для разделения функций и других логически законченных фрагментов пользуй­тесь пустыми строками или комментарием вида

//-----------------------------------------------------------------

Вложенные блоки должны иметь отступ в 3-4 символа, причем блоки одного уровня вложенности должны быть выровнены по вертикали. Желательно, чтобы закрывающая фигурная скобка находилась строго под открывающей или в колонке, соответсвующей уровню вложенности блока. Форматируйте текст по столбцам везде, где это возможно:

#include < string.h>

#include < stdlib.h>

int main(){

double m[10]; // комментарий

const char s[] = " 2, 38.5, 70, 0, 0, 1"; // комментарий

char *p = s; // комментарий

int i = 0; // комментарий

//--------------------------------------------------

do{

m[i++] = atof(p);

if (i > 9) break;

}while(p = strchr(p, '.'), p++);

for(int k = 0; k< i; k++)

printf(" %5.2f ", m[k]);

return 0;

}

Для улучшения ясности можно отступить от правила отступов:

if (is_best) best();

else if (is_bad) worse();

else if (is_vovochka) worstO:

Помечайте конец длинного составного оператора:

while(l){

while(gets(s)){

for(i = 0; i< 10; i++){

for(j = 0; j< 10; j++){

// две страницы кода

} // for(j = 0; j< 10; j++)

} // for(i = 0; i< 10; i++)

} // while(gets(s))

} // while(l)

He следует размещать в одной строке много операторов. Как и в русском языке, после знаков препинания должны использоваться пробелы:

f=a+b; // плохо! Лучше f = а + b;

Очень много конкретных рекомендаций по программированию и проектирова­нию программ на C++ содержится в книге Алена И. Голуба [9]. Массу интерес­ного можно почерпнуть, осилив без малого 1000 страниц книги Б. Страуструпа [17]. В заключение хотелось бы привести цитату из этой книги: «Вопрос " Как писать хорошие программы на C++? " напоминает вопрос " Как писать хорошую английскую прозу? ". Есть два совета: " Знай, что хочешь сказать" и " Тренируйся. Подражай хорошему стилю". Оба совета годятся как для C++, так и для английской прозы, и обоим одинаково сложно следовать.»



Поделиться с друзьями:

mylektsii.su - Мои Лекции - 2015-2024 год. (0.014 сек.)Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав Пожаловаться на материал