валидация входных данных [re: Что значит: "уметь прогать"]

zorin29

Ну и если уж на то пошло — работает правильно включает корректность при "нехороших данных"
Я считаю, что ты неправ. По-моему, "работает правильно" - это "дает правильный ответ при всех валидных входных данных".
Если код работает предсказуемо и адекватно при невалидных входных данных - это его плюс, но этот плюс не всегда важен.
Оцени с позиции красоты мой код:

#define MAX_COMPUTABLE 13
#define SUCCESS 0
#define NEGATIVE -1
#define TOO_HIGH -2

int factorial(int n, int *result)
{
if (n<0)
return NEGATIVE;
if (n>MAX_COMPUTABLE)
return TOO_HIGH;
if (n==0)
{
*result = 1;
return SUCCESS;
}
int f;
int err = factorial(n-1, &f);
if (err != SUCCESS)
return err;
*result = f * (n-1);
return SUCCESS;
}

Лично мне кажется, что он нисколько не красив. А вот этот код, по-моему, красив:

int factorial(int n)
{
if (n==0)
return 1;
else
return n*factorial(n-1);
}

Dasar

Оцени с позиции красоты мой код:
для этого и придумали exception-ы

zorin29

т.е. если бы я писал

if (n<0)
throw new ArgumentException("N is negative");
if (n>MaxComputable)
throw new ArgumentException("N is above maximum allowed value");
return n*factorial(n-1);

стало бы красиво? Я так не думаю.

Dasar

