Методику структурного подхода к алгоритмизации и программированию. Структурное программирование. Что нам говорит Флойд о парадигмах

Лекция 1. Объектно-ориентированное программирование.

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

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

Другими факторами, влияющими на эволюцию методов проектиро­вания и создания ПП, являются:

Изменение архитектур вычислительных средств (ВС) в интересах повышения
производительности, надежности;

Упрощение взаимодействия пользователей с ВС и интеллектуализация ВС.
Действие двух последних факторов сопряжено с ростом сложности программного обеспечения ВС. Сложность представляет неотъемлемое свойство про­граммирования и программ, которое проявляется во времени и стоимости создания программ, в объеме или длине текста программы, характеристиках ее логической струк­туры, задаваемой операторами передачи управления (ветвления, циклы, вызовы подпрограмм).

Выделяют 5-ть источников сложности программирования:

Решаемая задача;

Язык программирования;

Среда выполнения программы;

Технологический процесс коллективной разработки и создания ПП;

Стремление к универсальности и эффективности алгоритмов и типов данных.

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

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

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


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

Теоретическое оформление структурный подход получил в нача­ле 70-х годов в работах теоретиков и практиков программирования(А.П.Ершова, Э. Йодана, Н.Вирта). Следует отметить появле­ние структурного программирования, в котором нашла определенное отражение идея упорядочивания структуры программы. Структурное программирование ориентирует на составление программ, структура которых близка к «дереву» опе­раторов или блоков. Использование структуры типа «дерево» в качестве своеоб­разного эталона объясняется тем, что это простая для анализа и реализации структура.

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

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

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

Развитие языков в направлении повышения эффективности составления приклад­ных программ привело к разделению языков по следующим уровням:

Низкий уровень (машинно-ориентированные языки - языки ассемблера),

Высокий уровень (процедурно-ориентированные языки: FORTRAN, ALGOL,

Уровень решаемой задачи (проблемно-ориентированные языки - SQL).

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

Результатом обобщения понятия «тип данных» являются классы объектов (C++), которые могут содержать в качестве эле­ментов не только данные определенного типа, но и методы их обработки (функ­ции).

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

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

Язык программирования позволяет описать свойства моделируемых объектов и порядок манипуляции с объектами или порядок их взаимодействия, сообразуясь с условиями решаемой задачи. Первые языки программирования ориентировались на математические объекты, на определенную модель вычислителя. Поэтому они содержали такие конструкции как переменная, константа, функция, формальные и факти­ческие параметры. Программисты представляли свои программы в виде взаимодей­ствующих функций и модулей. Характер програм­мирования был процедурно-ориентированным, поскольку первостепенное внимание уделялось последовательностям действий с данными. Соответственно такие языки программирования, как FORTRAN, PL-1, С называют процедурно-ориентированными.

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

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

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

Объектный подход начал развиваться в программировании с 70-х годов (Smalltalk, CLOS, Ada). Эти языки называются объектными . Иерархическая классификация объектов инаследование свойств являются отправными идеями появившегося в 80-х годах объектно-ориентированного подхода. Одной из причин сравнительно медленного становления объектно-ориентированного стиля программирования яв­ляется его существенное отличие от процедурно-ориентирован­ного стиля.

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

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

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

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

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

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



Бурное развитие технологий программирования, основанных на объектном подходе, позволило решить многие проблемы. Так были созданы среды, поддерживающие визуальное программирование, например, Delphi, C++ Builder, Visual C++ и т. д. При использовании визуальной среды у программиста появляется возможность проектировать некоторую часть, например, интерфейсы будущего продукта, с применением визуальных средств добавления и настройки специальных библиотечных компонентов. Результатом визуального проектирования является заготовка будущей программы, в которую уже внесены соответствующие коды.

Можно дать обобщающее определение: объект ООП - это совокупность переменных состояния и связанных с ними методов (операций). Упомянутые методы определяют, как объект взаимодействует с окружающим миром.

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

Структурное программирование (СП) возникло как вариант решения проблемы уменьшения СЛОЖНОСТИ разработки программного обеспечения.

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

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

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

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

Подход базируется на двух основных принципах:

· Последовательная декомпозиция алгоритма решения задачи сверху вниз.

· Использование структурного кодирования.

Напомним, что данная методология является важнейшим развитием императивной методологии.

Происхождение, история и эволюция. Создателем структурного подхода считается Эдсгер Дейкстра. Ему также принадлежит попытка (к сожалению, совершенно неприменимая для массового программирования) соединить структурное программирование с методами доказательства правильности создаваемых программ. В его разработке участвовали такие известные ученые как Х. Милс, Д.Э. Кнут, С. Хоор.

Методы и концепции, лежащие в основе структурного программирования. Их три

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

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

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

Структурные языки программирования. Основное отличие от классической методологии императивного программирования заключается в отказе (точнее, той или иной степени отказа) от оператора безусловного перехода .

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

Теорема о структурировании (Бёма-Джакопини (Boem-Jacopini)): Всякую правильную программу (т.е. программу с одним входом и одним выходом без зацикливаний и недостижимых веток) можно записать с использованием следующих логических структур - последовательность, выбора и повторение цикла

Следствие 1: Всякую программу можно привести к форме без оператора goto.

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

Следствие 3: Сложность структурированных программ ограничена, даже в случае их неограниченного размера.

Структурное программирование- это не самоцель. Его основное назначение- это получение хорошей ("правильной") программы, однако даже в самой хорошей программе операторы перехода goto иногда нужны: например - выход из множества вложенных циклов.

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

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

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

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

Основы структурного программирования. Теоретическим фундаментом структурного программирования является теорема о структурировании , из которой следует, что алгоритм (программа) решения любой практически вычислимой задачи может быть представлен с использованием трех элементарных базисных управляющих структур: структуры следования (последовательности); структуры ветвления, структуры цикла, изображенных на рис. 6.5-6.7 соответственно, где Р - условие, S - оператор.

Структура следования представляет собой естественный ход выполнения алгоритма - любую последовательность операторов, выполняющихся друг за другом (см. рис. 6.5). В языке программирования это соответствует последовательности операторов ввода, вывода и операторов присваивания.

Представляет фактор принятия решения, включает проверку некоторого логического условия Р и, в зависимости от результатов этой проверки, выполнение оператора S1 либо оператора S2. В языках программирования (например, Pascal) реализуется оператором if Р then SI else S2 (см. рис. 6.6).

Структура цикла (цикла с предусловием) представляет фактор повторяемости вычислений, обеспечивает многократное повторение выполнения оператора S, пока выполняется (истинно) логическое

Рис. 6.5. Структура следования

Рис. 6.6.

условие Р. В языках программирования (например, Pascal) реализуется оператором while Р do S (см. рис. 6.7).

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

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

Каждая из структур может рассматриваться как один функциональный блок с одним входом и одним выходом. Блоки S, SI, S2, входящие в состав базисных управляющих структур, сами могут быть одной из них, поэтому возможны вложенные конструкции. Однако, какова бы ни была степень и глубина «вложенности», важно, что любая конструкция в конечном итоге имеет один вход и один выход. Следовательно, любую сложную структуру можно рассматривать как «черный ящик» с одним входом и одним выходом. Таким образом, можно ввести преобразование любой структуры в функциональный блок. Тогда всякий алгоритм, составленный из стандартных структур, поддается последовательному преобразованию к единственному функциональному блоку, и эта последовательность преобразований может быть использована как средство понимания алгоритма и доказательства его правильности. Обратная последовательность преобразований может быть использована в процессе проектирования алгоритма с постепенным раскрытием единственного функционального блока в сложную структуру основных элементов.

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

Методика разработки программ. Распространены две методики (стратегии) разработки программ, относящиеся к структурному программированию: программирование «сверху вниз»; программирование «снизу вверх».

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

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

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

Общие принципы разработки программных проектов. Использование технологии структурного программирования при разработке серьезных программных проектов основано на следующих принципах:

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

Программирование - процесс составления программ (перевода алгоритма на язык программирования).

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

В настоящее время “язык программирования” и “алгоритмический язык” часто выступают как синонимы.

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

Нисходящее пошаговое проектирование;

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

Модульное программирование;

Сквозной структурный контроль.

Структурное программирование предполагает составление алгоритма задачи из конструкций строго определенного вида.

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

Каждая структура должна иметь один вход и один выход. На каждом шаге нисходящего проектирования следует составлять алгоритм одного из трех указанных видов (рис. 7.2).

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

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

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

а). Линейная: б). Разветвление: в). Ветвление с

