Правильная ссылка на эту страницу
http://az-design.ru/Projects/AzBook/src/005/02YE050.shtml

ГЛАВА 5. СТИЛЬ В ПРОГРАММИРОВАНИИ: ПРОСТОТА И ЯСНОСТЬ

  ...насколько простым становится процесс проектирования, если мы пользуемся критерием: “это должно легко объясняться”. Если делается попытка ввести в систему нечто, требующее для своего объяснения более строки или абзаца, откажитесь от нее — она бесполезна.
  Смит Software Engineering, p.71.
  Я думаю нам следует сравнить наши методы управления с порядком подготовки технических чертежей. В больших организациях чертеж обычно подписывает его исполнитель и непосредственный руководитель, подтверждающий, что чертеж отвечает соответствующим нормам. В программировании вы обычно не встречаетесь с этой второй подписью — по правде сказать, не бывает и первой. Ясности изложения и стилю не придают, по-видимому, никакого значения — лишь бы программа работала должным образом. Мне представляется важным, чтобы программы удовлетворяли еще и некоторым эстетическим нормам.
  Макилрой Software Engineering, p.89.
  Д-р Макклюр: я знаю, что в очень редких программирующих организациях непосредственные руководители дают себе труд прочесть программный код, написанный их подчиненными, и попытаться понять его. Я полагаю, что это совершенна необходимо. Проф. Бакстон: я знаю, что в очень редких программирующих организациях руководитель способен прочесть программный код;*к присутствующим это, естественно, не относится.
  Software Engineering, p.89,
  *) Цитаты в начале главы взяты из сборника Software Engineering (ред. Наур и Ренделл), сектор научных исследований НАТО, Брюссель 39, Бельгия, январь 1969 г., и сборника Software Engineering Techniques (ред. Бакстон и Ренделл), сектор научных исследований НАТО, Брюссель 39, Бельгия, апрель 1970 г.

5.0. Введение

       Прямо или косвенно в нескольких последних главах мы старались подчеркнуть, что каждый программист часто склонен переоценивать свои способности. Мы показали, например, что нисходящая схема, предложенная в гл.2, обычно является наиболее логичной и организованной формой проектирования программ. Тем не менее немногие программисты на самом деле пользуются этим подходом, потому что часто они полагают, что могут спроектировать законченную программу “сходу”; кажется, что они рассчитывают (по крайней мере, они поступают, как если бы они так рассчитывали) на то, что они могут спроектировать свою программу в процессе ее кодирования.
       Предметом рассмотрения предыдущих глав было следующее положение: программирование требует внимательного, осторожного и организованного подхода. Довольно трудно добиться того, чтобы большая программа выполнялась правильно; не следует вводить дополнительные трудности, обращаясь без особой нужды к изощренным и сложным алгоритмам. Аналогичный принцип принят во многих технических отраслях; он часто упоминается как принцип KISS1) {KISS — Keep It Simple Stupid (“Будь попроше, дурачок”.— англ.).— Прим. перев.}. Повторяем, что это всего лишь принцип; мы не располагаем теоремами или статистическими исследованиями, которые могли бы служить доказательством того, что простота проекта обеспечивает лучшую программу. Тем не менее обычно можно наблюдать, что лучшие программисты сознательно или подсознательно пользуются этим принципом.
       Предыдущие главы неоднократно касались и другого предмета: в конечном итоге большинство программ используется и модифицируется другими программистами. Одним неофициальным исследованием несколько лет назад было установлено, что в среднем американский программист меняет место работы каждые 1.4 года. Хотя и нет соответствующей статистики, но мы могли бы оценить среднее время жизни программы для ЭВМ, увеличив этот срок вдвое, если не больше2) {В одном неофициальном обзоре крупной организации, разрабатывающей ЭВМ, отмечалось, что, после того как программа кем-то написана, обычно, прежде чем ее переделают заново, она будет сопровождаться программистами следующих десяти поколений.}. Отсюда следует совершенно определенный вывод: если имеется в виду, что рано или поздно программы должны сопровождаться кем-то другим, то их следует писать так, чтобы их можно было прочесть и понять. Как отметил Дж. Вейнберг в своей замечательной книге The Psychology of Computer Programming (Психология программирования), возникающие здесь трудности частично связаны с тем, что многие программисты считают составленные ими программы личной собственностью.
       Настоящая глава имеет целью подробно рассмотреть две названные выше проблемы. Во-первых, что может быть сделано для упрощения программирования, дабы на этапах кодирования и отладки мы сталкивались с меньшими трудностями? Во-вторых, каким образом добиться того, чтобы облегчить чтение и понимание программ тем, кто их не составляет? Мы начнем с краткого обзора некоторых рекомендаций предшествующих глав, многие из которых рассматривались как средство упрощения программ (с тем чтобы ускорить их отладку, тестирование и т.д.). Затем мы обсудим ряд методов и приемов программирования, способствующих повышению читаемости разрабатываемых программ.

5.1. Обзор предложений по разработке простых программ

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

5.1.1. Пользуйтесь методами структурного программирования

       В гл.4 рассмотрен подход к разработке программ, известный как “структурное программирование”. Одной из причин возникновения этого подхода было желание иметь более “читабельные” программы и программные листинги” Исключая программы с сетевыми структурами, которые часто возникают при неограниченном употреблении операторов GO TO, мы значительно снижаем возможности программиста в неупорядоченной организации листингов его программ.

5.1.2. Без крайней необходимости избегайте многоцелевого применения

       Свойственные многим современным операционным системам возможности, реализации многоцелевого применения являются чрезвычайно мощными средствами. Они удобны в приложениях, связанных с совмещением вычислений и операций ввода-вывода, систем коллективного использования и систем, работающих в реальном масштабе времени, а также в других случаях, где используются операции, несогласованные во времени. Однако структура большинства программ многоцелевого применения очень сложна; существует ряд тонких ошибок, которые могут долго сохраняться в такой программе, после того как вы сочли ее уже отлаженной, а потом внезапно обрушиться на какого-то несчастливца, мучительно пытающегося понять, что же в такой программе происходит. Поэтому без крайней не обходимости вам следует стараться избегать использования таких операторов, как ATTACH и DELETE в Системе IBM/360, или оператор FORK в языке XSS-940, или оператор ZIP в языке В5500 фирмы Burroughs.

5.1.3. Не допускайте произвольного употребления системы команд или языка программирования

       Как мы отметили в гл.1, в некоторых машинах встречаются неопределенные команды; их исполнение иногда приводит к довольно странным, неописанным в официальных руководствах, но тем не менее предсказуемым результатам. Во многих случаях, однако, результат оказывается совершенно непредсказуемым. К счастью, в большинстве современных машин неопределенные коды операций встречаются довольно редко; тем не менее некоторые программисты, работающие на языке ассемблера, все-таки находят способы непредписсанного использования системы команд их машины. Стоит еще раз повторить вывод, сформулированный в гл.1: не пытайтесь приспособить систему команд к вашей специальной задаче, если у вас нет полной уверенности в своих действиях; если вы понимаете, что вам необходимо отступить от исходного назначения данной команды, то вам следует позаботиться о ясном и достаточно полном комментарии ваших действий в листинге программы, чтобы они были понятны тем, кто будет сопровождать программу после вас.
       Аналогичные замечания уместны, конечно, и в отношении языков программирования высокого уровня. Программирование на Фортране, кажется, особенно искушает к применению подобных приемов; в Коболе программисты нашли ряд приемов для выполнения таких функций, как присваивание в качестве начального Значения таблицы некоторой константы; болыпийство этих приемов страдает отсутствием ясности и к тому же они связаны с определенной машиной. В большинстве случаев лучше их избегать и пользоваться только стандартными средствами языка.

