Список рекомендуемой литературы



1. Павловская Т. А. Паскаль. Программирование на языке высокого уровня: учебник. СПб.: Питер, 2008. 393 с.

2. Немнюгин С. А. Turbo Pascal. Программирование на языке высокого уровня: учебник. 2-е изд. СПб.: Питер, 2008. 544с.

3. Фаронов В. В. TurboPascal 7.0. Учебный курс: учеб. пособие. М.: Кнорус, 2009. 368 с.

4. Мишенин А. И. Сборник задач по программированию: учебно-метод. пособие. М.: Финансы и статистика, 2009. 224с.


Глава 5. Технология структурного программирования

Структурное программирование

Особенности структурной программы

Эдсгер В. Дийкстра, опираясь на теорему Бома и Джакопини, ввел понятие структурного программирования, которое часто называют “программирование без GOTO” (управляющая конструкция перехода не используется при написании программ).

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

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

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

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

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

Критерии качества программы

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

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

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

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

Создание структурных программ

Этапы решения задачи на ЭВМ

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

1 этап - постановка задачи

Создание любой программы начинается с постановки задачи. Изначально задача формулируется в терминах предметной области, и необходимо перевести ее на язык понятий, более близких к программированию. Поскольку программист редко досконально разбирается в предметной области, а заказчик — в програм­мировании (простой пример: требуется написать бухгалтерскую программу), по­становка задачи может стать весьма непростым итерационным процессом. Кроме того, при постановке задачи заказчик зачастую не может четко и полно сформу­лировать свои требования и критерии.

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

Постановка задачи завершается созданием технического задания, а затем внешней спецификации программы, включающей в себя:

· описание исходных данных и результатов (типы, форматы, точность, способ передачи, ограничения)1;

· описание задачи, реализуемой программой;

Под типами и форматами не имеются в виду типы языка программирования.

· способ обращения к программе;

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

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

2 этап - выбор модели и метода решения задачи

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

3 этап - разработка внутренних структур данных

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

При решении вопроса о том, как будут организованы данные в программе, полезно задать себе следующие вопросы.

· Какая точность представления данных необходима?

· В каком диапазоне лежат значения данных?

· Ограничено ли максимальное количество данных?

· Обязательно ли хранить их в программе одновременно?

· Какие действия потребуется выполнять над данными?

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

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

4 этап - проектирование

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

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

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

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

Поскольку неизвестно, какие изменения придется выполнить, это пожелание напоминает создание «общей теории всего»; на практике надо ограничиться разум­ными компромиссами. Программист, исходя из своего опыта и здравого смысла, решает, какие именно свойства программы может потребоваться изменить или усовершенствовать в будущем.

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

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

5 этап - структурное программирование

Программирование здесь рассматривается «в узком смысле», то есть понимается как запись программы на языке программирования по готовому алгоритму. Этот процесс часто называют кодированием, чтобы отличить его от полного цикла разработки программы.

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

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

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

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

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

6 этап - нисходящее тестирование

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

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

Существует две стратегии тестирования: «черный ящик» и «белый ящик». При использовании первой внутренняя структура программы во внимание не принимается и тесты составляются так, чтобы полностью проверить функционирова­ние программы на корректных и некорректных входных воздействиях.

Стратегия «белого ящика» предполагает проверку всех ветвей алгоритма. Общее число ветвей определяется комбинацией всех альтернатив на каждом этапе. Это конечное число, но оно может быть очень большим, поэтому программа разби­вается на фрагменты, после исчерпывающего тестирования которых последние рассматриваются как элементарные узлы более длинных ветвей. Кроме данных, обеспечивающих выполнение операторов в требуемой последовательности, тесты должны содержать проверку граничных условий (например, переход по условию х > 10 должен проверяться для значений, больших, меньших и равных 10). Отдельно проверяется реакция программы на ошибочные исходные данные.

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

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

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

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

Правила программирования

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

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

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

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

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

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

· Имена переменных должны отражать их смысл. Правильно выбранные имена могут сделать программу в некоторой степени самодокументированной. Неудачные имена, наоборот, служат источником проблем. Сокращения ухудшают читаемость, и часто можно забыть, как именно было сокращено то или иное слово. Общая тенденция состоит в том, что чем больше область видимости переменной, тем более длинное у нее имя. Перед таким именем можно поставить префикс типа (одну или несколько букв, по которым можно определить тип переменной). Для счетчиков коротких циклов, напротив, лучше обойтись однобуквенными именами типа i или k.

· Следует избегать использования в программе чисел в явном виде. Константы должны иметь осмысленные имена, заданные в разделе описания const. Символическое имя делает программу более понятной, а, кроме того, при необходимости изменить значение константы потребуется изменить программу только в одном месте. Конечно, этот совет не относится к константам 0 и 1.

· Для записи каждого фрагмента алгоритма необходимо использовать наиболее подходящие средства языка. Например, ветвление на насколько направлений по значению целой переменной более красиво записывается с помощью оператора case, а не нескольких If. Для просмотра массива лучше пользоваться циклом for. Оператор goto применяют весьма редко, например для принудительного выхода из нескольких вложенных циклов, а в большинстве других ситуаций лучше использовать другие средства языка, такие как процедуры break или exit.

· Программа должна быть «прозрачна». Если какое-либо действие можно запрограммировать разными способами, то предпочтение должно отдаваться не наиболее компактному и даже не наиболее эффективному, а такому, который легче для понимания. Особенно это важно тогда, когда пишут программу одни, а сопровождают другие, что является широко распространенной практикой. «Непрозрачное» программирование может повлечь огромные затраты на поиск ошибок при отладке.

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

· Для организации циклов пользуйтесь наиболее подходящим оператором. Цикл repeat применяется только в тех случаях, когда тело непременно потребуется выполнить хотя бы один раз, например при проверке ввода. Цикл for используется, если число повторений известно заранее и параметр имеет порядко­вый тип, цикл while — во всех остальных случаях. При записи итеративных циклов (в которых для проверки условия выхода используются соотношения переменных, формируемых в теле цикла) необходимо предусматривать аварийный выход по достижении заранее заданного максимального количества итераций. Чтобы цикл легче читался, надо стремиться объединять инициализацию, проверку условия выхода и приращение в одном месте.

· Более короткую ветвь if лучше поместить сверху, иначе вся управляющая структура может не поместиться на экране, что затруднит отладку.

Документирование программы

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

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

Комментарии должны представлять собой правильные предложения без сокращений и со знаками препинания и не должны подтверждать очевидное (комментарии в этой книге не могут служить образцом, поскольку они предназначены для обучения, а не для сопровождения). Например, бессмысленны фразы типа «вызов функции f» или «описание переменных».

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

{ Комментарий, описывающий.

что происходит в следующем ниже

блоке программы } Непонятный блок программы.

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

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

В заключение порекомендую тем, кто предпочитает учиться программирова­нию не только на своих ошибках, очень полезные книги Фредерика Брукса [3] и Алена Голуба [4].


Дата добавления: 2018-09-22; просмотров: 346; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!