Оформление программ и отступы
Общие замечания
Паскаль, как и многие другие языки программирования, допускает достаточно свободное оформление программ. Вы можете ставить пробелы и переводы строк как вам угодно (за исключением, конечно, ряда понятных требований типа пробелов в выражении if a mod b=c then).
Тем не менее, следует придерживаться определенных правил — не для того, чтобы программа компилировалась, а для того, чтобы программу было бы легче читать человеку. Это важно и в ситуации, когда вашу программу будет читать кто-то другой, так и в ситуации, когда вашу программу будете читать вы сами. В хорошо отформатированной программе легче находить другие ошибки компиляциии, легче находить логические ошибки в коде, такую программу легче дописывать и модифицировать и так далее.
Основная цель форматирования программы — чтобы была лучше видна ее структура, то есть чтобы были лучше видны циклы, if'ы и прочие конструкции, важные для понимания последовательности выполнения действий. Должно быть легко понять, какие команды в каком порядке выполняются в программе, и в первую очередь — какие команды относятся к каким циклам и if'ам (циклы, if'ы и подобные конструкции ниже я буду называть управляющими конструкциями).
Поэтому следует придерживаться некоторых правил. Есть множество разных стандартов, правил на эту тему; во многих проектах, которые пишут целые команды программистов, есть официально требования, и каждый программист обязан их соблюдать вплоть до пробела. На наших занятиях я не буду столь строго относиться к оформлению, но тем не менее я буду требовать соблюдения ряда правил (это — некоторая "выжимка", то общее, что есть практически во всех стандартах), а также буду настоятельно рекомендовать соблюдать еще ряд правил.
Разделы ниже будут пополняться по мере того, как я что-то буду вспоминать или видеть в ваших программах.
Обязательные требования
- Используйте здравый смысл. Любое из указанных ниже правил можно нарушать, если здравый смысл подсказывает вам, что лучше сделать не так — но такие ситуации скорее исключение, чем правило.
- На каждой строке должно быть не более одной команды и/или управляющей конструкции.
- Исключение: очень тесно связанные между собой по смыслу команды типа assign и reset.
- Исключение: управляющая конструкция, внутри которой находится только одна короткая команда, например:
if a>0 then inc(i);
- Исключение: цикл for со вложенным if'ом, имеющий смысл "пройти только по элементам, удовлетворяющим условию":
for i:=a to b do if x[i]<>0 then begin // больше кода тут быть не должно!
- В коде должны быть отступы — некоторые строки должны быть написаны не вплотную к левому краю, а с несколькими пробелами вначале:
if a=0 then begin b:=2; // в этой строке отступ c:=c+2; // и в этой тоже end;
Основной принцип отступов — программу можно представить себе как последовательность вложенных блоков. Основной блок — сама программа. В нем могут быть простые команды, а также сложные блоки — if'ы, циклы и т.д. Код внутри if'а или внутри цикла — это отдельный блок, вложенный в основной блок. Код внутри цикла внутри if'а — это блок, вложенный в другой блок, вложенный в третий. Пример: следующему коду:read(n); for i:=1 to n do begin read(a[i]); if a[i]>0 then begin writeln(a[i]); k:=k+1; end; end; if n>0 then writeln('!');
соответствует следующая структура блоков:+--------------------+ | основная программа | | +-----------+ | | | цикл for | | | | +-------+ | | | | | if | | | | | +-------+ | | | +-----------+ | | +------+ | | | if | | | +------+ | +--------------------+
Так вот, в пределах одного блока отступ должен быть один и тот же. А для каждого внутреннего блока отступ должен быть увеличен. (При этом заголовок цикла или if'а считается частью внешнего блока и пишется без отступа.) - То же самое можно сказать по-другому: внутренний код управляющей конструкции должен быть написан с отступом. Если в одну управляющую конструкцию вложена другая, то отступ у внутреннего кода должен быть удвоен, и т.д. В результате все команды, которые всегда выполняются одна за другой, должны идти с одним отступом (их первые символы должны идти один под другим), а если где-то порядок может меняться, отступы должны быть разные.
Придерживайтесь одинакового размера "базового" отступа везде в программе, обычно его берут 2 или 4 пробела. Один пробел — слишком мало.
Пример отступов:for i:=1 to n do begin read(a); // вошли внутрь for --- появился отступ: 4 пробела if a<>0 then begin inc(m); // вошли еще и внутрь if --- отступ стал в два раза больше b[m]:=a; end; end; for i:=1 to m do writeln(b[i]); // если выше единичный отступ был 4 пробела, то и здесь тоже 4, а не 2!
- Элементы, обозначающие окончание части или всей управляющей конструкции (else и/или end) должны находиться на отдельных строках и на том же уровне отступа, что и начало управляющей конструкции. (К begin это не относится, т.к. начало управляющей конструкции видно и так.)
Примеры:
Неправильно:for i:=1 to n do begin read(a); s:=s+a; end; // end очень плохо заметен if s>2 then writeln(s) else begin // else очень плохо заметен writeln('Error'); halt; end; // end плохо заметен
Правильно:for i:=1 to n do begin read(a); s:=s+a; end; // end сразу виден if s>2 then writeln(s) else begin // else сразу виден и разрывает последовательность строк: writeln('Error'); // видно, что это две ветки halt; end; // видно, что end есть и не потерян
Допускается размещать фразу end else begin на одной строке. - Бывает так, что у вас идет целая цепочка конструкций if, разбирающая несколько случаев:
if dir='North' then ... else if dir='South' then ... else if dir='East' then ... else if dir='West' then ... else writeln('Error!');
По смыслу программы это — многовариантное ветвление, здесь все случаи равноправны или почти равноправны. Тот факт, что в программе каждый следующий if вложен в предыдущий — это просто следствие того, что в паскале нет возможности сделать многовариантное ветвление. Поэтому такой код надо оформлять именно так, как указано выше, т.е. все ветки else if делать на одном отступе. (Не говоря уж о том, что если на каждый такой if увеличивать отступ, то программа очень сильно уедет вправо.)
Но отличайте это от слудеющего варианта:if a=0 then writeln(-1); else begin if b=0 then x:=1; else x:=b/a; writeln(x); end;
Здесь вариантыif a=0
иif b=0
не равноправны: вариантb=0
явно вложен внутрьelse
. - Команды, выполняющиеся последовательно, должны иметь один и тот же оступ.
Примеры:
Неправильно:read(a); b:=0; c:=0; for i:=1 to a do begin b:=b+i*i; c:=c+i; end;
Все равно неправильно (for всегда выполняется после c:=0, поэтому отступы должны быть одинаковыми):read(a); b:=0; c:=0; for i:=1 to a do begin b:=b+i*i; c:=c+i; end;
Правильно:read(a); b:=0; c:=0; for i:=1 to a do begin b:=b+i*i; c:=c+i; end;
- Не следует без необходимости переносить на новую строку части заголовка управляющих конструкций (условия в if, while, repeat; присваивание в заголовке for; параметры процедур и т.д.). С другой стороны, если заголовок управляющей конструкции получается слишком длинным, то перенести можно, но тогда перенесенная часть должна быть написана с отступом, и вообще форматирование должно быть таким, чтобы было четко видно, где заканчивается заголовок управляющей конструкции, и хорошо бы выделить структуру заголовка (парные скобки в условии и т.п.)
Примеры:
Неправильно:if a=0 then // условие короткое, лучше в одну строку ... for i:=1 to 10 do // аналогично ... {слишком длинно --- лучше разбить} if (((sum+min=min2+min3) or (sqrt(sumSqr)<30)) and (abs(set1-set2)+eps>thershold)) or (data[length(s)-i+1]=data[i]) or good then...
Правильно:if a=0 then ... for i:=1 to 10 do ... {четко видно, где заканчивается условие, плюс выделены парные скобки} if ( ( (sum+min=min2+min3) or (sqrt(sumSqr)<30) ) and (abs(set1-set2)+eps>thershold) ) or (data[length(s)-i+1]=data[i]) or good then...
- В секции var все строчки должны быть выровнены так, чтобы первая буква первой переменной в каждой строке были бы одна под другой; это обозначает, что у второй и далее строк должен быть отступ 4 пробела. Аналогично в остальных секциях, идущих до кода, (type, const и т.д.) надо все строки варавнивать по первому символу:
type int=integer; float=extended; var i:integer; s:string;
- Разделяйте процедуры/функции друг от друга и от основного текста пустой строкой (или двумя); используйте также пустые строки внутри длинного программного текста, чтобы разбить его на логически связные блоки.
Не столь обязательные требования, но которые я настоятельно рекомендую соблюдать
- Пишите begin на той же строке, что и управляющая конструкция, ну или хотя бы на том же отступе, что и управляющая конструкция:
Совсем плохо:for i:=1 to n do begin read(a[i]); ...
Более-менее:for i:=1 to n do begin read(a[i]); ...
Еще лучше:for i:=1 to n do begin read(a[i]); ...
Пример хорошо отформатированной программы:
function sum(a, b: longint): longint; begin sum := a + b; end; var i, a, b, s: longint; x, y: double; arr: array [1..1000] of boolean; begin read(a, b); arr[1] := true; for i := 2 to 1000 do if ((a > 0) and (arr[i-1])) then arr[i] := true; for i := 1 to 1000 do arr[i] := false; s := 0; if (a < 0) then begin a := -a; if (b < 0) then begin b := -b; s := a + b; end else begin while (s <= 0) do begin case a of 1: begin s := s + 3; end; 2: begin s := s - 4; a := a - 1; end; else s := 1; end; end; end; end else if (b < 0) then begin b := -b; s := (a + b) * (a - b); end else begin s := sum(a, b) * sum(a, b); end; writeln(s); end.