5.1.4. Не пишите программы, которые модифицируются в процессе исполнения

       Почти все языки программирования позволяют программисту модифицировать программу во время ее исполнения. В программе на языке ассемблера такие действия выступают в явной форме; в языках высокого уровня модификация программы иногда маскируется такими эффектными терминами, как “переключатели программ” и т.д. Так, оператор ALTER в Коболе фактически позволяет программисту изменять программный код во время исполнения, назначенный оператор GO TO в Фортране в определенной степени характеризуется тем же свойством, возможность в ПЛ/1 определить переменную типа метки может рассматриваться как более формализованная и утонченная форма назначенного GO TO.
       Среди недостатков модифицируемых во время исполнения программ можно выделить два, которые представляются не очень широко известными; такие программы обычно нереентерабельны и возможность их использования в последовательности многократных обращений менее вероятна. Реентерабельной называется программа, которая может конкурентно (а в мультипроцессорных системах одновременно) использоваться двумя или большим числом процессов. Ясно, что если программа изменяется во время ее исполнения, то могут возникнуть серьезные трудности при конкурентном ее использовании двумя или более заданиями или пользователями. Теоретически назначенный оператор GO TO в Фортране и операторы, использующие переменные типа метки, в языке ПЛ/1 не обязательно приводят к потере реентерабельности рассматриваемой программы; однако в большинстве версий Кобола использование оператора ALTER совершенно определенно исключает это свойство программы. Что касается Фортрана и Кобола, то в действительности все это рассмотрение носит до некоторой степени академический характер, поскольку до настоящего времени мало кто из разработчиков ЭВМ поставляет компиляторы с языков Фортран и Кобол, генерирующих реентерабельный объектный код — сами компиляторы и рабочие программы операционных систем, в которые они входят, могут быть реентерабельными, но Фортран- и Кобол-объектный код обычно этим свойством не обладают.
       Другое нежелательное свойство модифицируемых программ состоит, в том, что обычно они не могут исполняться несколько раз подряд. Если мы хотим выполнить повторно модифицируемую во время работы программу, то нам придется повторить заново загрузку исходной программы. В этом, конечно, нет необходимости в том случае, если программист предусмотрел соответствующее автоинициирование своей программы. Заметим, что с той же трудностью можно столкнуться и при задании значений переменных: если программист пользуется оператором DATA в Фортране или предложением VALUE в Коболе, то всякое исполнение программы требует повторной загрузки исходной программы. (В ПЛ/1 это обычно не так: переменные, описанные с помощью атрибута INITIAL, получают свои начальные значения при входе в блок, содержащий их описание; это, однако, требует дополнительных ресурсов.)
       Наше последнее критическое замечание по поводу модифицируемых программ имеет более практический характер и ближе к предмету этой главы: такая программа значительно труднее в пониманий и отладке. Это в особенности верно в случае очень больших программ. Команда, модифицирующая код программы, может быть расположена в ее начале, а модифицируемые команды — совсем в другой части программы. Таким образом, в процессе отладки программист может локализовать ошибку, сужая анализируемый фрагмент программы до отдельной подпрограммы, однако если в такой подпрограмме есть команды, модифицированные операциями в каких-то других частях программы, и если листинг программы содержит несколько сот страниц, то может оказаться практически невозможным установить, каким же образом изменяется эта ошибочная подпрограмма.
       Если плох оператор ALTER, то что можно сказать о других формах модификации программ? Соотнося возможные случаи (и имея в виду наше обсуждение структурного программирования в гл.4), я бы сравнил использование программистами вычисляемого оператора GO TO (или GO TO DEPENDING ON в Коболе) с мелким правонарушением (если не имеется в виду сознательное его применение как средства моделирования Алгол-оператора CASE — одной из допустимых форм “черного ящика”, рассмотренных в гл.4); использование назначенного оператора GO ТО — с уголовно наказуемым действием, применение оператора ALTER — с непредумышленным убийством, а написание модифицируемых программ на языке ассемблера — с действиями матерого убийцы. Хотя использование вычисляемого GO ТО приводит к тому, что программа становится неструктурированной и несколько более трудной в отладке, мы по крайней мере можем перечислить все способы, которыми могла бы исполняться программа. В случае назначенного оператора GO TO (или использования в управляющих операторах в ПЛ/1 переменных типа метки) нам часто не удается опеределить по одной только записи оператора GO TO, в какие части программы может передаваться управление. То же самое в основном можно сказать и относительно оператора ALTER, причем здесь имеется тот дополнительный недостаток, что этот оператор вызывает нереентерабельность программы (хотя, справедливости ради, стоит заметить, что эту трудность можно было бы разрешить средствами компилятора Кобол-программ). Модифицированные команды в программах на языке ассемблера следует считать наибольшим злом, поскольку в этом языке программисту предоставляются практически неограниченные возможности реализации модифицируемых программ.
       Здесь логично задать следующий вопрос: если самомодифицирующиеся программы обладают столь многими недостатками, то почему программистам так нравится этот подход? Обычно в ответ можно услышать о соображениях удобства и эффективности. Программисту часто кажется проще и легче реализовать определенные программные ситуации с помощью операторов ALTER или назначенного GO TO; кроме того, программисту часто кажется, что в такой реализации программа будет исполняться быстрее, чем в любой другой. Однако обычно использование модифицируемых программ кажется более удобным лишь потому, что программист недостаточно думал о поисках другого способа решения задачи. Если программисту представляется, что программирование с операторами ALTER и назначенным GO ТО более удобно, то, как правило, это объясняется тем, что ему не удалось учесть те дополнительные потери машинного времени, возникающие при отладке такой его программы. Существуют лучшие способу написания программ, и часто они оказываются столь же удобными; в большинстве случаев они столь же и эффективны.

* ЭТА ПОДПРОГРАММА ОТПЕЧАТАЕТ ВСЕ СЛУЧАИ ВХОЖДЕНИЯ ИЛИ
* НЕВХОЖДЕНИЯ ДАННОГО ЭЛЕМЕНТА В ДАННУЮ ОБЛАСТЬ ПАМЯТИ.
* ПОРЯДОК ОБРАЩЕНИЯ ПРИВОДИТСЯ НИЖЕ:
*     BAL   R14, WSEARCH    ПОИСК  ВСЕХ  ВХОЖДЕНИЙ ЭЛЕ-
*                           МЕНТА
*     BAL   R14, NSEARCH    ПОИСК ВСЕХ НЕВХОЖДЕНИЙ ЭЛЕ-
*                           МЕНТА
*   R1 СОДЕРЖИТ АДРЕС ПЕРВОГО СЛОВА ПРОСМАТРИВАЕМОЙ ОБ-
*      ЛАСТИ ПАМЯТИ
*   R2 СОДЕРЖИТ ПОЛОЖИТЕЛЬНОЕ ЦЕЛОЕ — ЧИСЛО СЛОВ, ПОДЛЕ-
*      ЖАЩИХ ПРОВЕРКЕ
*   R3 СОДЕРЖИТ ОТЫСКИВАЕМЫЙ ЭЛЕМЕНТ
*  "МАСКА" ОПРЕДЕЛЯЕТ ТЕ РАЗРЯДЫ, КОТОРЫЕ ИСПОЛЬЗУЮТСЯ
*          ПРИ СРАВНЕНИИ:
* ЕСЛИ N-Й РАЗРЯД МАСКИ РАВЕН 1, ТО N-Й РАЗРЯД СОДЕРЖИМОГО R3
* И СЛОВА ИЗ ОБЛАСТИ ПАМЯТИ БУДУТ СРАВНИВАТЬСЯ НА РАВЕН-
* СТВО (ИЛИ НЕРАВЕНСТВО)
WSEARCH L      R4,SWITCHI      ВЫБРАТЬ   КОМАНДУ  СРАВНЕНИЯ
        В      SEARCH          ВОЙТИ В РАЗДЕЛ ОБЩИХ КОДОВ
NSEARCH L      R4,SWITCH2      ВЫБРАТЬ ДРУГУЮ КОМАНДУ СРАВНЕНИЯ
SEARCH  ST     R4,COMPARE      ФИКСИРОВАТЬ КОМАНДУ СРАВНЕНИЯ
        N      R3,MASK         СМОТРЕТЬ ТОЛЬКО РАЗРЯДЫ, ОПРЕ-
                               ДЕЛЯЕМЫЕ МАСКОЙ
        L      R4,ZERO         НАЧАЛЬНОЕ   ЗНАЧЕНИЕ  СЧЕТЧИКА 
                               СЛОВ ПАМЯТИ
LOOP    L      R5,(R4,R1)      ВЫБРАТЬ     СЛОВО   ИЗ   ПАМЯТИ,
                               ИСПОЛЬЗУЯ R1 КАК БАЗУ
        N      R5,MASK         СМОТРЕТЬ ТОЛЬКО РАЗРЯДЫ, ОПРЕДЕ-
                               ЛЯЕМЫЕ МАСКОЙ
        CR     R5,R3           СРАВНЕНИЕ ДВУХ ЭТИХ ЗНАЧЕНИЙ
