Кидать ли exception при проверки правил бизнес логики?

6yrop

Есть некоторые бизнес-правила, которые лучше проверять в BLL (business logic layer). Есть два варианта

public void Validate
{
if (dataItem.Total > 25000)
{
throw new ApplicationException("слишком большая сумма")
}
if (dataItem.Date > now.AddMonth(-10
{
throw new ApplicationException("слишком рано")
}
}

второй вариант

public void Validate(ValidationContext validationContext)
{
if (dataItem.Total > 25000)
{
validationContext.AddError("слишком большая сумма");
}
if (dataItem.Date > now.AddMonth(-10
{
validationContext.AddError("слишком рано");
}
}

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

sbs-66

Если приложение может продолжать работать корректно, то надо просто выдать пользователю предупреждение (Tip). Если не может - надо кинуть исключеине специального вида, которое снаружи перехватывать и обрабатывать (например тоже выводить Tip).

Dasar

первый вариант - лучше, потому что проще и надежнее
второй вариант - лучше, потому что предоставляет больше сервиса, но менее надежный

klyv

Исключения по определению кушают больше ресурсов.

Maurog

на мой взгляд так лучше:
bool Validate(ValidationContext validationContext)

6yrop

первый вариант - лучше, потому что проще и надежнее
второй вариант - лучше, потому что предоставляет больше сервиса, но менее надежный
чем же второй вариант менее надежнее?
как раз наоборот, в первом варианте сложности с сопровождение — программист, который вызывает метод Validate должен прочитать доку о том, что ошибки возвращаются через эксепшены, т.е. есть вероятность того, что он напишет
...
Validate;
... //делаем еще что-то

и при этом он не думает о том, что при не выполнении бизнес правил кусок кода "делаем еще что-то" не выполнится.

trobak

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

6yrop

И если в первом случае, при необработанном исключением приложение может и грохнется, то во втором - при необработанном validationContext можно зафигачить в базу запись, противоречащую бизнес-логике.
И возвращаемый bool тоже не поможет - ничто не заставит программиста проверять возвращаемое значение.
Имхо, получить кривую базу много хуже, чем грохнуть клиентскую часть.
а при чем здесь вообще база? может ее вообще нет. Логика у вас значит такая. Весь execution path после throw в методе Validate не должен быть выполнен, если проверка не прошла. И это вы закладываете в способ написания метода Validate который содержит сотни вот таких примитивных проверок. Т.е. такой вариант написания метода Validate накладывает дополнительно ограничение. Второй же вариант работает просто как сборщик ошибок, и ни чего другого об этом методе знать не надо (при написании его тоже). Что с этими ошибками делать, мы решаем вне метода Validate, и тем самым, когда мы захотим что-то поменять в схеме обработки ошибок "тупой" "длинный" метод Validate нам трогать не надо. Таким образом, действуем в соответствии с главным принципом программирования — разделение ответственности по разным блокам. :)

6yrop

В общем, как всегда - чем больше сервиса, тем сложнее и больше вероятность ошибок
это ты откуда взял? Обратный пример — Excel предоставляет гораздо больше сервиса, чем калькулятор. На калькуляторе я делал гораздо больше ошибку, когда перемножал столбцы цифр на физпраке :)

vall

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

kruzer25

ИМХО,
public void Validate(ValidationContext validationContext)
{
if (dataItem.Total > 25000)
{
validationContext.AddError('Total',"слишком большая сумма");
}
if (dataItem.Date > now.AddMonth(-10
{
validationContext.AddError('Date',"слишком рано");
}
}

kruzer25

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

kruzer25

т.е. есть вероятность того, что он напишет
Для этого надо писать
public void Validate /* throws ValidateException*/

kruzer25

то во втором - при необработанном validationContext можно зафигачить в базу запись, противоречащую бизнес-логике.
Для этого надо в Validate же (или где-нибудь наверху, в общем обработчике) смотреть, есть ли ошибки в ValidationContext, и если есть - кидать собственно исключение.

kruzer25

Он говорит про вероятность ошибок программиста, а не пользователя. Для пользователя это будут баги в программе - и да, думаю, что в экселе багов гораздо больше, чем в калькуляторе.

Dasar

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

if (p == NULL)
context.AddError(...);
...
...
if (p->GetLength > 100)
context.AddError(...);

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

if (СодержитЦиклы(структура
context.AddError("Структура содержит циклы");
...
...
if (ПриОбходеСтруктурыКакДереваНашлиКакуюТоХеротень(структура
context.AddError("Нашли херотень");

feliks28

А мы меняем последующие if'ы на else if'ы и дальше не идем.
Все еще менее надежно?

Dasar

> А мы меняем последующие if'ы на else if'ы и дальше не идем.
тогда это будет чисто первый вариант - после первой же ошибки прекращаем работу, и от передачи ошибки через context вообще никакой пользы нет.

kruzer25

По этому, надо исходную функцию validate переписать на две, что-нибудь вроде:
private function validateTotal {
if(dataItem.Total > 25000) {
throw ValidationException::forSingleField('Total','слишком большая сумма');
}
if(dataItem.Total < 2500) {
throw ValidationException::forSingleField('Total','слишком маленькая сумма');
}
}
private function validateDate {
if(dataItem.Date > now) {
throw ValidationException::forSingleField('Date','дата рождения не может быть в будущем');
}
}
private function validatePassport {
if(dataItem.Date.addYears(14) > now) {
if(dataItem.Passport) {
throw ValidationException::forMultipleFields(array('Date','Passport''Указан паспорт для человека <14 лет');
}
} else {
if(!dataItem.Passport) {
throw ValidationException::forSingleField('Passport','Не указан номер паспорта');
}
if(!someCheckingForPassport) {
throw ValidationException::forSingleField('Passport','Некорректный номер паспорта');
}
}
}

public function validateAll {
try {
validateTotal;
} catch(ValidateException e) {
validationContext.addErrorFromException(e);
}
try {
validateDate;
} catch(ValidateException e) {
validationContext.addErrorFromException(e);
}
try {
validatePassport;
} catch(ValidateException e) {
validationContext.addErrorFromException(e);
}
if(validationContext.hasErrors {
throw validationContext.getContentException;
}
}

olga1969

А мы меняем последующие if'ы на else if'ы и дальше не идем.
Все еще менее надежно?
Ну тогда если первый if сработает, то следующие все выпадают, а там находится проверка других полей, и ее мы тогда проскочим. Если в dataItem несколько полей, нам-то ведь нужно их все проверить.

feliks28

Ну, а лишнего ничего в первом случае скомпилировано не будет? Не знаю, честно говоря в какой вид исключения компилируются. В такое же дерево if'ов?

Dasar

тогда уж так:

public function validateAll {
foreach (Executer validator in new Executer[]{validateTotal, validateDate, validatePassport})
{
try
{
validator;
}
catch (Exception exc)
{
context.AddError(exc);
}
}
}

feliks28

Ну я все-таки не в пустоту отвечал а на блок

тем, что проверки же у нас не просто так стояли, они проверяли данные на корректность.
соответственно, если мы идем дальше (а не кидаем exception то допускаем работу с некорректными данными.

kruzer25

Кстати, да.

Dasar

> Ну, а лишнего ничего в первом случае скомпилировано не будет?
лишний код вставляют только блоки try,
сам throw - вызывает только код размотки стека.

6yrop

тогда это будет чисто первый вариант - после первой же ошибки прекращаем работу, и от передачи ошибки через context вообще никакой пользы нет.
вреда тоже нет :), да, в вырожденном случае эти варианты совпали. Но во втором случае идти дальше или нет решает автор метода Validate, он вник в бизнес логику и он может принять это решение. Первый способ навязывает решение, исходя из программной архитектуры. Заказчики вас не поймут :smirk:

evgen5555

Кстати, юниксойды тут ещё не предлагали вставлять assert'ы?

Dasar

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

kruzer25

assert-ы - это всё-таки что-то отладочное, и предназначены они не для того, чтобы отлавливать некорректные введённые пользователем данные, а для того, чтобы отлавливать ошибки разработчиков.
На production-сервере вообще эти assert-ы запросто могут быть отключены, и окажешься ты тогда в глубокой жопе.

Dasar

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

void Validate(Item item, ValidationContext context)
{
Executer[] validators = new Executer[]
{
delegate{if (item.Total > 25000) throw new Exception("Сумма слишком большая", "Total");},
delegate{if (item.Date.AddYears(14) > DateTime.Now && item.Passport != null) throw new Exception("...", "Date", "Passport");},
...
}
foreach (Executer validator in new Executer[]{validateTotal, validateDate, validatePassport})
{
try
{
validator;
}
catch (Exception exc)
{
context.AddError(exc);
}
}
}

olga1969

Кстати зря ржете, лучше бы предложили: как код, который привел penartur записать проще.
да нормальный у него код в общем и целом.
ну, я тоже хотел написать что-то подобное, только все в одном методе, и еще не кидать эксепшн после каждой ошибки, а собирать соответсвующие сообщения об ошибках (которые для юзера потом надо в ЕррорОкне написать) в какой-нибудь String через разделитель типа "\n" и потом кидать эксепшн в самом конце, если таки есть ошибка. Ну, собственно, penartur так и делает в validateAll.
А что тебе в его коде не нравится ?
П.С.

т.е. есть вероятность того, что он напишет


Для этого надо писать
 public void Validate /* throws ValidateException*/  


А что, в C# если метод кидает эксепшн, это не обязательно объявлять ? Ну, как в Яве, чтобы потом, если программист, который вызывает эту функцию, забудет обернуть ее в try-catch блок, был пойман компилятором.

kruzer25

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

evgen5555

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

kruzer25

А что, в C# если метод кидает эксепшн, это не обязательно объявлят
Я хз, в php это необязательно.
если программист, который вызывает эту функцию, забудет обернуть ее в try-catch блок, был пойман компилятором
При этом теряется одно из существенных преимуществ exception-ов (если не самое важное) - что тебе надо всегда думать, а не кинет ли эта функция, которую ты тут вызываешь, exception - вместо того, чтобы ловить исключения там, где это действительно нужно, ты их будешь ловить везде.
Чем такие exception-ы отличаются от чего-нибудь вроде
Exception SomeFunction(arg1,arg2,&res) {
...
return new EmptyException;
}
Exception MyFunction(arg1,arg2,&res) {
someres = null;
if(!SomeFunction(...,...,someres)->isEmpty) return new MyException(...);
...
return new EmptyException;
}

?

Dasar

> А что тебе в его коде не нравится ?
что слишком много вспомогательных слов надо писать, в частности, объявления методов
> еще не кидать эксепшн после каждой ошибки, а собирать соответсвующие сообщения об ошибках
в том-то и дело, что удобнее расчитывать именно на exception.
пример: пользователь ввел число, которое не влазит в используемый тип данных, если мы расчитываем не на exception-ы, то проверка тупо на этом свалится без проверки других полей.

kruzer25

в частности, объявления методов
Каких методов?
Если ты про всякие там validatePassport - то оно и потом использоваться будет, не в одной этой validateAll (правда, validateAll я действительно неправильно назвал - всё-таки, оно проверяет не всё, а конкретную форму).

Dasar

Если ты про всякие там validatePassport - то оно и потом использоваться будет, не в одной этой validateAll
зачем?

olga1969

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

6yrop

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

kruzer25

Если я validateAll переименую в validateSomeForm1, понятно станет?

olga1969

в том-то и дело, что удобнее расчитывать именно на exception.
пример: пользователь ввел число, которое не влазит в используемый тип данных, если мы расчитываем не на exception-ы, то проверка тупо на этом свалится без проверки других полей.
если честно, не понял...
ну, вот беру я это число и проверяю
 if (myNumber> MAX or myNumber<MIN){
errString+=" Введенное число вне диапазона";
}

Проверили и поехали дальше:
 
//...
if (myPassport == ""){
errString+=" Введите номер паспорта";
}
else if (!passport.isValid{
errString+=" Ваш пасспорт левый";
}

Ну а потом проверка следующих полей. Если логика проверки там зависит от предыдущих результатов, то заведите переменные и храните там эти результаты.
ну в конце метода:
  if (errString != ""){
throw new MyValidateXXXFormException(errString);
}

// и даже
return 0;


Так не устраивает?

Dasar

не понятно.
должен быть один метод ValidateManData
зачем нужны методы ValidateSomeForm или ValidatePassport мне не понятно

Dasar

> Так не устраивает?
конечно, не устраивает
потому что не понятно, что происходит с непредусмотренными ошибками

olga1969

потому что не понятно, что происходит с непредусмотренными ошибками
это как это так ? :)
что, все предусмотреть не вариант?

olga1969

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

kruzer25

В некоторых случаях Man должен вводить свой паспорт, а в некоторых случаях достаточно только имени и фамилии латиницей.

kruzer25

Ну в Java, насколько я тебя понял, это обязаловка для всех, а не только для конкретной функции validate ;)

bleyman

Комменты прочитал падеаганале, там Пенартур.
1) Обязательно найти как можно больше ошибок. В смысле, не валиться по первой же. Представь себе компайлер, который ругается на первую замеченную ошибку, не давая тебе возможности посмотреть на остальные и догадаться, что за фигня на самом деле произошла.
2) Конечно, нужно кидать эксепшен, а не возвращать false или выставлять в true поле в каком-то левом контексте. И пусть программист выше сам думает, что он хочет сделать, уронить всю прогу (неправильный вариант! проигнорировать ошибку и позволить клиенту загнать её в базу (неправильный вариант!) или сообщить клиенту о его ошибках и не пускать в базу, пока не исправит. Особенно кошерно получится если процесс записи в базу контролируется тем же слоем бизнес-логики, так что даже очень корявый индус не сможет заставить его записать некорректные данные.
Так что проверить, записывая в контекст инфу, а потом радостно кинуть эксепшен, если чо-нить записалось. Чоткие пацаны всегда так делают!

6yrop

сообщить клиенту о его ошибках и не пускать в базу, пока не исправит.
как список ошибок передается на presentation layer? через проброс эксепшна или просто коллекцией?

sbs-66

Я, вообще, не очень понимаю, какая связь валидации введённых пользователем в форму данных с непредвиденными ошибками. Ясно, что пользователь может вводить невалидные данные, и их надо проверять перед использованием, и это никак не непредвиденная ошибка. Их надо обрабатывать штатно. Ну, скажем в случае с формой - проверить все данные, найти ошибки и сообщить о них пользователю, возможно через специальное исключение.
Непредвиденная ошибка - это когда ты, например, создал файл и сохранил куда-то его имя, а потом попытался по этому имени обратиться, а файла там нет. Т.е. какие-то внешние факторы, которые по идее должны быть валидны, но из-за каких-то непонятных внешних событий теоретически могут испортиться (пользователь что-то удалил руками). Тут тоже надо кидать исключение, если дальше продолжать работу невозможно. Ну и выдавать пользователю диагностику - то-то сломалось, того-то нету. Если работу продолжить возможно, но надо на более высоком уровне совершить дополнительные телодвижения (заново сгенерить файл, например то можно снаружи перехватить исключение и сделать нужную обработку.
Ну и третий класс ошибок - это когда у тебя в алгоритме предполагаются какие-то внутренние инварианты. Ну, например, ты полагаешь, что в сформированном тобой хеше будут находиться все элементы из данного массива, потому что он так строится (или что построенный в другом методе граф является деревом). Такие вещи можно проверять assert'ами, которые тоже должны генерировать исключения, но их в общем случае надо перехвативать только на самом высоком уровне, выдавать диагностику пользователю и завершать текущую работу, если возможно, или всё приложение, если ошибка критическая. Эти ошибки по идее будут возникать только при некорректном изменении кода (предполагается одно его поведение, а код стал делать другое при ошибках в программах и при потере целостности данных. Т.е. в штатном режиме пользователь их видеть не должен, а если видит - то значит надо что-то исправлять в программе.

olga1969

как список ошибок передается на presentation layer? через проброс эксепшна или просто коллекцией?
Одно другому не мешает. Создаешь свой класс для эксепшн, наследуя от стандартного класса какого-нибудь, в зависимости от ситуации, и добавляешь туда свои поля, какие нужны именно тебе для отображения ошибок.
Кидаешь ексепшн, его ловит presentation layer, в блоке catch берешь объект этого эксепшена и достаешь нужные тебе поля. Кидаешь их в ЕррорОкно, чтоб юзер увидел ошибки.

6yrop

зачем передавать через throw, если его там ждут через catch?

olga1969

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

6yrop

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

kruzer25

кхм, я не совсем понял
Он, наверное, имеет в виду, что, если exception приходится ловить сразу, то весь смысл exception-ов пропадает ;)

olga1969

а просто положить в коллекцию не судьба?
ну, можно и через коллекцию :)
как возвращаемое значение или как аргумент метода, переданный из presentation layer.
кому как нравится :)
собственно, так раньше и делали вроде, а потом придумали эксепшн'ы, потому что варианты с возвращаемым значением или передаваемым параметром, в который записывалась информация об ошибке, кому-то не нравились...

kruzer25

а потом придумали эксепшн'ы, потому что варианты с возвращаемым значением или передаваемым параметром, в который записывалась информация об ошибке, кому-то не нравились...
...и ещё, чтобы exception-ы могли лететь далеко ;)

sbs-66

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

olga1969

..и ещё, чтобы exception-ы могли лететь далеко
ага
а может и еще зачем-то, я уже все забыл почти...

6yrop

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

bleyman

зачем передавать через throw, если его там ждут через catch?
Чтобы его могли ждать где угодно. Эксепшены придумали для того, чтобы можно было ловить ошибки сколь угодно далеко (столь далеко, как этого требует внутренняя логика проги).
Если ты отсабклассишься от аппликейшенэксепшена, инкапсулируешь массив сообщений об ошибках и оверрайднешь ToString, то пользователь хрени (то есть ты же) сможет вообще ни о чём не думать: когда юзер заполняет поля и тыкает "сабмит", исполнение уходит куда-то вглубь, ктулху ворочается во сне, тут хуяк! прилетает эксепшен! А ловится он непосредственно в мессаджлупе, и пользователю выводится единообразное красивое окошко: "Операцию не удалось осуществить, потому что: бла бла бла", где перечисляются разнообразные причины — бизнеслогика порушена там-то и там-то, база данных недоступна, прав не хватает, етс. Поскольку это происходит в одном месте, то можно даже расщедриться на что-то большее, чем стандартный мессаджбокс, даже можно ричтекстбокс зафигачить или простенький хтмл.

6yrop

оверрайднешь ToString
в реальности этого не достаточно, части сообщения надо подкрасить разными цветами, задать разные шрифты, кое-что оформить гиперссылками и т.п. Извращаться вставлять в ToString html не гуд, теоретически BL используется разными клиентами.
бизнеслогика порушена там-то и там-то

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

6yrop

исполнение уходит куда-то вглубь
т.е. ты предполагаешь длинную цепочку вызовов
PresentationLayerMethod->Method1->Method2->Method3->BusinessLayerValidate. Когда ты кидаешь эксепшен в BusinessLayerValidate и ловишь только в PresentationLayerMethod ты делаешь сильное ограничение на методы Method1, Method2, Method3 — система должна остаться в рабочем состоянии, если методы Method1, Method2, Method3 прерываются на середине.
Это потенциальный источник ошибок при развитии системы. Такой стиль программирования как раз меее надежный.

6yrop

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

kruzer25

система должна остаться в рабочем состоянии, если методы Method1, Method2, Method3 прерываются на середине
Ну так это естественно.
И исключения тут ни на что не повлияют - например, где-то внутри случилась непредусмотренная мегажопа, и у тебя тоже всё вылетело.
Если exception не может лететь дальше одного шага - можно обойтись штатными средствами, без всяких исключений, и исключения становятся такой же фишкой языка, как фигурные скобки вместо begin и end.

6yrop

Ну так это естественно.
И исключения тут ни на что не повлияют - например, где-то внутри случилась непредусмотренная мегажопа, и у тебя тоже всё вылетело.
что естественно? что система должна быть устойчива к прерыванию любого метода в любой точке? Это не так.

kruzer25

что система должна быть устойчива к прерыванию любого метода в любой точке? Это не так.
Почему не так?

6yrop

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

Werdna

Что-то в теме не видно хороших плюсовиков, например, Бачана. Может дурно пахнет? :)
По делу — если и кидать эксепшены, то они должны быть отнаследованы от std::exception.
Кидать надо при критических ошибках, ворнинги надо аккумулировать и выдавать кучей.
А вообще, надо что-то читать отличное от MSDN, а то дурно пахнет в треде. :crazy:

kruzer25

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

try {
begin;
//some dirty work
//DB1
//some dirty work
//DB2
//some dirty work
commit;
} catch(ExceptedException $e) {
rollback;
//process this exception
} catch(Exception $e) {
rollback;
throw $e;
}

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

try {
callSomeFunctionThatCanThrowAnExceptionOrThatCallsSomeOtherFunctionThatCanThrowAnExceptionReallyWeShouldDoThatForEveryFunction;
} catch(ExceptedException $e) {
//process this exception
//cleanup...
} catch(Exception $e) {
//cleanup...
throw $e;
}

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

6yrop

callSomeFunctionThatCanThrowAnExceptionOrThatCallsSomeOtherFunctionThatCanThrowAnExceptionReallyWeShouldDoThatForEveryFunction
жжошь :grin: :grin: :grin:

kruzer25

Ты с таким бизнес-кодом будешь жечь ещё сильнее.

6yrop

 
Смотря как писать.
У нас, например - далеко не каждого;
  

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

6yrop

Ты с таким бизнес-кодом будешь жечь ещё сильнее.
с каким?

kruzer25

Из первого совсем не следует второе, потому что подавляющее большинство методов с самого начала устроены так, что им пофигу, свалятся они там где-нибудь посередине, или нет.
Даже какой-нибудь страшный монстр, которых не должны создавать хорошие мальчики, вроде
function checkAndChange {
checkDate(this.dataItem.date);
this.customer.date = this.dataItem.date;
checkPassport(this.dataItem.date);
this.customer.passport = this.dataItem.passport;
}

или даже что-нибудь нормальное:
function change {
this.check;
customer = Customers::get(this.dataItem.customerId);
customer.date = this.dataItem.date;
customer.passport = this.dataItem.passport;
customer.save;
}

не заметят, если где-нибудь посередине, внутри какого-нибудь метода, произойдёт жопа и вылетит исключение.
Исключения надо обрабатывать только там, где ты работаешь с транзакциями (т.е. где находятся собственно begin-ы и commit-ы). (под словом "надо" имею в виду, что ты обязан их там обрабатывать, хотя, конечно, по желанию можно сделать обработку и ещё где-нибудь посередине, если это там нужно). Никакого усложнения "каждого метода".
А в твоём варианте - действительно, в каждом методе при каждом действии надо смотреть, не вылетело ли исключение, и обрабатывать его. В твоём случае, где, если позвали функцию, которая может кинуть исключение, не в блоке try...catch, то компилатор выругается на синтаксическую ошибку - как раз и придётся усложнять каждый метод, хотя в большинстве случаев это будет просто проброска исключения дальше - та вещь, которой мог бы заняться и язык, и о которой тебе совершенно необязательно знать в данном месте.

kruzer25

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

6yrop

А в твоём варианте - действительно, в каждом методе при каждом действии надо смотреть, не вылетело ли исключение
ты про какой "мой" вариант пишешь? я ваще-то за вариант без исключений (второй вариант из первого поста в треде)

6yrop

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

6yrop

Для пояснения приведу пример в схеме PresentationLayerMethod->Method1->Method2->Method3->BusinessLayerValidate.
пусть метод Method2 был таким
Method2
{
    ...
    Method3;
    ...
}
Клиент заказал некоторый мониторинг. Программист посмотрел на код и видит, что это реализовывается всего двумя строчками когда
Method2
{
    Write("начало обработки");
    ...
    Method3;
    ...
    Write("конец обработки");
}
Внутрь метода Method3 программист решил не заглядывать, поскольку мониторинг требовался как раз на уровне метода Method2.
При исключениях типа нет коннекшна к базе, нет прав и т.д. записи "конец обработки" естественно не должно быть. А вот в штатной ситуации журнал должен быть полным.

Dasar

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

6yrop

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

Dasar

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

class A
{
public void Open
{
if(isOpened)
throw new Exception("Объект A уже открыт");
this.isOpened = true;
this.stream = File.OpenStream("a", FileMode.Open);
....
}
bool isOpened = false;
Stream stream;
}

Метод A.Open написан плохо, т.к. в случае ошибки (невозможно открыть файл 'a') объект A остается в некорректном состоянии, т.к. с одной стороны объект считает, что он открылся (isOpened = true с другой стороны - объект не открыт (stream == null).
причем такой объект уже никаким внешним образом из такого некорректного состояния не вывести, даже если проблема уже пропала.

kruzer25

class A
{
public void Open
{
if(isOpened)
throw new Exception("Объект A уже открыт");
this.isOpened = true;
this.stream = File.OpenStream("a", FileMode.Open);
....
}
bool isOpened = false;
Stream stream;
}
О, наконец-то я понял, что имел в виду.
Мне просто написать так, как в этом примере (сначала меняем состояние флага, а потом открываем файл) просто в голову не придёт.
Да, это проблема языков, где исключения могут лететь далеко - что для работы с ними нужен мозг.

6yrop

Метод A.Open написан плохо
напиши правильный вариант

6yrop

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

kruzer25

Сначала открыть файл, а потом поменять статус ;)
У меня просто в голове не укладывается, как можно написать по другому - какого хрена мы меняем статус на "открыт", когда ещё ничего не открыли?
ЗЫ: А если у нас такая ситуация, когда два раза открывать нельзя - для этого и нужны транзакции.

6yrop

А во-первых, зачем какие-то правила, если можно без них?

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

6yrop

то есть вот так?
class A
{
public void Open
{
if(isOpened)
throw new Exception("Объект A уже открыт");
this.stream = File.OpenStream("a", FileMode.Open);
this.isOpened = true;
....
}
bool isOpened = false;
Stream stream;
}

Но обе переменные isOpened, stream меняются не в конце метода, т.е. правило озвученное -ем не соблюдается.

kruzer25

Не должен.
Потому что, по хорошему, мозг вообще не должен знать, что из этого конкретного метода, который ты вызываешь, может прилететь исключение. Мозг должен знать, что в этом твоём методе ты будешь обрабатывать исключения типа XXX - и тогда тебе надо всё обернуть в try...catch(XXXException $e а остальные исключения пусть летят дальше; или мозг знает, что в этом твоём методе ты не будешь обрабатывать чужие исключения - и тогда никаких try...catch, пусть летят дальше.

6yrop

Не должен.
гыыы :grin: :grin: :grin:
Потому что, по хорошему, мозг вообще не должен знать, что из этого конкретного метода, который ты вызываешь, может прилететь исключение. Мозг должен знать, что в этом твоём методе ты будешь обрабатывать исключения типа XXX - и тогда тебе надо всё обернуть в try...catch(XXXException $e а остальные исключения пусть летят дальше; или мозг знает, что в этом твоём методе ты не будешь обрабатывать чужие исключения - и тогда никаких try...catch, пусть летят дальше.

я ему про Ерёму, а он мне про Емелю :grin:

bleyman

1) Я специально написал, что раз уж ты можешь обрабатывать практически все ошибки в одном месте, то в это одно месте ты можешь воткнуть веббраузерконтрол, которому скармливать содержимое своего эксепшена (особенно если ты _свой_ эксепшен всё-таки сделаешь). Тут главное не увлечься и не нарваться на inner platform effect во всей его красе.
2) Насчёт методов и прочего — окей, если у тебя проверка бизнеслогики вообще совершенно отдельная, то, конечно, заводи специальный синглтон или проперти у Документа или whatever seems fit, засовывай туда варнинги и проверяй когда необходимо, пропуская некоторые действия (типа дополнительных, оказавшихся бессмысленными проверок) и запрещая другие (типа записи в базу). Так нужно делать и я не понимаю, откуда у тебя вообще взялись сомнения.
Эксепшены следует юзать в ситуации, когда ты видишь действительно исключительную ситуацию, пардон май френч. То есть, вот ты говоришь, тяжело писать методы, которые ничего не порушат, если их прервать на середине (вообще говоря не очень тяжело, особенно в языках с GC, особенно если эти методы ничего не изменяют внешнего а только собирают и обрабатывают инфу, ну да ладно). В тот момент, когда ты понимаешь, что тебе становится ещё тяжелее писать методы, которые ничего не порушат _дополнительно_ если где-то по пути обнаружилось что-нибудь порушенное, вот тогда нужно расслабицо и кинуть эксепшен.

Dasar

Я, вообще, не очень понимаю, какая связь валидации введённых пользователем в форму данных с непредвиденными ошибками. Ясно, что пользователь может вводить невалидные данные, и их надо проверять перед использованием, и это никак не непредвиденная ошибка. Их надо обрабатывать штатно. Ну, скажем в случае с формой - проверить все данные, найти ошибки и сообщить о них пользователю, возможно через специальное исключение.
ты знаешь магический способ предусмотреть все варианты некорректного ввода?
тут, например, некоторые товарищи приводили пример с проверкой на больше/меньше, так даже в нем есть проблема, если при вводе будете введено число "NaN", т.к. NaN эту самую проверку легко обойдет, и упадет где-то дальше.

Dasar

> напиши правильный вариант
правильный вариант, вообще, будет (избегаем дублирование информации):

class A
{
public void Open
{
if (isOpened)
throw new Exception("Объект уже открыт");
stream = File.Open(...);
}
bool IsOpened {get {return stream != null;}}
Stream stream;
}

если в лоб переписывать тот вариант, то:

public void Open
{
if (isOpened)
throw new Exception("Объект уже открыт");
FileStream stream = File.Open(...);
...
this.stream = stream;
this.isOpened = true;
}

kruzer25

то есть вот так?
this.stream = File.OpenStream("a", FileMode.Open);
this.isOpened = true;
Да.
Но с учётом того, что isOpened, кроме своей основной роли (открыли ли файл) играет ещё и дополнительную (инициализировано ли всё содержимое класса, выполнено ли всё то, что там в многоточии твой пример целиком надо переписать так:
class A
{
public void Open
{
if(isInitialized) throw new Exception("Объект A уже открыт");
this.stream = File.OpenStream("a", FileMode.Open);
this.isOpened = true;
....
this.isInitialized = true;
}
bool isOpened = false;
bool isInitialized = false;
Stream stream;
}

kruzer25

class A
{
  public void Open
  {
    if (isOpened)
     throw new Exception("Объект уже открыт");
    stream = File.Open(...);
  }
  bool IsOpened {get {return stream != null;}}
  Stream stream;
}
Не нравится мне такой вариант, а именно - то, что тут считается, что isOpened и stream != null - одно и то же.
Нихрена неочевидно, непортируемо и трансанально.
Зачем на флагах-то экономить?

6yrop

class A
{
  public void Open
  {
    if (isOpened)
     throw new Exception("Объект уже открыт");
    stream = File.Open(...);
  }
  bool IsOpened {get {return stream != null;}}
  Stream stream;
}
так надо было сразу так писать. И если бы ты так сразу написал, то твой пример ничего бы не иллюстрировал. А раз уж ты что-то иллюстрируешь этим примером, то сохраняй флаг. Вообще, использовать флаг или обойтись null-ом это отдельный открытый вопрос, как правильно отметил Пенартур. Если хочется обсудить флаги, открывайте новую тему. А раз уж ты начал с примера с флагами, то давайте их сохранять, а не уводить тему в сторону.

  public void Open
  {
    if (isOpened)
     throw new Exception("Объект уже открыт");
    FileStream stream = File.Open(...);
    ...
    this.stream = stream;
    this.isOpened = true;
  }
 

Если мы открываем файл на чтение (т.е. операционная система лочит его) и в " ... " случится эксепшен, то кто сможет отпустить файл кроме сбощика мусора? Метод Open уже закончился из-за эексепшена, а вне метода ссылки на экземпляр стрима нет, т.е. метод A.Close или A.Dispose в такой ситуации не поможет.

kruzer25

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

6yrop

public void Open
{
if (isOpened)
throw new Exception("Объект уже открыт");
FileStream stream = File.Open(...);
...
this.stream = stream;
this.isOpened = true;
}
кстати, что такое конец метода? Формально переменная this.stream меняется не в конце метода. Вдруг в строчке "this.stream = stream" случится эксепшен. Да, это мало вероятно, но вдруг определен какой-нибудь оператор неявного приведения типов, и он вызовется при присвоении. Или проще, переменная всегда меняется через свойство "this.Stream = stream". Или ты еще введешь ограничение, что при "изменении состояния сущностей" нельзя пользоваться неявными операторами преобразования типов и свойствами?

6yrop

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

какой-то бред! Берем исходник произвольно выбранного метода из .NET Framework-а, например StreamWriter.Write

public override void Write(char value)
{
if (this.charPos == this.charLen)
{
this.Flush(false, false);
}
this.charBuffer[this.charPos] = value;
this.charPos++;
if (this.autoFlush)
{
this.Flush(true, false);
}
}

приватная переменная класса charPos меняется в середине метода. Никто это бредовое правило не соблюдает.

6yrop

Так нужно делать и я не понимаю, откуда у тебя вообще взялись сомнения.
о кул :D У меня то сомнений не было, а сейчас тем более нет. Это мой коллега выступает за пробросы эксепшенов. Он бауманец, и говорит, что при разработке софта надо пользоваться справочниками, так же как инженеры пользуются справочниками по сопромату. И для него справочник это MSDN :smirk: , и там есть статейка
http://msdn2.microsoft.com/en-us/library/aa581779.aspx#aspne...
в которой приведен код
 
     // one supplier
     if (discontinued)
     {
     // Get the products we buy from this supplier
     Northwind.ProductsDataTable productsBySupplier = Adapter.GetProductsBySupplierID(product.SupplierID);
     if (productsBySupplier.Count == 1)
     // this is the only product we buy from this supplier
     throw new ApplicationException("You cannot mark a product as discontinued if its the only product purchased from a supplier");
     }
  

и для него это железный аргумент. Он говорит, что думать сам он не хочет, за него думает Microsoft :smirk: . Оправдывает он это тем, что думать самому экономически не выгодно ;)
P.S. ну я конечно слегка передернул ;) , но настрой передал

Dasar

> кстати, что такое конец метода? Формально переменная this.stream меняется не в конце метода. Вдруг в строчке "this.stream = stream" случится эксепшен. Да, это мало вероятно,
серебряной пули не бывают, бывали лишь правила, которые упрощают жизнь в большинстве случаев.
это правило такое же.

Dasar

Не нравится мне такой вариант, а именно - то, что тут считается, что isOpened и stream != null - одно и то же.
Нихрена неочевидно, непортируемо и трансанально.
Зачем на флагах-то экономить?
одно из основных зол при программирование - это дублирование (кэширование) информации.
т.к. как только появляется дублирование - так сразу возникает задача синхронизации дублей.
доп. флаги - такое же зло, т.к. сразу становится намного сложнее решать задачи: переинициализации, деинициализации и т.д., т.к. легко можно получить состояния: isInitialized - false, а IsOpened - true и т.д.
флаги стоит вводить - только если нельзя это знание вычислить на основе уже имеющего знания.
если для работы объекта - нужно чтобы файл был открыть, то значит - что объект открыт, если открыт файл, зачем придумывать какие-то искусственные флаги, которые ничего не означают?

feliks28

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

olga1969

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

trobak

А если при этом еще флаг часто используется, то что, весь это код каждый раз заново рисовать?
ну видно же в приведенном примере, что этот код написан один раз в инлайновой функции

kruzer25

А если при этом еще флаг часто используется, то что, весь это код каждый раз заново рисовать?
В коде (насколько я понимаю, это был C#) ничего заново писать не надо, оно само считается при обращении к флагу isOpened.

olga1969

ну видно же в приведенном примере, что этот код написан один раз в инлайновой функции

В коде (насколько я понимаю, это был C#) ничего заново писать не надо, оно само считается при обращении к флагу isOpened

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

но isOpened флаг ведь! И в коде он там потом используется.
короче, ладно, проехали.

Dasar

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

Dasar

Не нравится мне такой вариант, а именно - то, что тут считается, что isOpened и stream != null - одно и то же.
Нихрена неочевидно, непортируемо и трансанально.
как раз наоборот.
Скажи своими словами за что отвечает флаг IsOpened?
флаг IsOpened отвечает за то, что все необходимые ресурсы для класса созданы и открыты.
именно этот код мы и видим в IsOpened

bool IsOpened
{
get {return stream != null;}
}

если нам нужно несколько ресурсов, то код будет таким

bool IsOpened
{
get {return inStream != null && outStream != null && errStream != null ;}
}

Внимание.
Как по коду с явным флагом IsOpened понять за что отвечает этот флаг?
просмотреть каждую строчку кода во всем классе? почитать ТЗ на класс?

kruzer25

флаг IsOpened отвечает за то, что все необходимые ресурсы для класса созданы и открыты.
Да.
Но то, что stream == null, вообще-то, не означает, что ресурсы не созданы и не открыты. Как и то, что stream != null не означает, что всё замечательно. Всё зависит от той функции, которой ты открываешь файл.

Ulala

Но тут столкнулся с человеком, который упорно отстаивает первый вариант, при этом ссылается на статьи из MSDN-а (завтра запощу ссылку

Искал ссылку и не нашел. Может ее не было?

6yrop

как оказалось в .NET 3.5 тоже предлагают эексепшены :mad:
http://weblogs.asp.net/scottgu/archive/2007/07/16/linq-to-sq...

evolet


public void Open
{
if (isOpened)
throw new Exception("Объект уже открыт");
FileStream stream = File.Open(...);
...
this.stream = stream;
this.isOpened = true;
}

Если мы открываем файл на чтение (т.е. операционная система лочит его) и в " ... " случится эксепшен, то кто сможет отпустить файл кроме сбощика мусора? Метод Open уже закончился из-за эексепшена, а вне метода ссылки на экземпляр стрима нет, т.е. метод A.Close или A.Dispose в такой ситуации не поможет
что-бы такого не было, деструктор FileStream должен освобождать захваченные ресурсы(разлочить файл С# для этого есть команда using, так что FileStream.Dispose будет по-любому вызван в конце видимости локального stream

evolet

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

6yrop

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

Да, ссылка на Страуструпа будет выглядеть более авторитетно, чем какая-то статейка из MSDN-а. Ты не мог бы привести эту ссылку? (сейчас попробую найти)

sylar

По моему скромному мнению:
кидать ексепшоны (тем более нетипизированные и уровня приложения - когда прилетает такой эксепшон имхо это значит что приложение будет завалено, нужно освободить ресурсы и кинуть его дальше ) в этой ситуации не очень правильно.
Валидатор он должен валидировать - т.е. если что-то неправильно то это вполне штатная ситуация.
Т.е. на мой взгляд сигнатура должна быть такая:
bool Validate(IDataItem dataItem).
т.е. чтоб тупо говорило Да или Нет.
если нужны подробности то можно добавить перегруженный метод
bool Validate(IDataItem data, CallOnError function);
(т.е. кого вызывать если что-то пошло не так - туда например писать сообщение, либо типизированный ексепшн туда складывать чтоб его потом кинуть можно было и т.п.)
Т.е. я хочу сказать что дело валидатора сказать валиден ли объект или нет, а что с этой информацией делать и как ее обрабатывать не его уровень компетенции. Он вызывает function, складывает туда аргументы какие попросят и все - как валидатор считает свою работу выполненной.
Учитывая то что бизнес-логика будет изменяться (несмотря на то что есть ТЗ, все долго согласовывалось и т.п.) то я бы сделал вот эти самые валидаторы плагинами (ну то есть подключал при помощи IoC). С объектами тоже бы через их интерфейсы работал...
И основные цифры (25000,-10) там же (в конфиге IoC) бы и указывал а не хардкодил. Там же в ресах сборки писал бы мессаги (а то вдруг вот помимо английского и русского захочется например суахили подключить для отображения ошибок). и т.д. (это все от лени...)
По поводу обработки ексепшнов имхо политика простая :)
Ведь все на что юзер реально может повлиять он вводит через UI или файлы загружает или еще как. Вот там же и нужно проверять валидность данных (при использовании MVP это уровень презентеров) и писать юзеру красным если что не так чтоб возраст отрицательным не вводил и т.д.
А если каким-то образом в системе оказались невалидные данные то это п..ц - этого быть НЕ ДОЛЖНО.
Соответственно если в приложении происходит какая-то ошибка, не зависящая от действий пользователя то имхо следует писать "Произошла ошибка приложения, сохраняйте спокойствие, письмо ушло разработчикам, скоро с вами свяжется наша зондеркоманда и все будет хорошо..."
Ну и перенаправлять на главную или куда там еще.
Зачем пользователю знать что "слишком большая сумма" и "слишком рано" ? Как он на это повлиять может ? запугаете пользователя почем зря :)
Но это если пользователь ничего поделать не может. если там всякая фигня типа с базой конекта нет и т.п. то так и писать что "Через некоторое время повторите операцию".
вот вкратце и "все, что я могу сказать по этому поводу"

Dasar

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

evolet

Я помотрел, я немного спутал, у Страуструпа я не нашел там рекомендаций на эту тему. Он даже наоборот вроде как ничего не имеет против для использования исключений как своеобразных управляющих конструкций. Но я вспомнил, что про это я читал в бумажной книжке "Стандарты программирования на C++" - Саттер, Александреску (серия С++ In-Depth под редакцией Страуструпа). Как раз по теме треда там советы 70 и 72. Хоть это и не Страструп, но авторы тоже, имхо, достаточно авторитетные дядьки, к советам которых можно прислушаться.
PS че-то по-быстрому в инете я эту книжку нашел только в rapidshare : http://rapidshare.com/files/50079697/standarty_programmirova...
Оставить комментарий
Имя или ник:
Комментарий: