2003-03-04 16:12:46 UTC

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

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

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

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

Вообще очень отрадно, что в парсере есть такая полезная вещь, и помимо того, что она есть – в отличие от того же C++ где события, говорящие о возникновении исключения всегда должен генерировать сам программист, — в парсере, помимо ручной генерации с помощью ^throw, эти исключения генерируются автоматически при возникновении ошибок. Кроме того, существуют встроенные системные ошибки, которых в большинстве случает достаточно, если нет можно и свои придумать c использованием оператора ^throw.

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

@method[param1;param2]
^if(условие проверки входных параметров){
	здесь присваиваем допустимые значения если
	параметры не удовлетворяют условию
}
код метода

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

@method[param1;param2]
код метода

@another_method[]
^try{
	^method[$param1;$param2]
}{
	$exception.handled(1)
	$param1[...]
	$param2[...]
	^method[$param1;$param2]
	
}

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

Использование оператора ^try в некоторых случаях помогает увеличить производительность ну или снизить нагрузку на сервер. Не знаю как у других, у меня было (и наверно ещё есть) много мест в коде, где тот или иной участок кода должен выполняться в зависимости от некоего условия (существования файла на диске, URI определённого вида, проверяемое с помощью match и т.д.) и при невыполнении этого условия в коде возникает ошибка (unhandled exception). Так вот, проще код, где может возникать ошибка, поместить в ^try и при выполнении всех условий все будет хорошо и не будет лишней операции проверки (запроса к диску, match и т.д.) а при возникновении ошибки устанавливать флаг $exception.handled(1) и выполнять другой код в обработчике ошибки.

Маленький пример:

@method[]
^if(-f "/sections.cfg"){
	$table[^table::load[/sections.cfg]]
}

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

@method[]
^try{
	$table[^table::load[/sections.cfg]]
}{
	$exception.handled(1)
}

Здесь в любом случае делается только одна дисковая операция, — загрузка файла для создания таблицы. Аналогичная ситуация и с запросами к БД, — если проверка делает SQL запрос к БД и в коде который должен выполнится есть ещё один запрос, — мы опять экономим один запрос и соответственно снижаем нагрузку на сервер БД и увеличиваем производительность.

В приведенном выше примере кода с ^try в обработчике флаг $exception.handled(1) зажигается при возникновении любой ошибки, однако это дело можно ограничить только определёнными типами ошибок, т.е. устанавливать этот флаг по условию.

Помимо автоматического возбуждения исключительных ситуаций, — можно их делать самому (через ^throw) при возникновении некоего события в коде формально не являющегося ошибкой, однако это логическая ошибка, и она должна быть соответствующим образом обработана. Для примера расскажу немного о работе этого сайта. Здесь всё запросы к несуществующим страницам и каталогам перенаправляются на одну страницу, т.е. здесь практически не может быть ошибки 404, пока есть эта страница (одна на весь сайт). Однако хочется, чтобы на запросы с бредовыми URL выдавалась соответствующая страница. Например, запрос материала с номером большим, чем максимальный (пробуем вызвать мнение 500 :)) формально удовлетворяет правилам и парсер не орёт что что-то не так, однако это бред, и поэтому здесь я руками генерирую исключение, говорящее о том, что такого ресурса нет, и обработчик этого типа исключений (один на весь сайт) вызывает код создания 404-й страницы. Единственное пока исключение – это раздел новостей, там пока ещё не всё гладко, но работа ведется, и думаю, со временем на любой бредовый URI будет выдаваться 404 страница.

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

Далее читаем книжку для «правильных» программистов – «Язык программирования С++» Б. Страуструп. Там намного больше информации о философии исключений и по правде говоря, я у него этому и научился. Разумеется не забываем официальную документацию по парсеру, ну и меня конечно :) — Я ведь хороший, правда?

2003-03-04 16:12:46 UTC noweb parser programming web