COMPARE BE     FOUND           *** ЭТА КОМАНДА—ИЗМЕНЯЕМАЯ ***
NEXT    LA     R4,4(R4)        ПРИРАЩЕНИЕ   ЗНАЧЕНИЯ   СЧЕТ-
                               ЧИКА ПАМЯТИ 
        ВСТ    R2,LOOP         ПОВТОРИТЬ ЦИКЛ НА СЛЕДУЮЩЕМ
                               СЛОВЕ
        BR     R14 ВЫХОД
* ВОЙТИ ЗДЕСЬ В СЛУЧАВ УСПЕШНОГО СРАВНЕНИЯ
FOUND   L      R5,(R4,R1)      СНОВА ВЫБРАТЬ СЛОВО ИЗ ПАМЯТИ
        STM    R1,R5,TABLE     УПРЯТАТЬ ЗНАЧЕНИЯ РЕГИСТРОВ
        LR     R1,R4           ПОСЛАТЬ АДРЕС  СЛОВА ПАМЯТИ
                               В R1
        BAL    R13,PRINT       ОБРАЩЕНИЕ К ПОДПРОГРАММЕ ПЕЧАТИ АДРЕСА
        L      R1,TABLE+4      ВЫБРАТЬ СОДЕРЖИМОЕ СЛОВА ПАМЯТИ
        BAL    R13,PRINT       НАПЕЧАТАТЬ СОДЕРЖИМОЕ СЛОВА
        LM     R1,R5,TABLE     ВОССТАНОВИТЬ ЗНАЧЕНИЯ РЕГИСТРОВ
        В      NEXT            ВЕРНУТЬСЯ К ПРОВЕРКЕ СЛЕДУЮ-
                               ЩЕГО СЛОВА
SWITCH1 BE     FOUND           *** ЭТО ИСПОЛЬЗУЕТСЯ ПРОГРАМ-
                                   МОЙ WSEARCH ***
SWITCH2 BNE    FOUND           *** ЭТО ИСПОЛЬЗУЕТСЯ ПРОГРАМ-
                                   МОЙ NSEARCH ***
MASK    DS     F               СЛОВО МАСКИ
ZERO    DC     F'0'
TABLE   DS     5F              ПАМЯТЬ ДЛЯ СОХРАНЕНИЯ РЕ-
                               ГИСТРОВ

Рис. 5.1а. Программа поиска в области памяти, написанная на языке ассемблера Системы IBM/360.

       Проиллюстрируем эту точку зрения некоторыми примерами. Наиболее широко метод модифицируемых программ используется для придания некоторому модулю функций по решению нескольких похожих задач. Так, на рис. 5.1а показана подпрограмма на языке ассемблера Системы IBM/360, которая может использоваться для поиска в заданной области памяти всех случаев вхождения или всех случаев невхождения определенного элемента. Простым изменением одной команды условного перехода программист обеспечивает использование этой подпрограммы для решения двух различных, хотя и похожих, задач/С точки зрения минимизации машинного времени с большой уверенностью можно сказать, что решение, показанное на рис. 5.1а, достаточно эффективно. С другой стороны, подпрограмма, показанная на рис. 5.16, чуть менее эффективна, но обладает всеми описанными выше достоинствами: она реентерабельна допускает повторное использование, проще в отладке и для понимания.
       На рис. 5.2а показана аналогичная ситуация в программировании на ПЛ/1. В зависимости от результатов некоторых сравнений программист желает выполнить слегка отличающиеся последовательности действий. Хотя такой метод кодирования, может быть, проще, подход с использованием операторов IF-THEN, показанный на рис. 5.26, представляет собой значительно лучший способ действий (хотя он и может привести к несколько менее эффективному программному коду в зависимости от особенностей используемого компилятора). Рис. 5.3а и рис. 5.3б иллюстрируют аналогичную ситуацию в Фортране; рис, 5.4а и рис. 5.4б показывают ту же задачу на Коболе. Во всех приведенных примерах предпочтительная реализация решения может потребовать несколько большего времени ЦП и несколько большего внимания программиста к предварительному планированию. Должно быть очевидным, однако, что результирующая программа оказывается значительно лучше.

* ЭТА ПОДПРОГРАММА ВЫПОЛНЯЕТ ТУ ЖЕ ОБРАБОТКУ, ЧТО И
* ПОДПРОГРАММА РИС. 5.1 А
* ОНА ВЫЗЫВАЕТСЯ  ТОЧНО ТАК ЖЕ И ИСПОЛЬЗУЕТ ТЕ ЖЕ СО-
* ГЛАШЕНИЯ ОТНОСИТЕЛЬНО АРГУМЕНТОВ. ОДНАКО ОНА НЕ МО-
* ДИФИЦИРУЕТСЯ ВО ВРЕМЯ ИСПОЛНЕНИЯ
WSEARCH   LA    R6,0         ИСПОЛЬЗОВАТЬ R6 ДЛЯ СЧЕТЧИКА
                             ТАБЛИЦЫ
          В     SEARCH       ВОЙТИ В РАЗДЕЛ ОБЩИХ КОДОВ
NSEARCH   LA    R6,4         УСТАНОВИТЬ R6 ДЛЯ ВЫБОРА ДРУ-
                             ГОЙ КОМАНДЫ
SEARCH    N     R3,MASK      СМОТРЕТЬ ТОЛЬКО РАЗРЯДЫ, ОП-
                             РЕДЕЛЯЕМЫЕ МАСКОЙ
          L     R4,ZERO      НАЧАЛЬНОЕ ЗНАЧЕНИЕ СЧЕТЧИКА
                             СЛОВ ПАМЯТИ
LOOP      L     R5,(R4,R1)   ВЫБРАТЬ СЛОВО ИЗ ПАМЯТИ, ИС-
                             ПОЛЬЗУЯ R1 КАК БАЗУ
          N     R5,MASK      СМОТРЕТЬ ТОЛЬКО РАЗРЯДЫ, ОП-
                             РЕДЕЛЯЕМЫЕ МАСКОЙ
          CR    R5,R3        СРАВНЕНИЕ ЭТИХ ДВУХ ЗНАЧЕ-
                             НИЙ 
          EX    O,SWITCH(R6) ИСПОЛНИТЬ СООТВЕТСТВУЮЩУЮ
                             КОМАНДУ ПЕРЕХОДА
NEXT      LA    R4,4(R4)     ПРИРАЩЕНИЕ   ЗНАЧЕНИЯ  СЧЕТ-
                             ЧИКА ПАМЯТИ
          ВСТ   R2,LOOP      ПОВТОРИТЬ  ЦИКЛ  НА СЛЕДУЮ-
                             ЩЕМ СЛОВЕ
          BR    R14 ВЫХОД
* ВОЙТИ ЗДЕСЬ В СЛУЧАЕ УСПЕШНОГО СРАВНЕНИЯ
FOUND     L     R5,(R4,R1)   СНОВА ВЫБРАТЬ СЛОВО ИЗ ПАМЯТИ
          STM   R1,R6,TABLE  УПРЯТАТЬ ЗНАЧЕНИЯ РЕГИСТРОВ
          LR    R1,R4        ПОСЛАТЬ АДРЕС СЛОВА ПАМЯТИ
                             В R1
          BAL   R13,PRINT    НАПЕЧАТАТЬ АДРЕС СЛОВА 
          L     Rl,TABLE+ 4  СНОВА ВЫБРАТЬ СОДЕРЖИМОЕ
                             СЛОВА ПАМЯТИ
          BAL   R13,PRINT    НАПЕЧАТАТЬ ЕГО 
          LM    R1,R6,TABLE  ВОССТАНОВИТЬ ЗНАЧЕНИЯ РЕГИСТРОВ
          В     NEXT         ВЕРНУТЬСЯ К ПРОВЕРКЕ СЛЕДУЮ-
                             ЩЕГО СЛОВА
*
* КОНСТАНТЫ И ДАННЫЕ — ДЛЯ РЕЕНТЕРАБЕЛЬНОСТИ ИХ МОЖНО
* БЫЛО БЫ ПОМЕСТИТЬ В РАЗДЕЛЕ ДАННЫХ
ZERO      DC     F'0'
MASK      DS     F           СЛОВО МАСКИ
TABLE     DS     BF          ПАМЯТЬ  ДЛЯ  СОХРАНЕНИЯ  РЕ-
                             ГИСТРОВ
