учебники, программирование, основы, введение в,

 

Общее понятие о стилях программирования

Почему нет универсальных методов?
К сожалению, из курсов наших университетов (как это обычно бывало во времена кризиса России) практически исчезла исключительно важная для мировоззрения и просто общей культуры наука: логика. Этот факт особенно прискорбен потому, что за XX век логика заставила научное мировоззрение перейти на качественно новый уровень. Она впервые заставила науку задуматься над границами собственных возможностей.
Здесь стоит сослаться хотя бы на теорему Гёделя о неполноте, которая утверждает, что нет ни одной полной теории, описывающей хотя бы натуральные числа. Далее, теорема о неполноте тесно взаимосвязана с теоремой об универсальном алгоритме, и обойти ее невозможно (любая попытка обойти ее сама себя опровергает). Теорема об универсальном алгоритме влечет как следствие принципиальную непополнимость универсального алгоритма (это чисто функциональный факт, он никак не зависит от конкретного построения универсального алгоритма и даже от конкретного понятия вычислимости, положенного в основу). Вся архитектура и идеология современных вычислительных систем построена на базе понятия универсального алгоритма (например, само существование транслятора с «универсального» языка типа C++ является ее практической реализацией). Таким образом, понятие ошибки в программе не устранимо даже в принципе.
Значит, нет универсального метода, который позволяет с гарантией и в ограниченное время решить любую поддающуюся решению программистскую задачу.
Вы говорите, что люди же решают задачи. Но посмотрите: каждый специалист имеет свои излюбленные задачи, которые он хорошо решает. А для Вашего профессионального роста и трезвой самооценки очень полезно побывать в такой ситуации, когда Ваши излюбленные методы отказывают. Если Вы сумели проанализировать свою неудачу, Вы достойны звания специалиста, если же нет, Вы находитесь на пути к догматизму либо шарлатанству.
Часто теоретические рассмотрения первого уровня приводят к наукообразным выводам, которые рассыпаются, при углубленном исследовании. Но в данном случае повышение глубины лишь усиливает эффекты неуниверсальности.
Рассмотрим пример типичной ошибки, которую неоднократно делают при теоретическом обосновании с помощью простейших моделей вычислимости.
Все, что может быть вычислено, может быть вычислено на машине Тьюринга. Машину Тьюринга можно промоделировать на языке С++. Поэтому ничего, кроме языка С++, знать не нужно.
Но тогда не нужно было бы изобретать и сам язык С++, поскольку машины конструируются таким способом, что любую машину Тьюринга можно промоделировать и в системе машинных команд.
Поскольку даже в теории сложности есть достаточно независимые между собой иерархии сложности (например, выигрывая во времени, мы часто проигрываем в памяти и наоборот), то видно, что для разных целей нужны разные средства.
Если же подняться еще на один уровень и рассматривать программы не просто как функции сами по себе, а как средство решения внешних задач, то мы наталкиваемся на наличие разных вариантов самой математики (классическая и разные виды конструктивных).
Таким образом, ни абсолютной истины, ни абсолютного совершенства, ни универсальных методов человеку не дано. Это вывод философский, но философия также является практический наукой, если уметь ее применять. Так уж давайте применять не только те научные результаты, которые нам льстят.

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifСтили, их ипостаси, методологии, методики, технологии

