19 июля 2010 г.

Стиль оформления кода

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

К примеру, когда разговор заходит о стиле оформления кода, обычно вы встречаете обсуждение нескольких стилей, какой из них удобнее, нагляднее и вообще "лучше" и "круче" в сферическом вакууме. Иногда можно встретить заметки в личных блогах, которые описывают стиль автора и чем он ему приглянулся. Другие читают это, вдохновляются и разрабатывают свой стиль: "я прочитал XYZ и выработал свой стиль: смесь этого с тем, добавив то, но вот здесь я делаю ABC, потому что DEF". "Нет такой вещи как 'правильный' стиль оформления - у каждого свои подходы". "Если так делает Embarcadero - это не значит, что это правильно: оформлять можно по-разному".

Звучит знакомо?

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

Если вы немного подумаете, то отсюда следует такой вывод: в Delphi есть единственный правильный стандарт оформления кода, а все остальные - неверны.

Единственный верный стандарт


Этот стандарт - стандарт оформления кода от Borland/CodeGear/Embarcadero. То, в чём написаны исходники самой Delphi.

Позвольте мне пояснить, что в этом случае означает слово "правильный":

- Он не идеален.
- Вы можете предложить более читабельный вариант.
- Но это - единственный стиль оформления, который может претендовать на звание "стандарт".

Почему это так


Вы можете сказать: "но как же, вот я использую такое оформление - смотри, насколько оно лучше! Как этот ваш стиль может быть правильным, а мой - нет? Похожее оформление использует даже <человек-с-большим-именем>!".

Дело в том, что этот ваш стиль оформления не соблюдается никем, кроме вас (окей: и ещё пяти человек, которых вы вдохновили на него). Со стандартом оформления кода Delphi же знакомы все. Чувствуете разницу?

Посмотрите на это с другой стороны. Вот есть ANSI/ISO стандарт языка C++. Почему никому не приходит в голову его улучшить? Расширить - возможно, но не изменить/улучшить (похоже, расширение стандарта языка не имеет аналогии для стиля оформления кода). Почему? Потому что этим вы убиваете весь смысл стандарта! Его цель - быть единым и правильным. Вводя свой стандарт вы ничего не улучшаете. Вы просто вносите хаос, создавая ещё один стандарт (как стандартов может быть два?!). Джоэль Спольски хорошо сказал на эту тему.

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

Отлично, какое-то время вы работаете. Более или менее успешно, хотя Петя постоянно норовит скатится к своему оформлению (привычка-с!), и ему приходится срочно перелопачивать код перед commit-ом, чтобы не получить по шапке.

А потом за ваш проект берётся другая команда/контора, в которой принят другой стандарт оформления кода.

Опс.

Теперь они вынуждены переформатировать все исходники, или они превратятся в месиво стилей.

Другой пример: как насчёт автогенерируемого Delphi кода? Ведь он использует оформление, которое отличается от вашего. Будете тратить своё время на переписывание автосгенерированного кода или же оставите всё как есть, сохраняя смешение двух стилей (Delphi и вашего) в одном модуле? Как ни посмотри, и так и сяк - недостатки.

Как насчёт лёгкости чтения? Конечно, стиль оформления, на котором вы пишете, читается легче всего. Насколько легко вы воспримете стиль, в корне отличный от вашего? Я думаю, далеко не сразу. Как насчёт сразу двух-трёх разных стилей? Как на ваш стиль отреагируют ваши коллеги? Это положение особенно усугубляется, когда вы разрабатываете компонент, утилиту или библиотеку, которая будет доступна в исходных кодах вашим клиентам. Её увидит весь мир. Насколько они будут рады иметь дело с вашим доморощенным стилем?

С другой стороны, исходники Delphi читают все. Более того, у всех перед глазами фрагменты автосгенерированного кода. Поэтому ни у кого не возникнет особых затруднений с чтением кода, оформленного "как в Delphi". Разве это не здорово? Если вы пишете так, вы можете дать это любому человеку - и у него не будет проблем с восприятием этого кода. И он даже сможет писать так же. Новому работнику не надо будет учить новый для него стандарт: ведь со стилем Delphi он уже знаком.

