Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, reductor, Вы писали:
R>>Я? представляю, а что? R>>А можно ли услышать что такое "реальные объемы"? А то может у меня какие-то они нереальные, мало ли. R>>И почему даже если так, то нужно делать код менее верифицируемым, чем можно?
V>Кстати, сам я тоже склоняюсь к мысли, что по-возможности надо верифицировать код, а не побочные эффекты его работы. Заявление о том, что "раз программа прошла все тесты, значит она правильная" не вызывает у меня ничего, кроме скепсиса.
V>Однако, "реальные объемы кода" — это такие объемы в пересчете на одного разработчика, которые приняты сейчас в реальных проектах. Если С++ программист должен выдавать от 200 и до... (иногда 400-600) строк в день, то верификация таких объемов займет в разы больше времени и потребует гораздо более дорогих в почасовом плане ресурсов, чем ресурс автора целевого кода. В общем подход, основанный на полной верификации кода элементарно неприемлим для многих контор.
По-поводу теоретических размышлений на тему верификации, bug preventing и MSDN, хочу просто рассказать несколько фактов:
1. Сначала посчитаем количество строк:
Это директория с исходниками компилятора GHC. Который развивают и поддерживают в основном 2(два) человека и несколько мелких контрибьютеров. Их зовут Simon Peyton Jones и Simon Marlow. По поводу каждого крупного изменения, они как правило делают публикацию с формальным обоснованием. Компилятор работает на windows, linux, macos, нескольких других юниксах и нескольких процессорах и охват платформ только расширяется.
2. С недавних пор они оба работают в Microsoft Research. (там же с ними и Tony Hoare)
3. http://lists.seas.upenn.edu/pipermail/types-list/2005/000903.html
4. http://research.microsoft.com/projects/ilx/fsharp.aspx
5. По поводу количества строк, я ничего не скажу, для меня это несколько странный критерий. Но использование формальных методов на Java (Статический анализ, правила кодирования) в моем случачае показали увеличение производительности раза в 2-3. В основном за счет значительно сократившегося потока багрепортов и возможности на ранней стадии обнаружить огрехи в архитектуре и дизайне приложения.
6. Я не утверждал, а) что следует повсеместно внедрять конструкции с полной проверкой. даже в паре мест специально отметил, что этого не утверждаю. б) во всяком промежуточном и интерфейсном коде я сам частенько пишу абы как. контекст позволяет — там часто достаточно протестировать, чтобы быть уверенным, что все работает.
7. Однозначно не утверждал, что это единственная возможность и самое главное условие для получения корректного кода. Просто речь зашла именно об этом.
8. Формальный подход — это следствие, а не причина.
8. Хотел тут написать про языки изначально максимально верифицируемые и формальные (такие как хаскель), но как-то слишком громко выходит. Косвенные выводы см. пункт 1 :)
V>----------- V>Кстати, ты писал, что ничего не имеешь против exceptions. Тем более странно твое отрицание множественного return. Для меня эти возможности как бы равнозначные, с тем отличием, что exceptions — это автоматизированная разновидность обсуждаемого + возможность синтаксически обощать места кода по обработке ошибки.
Иксепшены запросто формализуются как частный случай continuations. Коими они, нужно сказать, в большом количестве языков и являются (Common Lisp, Scheme, Smalltalk, O'Caml, Haskell). И уж точно почти везде они лишь добавляют формальности (можно попробовать в Java не отловить нужный иксепшен и посмотреть что на это скажет компилятор).
V>Предвижу замечания, насчет того, что exceptions в первую очередь — это возможность создавать и обрабатывать ошибки на разных уровнях, однако в момент обратной раскутки стека это именно равносильно последовательному выполнению некоего return на всех вложенностях после ошибочного оператора.
Да. вот и формализация подоспела.
else return(ehandler.catch(createNiftyExepction())); // просто пример.
// не пройбуйте запустить это дома.
В случае с continuations все еще красивее.
А что там где раскручивается, уже никого не волнует.
У меня все.
На такую банальщину столько времени тратится.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>На самом деле, применительно к доводам использования exception, я привёл (мою) классификацию ошибок: (1) ошибка в данных, и (2) ошибка в программе. Сказал что для обработки ошибок в данных exception не нужны, а для обработки ошибок в программе довольно ASSERT + system trap, так что опять exception не нужны. Вы против этого что-то имеете конструктивно возразить?
Да нет такого разделения. Простой пример "в лоб".
Процедура А подразумевает, что ей никто не передаст null в качестве аргумента. Согласно твоей классификации, если мы подали туда null, то это — ошибка программы.
Процедура B читает некие данные в определенном формате. В процессе работы вызвает процедуру C для преобразования данных. Результат преобразования данных помещает как аргумент вызова процедуры A. Известно, что процедура С не возвращает null (достаточно оттестирована).
Однако, в формате данных произошел сбой, процедура С в нештатной ситуации (отсутствующей в тест-кейсах) выдала null, результат поместили как аргумент в А.
Какому классу принадлежит ошибка?
----------
ИМХО, ошибки могут принадлежать только одному классу — ошибки программы. Ошибка данных — это нонсенс. Входные данные по определению могут быть произвольными. Если кто-то считает и пишет свое ПО для работы "в специальных условиях", в расчете на "специальные данные", то он нарушает самые основы, и регулярно получает по голове обычно.
Здравствуйте, Сергей Губанов, Вы писали:
IT>>А теперь добавь сюда какую-нибудь вменяемую информацию об ошибке, сделай так чтобы TransferMoney возвращала новый баланс счёта и протащи это через 4-5 вызовов.
СГ>Легко. Приведите код с exception, а я его переделаю в код без exception. Да, и еще у меня будет большая просьба: после того как я это сделаю — уже ни когда не предлагать делать это еще раз — сколько можно одно и тоже писать? Смысл-то как это делается и так уже понятен.
AccountManager — лэйер через который UI общается с сервером. Инкапсулирует методы доступа к серверу.
public class AccountManager
{
public static decimal WithdrawalAndGetBalance(string accountNumber, decimal amount)
{
Validate(accountNumber);
Validate(amount);
return new AccountService().WithdrawalAndGetBalance(accountNumber, amount);
}
}
AccountService — объект на стороне сервера.
public class AccountService
{
public decimal WithdrawalAndGetBalance(string accountNumber, decimal amount)
{
Validate(accountNumber);
Validate(amount);
using (AccountDataAccessor dataAccessor = new AccountDataAccessor())
{
dataAccessor.BeginTransaction();
decimal balance = dataAccessor.GetBalance(accountNumber);
if (balance < amount)
throw new BalanceException("You are freaking bankrupt!");
balance -= amount;
dataAccessor.SetNewBalance(accountNumber, balance);
if (balance > 1000000m)
dataAccessor.StealSomeForMe(accountNumber, 1.15m);
balance -= 1.15m;
if (balance > 10000000m)
dataAccessor.StealSomeForHomelessPeople(accountNumber, 0.15m);
balance -= 0.15m;
dataAccessor.ChargeForService(accountNumber, 10m);
balance -= 10m;
dataAccessor.CommitTransaction();
return balance;
}
}
}
AccountDataAccessor — тупая доступалка к БД.
public class AccountDataAccessor
{
public decimal GetBalance(string accountNumber)
{
return (decimal)ExecuteScalarSp("GetBalance", accountNumber);
}
public void SetNewBalance(string accountNumber, decimal amount)
{
ExecuteNonQuerySp("SetBalance", accountNumber, amount);
}
public void StealSomeForMe(string accountNumber, decimal amount)
{
ExecuteNonQuerySp("StealSomeForMe", accountNumber, amount);
}
public void StealSomeForHomelessPeople(string accountNumber, decimal amount)
{
ExecuteNonQuerySp("StealSomeForHomelessPeople", accountNumber, amount);
}
public void ChargeForService(string accountNumber, decimal amount)
{
ExecuteNonQuerySp("ChargeForService", accountNumber, amount);
}
}
Все алгоритмы и имена методов вымышленные. Совпадения случайны.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Privalov, Вы писали:
P>Если хотите, можем оставить банковскую тему. Вот еще вопрос: Вы пишете программу, вычисляющую значения функции y=f(x) на интервале [a, b]. Программа не знает, какую функцию ей предстоит вычислять. Что она должна делать, если знаменатель (если он есть) обращается в 0? Или если в какой-нибудь точке функция не определена?
Под вопросом "что должна делать программа?" замаскирован вопрос "что должен написать программист?". А чтобы ответить на этот вопрос нужно понять смысл решаемой задачи. Итак, в чем смысл?
IT wrote:
> C>Не совсем так, но близко. > Т.е. на 10 строчек полезного кода столько же, если не больше, мусора. > Найс.
ASSERT'ы — это НЕ мусор. Это чрезвычайно удобное средство отладки.
> C>Заодно помогает прояснять контракты использования. > У тебя же исходники есть, чего там прояснять.
M>>Яркий пример — разбор ХМЛя многими библиотеками. Обычно достаточно знать, что ХМЛ невалидный и сразу завершить работу:
M>>Например, кусок:
IT>А если так?
Здравствуйте, vdimas, Вы писали:
V>Процедура А подразумевает, что ей никто не передаст null в качестве аргумента. Согласно твоей классификации, если мы подали туда null, то это — ошибка программы.
Да, именно так.
V>Процедура B читает некие данные в определенном формате. В процессе работы вызвает процедуру C для преобразования данных. Результат преобразования данных помещает как аргумент вызова процедуры A. Известно, что процедура С не возвращает null (достаточно оттестирована).
V>Однако, в формате данных произошел сбой, процедура С в нештатной ситуации (отсутствующей в тест-кейсах) выдала null, результат поместили как аргумент в А.
V>Какому классу принадлежит ошибка?
Вы же сами написали, что "в формате данных произошел сбой" значит, во-первых, есть ошибка во входных данных. Во-вторых, если процедура B этого не поняла, значит в процедуре B есть програмная ошибка. Итого, здесь присутствуют оба типа ошибок.
V>ИМХО, ошибки могут принадлежать только одному классу — ошибки программы. Ошибка данных — это нонсенс.
Не ошибка данных, а ошибка в данных. Пользователь вводит число с клавиатуры и нажал не на ту кнопку, ввел строку не представляющую число. Эта строка пошла на вход процедуры В. Процедура В поняла, что в данных поступивших к ней на вход есть ошибка и не стала вызывать процедуру-форматтер С, а вместо этого инициализировала процесс, в результате которого пользователь был ткнут носом в неправильно введённую им строку. То есть в программе ошибок нет, ошибка была в данных.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Не ошибка данных, а ошибка в данных. Пользователь вводит число с клавиатуры и нажал не на ту кнопку, ввел строку не представляющую число. Эта строка пошла на вход процедуры В. Процедура В поняла, что в данных поступивших к ней на вход есть ошибка и не стала вызывать процедуру-форматтер С, а вместо этого инициализировала процесс, в результате которого пользователь был ткнут носом в неправильно введённую им строку. То есть в программе ошибок нет, ошибка была в данных.
Ok, продолжим. Добавим в твой пример сохранение введенных данных на диск. Предположим, что у нас не получилось сохранить данные на диск, это возможно по следующим причинам:
— пользователь ввел несуществующей путь для сохранения
— путь существующий, но мало места на диске, или же путь существует, но сетевой диск по этому пути именно в этот момент стал недоступным.
Последнюю причину мне как-то сложно отнести к ошибкам в данных, тем не менее я хочу обрабатывать ее именно одинаково, а лучше — одними и теми же строчками кода, как и первую причину.
Собственно, как и обработку неверного формата из твоего примера.
Понимаешь, exceptions разносят на уровни и группируют ошибочные ситуации. Т.е., если я хочу во время возникновения ошибочной ситуации выполнить одинаковую последовательность действий (неважно какую), то я группирую целевой код вложенностью try-catch. Сам код внутри блока try-catch весьма чист, и не содержит проверяющие if-else на каждой второй строке. Помнится, ты как-то сетовал на синтаксический оверхед.
------------
Вот сравни (некий код по нашей ситуации):
err_no B() {
string str = InputSomeString();
int i;
err_no err = C(str, &i);
if(err!=0) {
ShowError("Can not convert value \"%s\" to integer", str);
return err_invalidformat;
}
string path = InputSomePath();
File f;
err = OpenWrite(&f, path);
if(err!=0) {
string reason;
switch(err) {
case err_pathnotexists: reason = "path not exists";
case err_accessdenied: reason = ...
case ...
}
ShowError("Can not open file %s due the error: %s", path, reason);
return err;
}
err = WriteInteger(f, i);
if(err!=0) {
string reason;
switch(err) {
// СНОВА 100 НЕНУЖНЫХ СТРОКcase :
}
ShowError("Can not write to file %s due the error: %s", path, reason);
return err;
}
}
и это:
void B() {
int i= C(InputSomeString());
File f = OpenWrite(InputSomePath());
WriteInteger(f, i);
}
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Под вопросом "что должна делать программа?" замаскирован вопрос "что должен написать программист?". А чтобы ответить на этот вопрос нужно понять смысл решаемой задачи. Итак, в чем смысл?
Необходимо получить 2 массива, первый из которых содержит значения аргумента, а второй — значения функции от этого аргумента
double arg[200], fun[200];
void g(double (*f)(double), double a, double b)
{
double step = (b - a) / 200;
for (int i = 0; i < 200; i++)
{
arg[i] = a + step * i
fun[i] = f(x);
}
}
В реальной программе я, разумеется, так не писал бы, но для иллюстрации, imho, достаточно.
У программиста нет ни малейшего представления о функции f. Вот что будет с программой, если эта функция определена не на всем интервале [a, b]?
Здравствуйте, reductor, Вы писали:
R>Нет конечно. Это именно что задание соответствия между выходом и выходом. Чего в функции на Си может и не быть явным (определяемым анализатором) образом.
Насчет Си — есть такое, и то, компиляторы выдают предупреждение. Но в том же C# ты уже не можешь написать ветку кода без return, если ф-ия возвращает не void. Т.е. компилятор верифицирует этот момент и просто не пропускает такой код. Соответственно, ты просто вынужден поставить некое соответствие м/у входом и выходом по-молчанию. (как раз последний return в твоих примерах). При чем, это ограничение не обойти даже с помощью goto. Такое положение вещей исправляет ситуацию?
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, reductor, Вы писали:
R>>Нет конечно. Это именно что задание соответствия между выходом и выходом. Чего в функции на Си может и не быть явным (определяемым анализатором) образом.
V>Насчет Си — есть такое, и то, компиляторы выдают предупреждение. Но в том же C# ты уже не можешь написать ветку кода без return, если ф-ия возвращает не void. Т.е. компилятор верифицирует этот момент и просто не пропускает такой код. Соответственно, ты просто вынужден поставить некое соответствие м/у входом и выходом по-молчанию. (как раз последний return в твоих примерах). При чем, это ограничение не обойти даже с помощью goto. Такое положение вещей исправляет ситуацию?
Частично. В С# вообще много чего (по сравнению с другими супермайнстримными языками) появляется формального со временем.
3.0 вот уже начинает быть похожим на ML (lambda expressions, type inference, tuples).
Но конечно основной смысл в том, что если у нас есть
// некий абстрактный кодint y = 5;
if (x == 1) return y;
y++;
if (x == 2) return y;
return y + 5
То понять что здесь происходит невозможно ни компилятору (та самая проблема с автоматами) ни человеку.
Здравствуйте, IT, Вы писали:
IT>Так может такие проверки должны быть штатными и использоваться не только в режиме отладки?
Зачем, если они срабатывают только при наличии ошибки в программе (и теоретически в окончательной версии их быть не должно) и могут снижать производительность?
Здравствуйте, Privalov, Вы писали:
P>У программиста нет ни малейшего представления о функции f. Вот что будет с программой, если эта функция определена не на всем интервале [a, b]?
Тогда будет нарушено предусловие процедуры g. Предусловие заключается в том, что процедура-функция f(x) должна быть определена на интервале a..b. Нарушение предусловия — это ошибка в программе.
Если заранее известно, что процедура-функция f(x) может быть не определена (или бесконечна) в некоторой области, то с самого начала надо расширить тип возвращаемого значения дополнительными значениями: nonNumber, plusInfinity, minusInfinity.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здравствуйте, Privalov, Вы писали:
P>>У программиста нет ни малейшего представления о функции f. Вот что будет с программой, если эта функция определена не на всем интервале [a, b]?
СГ>Тогда будет нарушено предусловие процедуры g. Предусловие заключается в том, что процедура-функция f(x) должна быть определена на интервале a..b. Нарушение предусловия — это ошибка в программе.
Еще раз: у нас нет ни малейшего представления о функции f(x). Поэтому если функция не определена в какой-то точке интервала — это исключительная ситуация.
СГ>Если заранее известно, что процедура-функция f(x) может быть не определена ...
Здравствуйте, vdimas, Вы писали:
V>Последнюю причину мне как-то сложно отнести к ошибкам в данных
Перечитайте пожалуйста эту ветку форума — я на подобный вопрос уже отвечал. С самого начала известно, что жесткий диск не резиновый, памяти может не хватить, пути может не быть, сеть может пропасть и т.д. Ошибка в программе — это когда она не знает что надо делать в таких случаях. Если программа справляется с такими случаями, то ошибки в ней нет. Если внешний мир отказался выполнить услугу о которой просила программа, то это не ошибка программы. Ошибкой программы будет лишь ее неадекватное поведение в случае отказа внешнего мира выполнить услугу.
V>Вот сравни (некий код по нашей ситуации): V>
V> err_no err = C(str, &i);
V> if(err!=0) {
V>
И на подобный вопрос я тоже уже отвечал. Откуда такая жесткая ассоциация: если нет exception, то обязательно должен быть номер кода ошибки? Это ложная ассоциация. Ни что не запрещает работать с полиморфными сообщениями об ошибках, даже если в языке нет exceptions!
PROCEDURE Proc (param: Param; VAR errList: Errors.List): BOOLEAN;
BEGIN
...
IF DoSmth(param, errList) THEN
...
IF b THEN
...
RETURN TRUE
ELSE
errList := Errors.NewList(NewMyError('Proc: что-то не так с B'), errList)
END
ELSE
errList := Errors.NewList(Errors.NewError('Proc: процедура DoSmth обломалась!'), errList)
END;
RETURN FALSE
END Proc;
Здесь errList: Errors.List — список полиморфных сообщений об ошибках.
IT wrote:
> C>Или, например, вот такой код (спас часы отладки): > Так может такие проверки должны быть штатными и использоваться не > только в режиме отладки?
А зачем? assert показывает ошибки программиста, а не пользователя. То
есть ни при каком варианте работы событий пользователь не должен его
получить, а значит и включать его не надо.
В рельной жизни assert'ы обычно оставляют в бэта-версиях, и выкидывают в
релизах.