SWITCH    BE     FOUND       ЭТО БУДЕТ ИСПОЛНЕНО ЕСЛИ
                             R6 = 0
          BNE    FOUND       ЭТО БУДЕТ ИСПОЛНЕНО ЕСЛИ 
                             R = l

Рис. 5.1б. Слегка измененная, версия той же программа для Системы IBM/360.

          DECLARE ORDERFILE OUTPUT;
          DECLARE SHIPMENTFILE OUTPUT;
          DECLARE CANCELFILE OUTPUT;
          DECLARE ERRORFILE OUTPUT;
          DECLARE (TRANSCODE,DOLLAR,TOTAL!,TOTALD,QUANTITY) FIXED;
          DECLARE SWITCH LABEL;
          TOTALQ = 0;
          TOTALD = 0;
LOOP:     SWITCH - ERROR;
          GET LIST (TRANSCODE, QUANTITY, DOLLAR);
          IF TRANSCODE = 1 THEN SWITCH = ORDER;
          IF TRANSCODE = 3 THEN SWITCH = SHIPMENT;
          IF TRANSCODE = 5 THEN SWITCH = CANCEL;
          GO TO SWITCH; 
ERROR:    PUT FILE (ERRORFILE) LIST (TRANSCODE,QUANTITY,DOLLAR);

GO TO LOOP;

ORDER: TOTALQ * TOTALQ + QUANTITY;

TOTALD * TOTALD + DOLLAR;

PUT FILE (ORDERFILE) LIST (TRANSCODE,QUANTITY,DOLLAR);

GO TO LOOP;

CANCEL: TOTALQ - TOTALQ - QUANTITY;

TOTALD = TOTALD - DOLLAR;

PUT FILE {CANCELFILE) LIST (TRANSCODE,QUANTITY,DOLLAR);

GO TO LOOP;

SHIPMENT: PUT FILE (SHIPMENTFILE) LIST (TRANSCODE,QUANTITY,DOLLAR);

GO TO LOOP;

Примечание: Эта программа предназначена для чтения карт сообщений и обработки их в соответствии с кодом сообщения. В этом крайне простом примере довольно легко уяснить смысл применения переменных типа метки операторов; в более реальном варианте этой программы действие этих переменных было бы менее ясным и легко скрывалось бы среди тысяч других операторов.

Рис. 5.2а. Программа на ПЛ/l, использующая переменные типа метка оператора.

           DECLARE ORDERFILE OUTPUT; 
           DECLARE SHIPMENTFILE OUTPUT;
           DECLARE CANCELFILE OUTPUT;
           DECLARE ERRORFILE OUTPUT;
           DECLARE.(TRANSCODE,DOLLAR,TOTALQ,TOTALD,QUANTITY) FIXED;
           TOTALQ = 0; 
           TOTALD = 0;
LOOP:      GET LIST (TRANSCODE,GUANTITY,DOLLAR);
           IF TRANSCODE = 1 THEN 
              DO;
              TOTALQ = TOTALQ + QUANTITY;
              TOTALD = TOTALD + DOLLAR;
              PUT FILE (ORDERFILE) LIST (TRANSCODE,QUANTITY,DOLLAR);
              END; 
           ELSE
              IF TRANSCODE = 3 THEN
                 PUT FILE (SHIPMENTFILE) LIST (TRANSCODE,QUANTITY,DOLLAR);
              ELSE
                IF TRANSCODE - 5 THEN
                   DO;
                   TOTALQ = TOTALQ - QUANTITY;
                   TOTALD = TOTALD - DOLLAR;
                   PUT FILE (CANCELFILE) LIST (TRANSCODE,QUANTITY,DOLLAR);
                   END;
                ELSE PUT FILE (ERRORFILE) LIST (TRANSCODE,QUANTITY,DOLLAR);
           GO TO LOOP;

Рис. 5.2б. Слегка измененный вариант той же программы на ПЛ/1.

       Короче говоря, мы видим, что различные формы модифицируемых программ обычно используются для обработки специальных случаев в условиях ветвления логики, а также в ситуациях, требующих выполнения “сходных” процессов. Из приведенного выше обсуждения вам должно быть ясным, что многие недостатки модифицируемых программ обычно не компенсируются небольшим выигрышем в машинном времени и сэкономленной памяти; вам должно быть также ясно, что для таких средств программирования, как оператор ALTER в Коболе и назначенный оператор GO TO в Фортране, существуют хорошие альтернативы. Наилучший с точки зрения модульности способ состоит, по-видимому, в оформлении каждого специального случая в виде замкнутой подпрограммы; именно такой подход отражен на рис. 5.4б. Во многих случаях не хуже оказывается и решение, основанное на применении IF-THEN, что иллюстрируется рис. 5.2б. К сожалению, как мы видели в гл.4, кажется лишь ПЛ/1 и Алгол располагают блочной структурой BEGIN-END, необходимой для реализации хоть отчасти изящного подхода, отличного от тривиального IF-THEN-построения, хотя язык КОБОЛ в этом отношении обычно достаточен. Распознав специальный случай в некоторой точке нашей программы, мы можем использовать команду безусловной передачи управления в некоторый специальный фрагмент программного кода, как это было показано на рис. 5.3б в примере в Фортране; однако такая структура программы намного менее желательна, так как при этом значительно снижается ясность программы. В подобной ситуации использование вычисляемого оператора GO TO следует рассматривать как меньшее зло. Наконец, мы можем использовать в программе логические признаки и многие другие приемы на языке ассемблера, как это показано на рис. 5.1б.

C
С    THIS PROGRAM ACCOMPLISHES THE SAME PURPOSE AS THE PL/I PROGRAM
С     IN FIGURE 5.2(A)
С
      INTEGER TCODE
      TOTALQ=0
      TOTALD=0
10    ASSIGN 20 TO SWITCH
      READ 100,TCODE,QUANT,DOLLAR
      IF (TCODE .EQ. -1) ASSIGN 30 TO SWITCH
      IF (TCODE .EQ. 3)  ASSIGN 40 TO SWITCH
      IF (TCODE .EQ. 5)  ASSIGN 50 TO SWITCH
      GO TO SWITCH 
С
С     ERROR ROUTINE 
С 
20    WRITE (3,200) TCODE,QUANT,DOLLAR
      GO TO 10 
С
С     ORDER-HANDLING ROUTINE
С 
30    TOTALQ=TOTALQ+QUANT
      TOTALD =TOTALD + DОLLAR
      WRITE (4,300) TCODE,QUANT,DOLLAR
      GO TO 10
С
С     CANCELLATION ROUTINE
С 
40    TOTALQ=TOTALQ-QUANT
      TOTALD=TOTALD-DOLLAR
      WRITE (5,400) TCODE,QUANT,DOLLAR
      GO TO 10 
С
С     SHIPMENT ROUTINE 
С 
50    WRITE (6,500) TCODE,QUANT,DOLLAR
      GO TO 10

Рис. 5.3а. Фортран-программа, использующая назначенные операторы GO TO.

      INTEGER TCODE
      TOTALQ=0
      TOTALD=0
10    READ 100, TCODE,QUANT,DOLLAR
      IF (TCODE .EQ. 1) GO TO 30
      IF (TCODE .EQ. 3) GO TO 40
      IF (TCODE .EQ. 5) GO TO 50
С
С     FALL THROUGH TO THE ERROR ROUTINE
С 
20    WRITE (3,200) TCODE,QUANTY,DOLLAR
      GO TO 10 
С
С     ORDER-HANDLING ROUTINE
С 
30    TOTALQ=TOTALQ+QUANT
      TOTALD=TOTALD+DOLLAR
      WRITE (4,300) TCOD,QUANT,DOLLAR
      GO TO 10 
С
С     CANCELLATION ROUTINE
С  
40    TOTALQ=TOTALQ-QUANT
      TOTALD=TOTALD-DOLLAR
      WRITE (5,400) TCODE,QUANT,DOLLAR
      GO TO 10 
С
С    SHIPMENT ROUTINE 
С
50   WRITE (6,500) TCODE,QUANT,DOLLAR
     GO TO 10