вырожденной ветвью:

С предусловием: - с постусловием: - с известным числом повторов:

Рис. 7.2. Виды управляющих структур

Языки программирования

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

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

Недостатки программирования на машинном языке:

Большие трудозатраты при записи программы;

Сложность процесса отладки программ;

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

Язык связан с конкретным типом ЭВМ.

Преимущества:

Высокая эффективность программ;

Возможность использования всех ресурсов аппаратуры ЭВМ.

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

Преимущества Ассемблера:

Символическая адресация;

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

Наличие средств контроля ошибок;

Достаточно высокая эффективность программ;

Использование всех возможностей ЭВМ.

Недостатки:

Излишняя детализация записи программ;

Отсутствие контроля за обращением к элементам памяти.

Языки высокого уровня не содержат машинно-зависимых операторов. Языки этого типа: Фортран, Алгол, Бейсик, Фокал, Пл/1, Паскаль, Кобол и др.

Достоинства программирования на этих языках:

Высокая производительность труда программистов;

Простота эксплуатации программ;

Возможность переноса программ с одной машины на другую (т.е. универсальность).

Недостатки:

Пониженная по сравнению с языками низкого уровня эффективность программ;

Не всегда оптимальное и полное использование ресурсов ЭВМ;

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

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

Интерпретатор - программа, которая транслирует каждый оператор исходной программы и сразу его выполняет.

Эту статью я подготовил во время дискуссий по поводу паттерна Мост, но тогда не опубликовал. Думал разобрались, так как было упомянута Domain Driven Design , и казалось, что необходимость проектирования и программирования именно в стиле ООП никем не оспариваются. Но все же со временем я столкнулся с непониманием. Это будет чисто историческая теоретическая статья. Конечно, даже без попытки обхвата всей широты темы. Но это так сказать посыл молодому разработчику, который читает по верхам и не может выбрать каких принципов и правил ему придерживаться, что первично, а что вторично.

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

Единственно прошу, если прочитали по диагонали - комментируйте сдержано.

Что нам говорит Флойд о парадигмах?

Термин «парадигма программирования» ввел Роберт Флойд (""R. W. Floyd."" ""Communications of the ACM"", 22(8):455-460, 1979. Русский перевод см. в кн.: Лекции лауреатов премии Тьюринга за первые двадцать лет (1966-1985), М.: МИР, 1993.). Он в своей лекции в 1979 году говорит о следующем:

Знакомый пример парадигмы программирования - это структурное программирование, которая, кажется, доминирующей парадигмой в методологии программирования. Она разделяется на две фазы. В первой фазе, нисходящего проектирования, проблема разделяется на небольшое количество более простых подпроблем. Это постепенное иерархическое разложение продолжается пока возникнет выделенные подпроблемы, которые достаточно просты, чтобы с ними справиться непосредственно. Вторая фаза парадигмы структурного программирования влечет за собой работу вверх от конкретных объектов и функций к более абстрактным объектам и функциям, используемые всюду в модулях, произведенных нисходящим проектированием. Но парадигма структурного программирования не универсальна. Даже её самые ярые защитники признали бы, что её отдельно недостаточно, чтобы сделать все сложные проблемы легкими. Другие парадигмы высокого уровня более специализированного типа продолжают быть важными. (Это не точный перевод, а авторская компиляция на основе лекции Р. Флойда, но максимально придерживаясь его слов. Формулировки изменены и скомпонованы лишь для выделения основной мысли Р.Флойда и понятного его изложения.)

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

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