Поскольку ничего универсального нет, можно удариться в другую крайность, скатившись на набор рецептов и потеряв саму суть метода: гибкость и широкую применимость в самых разных областях. Если мы ориентируемся на такой ползучий эмпиризм, то дальнейшее развитие быстро упирается в эффект мусорной кучи: несистематизированные рецепты знаниями так и не становятся. Для каждого класса задач нужно искать специализированные методы и подходы.
Чем уже класс задач, тем больше общих особенностей у этих задач. Это наиболее четко прослеживается в том случае, если класс задач выделяется не по номенклатурному признаку (например, бухгалтерские задачи, задачи автоматизации документооборота), а по критериям, отражающим суть вовлеченных в задачи структур. До некоторой степени, чем абстрактнее описание задачи, тем лучше для выработки метода программирования.
Рассмотрим пример. Пусть имеется предприятие с некоторым количеством различных станков, выпускающее детали. Оно получает разнообразные заказы, и в соответствии с приоритетами заказов и технологией обработки деталей нужно динамически строить расписание работы станков. Решив эту задачу и столкнувшись через некоторое время с задачей документооборота в фирме, производящей спецификации большого числа разнообразных лекарств для потребителя и для специалистов, можно усмотреть подобие двух задач. В хорошо организованной бюрократической машине каждый исполнитель работает подобно станку, выполняя свою операцию, точно так же имеется сеть зависимостей (некоторые исполнители могут работать лишь после того, как им подготовили материал другие), и те же методы (а часто даже и почти те же программы) переносятся на планирование бумажного потока.
Таким образом, для систематизации сведений и превращения их в знания необходимо выделять общие характеристики найденных частных решений. Эти общие характеристики превращаются в приемы, а затем, при их осознании и обобщении, в методы.
Метод — сильное оружие, и поэтому наряду с большими достоинствами он обязательно имеет большие недостатки. В частности, применение метода требует достаточно высокого интеллектуального уровня и владения рядом навыков, прививаемых точными науками, в частности, умением переходить к абстрактному от конкретного и обратно. Поскольку подавляющее большинство специалистов не владеют мышлением на таком уровне, чтобы они могли применять метод в чистом виде, метод конкретизируют в методику.
Методика — это набор шаблонов, процедур и рецептов, конкретизирующих метод. Например, метод многоаспектного моделирования, положенный в основу UML (Unified Modelling language), конкретизирован в методику.
Примером методики служит общая часть RUP (Rational Unified Process). Сам UML лишь поставляет материал для конкретной методики: формы, в которых можно строить систему взаимосвязанных моделей, описывающих разные стороны создаваемой программы, и процедуры отладки таких описаний. Методикой может пользоваться уже значительное меньшинство специалистов, поскольку ее применение методики требует элементарных операций конкретизации и композиции и доступно людям, владеющим комбинационным мышлением. Но для успешного применения в коллективе методика должна конкретизироваться далее.
Тот же самый RUP, помимо методики применения многоаспектных моделей, содержит часть, касающуюся регламента работы программистского коллектива согласно данной методике. Это — технология. В технологии расписываются действия, даются шаблоны документов, определяются роли и функции лиц, задействованных в проекте. Поскольку в программистских коллективах чернорабочий имеет квалификацию техника обычного «железного» производства, программистские технологии, в отличие от индустриальных, оставляют некоторый простор для конкретизации и модификации, давая возможность подставлять в шаблоны целые серии динамически определяемых конкретных действий. На «железном» производстве технология — строго определенная последовательность операций, примерно соответствующая отлаженной программе в программистском мире.
RUP позволил в десятки раз поднять производительность труда программистов при решении задач, с бедным содержанием и навороченным пользовательским интерфейсом. Знакомство с конкретной методикой индустриального программирования должно быть предметом отдельного курса, оно полезно и для тех, кто никогда в жизни не будет пользоваться такой методикой, поскольку может выйти непосредственно на уровень метода. Для них эта методика будет примером конкретизации метода, которую такой человек должен делать, если не желает оставаться непонятым.
Но, кроме методов, есть еще два этажа. Первый из высших этажей начали рассматривать в 70-е гг. XX века. Это методологии программирования. Методология на самом деле ортогональна методам: это надстройка на другом крыле программистского здания. В этом случае достаточно сослаться на структурное программирование. Метод реализуется при помощи методологии и в ее рамках.
Второй из высших этажей впервые был систематически исследован в книге. Он связан с тем, что в программировании мы имеем дело с намного более абстрактными и идеальными понятиями, чем в материальном производстве. Поэтому длина логических построений возрастает, сложность формализации используемых методов убывает, и мы в гораздо большей степени зависим от логики наших рассуждений, чем от «материи».
Логика построения отличается от логики оформления готовых рассуждений, которую преподают на стандартных курсах и на которой основана классическая версия математики. Уже с начала ХХ века в математике появилось конструктивное направление (вариантами которого являются, в частности, интуиционизм и советский конструктивизм), для которого главное не доказательство, а содержащееся в нем идеальное построение. Основатель конструктивного направления Л. Э. Я. Брауэр (Голландия) установил, что причины, по которым математическое доказательство не всегда дает построение, лежат в логике. Так появилось понятие конструктивной логики, в которой мы интересуемся не истинностью формул, а их реализуемостью (возможностью построения решения задачи заданными средствами).
В дальнейшем выяснилось, что разным классам средств и ресурсных ограничений соответствуют разные логики. Эти логики не просто расходятся, а противоречат друг другу. Для того чтобы сохранить хотя бы минимальную внутреннюю согласованность наших рассуждений в ходе построения сложного искусственного объекта (в нашем случае программы), нужно с самого начала определить класс средств и ресурсных ограничений, которые необходимо принимать во внимание.
Таким образом возникают классы способов построения программ, логически противоречащие друг другу и применимые каждый в своей области. Эти классы дают самую общую картину программ, базирующихся на выбранных моделях вычислительной обстановки.
Структурное программирование соответствует взгляду на вычислительную обстановку как на хранилище отдельных значений, связанных отношениями, и взгляду математики XIX века на мир как на то, что может быть описано числовыми характеристиками; роль программы при этом — вычисление новых значений по старым.
Автоматное программирование соответствует рассмотрению действий как изменяющих вычислительную обстановку существенно и глобально, в результате чего то, что было, может оказаться неверным.
Сентенциальное программирование рассматривает вычислительную обстановку как глобальную регулярную структуру значений, которая может проверяться на соответствие регулярно заданным шаблонам и изменяться столь же регулярной подстановкой в нее других подструктур и перекоммутированием связей.
Событийное программирование рассматривает вычислительный мир как нечто изменяющееся во времени, а программу как совокупность активных агентов, посылающих друг другу сообщения через внешний мир и производящих в нем действия, в результате которых внешний мир может активизировать других агентов.
Все эти четыре стиля используют лишь ограниченные (с точки зрения логических формул) фрагменты соответствующих конструктивных логик. Например, в структурном стиле можно считать, что в формуле лишь одна импликация, поскольку все действия преобразуют значения, а не функции.
Функциональное программирование в его нынешней форме соответствует взгляду математики конца ХХ века на мир как на совокупность структур высших порядков и функционалов, производящих на базе одних структур другие. Оно единственное использует полный язык конструктивной логики, в котором реализациями сложных формул являются функционалы.
Таким образом, появилось понятие стиля программирования как совокупности парадигм и методологий, соответствующих одному и тому же классу логических методов, используемых при построении программ.
На первом уровне все стили укладываются в следующий морфологический ящик по координатам действия-условия, локальность-глобальность, выявляющий назначение каждого стиля.