Рис. 5.3б Слегка измененный вариант той же Фортран-программы.

      MOVE 0 ТО TOTALQ.
      MOVE 0 ТО TOTALD. 
GET-INPUT. READ TRANSACTION-CARDS.
      IF TRANSACTION-CODE=1 THEN ALTER SWITCH-PARAGRAPH TO 
         PROCEED TO ORDER-ROUTINE
      ELSE IF, TRANSACTION-CODE=3 THEN ALTER SWITCH-PARAGRAPH TO 
         PROCEED TO CANCEL-ROUTINE
      ELSE IF TRANSACTION-CODE=5 THEN ALTER SWITCH-PARAGRAPH TO
         PROCEED TO SHIPMENT-ROUTINE
      ELSE ALTER SWITCH-PARAGRAPH TO PROCEED TO ERR OR-ROUTINE.
SWITCH-PARAGRAPH. GO TO ERROR-ROUTINE.
ERROR-ROUTINE.
      MOVE TRANSACTION-CODE TO ERROR-CODE.
      MOVE QUANTITY TO ERROR-QUANTITY.
      MOVE DOLLAR TO ERROR-DOLLAR.
      WRITE ERROR-FILE.
      GO TO GET-INPUT. 
ORDER-ROUTINE.
      COMPUTE TOTALQ=TOTALQ+QUANTITY.
      COMPUTE TOTALD=TOTALD+DOLLAR.
      MOVE TRANSACTION-CODE TO ORDER-CODE.
      MOVE DOLLAR TO ORDER-DOLLAR.
      MOVE QUANTITY TO ORDER-QUANTITY.
      WRITE ORDER-FILE.
      GO TO GET-INPUT.
CANCEL-ROUTINE.
      COMPUTE TOTALQ=TOTALQ-QUANTITY.
      COMPUTE TOTALD=TOTALD-DOLLAR.
      MOVE TRANSACTION-CODE TO CANCEL-CODE.
      MOVE DOLLAR TO CANCEL-DOLLAR.
      MOVE QUANTITY TO CANCEL-QUANTITY.
      WRITE CANCEL-FILE.
      GO TO GET-INPUT. 
SHIPMENT-ROUTINE.
      MOVE TRANSACTION-CODE TO SHIPMENT-CODE.
      MOVE DOLLAR TO SHIPMENT-DOLLAR.
      MOVE QUANTITY TO SHIPMENT-QUANTITY.
      WRITE SHIPMENT-FILE.
      GO TO GET-INPUT.

Рис.5.4а, Кобол-программа с операторами ALTER.

      MOVE 0 ТО TOTALQ.
      MOVE 0 ТО TOTALD.
GET-INPUT. READ TRANSACTION-CARDS.
      IF TRANSACTION-CODE=1 THEN PERFORM ORDER-ROUTINE
      ELSE IF TRANSACTION-CODE=3 THEN PERFORM CANCEL-ROUTINE
      ELSE IF TRANSACTION~CODE=5 THEN PERFORM SHIPMENT-ROUTINE
      ELSE PERFORM ERROR-ROUTINE.
      GO TO GET-INPUT.
ERROR-ROUTINE.
      MOVE TRANSACTION-CODE TO ERROR-CODE.
      MOVE QUANTITY TO ERROR-QUANTITY.
      MOVE DOLLAR TO ERRQR-DOLLAR.
      WRITE ERROR-FILE.
      EXIT. 
ORDER-ROUTINE.
      COMPUTE TOTALQ=TOTALQ+QUANTITY.
      COMPUTE TOTALD=TOTALD + DOLLAR.
      MOVE TRANSACTION-CODE TO ORDER-CODE.
      MOVE DOLLAR TO ORDER-DOLLAR.
      MOVE QUANTITY TO ORDER-QUANTITY.
      WRITE ORDER-FILE.
      EXIT.
CANCEL-ROUTINE.
      COMPUTE TOTALQ=TOTALQ-QUANTITY.
      COMPUTE TOTALD=TOTALD-DOLLAR.
      MOVE TRANSACTION-CODE TO CANCEL-CODE.
      MOVE QUANTITY TO CANCEL-QUANTITY.
      MOVE DOLLAR TO CANCEL-DOLLAR.
      WRITE CANCEL-FILE.
      EXIT. 
SHIPMENT-ROUTINE.
      MOVE TRANSACTION-CODE TO SHIPMENT-CODE.
      MOVE DOLLAR TO SHIPMENT-DOLLAR.
      MOVEL QUANTITY TO SHIPMENT-QUANTITY.
      WRITE SHIPMENT-FILE.
      EXIT.

Рис. 5.4б. Слегка измененный вариант той же Кобол-программы.

5.2. Дополнительные методы повышения читабельности программ

       Одним из качеств хорошего программиста можно считать его способность удерживаться от соблазна использовать ухищренные приемы программирования. Этот принцип не очень важен в программировании на Фортране и Коболе ввиду относительной ограниченности этих языков; однако в гибких языках высокого уровня типа ПЛ/1 или Алгола программист часто имеет возможность писать чрезвычайно неясные, нестандартные и усложненные программные коды. В языке ассемблера этот вопрос стоит еще острее: в машинном коде имеется практически бесконечное число возможных способов программирования одной и той же задачи. По-видимому, было бы несправедливым прибегать к широким обобщениям подобных ситуаций, поскольку в определенных условиях могут быть оправданы даже самые специфические особенности данного языка программирования. Мы пытаемся лишь выразить наше отношение к этому, которое можно сформулировать так: “Вам всегда следует использовать самые простые, наименее запутанные, наименее специфические средства языка, позволяющие должным образом решить рассматриваемую задачу. Не прибегайте к усложненному программированию только ради того, чтобы похвастаться перед своими коллегами, что вам все-таки удалось употребить команду XYZ!”
       Мы можем несколько развить высказанный принцип. Не тратьте свое время на изучение программы ваших коллег, пытаясь найти в них изолированные фрагменты программного кода. Не ищите неприятностей, пытаясь переписать чью-то сравнительно хорошо составленную 100-командную программу, чтобы получить чуть более умную 99-командную программу. Быть может, вам удастся сэкономить немного времени ЦП и немного памяти; но, с другой стороны, дополнительное время на программирование, компиляцию и тестирование часто может нивелировать эти выигрыши. Эту мысль часто бывает очень трудно довести до понимания некоторых программистов, особенно из числа молодых, блестящих и наиболее агрессивных. Часто кажется, что высшее наслаждение в жизни они находят в кропотливом изучении математического обеспечения разработчика ЭВМ и программ своих коллег (которых они обычно считают слишком старыми, неповоротливыми, слишком невежественными и слишком небрежными, чтобы создавать по-настоящему хорошие программы) с целью отыскания различных неэффективностей незначительного характера. Они могут высказаться критически о том, что коллега использовал три команды для вычисления функций XYZ, а он мог бы это сделать с помощью одной команды, или что в программе переменной ABC присваивается нулевое значение, в то время как она уже имеет это значение. Затем эти “бездельники от программирования”, как их выразительно называют в некоторых организациях, добьются сокращения на десять микросекунд времени исполнения программы, потратят десять минут на компиляцию программы с их улучшениями, не удосужатся внести каких-либо объяснений по поводу внесенных изменений и без всякой нужды вызовут к себе враждебное отношение со стороны коллег на несколько следующих недель.
       Как было отмечено в первых четырех главах этой книги, программисты часто не склонны использовать ряд здравых правил программирования. Так, они редко применяют нисходящую схему проектирования, предложенную в гл.2, хотя с интуитивной точки зрения это наиболее организованный подход к проектированию, кодированию и тестированию программ. Программисты редко на практике реализуют принцип модульности, хотя многие из них его проповедуют. Мало кто их программистов придерживается принципа использования стандартных подпрограмм, хотя они и соглашаются, что обычно это хорошая практика. Аналогично очень немногие программисты предпринимают сознательные усилия для обеспечения простоты их программ, даже если они и говорят, что сделали бы все возможное, чтобы минимизировать время, необходимое для их отладки.
       В чем же дело? Кажется, что причина в основном относится к области психологии: даже если программист осознает, что он делает программу более трудной в отладке и менее понятной, он не в состоянии превозмочь стремление к усложнению и применению специфических решений. Разновидностью этого может служить такое рассуждение программиста: “Если бы не предполагалось использовать эти (специфические) команды, то разработчик машины не включал бы их в систему команд,— если вы не применяете их, то вы не пользуетесь всеми возможностями, которые заложены в машине!” Иногда можно столкнуться и с диаметрально противоположной точкой зрения: программист упорно отказывается верить, что сложные многоцелевые программы, насыщенные ухищренными приемами программирования и модифицируемые в процессе исполнения, могут быть трудными в отладке; иногда можно встретить программиста, заявляющего: “Всякий хоть чего-нибудь стоящий программист должен понять такую программу!” Иногда программист прибегает к подобным сомнительным приемам, чтобы поднять свою репутацию в глазах коллег: “Ну да,— говорит он своим ошарашенным друзьям,— я записал решение этой задачи о начислении зарплаты в виде рекурсивной процедуры; это делает подпрограмму вычисления налогов значительно более изящной!” Эта поза обычно исчезает, когда программист становится более опытным и зрелым; к сожалению, зрелость не является универсальной чертой характера, и она не всегда растет пропорционально опыту. Поэтому руководитель программных разработок должен проявлять бдительность в отношении подобной позиции своих программистов.
       Иногда находятся обоснованные доводы против простейших решений; их важнейшим потенциальным недостатком является неэффективность. “Незамысловатая” программа может не использовать всех возможности развитого языка программирования или вычислительной машины; в результате программа может либо медленнее исполняться, либо занимать больше памяти. Это было очевидным в примерах, показанных на рис. 5.1б, 5.2б, 5.3б и 5.4б. Здесь, конечно, необходимо установить некоторые эквиваленты ценностей, однако большинство программистов не проводят соответствующего тщательного анализа; они не отдают себе отчет в том, что в большинстве программных разработок узким местом является недостаток времени на программирование и отладку, а не потребности программы в машинном времени и памяти. Если простейшее решение действительно требует чрезмерных количеств времени ЦП и (или) памяти, то у нас может появиться желание прибегнуть к некоторым ухищрениям; однако, как мы уже установили в этой главе, требование простоты обычно не приводит к существенному снижению эффективности.
       Обратите внимание на тонкое различие между принципом простоты в проектировании и принципом простоты в программировании. Вам следует добиваться того, чтобы ваш проект был как можно более совершенным; он должен быть модульным, обладать большей общностью и с наибольшей точностью отвечать вашей задаче. Однако, когда вы приступаете к написанию программ, ваши решения должны быть максимально простыми. Существует ряд специальных приемов программирования, перечисляемых ниже, которые должны служить средством упрощения результирующей программы и повышения ее читабельности; возможно, вы добавите к ним ваши собственные рекомендации.