Если код работает предсказуемо и адекватно при невалидных входных данных - это его плюс, но этот плюс не всегда важен.
это не плюс - это необходимое условие.
при невалидных данных - код, как минимум, должен вести себя адекватно
например, твой красивый код при передаче -1 вызывает stackoverflow и завершение всего процесса.
но если в нем было бы if (n <= 0) , вместо равенства - то это уже был хороший код
зы
в коде должен отсутствовать эффект дятла: когда залетевший дятел рушит весь город.
твой красивый код - резко усиливает небольшую ошибку в коде, например, вызов factorial(x-1 когда x - это, например, кол-во участников и никто не пришел

Dasar

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

zorin29

твой красивый код - резко усиливает небольшую ошибку в коде, например, вызов factorial(x-1 когда x - это, например, кол-во участников и никто не пришел
Понимаешь, это просто сферический код в вакууме. Ты ведь не знаешь, какую задачу он решает. Тем не менее, у тебя какие-то категорические требования к эффекту дятла.
Про factorial(x-1) - это вообще fail, мой факториал работает нормально с нулевым аргументом. Или у тебя в твоей модели может и отрицательное число участников прийти? :)
И я там специально написал именно if (n==0 чтобы подчеркнуть мой тезис о том, что СУЩЕСТВУЮТ СЛУЧАИ, когда в коде не следует заботиться о том, как он работает на невалидных данных. Например: олимпиадные задачи. Другой пример: учебные примеры кода.

Dasar

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

for (int i = 0; i < factorial(x-1); ++i)
{
}

придется писать

for (int i = 0; i < x < 0)? 0: factorial(x-1; ++i)
{
}

Dasar

Про factorial(x-1) - это вообще fail, мой факториал работает нормально с нулевым аргументом. Или у тебя в твоей модели может и отрицательное число участников прийти? :)
подумай еще раз. x - это кол-во участников. и что будет если никто не придет?

zorin29

первый exception лучше заменить на доопределение функции factorial на отрицательную плоскость. например, как возвращающую 0
опять таки, когда как. Может, этот ноль потом у тебя выплывет через 10 тысяч строк кода в ошибке деления на ноль? А все потому, что ты где-то по ошибке вычислил факториал от кода ошибки вместо факториала от числа участников?

zorin29

подумай еще раз. x - это кол-во участников. и что будет если никто не придет?
Понял тебя, угу.

Dasar

Может, этот ноль потом у тебя выплывет через 10 тысяч строк кода в ошибке деления на ноль? А все потому, что ты где-то по ошибке вычислил факториал от кода ошибки вместо факториала от числа участников?
так это уже будет проблема того участка кода, который поделил на 0, и именно там эту ошибку надо исправить

zorin29

Утверждение 1: существуют задачи, в которых проверка валидности входных данных не нужна.
Если ты со мной хочешь поспорить, то не приводи мне примеры. Как мы с тобой прекрасно оба знаем, чтобы опровергнуть утверждение 1, тебе нужно доказать, что для ЛЮБОЙ задачи с входными данными надо проверять их валидность.

zorin29

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

int participants = get_participants_count; // эта функция поломалась, и вернула код ошибки -5, вместо кол-ва участников.
// мы забыли проверить код ошибки
int fact = factorial(participants); // тут fact стал равен нулю
// ...
// TEN THOUSAND LINES LATER
// ...
double probability = x / fact; // division by 0

Так где, по-твоему, надо исправлять ошибку?

Dasar

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

zorin29

ОК, согласен, но как ты это поймешь по делению на 0, случившемуся в последней строке?
Я цитирую тебя же:
так это уже будет проблема того участка кода, который поделил на 0, и именно там эту ошибку надо исправить

Dasar

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

Dasar

ОК, согласен, но как ты это поймешь по делению на 0, случившемуся в последней строке?
в ходе code review - обратно оттрассирую выполнение программы, начиная с места возникновения ошибки.

zorin29

давай унесем в отдельную ветку, что-то типа "валидация входных данных"?
Я знаю три подхода к обработке невалидных входных данных:
1) Вернуть спецзначение, или заполнить спецпеременную. Это и мой код ошибки, и твое доопределение факториала на отрицательную полуось.
2) Бросить исключение.
3) Забить на эту обработку вовсе.
Все три подхода имеют право на существование. Лично я больше фанат 2-го подхода, но лишь потому, что работаю в .NET Framework, который и сам бросает исключения в случае каких-то ошибок. Если бы я работал под никсами, где принято возвращать код ошибки - использовал бы первый подход.
Третий подход дает наиболее лаконичный и выразительный код. Если тебе надо что-то проиллюстрировать, написать какую-то модельную функцию - 3-й подход тебе поможет сделать это быстро и красиво.
Я считаю, что главное - не следует смешивать подходы. Если ты бросаешь исключения - бросай везде, а не доопределяй факториал нулем.
Соответственно, то, что я написал в "красивом" коде if (n==0 а не if (n<=0) - это реализация моего правила. Если я уж решил чихать на невалидные данные (а я решил, ибо не проверяю верхнюю границу то на все их, а не на подмножество.
И в ответ на твое возражение о том, что верхнюю границу за меня проверит среда и бросит исключение - мой ответ: среда среде рознь. Какая-то, может, и не проверит, и не бросит.

Dasar

Я считаю, что главное - не следует смешивать подходы.
какой цели ты хочешь добиться?
сейчас ты фиксируешь форму, без зафиксированной цели.

Dasar

у меня следующая логика
(Ц1)главная цель кода - обеспечить удобство пользователю
(Ц2)вторичная цель - обеспечить удобство разработки программисту
(Ц3)подцель - для удобства пользователя, код должен быть построен так, чтобы он гасил (а не усиливал) побочные эффекты от ошибок (пользователя, программиста, окружения и т.д.)
для обеспечения Ц3 при условии Ц2:
1. обработку ошибок стоит строить на исключениях,
2. нет необходимости добавлять вспомогательные исключения, если они не сообщают доп. информацию.
при этом считается, что название метода, входные аргументы и цепочку вызовов - где произошла ошибка мы и так знаем
3. стоит договариваться о доопределении функций(кода) так, что если входные невалидные данные можно считать как редуцированный частный случай, то и возвращаться должны данные, которые можно считать за редуцированный частный случай.
0, пустая коллекция, null - это всё есть редуцированные частные случаи (в зависимости, от типа данных)

zorin29

стоит договариваться о доопределении функций(кода) так, что если входные невалидные данные можно считать как редуцированный частный случай, то и возвращаться должны данные, которые можно считать за редуцированный частный случай.
Ну да, если есть у меня соглашение, что отрицательный аргумент - это допустимый аргумент факториала, и факториал отрицательного числа считаем равным нулю, то я так и напишу.
А почему не следует смешивать подходы? Ну если у тебя половина функций при невалидных входных данных бросает исключение, а вторая половина - возвращает какое-то псевдозначение, то существует определенная вероятность, что ты сам забудешь, что как работает, и будешь неправильно эти функции вызывать.
Если же моя цель - написать лаконично рекурсивный факториал, то дополнительная проверка if (n<0) (или твой первый вариант if (n<=0) return 1) только затуманивает смысл функции. Аргумент о том, что "так она будет работать лучше в таких-то приложениях" для меня нисколько не важен, т.к. в приложениях я вообще не буду считать факториал рекурсивно.

zorin29

все, я потерял интерес к дискуссии. Думаю, этот вопрос следует отнести к вопросам стиля программирования. А стиль у каждого свой, а у меня - самый лучший :)

stm5872449

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

elenangel

видимо то, что предположение о корректности входных данных может оказаться неверным. Ваш К.О.

zorin29

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

Papazyan

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

Papazyan

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

zorin29

Я не вполне понял, что ты хотел сказать.

stm5872449

видимо то, что предположение о корректности входных данных может оказаться неверным. Ваш К.О.
Допустим, данные, полученные от пользователя, прошли полную проверку в части программы, ответственной за взаимодействие с этим самым пользователем. Ты предлагаешь все эти проверки многократно повторять во всех остальных частях программы?
При некоректных данных код должен работать некорректно, это очевидно.
Т.е. если при введении некорректных данных пользователь, например, получает полный доступ к используемой БД, то все норм? :)

