Почему Паскаль?
Почему Паскаль?
Предмет программирования можно, да, наверное, и нужно разбивать на два: на технологию написания программы и на алгоритмизацию, которая скорее искусство, чем технология. Опытные люди знают, что изобретение алгоритма зачастую главная проблема; если алгоритм написан, то остальное уже дело техники. Отсюда следует вроде бы логичный вывод: если написание программы по алгоритму — дело неизмеримо более простое, нежели алгоритмизация, то нюансы этой технологии имеют намного меньшую ценность, нежели умение алгоритмизации, а следовательно, и выбор языка программирования — дело не вполне существенное. Пусть это будет Бейсик, Паскаль, Си или что-то еще, неважно.
Это внешняя и очень поверхностная логика. Приведенные выше рассуждения имеют существенный недостаток. На самом деле четко разделить по времени написание программы и написание алгоритма нельзя. Нельзя сказать: “Вот это в чистом виде программа, а вот это в чистом виде алгоритм”. И если нельзя сесть и написать программу, не имея хотя бы в голове (а лучше на бумаге) алгоритма, то и разработка алгоритма очень сильно зависит от того, что разработчик может себе позволить при написании программы, а что нет.
Например, разработчик алгоритма не свободен в выборе структур данных. Действительно, конечная цель разработки — не алгоритм, а программа, написанная на конкретном языке, и если разработчик об этом забудет, то может оказаться, что созданные им структуры данных плохо реализуются на выбранном языке.
Далее, разработчик обязательно пожелает разбить свой алгоритм на части так, чтобы впоследствии это разбиение на отдельные блоки породило соответствующую структуру программы, но тогда ему не могут быть безразличны такие термины, как модуль, класс, объект, процедура, функция.
И так далее. В общем, получается, что алгоритм возникает не только из возможностей разработчика алгоритма, но и из возможностей языка, на котором будет потом написана программа. Таким образом, мы приходим к выводу, что выбор языка — дело существенное. Язык следует выбирать тщательно, причем не на этапе написания программы (это уже поздно), а на этапе разработки алгоритма.
Язык должен быть “хорошим”, это понятно, но что означает “хорошесть” в отношении языка программирования? Термин этот очень неопределенный, субъективный, и, значит, его необходимо несколько прояснить.
Конечно же, “хороший” означает хорошо подходящий для конкретной сферы деятельности. Сферу определить очень важно. К примеру, FoxPro хорош для разработки баз данных, но вряд ли стоит писать на нем программу, играющую в шахматы. QBasic достаточно хорош, если нужно написать небольшую арифметическую задачу, но вряд ли стоит на нем писать большую математическую библиотеку, и т.д., перечень примеров можно продолжать до бесконечности.
Кроме того, необходимо четко понимать, что хорошесть языка в одной сфере нельзя автоматически переносить на другую. Мне иногда приходится спорить с людьми, считающими, что процесс обучения необходимо строить на базе языка Си. И в этих спорах я часто слышу такой аргумент: “Язык Си очень хорош для промышленного программирования, а значит, на нем и надо учить”.
В этом утверждении кроется две ошибки. Во-первых, учим мы не языку, а программированию, и я уверен, что правильно обученный человек сможет освоить язык Си, даже если его этому не учили. А если специалист не в состоянии самостоятельно освоить язык, значит, его учили плохо.
Во-вторых, даже если Си идеален для промышленности (что совсем не факт), то даже это никак не гарантирует его качества как языка обучения. На мой взгляд, хорошему языку обучения должны быть присущи следующие свойства:
1. Поддержка структурного программирования и нисходящего проектирования.
2. Сильная типизация, причем именная, а не структурная.
3. Все объявления должны записываться до первой исполняемой команды.
4. Статический контроль типов.
5. Конструкции языка не должны быть перегружены вариантами исполнения.
6. В языке должно быть минимально необходимое количество конструкций и, по возможности, не должно быть конструкций, дублирующих смыслы.
7. Расширяемость языка его же средствами.
То есть девизом такого языка должен стать принцип Оккама “Не порождайте избыточных сущностей”.
Возможно, существуют различные языки, удовлетворяющие требованиям учебного процесса, но мой личный опыт говорит, что на сегодняшний день наилучшим образом для обучения подходят языки из паскаль-семейства: Паскаль, Модула, Оберон, Компонентный Паскаль.
Немного ниже я попробую предъявить несколько более конкретных аргументов, а сейчас еще несколько слов общего характера.
Всегда справедливо считалось, что хорошее мышление — это мышление, способное к творчеству. Еще одно справедливое утверждение гласит, что творчество должно быть свободным. В приложении к области программирования это звучит так: “программист должен иметь в своем распоряжении как можно больше средств для выражения своих мыслей” — или даже так: “программист не должен быть ограничен в своих языковых средствах”.
Но в этой логике есть изъян. За красивым словом упускается банальная истина, что мышление должно быть результативно и желательно максимума результата достигать минимумом средств. С ростом возможностей действительно возрастает свобода, но с ней возрастает и хаос и, как следствие, падает эффективность.
Отсюда вытекает важнейшая, на мой взгляд, педагогическая задача — воспитания дисциплины ума. Эффективный ум — это дисциплинированный ум, умеющий ограничить фонтан идей во имя реализации. А так как язык — это средство выражения мысли, то и он должен быть дисциплинирован. Кроме того, ограниченность средств порождает изящество мышления.
И не надо думать, что принцип Оккама придуман для программистов. Возьмите любую область знания, а в ней любую значимую теорию, и вы легко увидите, что в основе этой теории будет пара-тройка простых принципов и очень скупой язык вывода. И только программисты путают творчество с невоздержанностью.
В отношении творчества и дисциплины мне часто кажется, что педагогическая задача дисциплинирования ума более важна, чем воспитание творчества. Мысль крамольная, но я могу ее обосновать. Ведь, если честно, как развивать творческие способности, мы не знаем, похоже, они даются нам от бога, и единственное, что мы действительно можем сделать, — это облагородить творчество дисциплиной. А теперь вернемся к языку программирования Паскаль и попробуем предъявить несколько конкретных аргументов.
Поддержка структурного программирования
Главный строительный кирпич паскаль-программы — это процедура. Процедура имеет один вход, и все спецификации поступающих на вход данных описаны в одном месте, в заголовке. Процедура имеет один выход. Один вход и один выход — это простейший способ передачи управления, а это, как известно, фундаментальное требование структурного программирования.
Процедура может быть сложной. Текст процедуры может включать в себя тексты других, необходимых ей процедур, то есть процедура может быть сложным сооружением, составляющие процедуры также могут быть сложными сооружениями. Таким образом, программа есть дерево процедур. Дерево, как известно, есть простейшая форма графа, но в то же время она позволяет реализовать программы сложнейшей архитектуры, в которой управление между процедурами передается по простым и прозрачным правилам. Есть такой красивый термин — “иерархия”. Означает он строгое подчинение нижестоящих вышестоящим, причем не любым вышестоящим, а, так сказать, непосредственным начальникам. Рассмотрим пример такой иерархии (см. рисунок).
Здесь D и E подчинены C, но никак не B и даже не A. Для большей функциональности можно допустить, что D и E могут сотрудничать между собой. C и B подчинены A и могут сотрудничать между собой. В такой иерархической форме устроено очень многое. Так устроены почти все человеческие организации. Например, президент компании может дать распоряжение начальнику подразделения компании, но никак не сотрудникам этого подразделения, хотя формально они ему обязаны подчиниться. Но иерархия требует упорядочить взаимоотношения. Четкая структура взаимного подчинения минимизирует организационные проблемы и делает управляемой сколь угодно большую организацию. Следовательно, иерархия — это средство борьбы с хаосом во имя эффективности управления. Вот почему множество паскалевских процедур устроено именно так. Пример ниже:
MODULE example;
PROCEDURE a*;
PROCEDURE b;
PROCEDURE c;
BEGIN
END c;
BEGIN
c; (*правильный вызов*)
END b;
BEGIN
c;(*ошибка, необъявленный идентификатор*)
END a;
END example.
В Компонентный Паскаль (КП) введено несколько корректив. Во-первых, из двух понятий (процедура и функция) оставлено одно — процедура, но в описании процедуры можно указать, что она возвращает значение, в этом случае она работа ет как функция. Вторая корректива — это команда RETURN, позволяющая прервать работу процедуры в любом месте. Пример ниже
MODULE example;
IMPORT StdLog;
PROCEDURE G(a:INTEGER):INTEGER;
BEGIN
IF a = 1 THEN RETURN 1
(* альтернатива ELSE проверяться не будет*)
ELSE RETURN 2;
END;
END G;
PROCEDURE p*;
BEGIN
StdLog.Int(G(1));
StdLog.Int(G(2));
END p;
END example.
Если процедура велика, то между точкой, где реально необходимо прерваться, и концом процедуры может быть весьма значительное расстояние. Вспомним, что в TurboPascal для возврата подсчитанное значение необходимо присвоить имени функции, которое в этом случае есть переменная. Если от точки реального завершения до конца функции программист пропустит какую-либо операцию с участием имени функции, то результат работы функции может оказаться отличным от ожидаемого. И здесь, чтобы избежать возникающих угроз, программист может захотеть использовать команду безусловного перехода Goto и тем самым начнет развивать у себя привычку нарушать принципы структурного программирования.
Кстати, в Компонентном Паскале Goto отсутствует за ненадобностью. И если в процессе разработки у вас появилось такое чувство, что Goto вам бы не помешал, то это звоночек о том, что ваш метод проектирования хромает.
Статический контроль типов
Достаточно часто пишущие на Бейсике ученики, да и учителя сильно сомневаются вообще в самой идее контроля типов, аргументируя это тем, что паскалевские сложности делают программу длиннее. В этом они правы — программы действительно немного длиннее, что особенно заметно на коротких задачах. Но выигрыш от сильной типизации тоже значителен. Программы становятся надежнее и времени на отладку требуют меньше. Так что “длинная программа” вполне может оказаться короче “короткой бейсиковской”.
А если вы согласны с тем, что надежность — важный козырь, то вы легко согласитесь и с тем, что статический контроль настолько лучше динамического, насколько статическая картина проще для анализа, чем процесс.
Конечно, статический контроль типов ставит серьезные проблемы перед разработчиками языков и компиляторов, но это их проблемы, а не наши. А для завершения разговора небольшой пример на QBasic для тех, кто считает, что статический контроль типов — излишнее усложнение.
DIM u AS INTEGER
DIM w AS INTEGER
CLS
a = 7.3
b = 6.3
u = a
u = u + b
w = a + b
PRINT u, w
С точки зрения математика, величины u и w вычисляются идентично. Расчет второй величины выполняется немного короче, но для конечного результата это должно быть все равно. Но оказывается, что величины u и w имеют разное значение! То есть, используя QBasic, мы, оказывается, не можем рассчитывать на корректное выполнение базовых математических операций!
Такая программа на языке Паскаль просто невозможна.
Легкость модификации текста
Составной оператор Паскаля — необходимое средство структурирования текста внутри процедуры. Но два ключевых слова Begin и End мало того, что могут утяжелить программу, это половина беды. Эти два слова для многих учеников создают сложности в построении нужной структуры. Поясню примером. Пусть требуется построить цикл с группой операций внутри. Ученик понимает, что нужно построить составной оператор, включающий в себя все операции тела цикла, и он делает это вот так:
Begin
For I := 1 to n do
{еще какие-то команды}
End;
По-своему он прав. Он включил в составной оператор все, что касается цикла. Это, конечно, частный случай, но, убрав лишние Begin, Н.Вирт избавил очень многих от значительного числа таких и всяких прочих ошибок.
Еще более сложно построить корректно составной оператор в структуре вложенных IF. Конечно, эти сложности являются таковыми лишь для начинающих программистов, но ведь именно о них мы сейчас и говорим. Но и многоопытные, думаю, согласятся, что вот эта конструкция на КП
IF Условие THEN
Группа команд
ELSE
Группа команд
END
выглядит более изящно, нежели
if Условие then
begin
Группа команд
end
else
begin
Группа команд
end;
записанная на старом Паскале.
Модульность
В языке Компонентный Паскаль разработчики исчерпывающе реализовали концепцию модульного программирования. Но здесь применительно к процессу обучения программированию мы сталкиваемся с парадоксом, по крайней мере на первый взгляд. С одной стороны, модульность — это может быть наиболее важное качество языка, а с другой стороны, наименее нужное для обучения. Я не слишком ошибусь, если скажу, что редкий ученик напишет программу, в которой реально будет необходим более чем один модуль (хотя в нем скорее всего будут вызываться несколько чужих модулей). Это так, но, с другой стороны, мы и на этих редких тоже должны работать. И, кроме того, если вы поставили перед собой задачу рассказать о реально существующих технологиях современного программирования, то КП предоставляет вам возможность сделать это наиболее эффективно.
В заключение. Аргументы “за” можно продолжить, но я приведу только еще один. Есть такая форма записи программы — псевдокод. Это запись алгоритма на некотором промежуточном языке, что-то среднее между естественным языком и языком программирования. Когда-то я с группой своих старших учеников озадачился следующей проблемой: что есть хороший псевдокод? Должен сказать, что в той группе все владели по крайней мере двумя языками: Паскаль и Си примерно в равной степени. Так вот, наш первый вариант псевдокода подозрительно сильно был похож на язык Паскаль.
И тогда лично у меня возникла мысль, что Н.Вирт угадал в языке Паскаль естественный способ записи алгоритма.