5.2.1. Избегайте неоправданно сложных арифметических выражений

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

А=В*С+3

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

A = B*C + 3**D/ — 3* X + Y*4

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

А = (В*С)+3

       В случае выражения столь сложного, как в приведенном выше втором примере, вообще неясно, сможет ли внесение скобок сделать его более “читабельным”. Проще ли действительно следующая форма записи нашего примера?

А = (В * С)+(((3 ** D)/(-3)) * X + (Y * 4)

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

PART1 = B*C
PART2 = X * ((3 ** D)/(—3))
PART3 = Y*4
A = PART1+PART2 + PART3

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

5.2.2. Компонуйте вложенные условные операторные фразы

       Интересно отметить, что стандартными руководствами по программированию на Коболе во многих организациях специально оговаривается запрет на использование вложенных операторов IF-THEN-ELSE типа

IF Al THEN Bl ELSE IF A2 THEN IF A3 THEN B2 ELSE B3 ELSE B4

       He следует удивляться тому, что в подобной, записи такая операторная фраза трудна для понимания; в большинстве случаев вложенные условные операторные фразы оказываются довольно “читабельными”, если их записывать в следующей форме:

IF Al THEN Bl 
      ELSE IF A2 
           THEN IF A3 THEN B2
                      ELSE B3
           ELSE B4

       Как и в случае использования скобок в сложных выражениях, эта идея ступенчатой (или хорошо оформленной) организации составных условных операторных выражений может увести слишком далеко. Однако, если имеется всего лишь три или четыре уровня условных операторов (как в приведенном выше примере), аккуратное оформление и ступенчатая организация вашей программы были бы достаточными средствами разъяснения ее смысла другим читателям. Те же замечания справедливы, конечно, и в отношении таких языков, как ПЛ/1 и Алгол, но только не Фортран!

5.2.3. Организуя листинги ваших программ, старайтесь сделать их более “читабельными”

       В предыдущем разделе мы отметили, что соответствующей организацией вложенных условных операторных фраз можно сделать их более понятными. В действительности это лишь один пример некоторой более общей рекомендации: листинг вашей программы должен быть привлекательным на вид, чтобы читателю было легко — даже интересно — его изучать. Некоторые программисты полагают, что это скорее дело операторов по перфорации программ. Другие, работающие на таких языках, как Лисп и ПЛ/1 (и в меньшей степени Фортран, Кобол и Алгол), с успехом пользуются вспомогательными автоматизированными средствами оформления исходной программы, переупорядочения последовательности операторов и т.д. При использовании любых средств стоит иметь в виду следующие пожелания:
       1. В одной строке листинга программы не следует располагать больше одной операторной фразы исходной программы. Хотя и верно, что бумага представляет ценность и что деревья надо беречь (как мне однажды заметила одна группа серьезных программистов), я полагаю, что наши экологи от программирования могли бы приложить свои усилия в каком-нибудь более обещающем направлении (быть может, в отношении слишком многословных изданий по вычислительным наукам?). Однажды я имел сомнительную честь увидеть полный листинг довольно сложной операционной системы с разделением времени, занимающий около 15 листов; все 128 позиций листинга были заняты записями многочисленных операторов, разделенными точкой с запятой. В некоторых случаях оказываются более читабельными два или три оператора, расположенных в одной строке; в мини-ЭВМ типа PDP-8, например, применением оператора RTR можно за один раз сдвинуть вправо содержимое сумматора только на два разряда, таким образом, при необходимости сдвинуть содержимое сумматора вправо на шесть разрядов было бы разумным записать

RTR; RTR; RTR     /СДВИГ СУММАТОРА НА ШЕСТЬ БИТОВ ВПРАВО/

       Аналогичные ситуации иногда встречаются в языках высокого уровня, и поэтому записи нескольких операторов могут быть расположены в одной строке, однако обычно программисты слишком неумеренно пользуются этой возможностью.
       2. Пользуйтесь “полем идентификации”, которое обычно занимает колонки 73—80 образа перфокарты исходной программы. Первые четыре или пять символов могут использоваться для идентификации программы, а остальные могут служить порядковым номером; так, мы могли бы- видеть такое содержание поля идентификации

GLOP0010

       Между порядковыми номерами следует ввести интервал по крайней мере в 10, если не в 100, единиц, с тем чтобы в последующем можно было вставить новые операторы; такие вставки должны быть помечены идентификаторами ЗАПЛАТА или НОВЫЙ и т.д. Как мы отметили выше, обычно имеются различные пакеты программ по обеспечению сопровождения программ, позволяющие программисту изменять последовательность операторов в листинге программы. Кроме того, многие компиляторы и ассемблеры проверкой поля идентификации устанавливают правильность расположения операторов в исходной программе. Эти методы, конечно, не представляют собой какого-то новшества или чуда; однако они работают и кажутся более разумными, чем подход, выбранный программистом на Фортране, который, нарушая и эту рекомендацию, и приведенную выше, записывает следующую последовательность Фортран операторов;

10    А = В = С; GO TO 20;
20    X^Y*Z;     GO TO 30;
30    Q/R;       GO TO 40;

       На вопрос, почему он включил так много явно избыточных операторов GO TO, программист ответил: “У меня было слишком много неприятностей из-за перемешивания карт в колоде. При таком подходе нужно лишь, чтобы правильно лежали только первая и последняя карты — все остальные можно располагать как попало!”
       3. Если запись оператора занимает больше одной строки Листинга программы, пользуйтесь переносом, сохраняющим читабельность программы. Не разбивайте, например, имя переменной иликонстанты таким бессмысленным образом:

НОЙ = КОШКА+СОБАКА+АЛЛИГА
ТОР + ГИППОПОТАМ+3.14
159—2.78128

       Стоит употребить немного здравого смысла, чтобы определить такой способ переноса длинных операторов или выражений, который обеспечивает большую их читабельность.
       4. Хотя это и не очень важно, часто программа легче читается,если символы арифметических операций (+, —, /, и т.д.) предваряются и сопровождаются одним или большим числом пробелов (приэтом следует придерживаться постоянного числа таких пробелов).
       5. Программируя на языках, допускающих запись операторовв “произвольном формате”, старайтесь располагать начало записикаждого оператора в одном и том же столбце листинга программы. На языке ассемблера каждый компонент команды — код операции, поля регистров, поля адресов операндов, комментарии и т.д.— следует начинать с одних и тех же позиций. Как мы уже заметили в разд.5.2.2, операторы IF-THEN следует располагать ступенчато,если употребляются вложенные структуры; аналогичные и основанные на здравом смысле правила следует использовать в применении к DO-циклам в Фортране, блокам BEGIN-END в Алголе и ПЛ/1 и т.д.
       6. Встречается много случаев, когда имеет смысл организовать метки операторов, имена процедур, имена абзацев и т.д. в формевозрастающей последовательности. Так, в Фортране разумно употреблять номера операторов (т.е. номера, которые обычно пишутсяв колонках 1—7 исходной программы) в возрастающей последовательности; многие программисты на Коболе записывают имена абзацев в форме ПРОГРАММА-НАЧИСЛЕНИЯ-НАЛОГА-ООЮ,ПОДПРОГРАММА-ВЫХОДА-0020 и т.д., с тем чтобы читательзнал, где в листинге программы искать любую метку (и связанныйс нею программный код). Существуют возможности различных вариаций этого правила (как, например, приписывание операторам FORMAT в Фортране последовательных номеров специального множества), однако следует принять некоторый разумный наборсоглашений и постоянно придерживаться его.
       7. Снабжайте листинг программы подробным комментарием. Важность комментария уже обсуждалась в гл.1; здесь нас интересует только организация комментариев. Лично я всегда верил в лозунг, выдвинутый Крайтцбергом и Шнейдерманом в The Elements of FORTRAN Style, Harcourt, Brace, Jovanovich, New York, 1971. “Всегда документируйте программу подробнее, чем вам кажется необходимым”. Однако многие программисты и слушатели, которые читали листинги моих программ (как на практике, так и в этой книге), жаловались на то, что в них содержится слишком много комментариев и что операторы программы маскируются комментариями. Хотя по этому вопросу часто возникают горячие споры, следующие положения как будто всегда принимаются единодушно:
       а) Перед каждой подпрограммой, процедурой или модулем чрезвычайно полезно помещать “пролог”. Это словесное описание должно содержать краткое описание назначения модуля, способ обращения, характер действий и выходных данных, перечисление других подпрограмм, требуемых для его исполнения, других переменных и регистров, которые используются, разрушаются или как-то зависятот работы модуля, имя автора, дату создания первоначальной версии и дату последней модификации, а также краткое описание того, как модуль работает.
       б) Содержательный комментарий должен сопровождать наиболее важные разделы программы, однако они не должны отвлекать внимание читающего от самого программного кода. Наиболее просто, конечно, это достигается в языке ассемблера; в языках высокого уровня эта задача обычно решается соответствующей организацией программы. Нужно, чтобы читатель имел возможность опускать комментарий, если он ему не доверяет или предпочитает работать без комментариев.

