Здравствуйте, Cyberax, Вы писали:
>> Так может такие проверки должны быть штатными и использоваться не >> только в режиме отладки?
C>А зачем? assert показывает ошибки программиста, а не пользователя. То C>есть ни при каком варианте работы событий пользователь не должен его C>получить, а значит и включать его не надо.
Для библиотечного кода эти проверки должны присутствовать и как правило присутствуют, загляни хотя бы в исходники .NET FW.
C>В рельной жизни assert'ы обычно оставляют в бэта-версиях, и выкидывают в релизах.
В реальной жизни фича отладчика, о которой я говорил выше, позволяет отловить всё тоже самое и без всякого замусоривания кода макросами. В общем, в ASSERT похоже тоже можно относить к устаревшим средствам и правилам типа венгерки, SELECT* и единственного return.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Сейчас чего-нибудь изобразим.
Написали Вы много, но принципиальной разницы с предыдущим примером нет.
MODULE RsdnUI;
IMPORT StdLog, Errors := RsdnErrors, AccountManager := RsdnAccountManager;
PROCEDURE WithdrawalAndGetBalance*;
VAR newBalance: REAL; err: Errors.List;
BEGIN
IF AccountManager.WithdrawalAndGetBalance("12345", 1.0E2, newBalance, err) THEN
StdLog.String("New balance is: "); StdLog.Real(newBalance); StdLog.Ln
ELSE
StdLog.String("Oops: "); StdLog.Ln;
Errors.Print(err)
END
END WithdrawalAndGetBalance;
END RsdnUI.
MODULE RsdnAccountManager;
IMPORT Errors := RsdnErrors;
TYPE
AccountService = RECORD(*...*)END;
AccountDataAccessor = RECORD(*...*)END;
...
PROCEDURE (VAR this: AccountService) WithdrawalAndGetBalance (accountNumber: ARRAY OF CHAR; amount: REAL; OUT rest: REAL; VAR err: Errors.List): BOOLEAN, NEW;
VAR r: BOOLEAN; accessor: AccountDataAccessor;
(*...*)PROCEDURE Do (): BOOLEAN;
VAR ignored: Errors.List; (* ignored errors for steal-methods *)BEGIN
IF accessor.GetBalance(accountNumber, rest, err) THEN
IF rest >= amount THEN
IF accessor.ChargeForService(accountNumber, 10, err) THEN
rest := rest - amount - 10;
IF (rest > 1.0E6) & accessor.StealSomeForMe(accountNumber, 1.15, ignored) THEN rest := rest - 1.15 END;
IF (rest > 1.0E7) & accessor.StealSomeForHomelessPeople(accountNumber, 1.15, ignored) THEN rest := rest - 1.15 END;
RETURN accessor.SetBalance(accountNumber, rest, err)
END
ELSE
err := Errors.NewList(Errors.NewError("You are freaking bankrupt!"), err); (* Вот такое вот "кидание исключения" *)END
END;
RETURN FALSE
END Do;
(*...*)BEGIN r := ValidateAccountNumber(accountNumber, err) & ValidateAmount(amount, err);
IF r THEN
accessor.Init;
r := accessor.BeginTransaction(err) & Do() & accessor.CommitTransaction(err);
accessor.Close
END;
RETURN r
END WithdrawalAndGetBalance;
...
PROCEDURE WithdrawalAndGetBalance* (accountNumber: ARRAY OF CHAR; amount: REAL; OUT rest: REAL; VAR err: Errors.List): BOOLEAN;
VAR r: BOOLEAN; service: AccountService;
BEGIN r := ValidateAccountNumber(accountNumber, err) & ValidateAmount(amount, err);
IF r THEN
service.Init;
r := service.WithdrawalAndGetBalance(accountNumber, amount, rest, err);
service.Close
END;
RETURN r
END WithdrawalAndGetBalance;
...
END RsdnAccountManager.
MODULE RsdnErrors;
IMPORT StdLog;
CONST
max = 256;
TYPE
Error* = POINTER TO EXTENSIBLE RECORD
desc*: ARRAY max OF CHAR;
END;
List* = POINTER TO RECORD
head-: Error;
tail-: List
END;
PROCEDURE NewError* (desc: ARRAY OF CHAR): Error;
VAR e: Error;
BEGIN ASSERT(LEN(desc) < max, 20);
NEW(e); e.desc := desc$; RETURN e
END NewError;
PROCEDURE NewList* (head: Error; tail: List): List;
VAR u: List;
BEGIN ASSERT(head # NIL, 20);
NEW(u); u.head := head; u.tail := tail; RETURN u
END NewList;
PROCEDURE Print* (err: List);
BEGIN
WHILE err # NIL DO
StdLog.String(err.head.desc); StdLog.Ln; err := err.tail
END
END Print;
END RsdnErrors.
Здравствуйте, Privalov, Вы писали:
P>Еще раз: у нас нет ни малейшего представления о функции f(x). Поэтому если функция не определена в какой-то точке интервала — это исключительная ситуация.
Любая функция в некоторой области либо регулярна, либо не регулярна; определена или не определена; вычислима или не вычислима. Вы говорите что представления у нас о ней нет, это означает что мы должны думать о ней самое худшее. А раз так, то она может возвращать кроме чисел еще и nonNumber, plusInfinity, minusInfinity. До кучи, пусть она еще возвращает значение nonCalculable — нельзя вычислить.
СГ>>Если заранее известно, что процедура-функция f(x) может быть не определена ...
P>Не известно.
Здравствуйте, vdimas, Вы писали:
V>Сделаю акцент еще раз на том факте, что корректное поведение программы в случае использования множественного return в неожиданных местах обычно обеспечивается доп. ср-вами самого языка. В С++ это автоматический вызов деструкторов локальных объектов (что крайне удобно, т.к. эти деструкторы вызываются только для реально инициализированных локальных переменных), в Java/C# есть try-finally для анаолгичных целей (не столь элегантно, но тоже вполне можно использовать).
V>Таким образом, верификация даже такого кода может быть полной.
Погоди, верификация и корректность работы компилятора — не одно и то же. Язык просто даёт нам некие автоматически работающие механизмы и не более того. И там и там есть оговорки использования и т.п. Можно и в деструкторе RAII исключение пульнуть.
Другое дело, что верификация невозможна без спецификации, которой должна соответствовать программа. Нет спецификации — верификация невозможна. Вывал AV в некоторых ситуациях может расцениваться как штатное и запланированное поведение. То есть, сначала нужна полная спецификация: что можно, чего нельзя и т.п. Но такая спецификация по сложности сопоставима с самой программой, то есть, получим ту же проблему, но в профиль: языки спецификации, наследование спецификаций (множественное vs одиночное), синтаксический оверхед при специфицировании поведения и т.п.
V> Другое дело, что мы не сможем верифицировать отдельно взятый участок (вернее можем, поясню далее), т.е. необходимо больше информации, о том, что происходит "неявно" для случая С++.
Да ну? А что, в случае Java/C# работа GC уже стала 100% детерминированной? А полиморфный код тоже 100% сможем верифицировать?
V>Однако, и здесь нас ожидают бенефиты. Мы можем построить верификацию кода иерархически, т.е. проверифицировать сначала "прочистку", а затем код, использующий ее неявный вызов. Ввиду того, что целевой код неплохо сокращается в случае множественного return (или использования exceptions как продвинутой разновидности), мы можем получить как раз больше возможностей для верификации на "реальных объемах".
Не, гиблая затея на любых объёмах. Примерно такое же ожидание панацеи, как её в своё время ожидали от перехода к языкам высокого уровня. Просто проблемы сместятся с одного уровня на другой. Всё равно задач адекватного учёта внешних условий и построения точных формулировок никто не снимет.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, eao197, Вы писали:
E>Самое время синтаксический оверхед померять
Обязательно померяем И развернём его однострочный код, чтобы строчки посчитать, и количество ветвлений программы учтём. И даже поставим на вид то, что тырить без проверки результата не хорошо. Ну а уж за незакрытую транзакцию будем пинать ногами от всей души
Если нам не помогут, то мы тоже никого не пощадим.
IT wrote:
> C>А зачем? assert показывает ошибки программиста, а не пользователя. То > C>есть ни при каком варианте работы событий пользователь не должен его > C>получить, а значит и включать его не надо. > Для библиотечного кода эти проверки должны присутствовать и как > правило присутствуют, загляни хотя бы в исходники .NET FW.
Стоп. В FW куча проверок пост/пред-условий — это вполне нормально, если
клинет может передать неправильное значение. В случае FW клинетом
является пользовательское приложение.
Assertы нужны для проверки "невозможных" случаев, типа:
В данном случае проверяется инвариант класса — существование хотя бы
одного символа (с кодом 0). Если он нарушен — то это уже ошибка
программиста библиотечного класса или побочный эффект какой-нибудь
глобальной ошибки.
> C>В рельной жизни assert'ы обычно оставляют в бэта-версиях, и > выкидывают в релизах. > В реальной жизни фича отладчика, о которой я говорил выше, позволяет > отловить всё тоже самое и без всякого замусоривания кода макросами.
Ну да, просто заменить на замусоривание исключениями.
> В общем, в ASSERT похоже тоже можно относить к устаревшим средствам и > правилам типа венгерки, SELECT* и единственного return.
НЕТ! Тогда стоит к устаревшим правилам отнести и хороший стиль
программирования. В конце концов, он в .NET не нужен....
Здравствуйте, Cyberax, Вы писали:
C>Assertы нужны для проверки "невозможных" случаев, типа:
Это всё легко покрывается юнит-тестами. Или ты их заменяешь асёртами?
>> В реальной жизни фича отладчика, о которой я говорил выше, позволяет отловить всё тоже самое и без всякого замусоривания кода макросами.
C>Ну да, просто заменить на замусоривание исключениями.
Зачем? Предусловия в библиотечном или, например, в серверном коде проверять всё равно надо. А прикладной код нужно просто не лениться тестировать. В независимости от того используются асёрты или нет.
>> В общем, в ASSERT похоже тоже можно относить к устаревшим средствам и >> правилам типа венгерки, SELECT* и единственного return.
C>НЕТ! Тогда стоит к устаревшим правилам отнести и хороший стиль программирования. В конце концов, он в .NET не нужен....
Зачем передёргивать? Или ты хочешь сказать, что хороший стиль и асёрты — это синонимы?
ЗЫ. Не хочу с тобой продолжать бодаться, всё равно ни мне тебя, ни тебе меня похоже не переубедить. Скажу только, что лично я, никакой практической пользы от асёртов сегодня не вижу. Речь идёт о .NET/C#. Такое моё сугуболичное ИМХО. Пойду лучше Серёгу за забытый ролбэк отстегаю
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Сергей Губанов, Вы писали:
IT>>Сейчас чего-нибудь изобразим.
СГ>Написали Вы много,
А ты думал ты в сказку попал. Хватит в солдатиков в песочнице играться, welcome to the real world.
СГ>но принципиальной разницы с предыдущим примером нет.
А вот это мы сейчас и узнаем
Непонятно почему ты пропустил целый лэйер, но да бог с ним. Даже и с ним твой код рабочим назвать нельзя. Точнее он будет работать, но только в чистом, глубоком вакууме, т.к. увлёкшись бесконечными if'ами и &'ми ты совсем забыл сделать Rollback транзакции в случае ошибки. Ошибки двух методов StealSomeXXX ты тоже почему-то решил не обрабатывать. Отговорки типа ну я мол только идею продемонстрировал не принимаются. Впрочем, как раз её ты и "продемонстрировал" — сам понатыкал сосен и сам же в них запутался.
А теперь давай перепишем твой код на C# так чтобы он по идее был рабочим и можно было хоть что-то хоть как-то сравнивать. На строчки скупиться не будем (я же в своём примере не экономил). Я буду писать в своём стиле, но придерживаясь твоей идее. Любые замечания по существу принимаются.
Итак, первый слой — UI. Мой код из предыдущего примера.
Нормально, да? Мало того что я на него потратил примерно на порядок больше времени, так ещё после того как я его закончил мне захотелось плакать
Итого имеем. Увеличение количество строк кода в два раза. Усложнение логики за счёт ветвления кода в 5 раз. В принципе, это и есть реальные, даже несколько заниженные цифры. Код начинает ветвиться и разрастаться с ужасающей быстротой, так же быстро теряется наглядность и появляются повторяющиеся фрагменты типа dataAccessor.Close() в примере. Уж извини, старался держаться в рамках твоей идеи. Для того, чтобы это хоть как-то контролировать в реальной жизни (а не в песочнице), некоторые проверки начинают пропускаться или просто забываться делаться с соответствующими последствиями. При внесении изменений в такой, уже казалось бы отлаженный код, внести новый баг — это обычное дело. Чтобы этого избежать, нужно тщательнейшим образом разобраться в этом бреде и делать всё максимально аккуратно, а это время, много времени.
Код с исключениями, как нетрудно заметить, имеет линейную структуру, прост и нагляден. Сделать ошибку в нём трудно, вносить изменения просто.
Некоторые, особенно одарённые товарищи, додумываются вообще до просто таки гениальной идеи. Если мы уже всё равно передаём структуру err, то почему бы в неё не затолкать какой-нибудь код, который можно было бы проверить на клиенте и сделать что-нибудь этакое. Т.е. коды результата уже начинают использоваться не только по своему прямому назначению, но и для других, внебрачных целей. Понять потом логику таких программ можно только "поднявшись" до уровня таких гениев.
public bool GetBalance(string accountNumber, out newBalance, ref Errors err)
{
// oops!!! А ExecuteScalarSp параметра Errors не поддерживает.
// Ну дятлы её какие-то писали, не додумались до такой классной идее.
//
///////////return (decimal)ExecuteScalarSp("GetBalance", accountNumber);
}
// остальные упсы поскипаны.
В случае со "стандартной" ExecuteScalarSp, чтобы сохранить лицо, тебе скорее всего придётся написать длиннючий switch на все возможные коды возврата и подобрать к ним более менее вменяемые сообщения.
Да, кстати, использование ref и out параметров считается признаком плохого дизайна. FxCop на это заявляет "Consider a design that does not require that 'parameter_name' be an out parameter".
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
IT>public bool GetBalance(string accountNumber, out newBalance, ref Errors err)
IT>{
IT> // oops!!! А ExecuteScalarSp параметра Errors не поддерживает.
IT> // Ну дятлы её какие-то писали, не додумались до такой классной идее.
IT> //
IT> ///////////return (decimal)ExecuteScalarSp("GetBalance", accountNumber);
IT>}
IT>// остальные упсы поскипаны.
IT>
IT>В случае со "стандартной" ExecuteScalarSp, чтобы сохранить лицо, тебе скорее всего придётся написать длиннючий switch на все возможные коды возврата и подобрать к ним более менее вменяемые сообщения.
Насколько я понимаю, проблемы возникаеют когда встречаются два взаимоисключающий подхода (обработки исключений, в данном случае). Если бы стиль ref-out был общепринятым, то и ExecuteЧего-То-ТамSp его бы тоже поддерживал. И в этом случае пришлось бы написать длиннючий switch для того, чтобы Errors err преобразовать в исключения.
IT>Да, кстати, использование ref и out параметров считается признаком плохого дизайна. FxCop на это заявляет "Consider a design that does not require that 'parameter_name' be an out parameter".
Полагаю, FxCop не знает об использования стиля ref-out как альтернативы исключениям. И если ref-out плох, то отнюдь не потому, что FxCop его не понимает. Если бы более удобного подхода обработки исключений не было, чем ref-out, то и FxCop на него бы не ругался.
Здравствуйте, IT, Вы писали:
IT>Это всё легко покрывается юнит-тестами. Или ты их заменяешь асёртами?
Чего-то не понял а Вашей шутки. Конечно, безусловно, однозначно, в любую погоду никакие юнит-тесты в принципе не способны заменить ассерты. Грамотно расставленные ассерты предусловия, инвариантов и постусловия — это доказательство правильности программы вообще, а юнит-тесты — лишь свидетельство, что данные конкретные тесты проходят, но не доказательство правильности программы вообще.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Грамотно расставленные ассерты предусловия, инвариантов и постусловия — это доказательство правильности программы вообще...
Абсурд. ИМХО.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Любая функция в некоторой области либо регулярна, либо не регулярна; определена или не определена; вычислима или не вычислима. Вы говорите что представления у нас о ней нет, это означает что мы должны думать о ней самое худшее. А раз так, то она может возвращать кроме чисел еще и nonNumber, plusInfinity, minusInfinity. До кучи, пусть она еще возвращает значение nonCalculable — нельзя вычислить.
Вообще-то в таких случаях исключительную ситуацию создает аппаратура.
Я молчу о том, что вычисления компьютерные чуточку отличаются от вычислений на бумаге, и какая-нибудь бяка вполне может выскочить на ровном месте.
СГ>>>Если заранее известно, что процедура-функция f(x) может быть не определена ...
P>>Не известно.
СГ>А мне, например, это известно.
Здравствуйте, Privalov, Вы писали:
P>Здравствуйте, Сергей Губанов, Вы писали:
СГ>>Любая функция в некоторой области либо регулярна, либо не регулярна; определена или не определена; вычислима или не вычислима. Вы говорите что представления у нас о ней нет, это означает что мы должны думать о ней самое худшее. А раз так, то она может возвращать кроме чисел еще и nonNumber, plusInfinity, minusInfinity. До кучи, пусть она еще возвращает значение nonCalculable — нельзя вычислить.
P>Вообще-то в таких случаях исключительную ситуацию создает аппаратура. P>Я молчу о том, что вычисления компьютерные чуточку отличаются от вычислений на бумаге, и какая-нибудь бяка вполне может выскочить на ровном месте.
Да дело то не в том, что бяка может выскочить на неровном месте, а в том, что реальное программирование всегда происходит в режиме недостаточной информации. Сегодня мы были полностью уверены в том, что функция возвращает только числа, и полностью определена на всем интервале. А завтра прибежал заказчик и сказал, что под "полностью определена на всем интервале" он имел в виду "кроме, быть может, счетного множества точек".
Я уже говорил, что параноидальное программирование вредно. Потому что вместо написания
s = 0;
foreach(x in xRange)
foreach(y in yRange(x))
{
s+= f(x)*f(y);
}
return s;
мы вынуждены писать
foreach(x in xRange)
foreach(y in yRange(x))
{
X = f(x);
if (X == nonNumber)
return nonNumber;
Y = f(y);
if (Y == nonNumber)
return nonNumber;
XY = X*Y;
if (XY == infinity)
return infinity;
s+=XY;
if (s == infinity)
return infinity
}
}
Это я еще простое выражение взял. А если бы там было что-то типа f(x, y)*g(x) + f(y, x)*f(x, y)+g(y)/g(x*y), то можно было б просто удавиться от всех этих ветвлений.
На самом деле ситуация еще хуже — даже если мы параноидально предположили, что функция может возвращать infinity и nonNumber помимо нормальных чисел, совершенно не факт, что заказчику завтра не захочется отличать plusInfinity от minusInfinity. И все эти требования невозможно заранее зафиксировать. Но академическое программирование чуждо этих нелепых подробностей — в нем на построение программы отводится бесконечное время, потому что цена ресурсов равна нулю. Бесплатные студенты потратят 0 рублей на написание как 100 строк, так и 1000.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Да дело то не в том, что бяка может выскочить на неровном месте, а в том, что реальное программирование всегда происходит в режиме недостаточной информации.
[...]
Мне-то это понятно. Я, правда, не всегда могу нормально объяснять. За что мне время от времени еще в институте снижали оценки на экзаменах. Да и сейчас на всяких там совещаниях я мало разговариваю.
D>Насколько я понимаю, проблемы возникаеют когда встречаются два взаимоисключающий подхода (обработки исключений, в данном случае). Если бы стиль ref-out был общепринятым, то и ExecuteЧего-То-ТамSp его бы тоже поддерживал. И в этом случае пришлось бы написать длиннючий switch для того, чтобы Errors err преобразовать в исключения.
Не всегда. Например стороняя либа возвращает какие-то свои ошибки, а нам необходимо преобразовать их в коды ошибок/исключения которые используются в приложении
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, Privalov, Вы писали: P>Мне-то это понятно. Я, правда, не всегда могу нормально объяснять. За что мне время от времени еще в институте снижали оценки на экзаменах. Да и сейчас на всяких там совещаниях я мало разговариваю.
А ты в форумы пиши
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, dshe, Вы писали:
D>Насколько я понимаю, проблемы возникаеют когда встречаются два взаимоисключающий подхода (обработки исключений, в данном случае). Если бы стиль ref-out был общепринятым, то и ExecuteЧего-То-ТамSp его бы тоже поддерживал. И в этом случае пришлось бы написать длиннючий switch для того, чтобы Errors err преобразовать в исключения.
В принципе, AndrewJD уже ответил. В дополнении могу сказать, что в случае с исключениями нам делать ничего не надо, т.к. они просто пролетят выше. Если мы их хотим сконвертировать в коды возврата, то достаточно их отловить, выковырять из них сообщение и вернуть даже один код возврата на все случаи жизни, мол не шмагла. Если же метод будет возвращать тоже коды возврата, да ещё и без сообщений, то придётся обрабатывать их все, что-то можно забыть, что-то может быть добавлено разработчиками метода позже.
D>Полагаю, FxCop не знает об использования стиля ref-out как альтернативы исключениям. И если ref-out плох, то отнюдь не потому, что FxCop его не понимает. Если бы более удобного подхода обработки исключений не было, чем ref-out, то и FxCop на него бы не ругался.
Это просто более громоздкая конструкция. В том же примере можно было создать err в вызывающем методе и передавать его обычным способом. Т.е. out/ref параметров практически всегда можно избежать и их наличие очень точный индикатор проблем в дизайне.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.