Обработка эксепшенов

Baranov_Dron

Новичок
Здравствуйте!

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

PHP:
<?php
class ComponentOneException extends Exception
{}
class FacadeException extends Exception
{}

class ComponentOne
{
	public function getResult()
	{
		$result = '';
		
		try
		{
	        $file = new SplFileObject('file_one.txt');
            foreach ($file as $line)
            {
                $result .= $line;
            }
		}
		catch (RuntimeException $e)
		{
			throw new ComponentOneException($e);
		}
		
        return $result;
	}
}

class ComponentTwo
{}

class ComponentThree
{}

class Facade
{
	public function run()
	{
		try
		{
		    $one = new ComponentOne();
		    //Работа с компонентом один.
		    echo $one->getResult();
		}
		catch(ComponentOneException $e)
		{
			throw new FacadeException($e);
		}
		
		$one = new ComponentTwo();
		//Работа с компонентом два.
		
		$one = new ComponentThree();
		//Работа с компонентом три.
	}
}

try
{
    $facade = new Facade();
    $facade->run();
}
catch (FacadeException $e)
{
    //Обработка.
}
?>
Например в примере выше SplFileObject выбрасывает эксепшен RuntimeException.
И из-за этого класса у нас возникает куча обработчиков эксепшенов! На каждом уровне!
Что ужасно ухудшает читаемость кода! И делает его громоздким.
Стоит ли в main(иди как это называется...) просто ловить эксепшен RuntimeException,
а от ComponentOneException и FacadeException просто напросто избавится?

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

tz-lom

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

Baranov_Dron

Новичок
Форвард или сообщение - не суть.

Проблема в другом.
1) Работаем с SplFileObject в компоненте 1, мы являемся его клиентом! Потому поймав от него исключение мы решаем, что делать. Разумным будет отправить эксепшен, и в phpdoc указать об этом.
2) В фасаде мы работаем с компонентом 1, фасад является клиентом компонента 1. Поэтому он должен ожидать исключение от компонента 1. И в примере выше мы ловим исключение от компонента 1, и посылаем исключение от фасада.
3) main является клиентом фасада и оно должно ожидать исключение только от фасада. Клиент фасада не должен знать о компонентах и splFileObject и их исключениях. Иначе он будет слишком много знать.(получается нарушение принципа персональной ответственности). И значит каждый компонент должен только обрабатывать компоненты с которыми он работает и бросать свои исключения. А его клиент должен знать только о его исключениях. Но это как-то слишком громозко увеличит код...
А я могу наизобретать колесо...
 

whirlwind

TDD infected, paranoid
Фасад с чем работает? С компонентами. Значит любой эксепшн компонента = class ComponentOneException extends ComponentException. А фасад catch ComponentException всех компонентов в одном блоке.

ЗЫ. соответствующий уровень абстракции для ексепшенов работает так же как для других классов
 

Baranov_Dron

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

whirlwind

TDD infected, paranoid
Это у вас ошибка. Вы не в ту сторону думаете. Намека тоже не поняли. Если обработка исключения в фасаде не подразумевает ничего другого, кроме как форварднуть эксепшен, то и нет никакого смысла заморачиваться субклассами исключений. С точки зрения фасада это бессмысленно. Так же как и отлов RuntimeException. Оборачивайте весь код try/catch ловите базовый класс Exception и избавитесь от связи вниз по стеку.

Обработка исключений требуется в очень ограниченном количестве случаев. Где это может быть оправдано? В долгоиграющих приложениях, например отваливается соединение к удаленному серверу (IOException), ловится эксепшн, билдер строит новый сеанс заново. Если исключения обрабатываются чаще, возникает подозрение, что исключения используются не по назначению - превращаются в способ управления программой, что является плохим сигналом (ОО аналог goto). Другое дело java, который не позволяет ставить сетку на отлов (вернее наоборот - не позволяет НЕ обрабатывать исключение, даже если оно нафик не нужно), если у метода нет спецификатора throws.

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

PS. не забивайте голову некритичными вещами. В тех 1-5% когда это действительно понадобится в программе на PHP, вы без труда решите проблему.
 

Baranov_Dron

Новичок
whirlwind спасибо за развёрнутый ответ!
Насчёт иерархии исключений, как понимаю ваша иерархия напоминает иерархию исключений ZendFW?
И что вы подразумеваете под вертикальным и горизонтальным связыванием классов?