5.2.4. Избегайте сложных команд в языке ассемблера

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

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

SKIPL CAT,     ОБОЙТИ СЛЕДУЮЩУЮ КОМАНДУ ЕСЛИ CAT МЕНЬШЕ НУЛЯ
SKIPE DOG,     ОБОЙТИ СЛЕДУЮЩУЮ КОМАНДУ ЕСЛИ DOG РАВНО НУЛЮ
SKIPN MOUSE,   ОБОЙТИ СЛЕДУЮЩУЮ КОМАНДУ  ЕСЛИ MOUSE
               ОТЛИЧНО ОТ НУЛЯ
SKIPG BIRD,    ОБОЙТИ СЛЕДУЮЩУЮ КОМАНДУ ЕСЛИ BIRD БОЛЬШЕ НУЛЯ

       2. В некоторых машинах язык ассемблера содержит команду “исполнить”. Например, команда

EXE SWITCH

       могла бы вызвать исполнение команды, расположенной по метке SWITCH; после этого обычно управление к команде, следующей за командой ЕХЕ, если только команда, расположенная по метке SWITCH, не была командой передачи управления, вызова подпрограммы, обхода и т.д. В некоторых ситуациях команда ЕХЕ оказывается чрезвычайно полезной, поскольку является очень эффективной операцией “однокомандного обращения к подпрограмме”. К сожалению, простоты ради в некоторых машинах допускается вложенное использование команды ЕХЕ, т.е. команда, расположенная по метке SWITCH, сама может быть некоторой операцией ЕХЕ, и этот процесс вложения можно продолжать неопределенно глубоко. Трудно найти много практических примеров, где бы такая возможность была полезной, к счастью, ею располагают не очень многие машины!
       3. Существует также несколько машин, в которых допускается многоуровневая косвенная адресация, часто в дополнение к различным уровням пре- и постиндексации. Один уровень косвеннойадресации является наиболее распространенным (за особым исключением Систем IBM/360 и IBM/370) и обычно отражает разумную практику программирования; два уровня встречаются редко (слава богу!), хотя можно назвать несколько случаев, когда эта возможностьвесьма полезна; системы с более чем двумя уровнями редко кому-либо интересны, кроме неугомонных любителей изобретения способов использования многоуровневой косвенной адресации. Правда,есть польза в многоуровневой индексации многомерных таблиц (как это было сделано в операционной системе машины GE-435, котораяотличается одной из наиболее изощренных в мире схем адресации),однако существуют другие, чуть менее эффективные, но значительно более ясные способы решения той же задачи. Подчеркиваем еще раз, что должное применение всех этих рекомендаций предполагает обращение к здравому смыслу и соответствующий анализ возможных издержек; обычно за ясность приходится расплачиваться эффективностью. Если применяется сложный подход, то тщательное документирование совершенно необходимо.

5.2.5. Разные предложения

       Для всякого конкретного языка программирования или конкретной машины можно было бы высказать ряд дополнительных “что делать и что не делать”, которые будут способствовать созданию простых программ. Довольно грустно осознавать, что во многих больших организациях подобные наставления возведены в ранг стандарта, поскольку такая мера часто сводит на нет первоначальную идею. Наша цель в действительности состоит в том, чтобы определить некоторую позицию по отношению к разработке программ, которые могут читататься другими. Вместо того чтобы погружаться в подробное обсуждение каких-либо новых рекомендаций, мы лучше завершим эту главу простым перечислением предложений разного характера, которые помогают избежать “нечитабельности” программ, составленных на Фортране, Коболе и других языках высокого уровня.
       1. Если это возможно, то избегайте отрицаний в булевых выражениях; представляется, что их понимание составляет трудность длямногих программистов. Например, выражение

IF NOT FLAG THEN A = B ELSE X = Y;

       можно было бы переписать в следующей форме:

IF FLAG THEN X = Y ELSE A = B,

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

