D:\sideБлогФорматирование кода

⚠️ Обращайте внимание на даты.
Этот блог больше не ведётся с 17 января 2023, и на тот момент с написания этой страницы (17.09.2014) прошло 8 лет.

Видимо, мне всё же придётся об этом написать. Хотя бы с той целью, чтобы об этом знало немножко больше людей, занятых программированием. Друзья, облегчите себе и коллегам жизнь! Возьмите себе за правило форматировать код, чтобы его было легко читать. Это не так сложно, как кажется! …и вызывает привыкание! Блин, как будто уговариваю начать курить. Самое смешное, что я сам при этом не курю. Код форматирую, это да…

Форматирование… это в случае с кодом использование с целью личной выгоды некоторой “мягкости” синтаксиса в языках программирования. Подобная мягкость существует почти везде, касается в основном расстановки пробелов и переносов, но есть и некоторые другие “дыры”, которые позволяют писать один и тот же код немножко разными способами. И это просто отлично.

Почему эта проблема вообще возникает у новичков? Потому что вовсе не очевидно её существование. Код – текст. Какая разница, как он написан, если он работает во всех случаях одинаково? Как минимум три повода задуматься:

Проблема проистекает из того, что код – текст. Вы даже не представляете, сколько от этого на самом деле проблем, и способы с этим справиться уже придуманы (просто не развиты). Текст вовсе не похож на последовательность действий, к такому представлению нужно долго и упорно привыкать. Чтобы сделать такое представление нагляднее, придуман ряд норм, которые принято соблюдать в большинстве языков. Из-за особенностей языков они в разных местах разные, но обычно ими движут одни и те же проблемы.

Отступы

Как не надо

1
2
3
4
for(int i=0; i<n; ++i)
for(int j=0; j<n; ++j)
a[i][j] = i+j;
a[f(i)][f(j)] = z;

Частое явление – объединение нескольких подряд идущих действий в одно нечто. Цели могут быть разными. Самые частые случаи применения: разные ветки if/else, циклы и функции (в том числе анонимные).

Проблема: необходим быстрый способ различать начало и конец такого блока.

Иначе можно огрести интересных последствий. Например, в С, С++ и GML можно не использовать фигурные скобки ({, }) для выделения блока, если он состоит из одного действия (statement). Довольно часто возникают баги, когда после такого ставится строчка и ожидается, что она тоже будет в цикле. А оказывается, нет.

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

Как надо

1
2
3
4
5
6
for(int i=0; i<n; ++i) {
  for(int j=0; j<n; ++j) {
    a[i][j] = i+j;
  }
}
a[f(i)][f(j)] = z;

…и в некоторых языках такой код может сломаться ещё на стадии компиляции. Почему – становится очевидно, если отметить, что i и j определены в соответствующих заголовках блоков, и потому доступны только в них, а последнее действие находится снаружи. Не во всех языках это работает именно так, в некоторых можно определить переменную для блока с именем, которое уже определено снаружи. И это будет другая переменная. Ха!

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

В некоторых языках это даже не под грифом крайне желательно, а обязательно. В Python, HAML и Sass блоки обозначаются именно отступами. Несколько подряд идущих строк с одинаковым отступом считаются блоком. Неверный отступ припишет строчку не к тому блоку. Последствия могут быть интересными и захватывающими ваш компьютер.

Длинные вызовы функций

Как не надо

draw_sprite_general(sprite_index, image_index, section_left, section_top, section_width, section_height, x + something * i * image_xscale, y + something * j * image_yscale, image_xscale, image_yscale, image_angle, image_blend, image_blend, image_blend, image_blend, image_alpha);

Откуда вообще этот пример? Высосан из пальца, но встречается на практике в похожем виде. Это одна из функций в GML, назначение которой я решительно не понимаю. Точнее, не находил практичных случаев, в котором она абсолютно, критически необходима. Это, впрочем, только пример. Во многих местах можно найти аналогичные функции с диким количеством аргументов. Это не единственный способ сделать строчку кода длинной, но при форматировании таких мест ваша задача – грамотно расставить переносы. Здесь бывают некоторые проблемы касательно того, что неправильно поставленный перенос строки может быть воспринят синтаксически неправильно.

Основные правила, которые стоит при этом помнить:

  1. Особо длинным аргументам можно выделить отдельную строчку
  2. Оставляйте в конце строки запятую, чтобы было очевидно, что это ещё не всё. Не переносите её вниз, в начало. Это же относится к открывающей скобке в начале вызова (она нужна не во всех языках, просто в некоторых название функции может оказаться самостоятельным значением и присвоиться куда-нибудь)
  3. Связанные между собой аргументы разумно оставлять на одной строке (если это не противоречит правилу 1). Особенно если аргументов немного (до 4)

Как надо

draw_sprite_general(
  sprite_index, image_index,
  section_left, section_top, section_width, section_height,
  x + something * i * image_xscale,
  y + something * j * image_yscale,
  image_xscale, image_yscale,
  image_angle,
  image_blend, image_blend, image_blend, image_blend,
  image_alpha
  );

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

Отдельные строчки

Как не надо

somevalue=func(x+something*i*image_xscale,y+something*i*image_yscale)

Здесь очень не хватает пробелов. И обоих пунктов выше.

Во-первых, после запятой ставится пробел. Так принято в печатных текстах, поэтому в коде этот момент не мозолит глаза, а поскольку пробел ставится только после запятой (но не перед ней), это делает разделитель довольно характерным по рисунку, легко различимым глазом. Запятая же служит разделителем. Для парсера. Но он должен быть легко различимым и для вас.

Во-вторых, принято отделять операторы пробелами от своих “операндов”. Я сейчас про +, - и прочие аналогично применяемые. Это правило можно изредка нарушать (^_^), к примеру для демонстрации порядка действий в не слишком длинных выражениях. Но надо понимать, что синтаксис беспощаден, и группировка вроде a+b * c сработает совсем не так, как выглядит. Но если сомневаетесь, просто поставьте скобки и сэкономьте время на лишние пару запусков.

почти Как надо

somevalue = func(
  x + something * i * image_xscale,
  y + something * i * image_yscale
  ) * 2;

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

somevalue = func(
  x + something * i * image_xscale,
  y + something * i * image_yscale) * 2;

…либо список аргументов удалить ещё чуть дальше вправо:

somevalue = func(
    x + something * i * image_xscale,
    y + something * i * image_yscale
  ) * 2;

Последняя запись мне кажется наиболее разумной, поскольку получаются такие уровни отступов:

И всё?

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

Прочие советы по оформлению кода уже вынуждены менять его смысл, могут потребовать ввода временных переменных… в общем, это уже не оформление или форматирование, а рефакторинг. Это куда более сложная тема.

Ах да, поскольку статья нацелена на новичков, последний совет – во многих местах при публикации теряются отступы в коде. Используйте для публикации кода Pastebin, Pastie и аналоги. Там ещё и подсветка синтаксиса есть. Пожалейте читателей, особенно если вы просите их помочь.

Удачи! Держите код красивым.