Особенности парадигм более высокого уровня

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

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

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

Основы парадигмы структурного программирования

Мы знаем, что идеи о структурном программировании возникли после доклада Э. Дейкстры еще в 1965 году, где он обосновал отказ от оператора GOTO. Именно этот оператор превращал программы в неструктурированные (Спагетти-код), а Дейкстра доказал, что возможно написать программы без использования этого оператора в результате чего программы станут структурными.

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

Но что было тогда? Как описывает Йодан, все начинается с ответа на вопрос: «Что значит написать хорошую программу?». Вот первый критерий, на какие вопросы должна отвечать парадигма программирования высокого уровня. Если она не отвечает прямо на этот вопрос, а рассказывает вам как можно получить некоторые интересные характеристики вашей программы, то вы имеете дело с парадигмой низкого уровня - подходом при программировании.

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

Примечателен, довольно характерный спор программистов:
* Программист А: “Моя программа в десять раз быстрее вашей, и она занимает в три раза меньше памяти!”
* Программист Б: “Да, но ваша программа не работает, а моя - работает!”

Но программы постоянно усложняются и поэтому нам недостаточно того, что программа просто работает. Нужны определенные методы верификации правильности работы программы и самого программиста. Причем это не тестирование программы, а проведение некоторой систематической процедуры проверки именно правильности программы в смысле её внутренней организации. То есть уже тогда, говоря современным языком, говорили о ревизии кода (Code review).

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

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

Кроме того, центральное внимание уделяли размеру и неизменности модуля. Причем что касается неизменности модуля, то она рассматривалась не целиком, а с выделением отдельных факторов:
1. Логическая структура программы, т.е. алгоритм. Если вся программа зависит от некоторого специального подхода, то в скольких модулях потребуется внести изменения при изменении алгоритма?
2. Аргументы, или параметры, модуля. Т.е. изменение спецификации интерфейсов.
3. Внутренние переменные таблиц и константы. Многие модули зависят от общих таблиц, если изменяется структура таких таблиц, то мы можем ожидать, что модули также изменятся.
4. Структура и формат базы данных. В большей степени эта зависимость аналогична зависимости от общих переменных и таблиц, упомянутой выше, с той разницей, что с практической точки зрения базу данных удобнее считать независимой от программы.
5. Модульная структура управления программой. Некоторые пишут модуль не особенно задумываясь над тем, каким образом он будет использоваться. Но если изменились требования. Какую часть логической структуры модуля нам придется изменить?

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

Основы парадигмы объектно-ориентированного программирования

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

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

Уже признано, что причиной появления объектно-ориентированной парадигмы стала необходимость писать все более и более сложные программы, в то время как парадигма структурного программирования имеет некий предел, после которого развивать программу становится невыносимо сложно. Вот, например, что пишет Г. Шилдт:

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

Чтобы понять причину, почему именно объектно-ориентированное программирование, позволило писать более сложные программы и практически убрать проблему возникновения предела сложности, обратимся к одному из основоположников ООП - Гради Бучу (). Свое объяснение ООП он начинает с того, что значит сложность и какие системы можно считать сложными. То есть целенаправленно подходит к вопросу написания сложных программ. Далее переходит к вопросу связи сложности и человеческих возможностей понять эту сложность:

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

Затем он говорит о декомпозиции:

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

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

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

Теперь, если посмотреть внимательнее, оказывается, что объектно-ориентированная парадигма есть не что иное как моделирование вообще, наиглавнейший аспект которого наиболее четко выразил С. Лем:

Моделирование - это подражание Природе, учитывающее немногие ее свойства. Почему только немногие? Из-за нашего неумения? Нет. Прежде всего потому, что мы должны защититься от избытка информации. Такой избыток, правда, может означать и ее недоступность. Художник пишет картины, но, хотя мы могли бы с ним поговорить, мы не узнаем, как он создает свои произведения. О том, что происходит в его мозгу, когда он пишет картину, ему самому неизвестно. Информация об этом находится в его голове, но нам она недоступна. Моделируя, следует упрощать: машина, которая может написать весьма скромную картину, рассказала бы нам о материальных, то есть мозговых, основах живописи больше, чем такая совершенная «модель» художника, какой является его брат-близнец. Практика моделирования предполагает учет некоторых переменных и отказ от других. Модель и оригинал были бы тождественны, если бы процессы, происходящие в них, совпадали. Этого не происходит. Результаты развития модели отличаются от действительного развития. На это различие могут влиять три фактора: упрощенность модели по сравнению с оригиналом, свойства модели, чуждые оригиналу, и, наконец, неопределенность самого оригинала. (фрагмент произведения «Сумма технологий», Станислав Лем, 1967)

Таким образом, С. Лем говорит о абстрагировании как основе моделирования. В тоже время абстрагирование и есть главный признак объектно-ориентированной парадигмы. Г. Буч по этому поводу пишет:

Разумная классификация, несомненно, - часть любой науки. Михальски и Степп утверждают: «неотъемлемой задачей науки является построение содержательной классификации наблюдаемых объектов или ситуаций. Такая классификация существенно облегчает понимание основной проблемы и дальнейшее развитие научной теории». Почему же классификация так сложна? Мы объясняем это отсутствием «совершенной» классификации, хотя, естественно, одни классификации лучше других. Кумбс, Раффья и Трал утверждают, что «существует столько способов деления мира на объектные системы, сколько ученых принимается за эту задачу». Любая классификация зависит от точки зрения субъекта. Флуд и Кэрсон приводят пример: «Соединенное Королевство… экономисты могут рассматривать как экономический институт, социологи - как общество, защитники окружающей среды - как гибнущий уголок природы, американские туристы - как достопримечательность, советские руководители - как военную угрозу, наконец, наиболее романтичные из нас, британцев - как зеленые луга родины».
"""Поиск и выбор ключевых абстракций.""" Ключевая абстракция - это класс или объект, который входит в словарь проблемной области. """Самая главная ценность ключевых абстракций заключена в том, что они определяют границы нашей проблемы""": выделяют то, что входит в нашу систему и поэтому важно для нас, и устраняют лишнее. Задача выделения таких абстракций специфична для проблемной области. Как утверждает Голдберг, «правильный выбор объектов зависит от назначения приложения и степени детальности обрабатываемой информации».

Как мы уже отмечали, определение ключевых абстракций включает в себя два процесса: открытие и изобретение. Мы открываем абстракции, слушая специалистов по предметной области: если эксперт про нее говорит, то эта абстракция обычно действительно важна. Изобретая, мы создаем новые классы и объекты, не обязательно являющиеся частью предметной области, но полезные при проектировании или реализации системы. Например, пользователь банкомата говорит «счет, снять, положить»; эти термины - часть словаря предметной области. Разработчик системы использует их, но добавляет свои, такие, как база данных, диспетчер экрана, список, очередь и так далее. Эти ключевые абстракции созданы уже не предметной областью, а проектированием.

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

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

upd. Я тут подумал, тема острая, я комментировать не буду. Факты я изложил в статье, а скатываться на уровень холивара не хочу. Если это не помогло задуматься - ну, что ж значит не повезло в этот раз. Действительно, будет конструктивно - если напишите контрдоводы в отдельной статье. Я же не берусь разрушать массовые стереотипы.

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

  • Сергей Савенков

    какой то “куцый” обзор… как будто спешили куда то