IF A AND В OR NOT С THEN PERFORM X,

       На самом деле Кобол характеризуется возможностью написания составных булевых выражений, смысл которых кажется неопределенным читателю и часто совершенно не совпадает с намерениями программиста. Даже если нет неоднозначностей, такие выражения обычно с трудом понимают все, за исключением их автора.
       3. Избегайте беспорядочного использования оператора EQUIVALENCE в Фортране, оператора REDEFINE в Коболе и аналогичных операторов в других языках программирования — в том числе и языке ассемблера. Если для обозначения одной и той же константы, переменной или таблицы было употреблено несколько имен, то читателю программы будет трудно запомнить значения этих имен и причины такого переопределения (предполагаем, что причины были — часто к этому прибегают, компонуя исходную составную программу из нескольких различных модулей, с тем чтобы обеспечить согласование переменных).
       4. Заслуживают порицания передачи управления внутрь цикла или выход из цикла в произвольных точках (например, в DO-циклах на Фортране, PERFORMVARING-циклах на Коболе и т.д.). Многие языки предусматривают защиту от подобных шуток состороны программиста; программисты, упорно следующие такой практике, создают многие трудности тем, кто работает с их программами.
       5. Избегайте случаев произвольного изменения значения счетчика цикла. В большинстве языков существуют конструкции, позволяющие наращивать (или уменьшать) N раз значение счетчикадо тех пор, пока он не исчерпает все значения, после чего программа переходит к исполнению команды, расположенной непосредственно за телом цикла. Исследование программ на Фортране1) {Knuth, An Emperical Study of FORTRAN Programs, Software Practice and Experience, v.1, №2, p.105—133.} показало, что в 95% случаев DO-циклы характеризуются единичным отрицательным приращением счетчика цикла. Естественно ожидать,что то же справедливо и в отношении большинства других языков программирования. Поэтому крайне обескураживают ситуации,когда в программе встречается цикл, нормально исполняемый с единичным приращением счетчика, в котором иногда значение счетчикавдруг изменяется на 13 (неожиданное приращение обычно происходит где-то внутри тела цикла). Следует повторить еще раз, что в некоторых языках подобная практика не допускается (передачей начального значения счетчика в специальный регистр), однако во многих из них это достигается очень просто — к соблазну тех программистов, которые пытаются извлечь из этого какую-то пользу.

ЛИТЕРАТУРА

       1. Kernighan В.W., Plauger P.J., Programming Style: Examples and Countereampies, ACM Computing Surveys, Dec. 1974, pp. 303—319.
       2. Kernighan B.W., Plauger P.J., The Elements of Programming Style, McGraw-Hill, 1974.
       3. Schneiderman В., Kreitzberg C, The Elements of FORTRAN Style, Harcourt, Brace, Javanovich, 1971.
       4. Weinberg G.M., The Psychology of Computer Programming, Van Nostrand, Reinhold, 1971.
       5. Yohe J.M., An Overview of Programming Practices, ACM Computing Surveys, Dec. 1974, p. 221—246.

ВОПРОСЫ

       1. Как часто другие программисты читают ваши программы? Является ли такая практика обычной в вашей организации? Если нет, то почему?
       2. Почему важно, чтобы вашу программу могли прочесть другие программисты?
       3. Почему структурное программирование облегчает понимание программы?
       4. Обладает ли ваша вычислительная система какими-либо средствами многоцелевого применения? Используются ли они в каких-нибудь прикладных задачах в вашей организации? Если да, то попытайтесь собрать статистические данные для определения того, требуется ли больше времени на отладку и усилий на сопровождение таких программ (или систем), чем на разработки, не использующие таких средств. Существуют ли такие типы разработок, в которых многоцелевой подход позволяет создавать более простые и легкие для понимания программы?
       5. Приведите три примера специальных приемов программирования на Коболе, которые можно отнести к разряду ухищрений. Для каждого такого приема укажите возможные виды ошибочной их интерпретации посторонним программистам. Считаете ли вы, что это может затруднить сопровождение программ?
       6. Приведите три примера специальных приемов программирования на Фортране, которые можно отнести к разряду ухищрений. Для каждого такого приемаукажите возможные виды ошибочной интерпретации программы другим программистам. Считаете ли вы, что это может затруднить сопровождение программы?
       7. Приведите три примера специальных приемов программирования на ПЛ/1, которые можно отнести к разряду ухищрений. Для каждого такого приема укажите возможные виды ошибочной интерпретации программы другим программистам. Считаете ли вы, что это может затруднить сопровождение программы?
       8. Приведите три примера специальных приемов программирования на языке ассемблера, которые можно отнести к разряду ухищрений. Для каждого такого приема укажите возможные виды ошибочной интерпретации программы другим программистам. Считаете ли вы, что это может затруднить сопровождение программы?
       9. Приведите пример программы, модифицирующейся во время исполнения. Сколько времени ЦП экономится при таком подходе в сравнении с вариантом программы, не использующей модификацию? Выразите эту величину в процентах к полному времени исполнения программы, т.е., например, экономит ли применение оператора ALTER в Кобол-программе 1% полного времени ее исполнения? Имеет ли это большое значение? Оправдывает ли это использование модифицируемых программ?
       10. Почему запись вычисления вида A=B*C+3**D/—3*X+Y*4 трудна для понимания? Сколькими компонентами [операторами и (или), операндами] следовало бы ограничить запись одного вычисляемого выражения?
       11. Достигается ли применением скобок большая читабельность сложного арифметического выражения? Например, считаете ли вы, что выражение

А = (В * С) + (((3 ** D)/(-3)) - * X) + (Y * 4)

       прочесть проще, чем выражение из п.10? Можете ли вы предложить простой метод проверки правила парности скобок (т.е. требования, чтобы каждой правой скобке отвечала одна левая скобка), входящих в данное выражение? Сколько уровней скобок, на ваш взгляд, следовало бы допускать в записях арифметических выражений?
       12. Почему, по вашему мнению, во многих организациях запрещено пользоваться в Кобол-программах вложенными условными операторами? Какие из приводимых ниже доводов (выдвинутых опытными программистами-слушателяминескольких курсов программирования, руководимых автором) являются наиболее важными?
       а) “Вложенные условные операторы транслируются компилятором в ошибочный объектный код”.
       б) “Вложенные условные операторы транслируются компилятором в неэффективную машинную программу”.
       в) “Я не могу разобраться во вложенных условных операторах”.
       г) “Руководством по Коболу не рекомендуется пользоваться вложенными условными операторами”.
       д) “В синтаксисе Кобола не всегда просто определить однозначное соответствие между предложениями ELSE и условиями IF”.
       е) “В многоуровневые вложенные условные операторы трудно вносить новыепредложения или стирать старые”.
       ж) “Никто никогда не учил меня пользоваться вложенными условными операторами”.
       13. Было давно отмечено, что программирующие на Алголе и ПЛ/1 (среди прочих) обычно более охотно пользуются вложенными условными операторами,чем программирующие на Коболе. Согласны ли вы с этим? Считаете ли вы, что следствием этого являются большие трудности в понимании и сопровождении программ на Алголе и ПЛ/1 в сравнении с программами на Коболе?
       14. Часто высказывалось предположение, что вложенные условные операторы легче понимать, если они имеют соответствующую ступенчатую организацию(или оформление). Насколько, по-вашему, это важно?
       15. Имеются ли в вашей организации какие-нибудь стандарты на число операторов, которые могут быть записаны в одной строке листинга? Приняты ли этистандарты как обязательные для всех? Считаете ли вы, что это важная мера?
       16. Ряд других предложений по оформлению программ перечислен в разд.5.2.3. Приняты ли они официально в вашей организации? Считаете ли вы, что этоимеет большое значение?
       17. Многие из предложений, перечисленных в разд. 5.2.3, реализованы в составе некоторых языков программирования, например пакет программ TID4, поставляемых некоторыми разработчиками в составе Фортрана, пакет PRETTYP-RINT в составе языка Лисп, пакет NEATER2 в ПЛ/1, пакет Метакобол и т.д. Имеется ли возможность пользоваться какими-либо из этих автоматизированных средств в вашей организации? Используются ли они? Считаете ли вы, что эти средства оказывают существенную помощь? Если нет, то почему?
       18. В разд. 5.2.4 рекомендуется избегать применения в языке ассемблера вложенных операторов передачи управления. Считаете ли вы, что это важно? Можете ли вы представить такую ситуацию в программировании, когда такой подход требуется по существу? Можете ли вы предложить другой способ записи последовательности вложенных передач управления (такой, например, как рассмотренный в разд. 5.2.4), который был бы проще для понимания?
       19. Позволяет ли язык ассемблера вашей машины пользоваться вложенными командами ЕХЕ? Если да, то приведите пример, в котором такой способ программирования был бы полезным. Если бы использование вложенных команд ЕХЕ было запрещено (из соображений простоты), то какими альтернативными методами программирования можно было бы воспользоваться.
       20. Допускается ли вашей машиной многоуровневая косвенная адресация? Допускаются ли различные сочетания косвенной адресации и индексирования (например, преиндексирование и постиндексирование)?




<<< Пред. Оглавление
Начало раздела
След. >>>

Дата последнего изменения:
Thursday, 21-Aug-2014 09:11:10 MSK


Постоянный адрес статьи:
http://az-design.ru/Projects/AzBook/src/005/02YE050.shtml