Посмотрите на известные библиотеки и компоненты на Delphi. Откройте их код. Что вы увидите? Домашний стиль оформления автора кода? Или же стиль оформления кода Delphi?

(хинт для организаций: если вы хотите форсировать стиль оформления кода в своей конторе - не изобретайте "удобный" стандарт оформления: возьмите стандарт оформления кода Delphi!)

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

Стандарт и привычка


"Но у меня привычка писать так! С чего бы я стал ломать свои привычки?!"

Знаете, я когда-то писал по-другому, ещё со времён Turbo Pascal. Begin я сдвигал в блок, а отступ у меня был в один пробел, а не в два, вот так:
procedure Proc;
begin
DoSomething;
DoSomethingElse;
if Condition then
 begin
 DoAnotherThing;
 DoAnotherThingMore;
 end;
DoSomethingMore;
end;
Не суть важно, как возник этот стиль - я думаю, что у каждого когда-то был (или есть) такой "свой" персональный стиль оформления кода.

Так вот, после того, как я понатыкался ещё на кучу подобных "самоделкиных" стандартов (некоторые из которых были достаточно далеки от моего), я сказал себе: "всё, стоп, хватит с меня". И насильно заставил писать себя так:
procedure Proc;
begin
  DoSomething;
  DoSomethingElse;
  if Condition then
  begin
    DoAnotherThing;
    DoAnotherThingMore;
  end;
  DoSomethingMore;
end;
А потом это вошло в привычку.

Зачем вообще нужно себя так ломать? Тут, конечно, решать вам, но я считаю, что бонусы следования стандарту Delphi стоят того, чтобы изменить свои привычки. Избавляйтесь от плохих привычек (свой стандарт), заводите хорошие (стандарт Delphi).

Но что же это за стандарт оформления кода Delphi?


Ну, вы все его видели - достаточно открыть любой стандартный модуль Delphi. Если вы хотите начать ему следовать, вы можете или изучать скучное формальное описание (на момент написания заметки, сайт немного в дауне) или же вы можете открыть модуль Delphi и писать "по аналогии". К сожалению, никакого официального Delphi Programming Code Style Guidelines я не нашёл.

Здесь надо понимать, что оформление кода в Delphi не является строго формализованным. Оно иногда меняется со временем, но очень незначительно. Поэтому мелкие детали могут оставаться на ваш выбор, но основные вещи - фиксированы.

Автоматическое форматирование


Ну, с появлением Code Formatter в Delphi 2010, стиль кода Delphi получает ещё бонусных очков.

Понятно, что вы можете не ломать свои привычки, а просто прогонять свой код через средство автоматического форматирования кода.

(вероятно, это будет неплохой идеей: форсированно прогонять исходники через авто-форматтер перед commit-ом кода)

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

Но тут, в общем-то, ваш выбор. Лично я - за смену привычек.

Хорошим компромиссом видится автоматическое форматирование базовых вещей, а затем доводка тонких моментов вручную.

А бывают ли ситуации, когда можно/нужно нарушать стандарт?


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

Я дам только один пример. Это субъективно, поэтому можете не начинать "это не стоит того!". Просто лучшего мне в голову сейчас не приходит.

В Delphi все модули в uses пишутся так:
uses
  Windows, SysUtils, Classes, Controls;
Возможно, вы захотите нарушить это правило и писать так:
uses
  Windows,
  SysUtils,
  Classes,
  Controls;
Почему? Сравнение изменений в модулях намного проще делается во втором случае, чем в первом. Во втором вы увидите новые и удалённые строки - это новые и удалённые модули. В первом - вы увидите изменения в строке. Чтобы понять, какие модули были добавлены, а какие удалены - понадобится время.

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

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

