English version is in beta. All contents of the site should be already translated (mostly using machine translation), and everything should work properly. However, if you find any problems, please contact me.

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

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

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