Но у меня в голове всё равно кавардак, я люблю всё раскладывать по полочкам.
Так и здесь.

Мы имеет следующие постулаты:
Каждый класс имеет свой интерфейс.
У каждого класса есть свои клиенты, которые работают с ним через интерфейс.

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

На основание этого всего можно сделать один вывод - исключения являются частью интерфейса.
И даже phpDoc про это чётко говорит:
PHP:
	/**
	 * Возвращает результат работы компонента один.
	 * 
	 * @return string
	 * @throws ComponentOneException
	 */
	public function ComponentOne::getResult()
        {}
Но если мы выбросим из компонента обработку исключения от splFileObject, у нас получится:
PHP:
    /**
     * Возвращает результат работы компонента один.
     * 
     * @return string
     */
    public function ComponentOne::getResult()
    {
        $result = '';
        
        $file = new SplFileObject('file_one.txt');
        foreach ($file as $line)
        {
            $result .= $line;
        }
        
        return $result;
    }
Тоесть клиент компонента один не ожидает никаких исключений. Он считает, что компонент всегда работает стабильно! А про класс SplFileObject он ничего не знает, и знать не должен!

Где-то в моих рассуждениях ошибка или излишняя паранойя?
 

tz-lom

Продвинутый новичок
Baranov_Dron
а вы не подходите с точки зрения "исключение должно быть перехвачено" , исключение должно быть информативным,показывать код на котором произошла ошибка и по возможности описывать ошибку
whirlwind правильно говорит,перехват исключений - редкость, более того,отсюда следует что и выбрасывание исключений - редкость , а значит что:
1- большинство таких исключений можно не обрабатывать
2- они нужны как инструмент отладки,а не управления
хитрая система устройства такого тривиального механизма будет только мешаться под ногами
лично я бы даже не задумывался перехватывать эксепшн от SplFileObject - в 99.99999% он не будет выброшен,а в одном проценте - у нас проблемы которые надо залогировать,а не обработать
 

Baranov_Dron

Новичок
Я соглашусь с вами, если рассматривать эксепшены - как средство отладки.
Но правильно ли оно?
Не логичнее будет ли рассматривать эксепшены - как элемент интерфейса?
(тоесть метод помимо своих прямых результатов(return) может возвращать и непредвиденные значения(exceptions))
 

tz-lom

Продвинутый новичок
Baranov_Dron
нет,это не архитектурный изыск а именно исключение , ситуация возникающая при непредвиденных обстоятельствах
так:
PHP:
// ToDo: slaughter me before I commit
function sum($a,$b)
{
    if(!(is_int($a)&&is_int($b))) throw new FuuException();  // I hope maniac is near 
//do something
}
делать нельзя
 

Mols

Новичок
Baranov_Dron
ИМХО Вы бросаетесь в крайности.
Исключения они потому и исключения, что появляются в исключительных, то есть редких ситуациях.
Другими словами их не надо использовать в логике приложения в качестве управляющих флагов и т.п.
Вообще whirlwind и tz-lom сказали уже всё))).

[update]
Правда совсем уж к отладке всё сводить тоже не надо.
Есть ряд задач которые исключения вполне удобно помогают решать.
Например требуется, чтобы в ответе Вашего сервиса была жесткая структура.
Скажем ХМЛ в котором 2 основных ветки.
Первая - возвращаемые даные.(внутри этой ветки могут быть различные структуры)
Вторая - отчет об ошибках.(здесь только итемы, либо ничего)

Вполне удобно использовать эксепшены для того, чтобы быть уверенным, что Вы всегда отдадите как минимум формально верную структуру.
Но при этом достаточно обрабатывать 1-2 типа эксепшена которые будут пользователя информировать об типе ошибки и т.п.
И если эти 1-2 типа не сработали - ловить всё.
Ну запись в лог само собой)))
 

Baranov_Dron

Новичок
Всем спасибо за ответы!
Подумал, погуглил дальше и вроде разобрал кашу в голове(простите что не верю сразу и на слово, люблю разобраться в проблеме и понять суть, а не частности), то что я пытался выше описать - это по сути проверяемые исключения.
Я конечно же набрёл на грабли, но вроде выбрался из них.

Как вывод данной темы прикладываю скрин с книги Роберта Мартина.
Там прямо таки ответ на все мои вопросы.
Может кому-то(кто потом столкнётся с такими же проблемами).
 

Вложения

Сверху