28 комментариев :

  1. Во многих инструментах для работы с системами контроля версий есть события (хуки), позволяющие выполнить нужную команду перед коммитом. И это даёт возможность автоматически запускать автоформатирование кода перед каждым коммитом.

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

    ОтветитьУдалить
  2. Анонимный19 июля 2010 г., 17:52

    Труд программиста по законодательству РФ приравнивается к труду писателя.
    Писатели бывают разные, есть те кто умеет писать прозу, а есть и те, кто умеет писать стихи. Дополняя мысль хочу сказать, что высший идеал для Delphi программистов - писать Delphiстишья.

    А насчет исключений - это уже зависит направленности произведения. Если в коде очень часто встречаются операторы if(а такое даже в исходниках редко встречается), имеет смысл прменять конструкцию вида
    if Condition1
    then begin
    if Condition2
    then begin
    end
    else begin
    end
    end
    else begin
    end

    Особенно, если Condition подобно небольшому лирическому отступлению.

    ОтветитьУдалить
  3. >>> Особенно, если Condition подобно небольшому лирическому отступлению

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

    ОтветитьУдалить
  4. Анонимный19 июля 2010 г., 17:55

    В предыдущем посте плохо видны отступы, предполагалось что end будет вровень с begin, как и текст между begin и end...

    ОтветитьУдалить
  5. Анонимный19 июля 2010 г., 18:06

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

    ОтветитьУдалить
  6. >К сожалению, никакого официального Delphi Programming Code Style Guidelines я не нашёл.

    Может вот этот документ близок к официальному:)

    ОтветитьУдалить
  7. Анонимный20 июля 2010 г., 14:27

    А есть ли в Delphi 7 Code Formatter?

    ОтветитьУдалить
  8. В Delphi 7 встроенного форметтера кода нет.

    Но это не мешает вам использовать любое стороннее решение.

    Например, JEDI Code Formatter, DelForEx (видимо, сокращение от Delphi Formatting Expert) или любой другой вариант. Можете для начала посмотреть, нет ли такого инструмента в вашем любимом наборе экспертов.

    ОтветитьУдалить
  9. А я вот прогоняю через форматер каждый раз как напишу процедуру или изменю её код - это уже привычка. Стандартное форматирование дает возможность находить код там, где его ожидаешь, а не там где Вася вчера подумал лучше для него.

    ОтветитьУдалить
  10. Анонимный20 июля 2010 г., 20:44

    Hовичок спросил у Мастера:
    - Я видел программиста, который никогда не оформляет, не тестирует и не документирует программы. Hо все кто знает его считают его одним из лучших программистов в мире. Почему так?
    Мастер ответил:
    - Этот программист овладел Дао. Он больше не нуждается в оформлении; он не злится, когда система зависает, но принимает мироздание без раздражения. Он давно не нуждается в документации; он больше не беспокоится о том, что кто-то еще увидит его код. Он больше не нуждается в тестировании; каждая из его программ совершенна сама по себе, ясна и элегантна, ее назначение очевидно. Истинно вошел он в таинство Дао!

    ОтветитьУдалить
  11. Стиль должен быть.
    А какой он будет - дело десятое.
    Исходники от дельфи грешат дикими отступлениями. До момента внедрения FastMM исходник манагера памяти (вроде getmem.inc) "образец" стиля.
    Еще можно grids.pas (от версии дельфи 7, например) - тоже хороший стиль: голову сломаешь, чтобы понять эти 6 тыщ строк.

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

    ОтветитьУдалить
  12. >>> Исходники от дельфи грешат дикими отступлениями.

    Поздравляю, вы нашли баг:

    "I want to reiterate that on the Borland web site, and on the CDs that we ship with our product, these standards are the law. We want to present our code in a unified and easy to read style, and enforcing the rules in this guide is the simplest way to achieve that end." - источник.

    ОтветитьУдалить
  13. Мне не очень понятен тон ответа.
    Я не нашел бага. Я за то, что сами разработчики Delphi не всегда следуют своему же стандарту.

    Очень распространенное отклонение - надо ли после then писать с новой строки, если строка кода одна?

    Вот для примера код из controls.pas для дельфи 2007.

    function TWinControl.GetControlExtents: TRect;
    var
    I: Integer;
    begin
    Result := Rect(MaxInt, MaxInt, 0, 0);
    for I := 0 to ControlCount - 1 do
    with Controls[I] do
    if Visible or (csDesigning in ComponentState) and
    not (csNoDesignVisible in ControlStyle) then
    begin
    if Margins.ControlLeft < Result.Left then Result.Left := Margins.ControlLeft;
    if Margins.ControlTop < Result.Top then Result.Top := Margins.ControlTop;
    if Margins.ControlLeft + Margins.ControlWidth > Result.Right then
    Result.Right := Margins.ControlLeft + Margins.ControlWidth;
    if Margins.ControlTop + Margins.ControlHeight > Result.Bottom then
    Result.Bottom := Margins.ControlTop + Margins.ControlHeight;
    end;
    end;

    Хотя в стандарте, приведенном Вами они пишут

    8.2.3 if statement

    If statements should always appear on at least two lines.

    Example:

    // INCORRECT
    if A < B then DoSomething;

    // CORRECT
    if A < B then
    DoSomething;

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

    Например, в JEDI стандарт предписывает писать begin и end даже для одной строки блока.

    ОтветитьУдалить
  14. Имелось ввиду следующее: не в курсе как в Embarcadero, а в Borland со стилем было так: указанные по ссылке правила - это закон. И если вы нашли фрагмент кода, оформленный не по правилам, то это - баг. Ну не досмотрели, а не потому, что "они сами свой стандарт не соблюдают".

    К сожалению, нет никакого более нового варианта указанных правил.

    ОтветитьУдалить
  15. Спасибо, любопытно!
    В самом деле, стиль надо приближать к стандартному. Однако всё-таки иногда можно слегка от него отступить во имя удобства и красоты. Тот же упомянутый пример с then.
    If error then Exit; - не очень-то хочется разбивать это на две строки!
    или даже так
    if error then begin long_line_to_report_to_log_about_error; result := -1; exit; end;
    выстраивать это добро в столбик - страта строчек.

    ОтветитьУдалить
  16. Единственное расширение стандарта, которое используется нашей командой разработки - это префиксы перед именами локальных и глобальных (когда они бывают, конечно) переменных и констант. Для локальных переменных используем префикс "v", для глобальных переменных - "l", для всех констант - "c". Это, кстати, стыкуется с соглашением в Delphi для параметров методов использовать префикс "a" (тоже используем всегда).
    Долго думали, насколько стоит нарушать стандарт таким образом. После экспериментов решили, что читаемость кода повышается настолько, что оправдывает несовместимость.

    ОтветитьУдалить
  17. Конечно, префиксы не используются для односимвольных переменных (типа "i")

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

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

    Так вот в существующем стандарте есть явное противоречие, которое заключается в том, что много строк занимают операторы, не несущие никакой смысловой нагрузки. Например:

    01.  if exp1 then
    02.  begin
    03.    doIt1;
    04.  end
    05.  else
    06.  if exp2 then
    07.  begin
    08.    doIt2;
    09.  end
    10.  else
    11.  begin
    12.    doIt0;
    13.  end;


    01.  if exp1 then begin
    02.    doIt1;
    03.  end else if exp2 then begin
    04.    doIt2;
    05.  end else begin
    06.    doIt0;
    07.  end;

    В обоих случаях выполняется один и то же код, но количество строк отличается в два раза за счет строк не несущих смысла. Например в строках
    09.  end
    10.  else
    11.  begin
    смысловую нагрузку несет только слово .
    Применяя стандарт Delphi мы теряем читаемость.

    Дополнением к стандарту второго случая нужно только добавить правило обязательного применения , тогда будет подразумеваться, что последним словом в строке оператора всегда будет , что совершенно не сложно добавить в Code Templates

    А вот конструкцию,
    0.  // CORRECT
    1.  if Condition then
    2.  begin
    3.    DoThis;
    4.  end else
    5.    DoSomething;
    допускаемую в стандарте, я бы вообще запретил как вредную, особенно, если DoSomething представляет из себя функцию API с порядка 20-ю параметрами, выстроенными в столбец.

    ОтветитьУдалить
  19. >>> Применяя стандарт Delphi мы теряем читаемость.

    Вы забываете, что "читаемость" - понятие субъективное, как и "красота". Что-то, хорошо воспринимаемое одним, будет плохо восприниматься другим.

    К примеру, я ненавижу if ... then begin, потому что begin не будет на одной вертикальной позиции с end. Этот стиль затрудняет чтение кода для меня, потому что мне сложнее находить соответствия begin/end-ам.

    ОтветитьУдалить
  20. все здорово, только всетаки хотелось бы узнать как отключить перевод строки после THEN? или такой возможности у них вообще не предусмотрено?

    ОтветитьУдалить
  21. Я когда-то писал сайты в одну строку, а сейчас тоже как и вы делаю отступы. Так даже удобнее!

    ОтветитьУдалить
  22. Рассмотрим ситуацию из жизни (такое может быть):
    В софтверную компанию берут разработчика Delphi, который удовлетворяет компанию по всем параметрам (прошёл собеседование и разгромил своих конкурентов в «пух и прах»), а ему хочется работать именно в этой компании (к примеру ему нравится направление деятельности и возможности реализовать свой потенциал как разработчика ПО). Тем более ему там равных в разработке на Delphi нет, т.к. у него за плечами более крупные проекты.
    Допустим стандарт написания кода у компании свой, сильно отличающейся от стандарта Delphi.
    Разработчик когда начинает работать в этой компании и компания навязывает ему свой стандарт. Ему приходиться себя ломать. Продуктивность подает сильно и он не может реализовать свой потенциал (при том что он уже давно в ник в суть самой работы). Из месяца в месяц он ломает себя и энтузиазм уже не тот какой был до.
    Моё мнение не придерживаются стандарта только те кто не хочет изменять своим приычкам.
    Согласен с GunSmoker о ненависти к if ... then begin… Я не смог прочитать 1й вариант, а в2й с лёту.
    Для меня всегда был пример оформление библиотеки VCL.

    ОтветитьУдалить
  23. Перепутал. Код из 13 строк для меня более читабельный чем из 7.

    ОтветитьУдалить
  24. > количество строк отличается в два раза за счет строк не несущих смысла

    Сам ты, "не несущий смысла"! Эти строки как раз смысл и несут. Пусть не синтаксический, но они выполняют важную роль - помогают визуально определять границы блоков. и "if" всегда стоит в начале строки, а не абы где, где его не найти.

    Чего б тебе в одну строку без пробелов не писать, раз так тебя заботят лишняя пара #13#10?

    ОтветитьУдалить
  25. А я пишу вот так обычно:

    `procedure Proc;
    `begin
    ` DoSomething;
    ` DoSomethingElse;
    ` if Condition then begin
    ` DoAnotherThing;
    ` DoAnotherThingMore
    ` end else begin
    ` DoAnotherThing;
    ` DoAnotherThingMore
    ` end
    ` DoSomethingMore
    `end;

    Отступы в 2 пробела, если шрифт собьётся.

    Отличия в том, что begin на одной строке с then/else/do, а также в отсутствии точки с запятой там, где это не нужно.

    Смысл прост - легче читать. И на C/C++ также скобки ставил.
    А насчёт точек с запятой - а зачем писать то, что НЕ нужно вообще? Инкрустация? Производитель Delphi, видать, сам не знает, что его компилятор игнорирует...

    ОтветитьУдалить
  26. кто-то так пишет:

    for i:=0 to foo.Count-1
    do if condition
    then if another_condition then
    begin

    или

    with foobish.create
    do try
    barbish;
    finally
    Free;
    end;


    мотивируя тем, что "легче читать". Вопрос - кому легче ? :)

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.