Опубликован: 23.10.2005 | Доступ: свободный | Студентов: 4086 / 201 | Оценка: 4.44 / 4.19 | Длительность: 33:04:00
Специальности: Программист
Лекция 8:

Чувство стиля

Форматирование и презентация текста

Следующие правила определяют, как следует располагать программный текст на бумаге в реальной жизни или при моделировании этого процесса на дисплее компьютера. Более чем любые другие они взывают о "косметике" столь же важной для разработчиков ПО, как косметика от Кристиана Диора для ее покупательниц. Они играют немалую роль в том, как быстро ваш продукт будет понятен его пользователям - сопровождающим, повторно использующим, покупающим ваше ПО.

Форматирование

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

Основываясь на этом свойстве, форматирование текста следует гребенчато-подобной структуре (comb-like structure), введенной в языке Ada. Идея состоит в том, что каждая синтаксически важная часть класса, такая как инструкция или выражение должна либо:

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

Рис. 8.1. Гребенчато-подобная структура организации программного текста

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

Как пример, зависящий от размера его составляющих a, b и c, допустимы следующие формы представления инструкции выбора:

if c then a else b end

или

if
    c
then
    a
else
    b
end

или:

if c then
    a
else b end

Однако вы не можете использовать строку, содержащую просто if c или c end, так как они включают операнд вместе с чем-то еще, пропуская заканчивающий оператор в первом случае, а во втором - начинающий.

Подобным образом можно начать класс после предложения indexing так:

class C inherit                    -- [1]

или

class C feature                    -- [2]

или

class                            -- [3]
    C
feature

Нельзя писать

class C                        -- [4]
feature

поскольку первая строка нарушает правило.

Формы [1] и [2] используются в этой книге для небольших иллюстративных классов. Более практичные классы имеют одно или несколько помеченных предложений feature, они в отсутствие предложения inherit должны использовать форму [3] (она предпочтительнее, чем форма [2]):

class
    C                    
feature -- Initialization
    ...
feature -- Access
    и т.д.

Высота и ширина

Подобно большинству современных языков, наша нотация не придает особого значения окончаниям строк за исключением строк, завершающихся комментарием. Две или более инструкций (объявления) могут располагаться на одной строке, разделенные в этом случае точками с запятой:

count := count + 1; forth

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

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

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

Детали отступов

Гребенчатая структура использует отступы, для создания которых используется табуляция (но не пробелы!).

Вот какова иерархия отступов для основных видов конструкции, иллюстрируемых ниже следующим примером:

  • Уровень 0: ключевые слова, вводящие первичные предложения класса. Они включают: indexing (начинающее предложение индексации), class (начинающее тело класса), feature (начинающее предложение feature, исключая случай, когда feature находится на той же строке, что и class ), invariant (начинающее предложение инварианта) и заключительный end класса.
  • Уровень 1: начало объявления компонента - declaration ; разделы индексирования; предложения инварианта.
  • Уровень 2: ключевые слова, начинающиеся последующими предложениями подпрограммы. Они включают: require, local, do, once, ensure, rescue, end.
  • Уровень 3: Заголовочный комментарий подпрограмм и атрибутов; объявления локальных сущностей в подпрограмме; инструкции первого уровня.

Внутри тела программы может быть своя система отступов при гнездовании управляющих структур. Например, инструкция if a then... содержит две ветви, каждая с отступом. Эти ветви могут сами содержать инструкции цикла или выбора, приводящие к дальнейшему гнездованию. Еще раз заметим, что ОО-стиль этой книги приводит к простым подпрограммам, редко приводящим к высокому уровню гнездования.

Инструкция check задается с отступом. За ней, как правило, следует поясняющий комментарий, располагаемый на следующем уровне справа от охраняемой инструкции.

indexing
    description: "Пример форматирования"
class EXAMPLE inherit
    MY_PARENT
        redefine f1, f2 end
    MY_OTHER_PARENT
        rename
            g1 as old_g1, g2 as old_g2
        redefine
            g1
        select
            g2
        end
creation
    make
feature -- Initialization
    make is
            -- Сделать нечто
        require
            some_condition: correct (x)
        local
            my_entity: MY_TYPE
        do
            if a then
                b; c
            else
                other_routine
                    check max2 > max1 + x ^ 2 end
                    -- Из постусловия другой подпрограммы
                new_value := old_value / (max2 - max1)
            end
        end
feature -- Access
    my_attribute: SOME_TYPE
            -- Объяснение его роли (выровнено с комментарием для make)
    ... Объявления других компонентов и предложения feature ...
invariant
    upper_bound: x <= y
end --class Example

Пробелы

Белые пробелы (пробелы, табуляция, символы окончания строки) производят такой же эффект в программных текстах, как паузы в нотной записи.

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

Вот некоторые из следствий. Используйте пробелы:

  • Перед открывающей скобкой, но не после: f (x) (но не f(x) в стиле C, или f(x) ).
  • После закрывающей скобки, если только следующим символом не является знак пунктуации, такой как точка или точка с запятой, но не перед скобкой. Следовательно: proc1 (x); x := f1 (x) + f2 (y).
  • После запятой, но не перед: g (x, y, z).
  • После двух тире, указывающих на начало комментария: -- Комментарий.

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

p1; p2 (x); p3 (y, z)

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

p1 ; p2 (x) ; p3 (y, z)

Выбирайте любой стиль, но применяйте его согласованно. (Эта книга использует английский стиль.) Английский и французский стили отличаются и для двоеточий. И здесь английский стиль предпочтительнее, как в your_entity: YOUR_TYPE.

Пробелы должны появляться до и после арифметических операций, как в a + b. (В этой книге из экономии пробелы могут опускаться, например в выражении n+1.)

Для точек нотация отходит от соглашений, принятых в естественном языке, поскольку точки используются в специальной конструкции, первоначально введенной в языке Simula. Как вы знаете, a.r означает: применить компонент r к объекту, присоединенному к a. Здесь не должно быть никаких пробелов ни до, ни после точки. В вещественных числах, таких как 3.14, используется обычная точка.

Приоритеты и скобки

Соглашения о приоритетах в нотации соответствуют традициям и принципу Наименьших Сюрпризов во избежание ошибок и двусмысленностей.

Для ясности добавляйте скобки без колебаний; например, вы можете написать (a = (b + c)) implies (u /= v) несмотря на то, что смысл этого выражения не изменится, если все скобки будут опущены. В примерах этой книги зачастую расставлены "лишние" скобки, особенно в утверждениях, возможно, утяжеляя выражение, но избегая неопределенности.

Война вокруг точек с запятыми

С давних пор два равно известных клана живут в компьютерном мире и вражда между ними столь же ожесточена, как и в Вероне. Сепаратисты, наследники Algol и Pascal, сражаются за то, чтобы точка с запятой служила разделителем инструкций. Терминалисты, объединившиеся под знаменами PL/I, C и Ada, хотят каждую инструкцию завершать точкой с запятой.

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

Сепаратисты возносят хвалу элегантности их соглашения и его совместимости с математической практикой. Они рассматривают do instruction1; instruction2; instruction3 end как естественного родственника f (argument1, argument2, argument3). Кто в здравом уме, спрашивают они, предпочел бы писать f (argument1, argument2, argument3,) с ненужной заключительной запятой? Более того, они утверждают, что Терминалисты фактически являются защитниками Компиляторщиков, жестоких людей, чьей единственной целью является обеспечение легкой жизни для разработчиков компиляторов, даже если это приведет к трудной жизни разработчиков приложений.

Сепаратисты должны постоянно бороться с инсинуациями, например, что их языки не позволяют лишних точек с запятой. Снова и снова они должны повторять истину: что каждый язык, заслуживающий этого имени, начиная с признанного патриарха этого племени, Algol 60, поддерживает понятие пустой инструкции, допускающей все виды написания:

a; b; c
a; b; c;
; a ;; b ;;; c;

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

Методы современной пропаганды нуждаются в научном обосновании и статистике. В 1975 году Терминалисты провели исследование ошибок, для чего две группы программистов по 25 человек в каждой использовали языки, отличающихся, среди прочего, соглашениями о точках с запятой. Его результаты широко цитировались и послужили оправданием терминалистского соглашения в языке Ada. Из этих результатов следовало, что стиль Сепаратистов привел к десятикратному увеличению ошибок!

Взволнованные непрекращающейся вражеской пропагандой лидеры Сепаратистов обратились за помощью к автору настоящей книги, который, к счастью, вспомнил давно забытый принцип: цитаты хороши, но лучше прочитать первоисточник. Обратившись к оригинальной статье, он обнаружил, что язык Сепаратистов, используемый в сравнении, представлял мини-язык, предназначенный только для обучения студентов концепциям асинхронных процессов, в котором лишняя точка с запятой, как в begin a; b; end, рассматривалась как ошибка! Ни один реальный язык Сепаратистов, как отмечалось выше, такого правила не имеет. Из контекста статьи следовало также, что студенты, участвующие в эксперименте, имели предыдущий опыт работы с языком PL/I (Терминалистов) и посему имели привычки ставить точки с запятыми повсюду. Так что результаты этой статьи не дают никаких оснований отдать предпочтение Терминализму в ущерб Сепаратизму.

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

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

Правило Синтаксиса Точек с Запятой

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

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

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

  • терминалист: каждую инструкцию, объявление или предложение утверждения заканчивать точкой с запятой;
  • сепаратист: точки с запятой появляются между последовательными элементами, но не после последнего объявления компонента или локального предложения;
  • умеренный Сепаратист: его стиль подобен стилю Сепаратистов, но он не беспокоится о лишних точках с запятой, появляющихся в результате привычки, или в результате перемещения элементов;
  • минималист: вообще не ставит точек с запятой (исключая случаи, когда они требуются по принципу Стиля Точек с Запятой, приводимому ниже).

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

Принцип Стиля Точки с Запятой

Если вы предпочитаете рассматривать точку с запятой как заключительную часть инструкции, (стиль Терминалиста), поступайте так для всех применимых элементов.

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

Второе предложение управляет случаем двух или более элементов в одной строке:

found := found + 1; forth

Здесь точка с запятой должна всегда присутствовать. Ее пропуск будет ошибкой.

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

Следует полагаться на свой вкус, пока он согласован и соответствует принципу Стиля Точки с Запятой. (Что же касается этой книги, то вначале скорее по привычке, чем по реальной привязанности я придерживался стиля Сепаратистов, но затем, наслушавшись призывов начать новую жизнь с приходом третьего тысячелетия и порвать со старыми привычками, я удалил все точки с запятыми в течение одной ночи сплошного разгула.)

Утверждения

Следует именовать утверждения для большей читабельности текста:

require
    not_too_small: index >= lower

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

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

require
    index >= lower; index <= upper

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