zorin29

При некоректных данных код должен работать некорректно, это очевидно.
Под словом данные здесь понимается не то, что ввел пользователь, а то, что дано на вход функции.
Функции, работающие с вводом пользователя, должны работать корректно с любым возможным вводом. Т.е. для функций UI любые данные в этом смысле корректные.

zorin29

Допустим, данные, полученные от пользователя, прошли полную проверку в части программы, ответственной за взаимодействие с этим самым пользователем. Ты предлагаешь все эти проверки многократно повторять во всех остальных частях программы?
Ну ты как будто не допускал никогда ошибок.
Тут валидация понимается не как фильтрация пользовательского ввода от мусора, а как sanity check параметров вызова внутренних интерфейсов.
Каждый раз, пиша написывая создавая новую публичную функцию, ты задаешь себе вопрос: "могу ли я доверять всем, кто будет меня вызывать, в том, что они меня вызовут с правильными аргументами?". "А если не могу, то как мне реагировать на неправильные?"

Papazyan

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

Papazyan

Т.е. если при введении некорректных данных пользователь, например, получает полный доступ к используемой БД, то все норм? :)
Без экстрима, конечно. Кое-где данные нужно проверять, но в каждой функции делать, это уже перебор.

zorin29

я не понял тогда (и сейчас какой ты делаешь вывод из этого?

Papazyan

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

zorin29

так ведь работает же? :) пусть лучше работает, чем не работает :)

Marinavo_0507

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

Werdna

стало бы красиво? Я так не думаю.
А вообще, не пофиг ли на красоты? Вы на своих красотах окончательно повернулись.
Пример про факториал лажовый. И вот почему.
Проверка входных условий должна делаться или не делаться не от желаний разработчика, а согласно декларируемым предусловиям. Если программист в целях повышения производительности не делает никаких проверок — это его право, должно быть только это как-то оговорено.
Что касается самой программы, то она должна вести себя корректно всегда. При любых данных. Молчать программа имеет право только если она корректно отработала, а при каких-то ошибках должна быть внятная диагностика. Внятная — это значит, что тот, кто запускал сразу поймёт где ошибка.
Все кто пишут проги с плохой диагностикой — пидорасы.

zorin29

Твой максимализм делает тебе честь.
Объясни, пожалуйста, как соотносятся два твоих тезиса:
1. Если программист в целях повышения производительности не делает никаких проверок — это его право, должно быть только это как-то оговорено.
2. Что касается самой программы, то она должна вести себя корректно всегда. При любых данных.
Что-то читаю, читаю, а понять не могу. То есть типа программист, если не хочет, может и не делать проверок, но программа все равно должна выдавать хорошую диагностику?..
А вообще, не пофиг ли на красоты?
Как это в духе форумлокала! Ты зашел в тему, где обсуждается, какой код красив, а какой нет, для того, чтобы заявить, что тебе плевать на красоту? Так зачем заходил? :)

Werdna

1. Если программист в целях повышения производительности не делает никаких проверок — это его право, должно быть только это как-то оговорено.
2. Что касается самой программы, то она должна вести себя корректно всегда. При любых данных.
пример: в функции strlen не делать проверки на нулевой указатель. Каждый проверяет сам, чтобы не было лишних проверок, которые могут много стоить. Например, vector в STL не проверяет выход за размеры, и это правильно: почему я должен терять скорость из-за Пупкина, которому джанная фича нужна?
Программа же не должна падать на некорректных данных ни при каких условиях. Особенно если она отвечает удалённо. Хотя в то же самое время допустимо иногда грубо отвечать посылом нах без уточнений, но только в случае если кэп может ответить за вас.
О плохой и хорошей диагностике написано море. У большинства программ, которые пишут хомячки, диагностика ужасная.

Corrector

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