Действия

Условия

Стиль

Локальные

Локальные

Структурный

Глобальные

Локальные

Автоматный

Локальные

Глобальные

Событийный

Глобальные

Глобальные

Сентенциальный

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

http://localhost:3232/img/empty.gifКогда нужно использовать различные стили и как они взаимодействуют?

Опыт автора показал, что в очень многих случаях студенты, которые имеют все исходные данные для того, чтобы решить задачу подстановкой конкретных значений в общее утверждение, тем не менее теряются, не увидев в частном общее.
Пример. После того как была решена задача, можно ли разложить на множители многочлен x2 + (p - 1) над полем вычетов по модулю p, студенты глубоко задумались над вопросом, можно ли разложить многочлен x2 + 2 в поле по модулю 3.
В связи с этим дадим некоторые советы по практическому применению общих принципов.
Внимание!
Не воспринимайте то, что дано ниже, как шаблоны! За шаблонами идите по другим адресам.

  1. Если у Вас появляется много структурных или (не дай Бог) неструктурных переходов либо все время приходится присваивать значения признакам, а затем их проверять, посмотрите, нельзя ли выделить модуль в автоматном стиле.
  2. Если Вы начинаете думать о задаче как о вычислении (не обязательно над числами), пользуйтесь структурным программированием.
  3. Если Вы намерены переложить понравившуюся Вам нечисленную (например, алгебраическую, топологическую или логическую) теорему в алгоритм для решения Вашей задачи, подумайте о функциональном стиле.
  4. Если у Вас каждое следующее значение требует немногих данных, но различные значения обращаются к одним и тем же, даже не пытайтесь программировать рекурсивно, рекурсивное описание можете сохранить в качестве документации к программе, а саму программу пишите в терминах циклов и массивов.
  5. Если данные строго подразделяются на ветви, используемые разными последующими значениями, часто лучше писать рекурсивно.
  6. Если Вы начинаете смотреть на данные как на активные единицы, которые взаимодействуют между собой, воспользуйтесь объектно-ориентированным программированием.
  7. Если в предыдущем случае объекты в том виде, как они предоставляются современными системами программирования, не подошли, воспользуйтесь каким-либо пакетом для организации квазипараллельной работы в условном времени и программируйте от событий.
  8. Если Вам нужно преобразовывать сложные структурированные тексты в другие тексты, не поленитесь воспользоваться Рефалом (или новым языком сентенциального конкретизационного программирования, если он уже появится к тому времени, когда Вы будете это читать).
  9. В частности, если исходные данные или конечный результат имеет формат символьного файла, совершенно неудобоваримого для стандартных процедур ввода/вывода используемой Вами основной системы программирования, пишите препроцессоры или постпроцессоры на Рефале (либо, в крайнем случае, на Perl).
  10. Если Вы путаетесь в многочисленных условиях, когда удача либо неудача порою проявляется после нескольких попыток и неясно, куда нужно вернуться, посмотрите, нельзя ли проверку условий запрограммировать на языке Prolog(или на новом языке сентенциального унификационного программирования, если он уже появится к тому времени, когда Вы будете это читать).

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

  1. Структурное и автоматное программирование нужно как можно жестче разделять на уровне модулей. Вопрос о языковой совместимости и интерфейсах здесь не возникает, поскольку оба они реализуются стандартными средствами традиционных языков.
  2. Две ипостаси структурного программирования также нужно как можно жестче разделять средствами модульности.
  3. Параллельная ипостась автоматного программирования в настоящий момент может быть реализована практически только последовательными средствами, воспользуйтесь системой моделирования в условном времени.
  4. Событийное программирование стоит выделять в самостоятельные модули либо целиком выносить в Perl-программы.
  5. В настоящий момент имеется целый ряд языков, основанных на идеях функционального программирования и продолжающих тенденции языка LISP на более современном уровне. Они имеют интерфейсы с С++ и Java, так что для написания модуля в функциональном стиле внутри большой, в основном традиционной, программы лучше воспользоваться Ocaml или Haskell.
  6. Common Lisp обладает достаточно удовлетворительно проработанными интерфейсами с C++. Эти интерфейсы могут эффективно использоваться профессионалами. В отношении же концептуальной целостности Lisp остается несравненным, и, если значительная часть Вашей программы функциональная, стоит пользоваться им.
  7. Common Lisp обладает удовлетворительно проработанными интерфейсами с Prolog. Но автор не считает разумным писать программу, соединяя функциональный стиль и унификационную ипостась сентенциального, и будет благодарен за примеры противного, если такие найдутся.
  8. Тем не менее указанный в предыдущем пункте интерфейс полезен, если Вы решили использовать задел, накопленный на одном из двух упомянутых языков, в новой программе, написанной на другом. Но будьте готовы к тому, что концептуальные несовместимости могут в некоторый момент оказаться хуже необходимости переписать используемые программы по-своему.
  9. Хороших интерфейсов с Рефалом нигде нет. Но, несмотря на это, есть один практический совет. Системы ввода/вывода Common Lisp и Prolog настолько неудобны и морально устарели, что написание пре- и постпроцессоров обработки входных и выходных данных на Рефале (либо Perl) окупается, а в Lisp или Prolog есть смысл пользоваться лишь простейшими возможностями ввода и вывода, принимающими предварительно обработанные и полностью укладывающиеся в структуру языка данные и выдающими их также без учета пользовательского формата.

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gif

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика