Оформление программ и отступы

Общие замечания

Паскаль, как и многие другие языки программирования, допускает достаточно свободное оформление программ. Вы можете ставить пробелы и переводы строк как вам угодно (за исключением, конечно, ряда понятных требований типа пробелов в выражении 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 begin
            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.