Pull to refresh

Comments 98

Объясните подробней, как это относится к концепции Хаскеля? Интересно.
Здесь в статье речь шла о избыточности проверок: классы/компоненты/модули обычно устраиваются так, что они могут работать полностью независимо от остальных, — бери и включай в другой проект. Из-за своей универсальности им приходится проводить самостоятельные проверки входных данных: в одном проекте их посадят за ту самую баррикаду и будут кормить только отборными продуктами, а в другом — отправят на передовую, и мало ли, что их ожидает. Авторы же концепции предлагают, ориентируясь на конкретный проект, убирать лишние проверки.

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

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

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

Дальше идёт следующий встроенный барьерный механизм: например класс Functor. Он позволяет совершать операции над полиморфным типом: то есть для типа f a (а — параметр) и функции (a -> b)
получать тип f b:
fmap :: Functor f => (a -> b) -> f a -> f b

Это позволяет, в частности обрабатывать чистой логикой небезопасные значения. Примерно так

let input = readFile "input.file"
let result = fmap clearLogic input
output result


Вообще система типов хаскеля достаточно сложна и позволяет для внутренней части создать типы не допускающий неверных подходов.

Мне кажется, что именно поэтому автор сказал про хаскель. Баррикады там, пожалуй, единственный возможный способ сделать что-то действительно рабочее и не слишком сложное для понимания.
Чёрт, я написал столько слов про хаскель и не сказал магического слова «монада».
Восполню недостаток:

любая монада на самом деле является аппликативным функтором. Только в силу исторических причин
объявление монады не означает объявления функтора.

Соответственно, монада IO, про которую написано на каждом столбе, является функтором и позволяется безопасно перерабатывать значения внутри себя не опасаясь любых побочных эффектов. Это куда лучше, там традиционный монадный синтаксис:
>>= :: Monad m => (a -> m b) -> m a -> m b

Поскольку такая запись предполагает, что каждый участок вычислений [a -> m b] чистые данные превращает в какую-то гадость (то есть порождает внешние эффекты, ведь m = IO)
UFO just landed and posted this here
ой, да все знают «как надо», но почему-то на выходе получается другое. и сразу же оп и все знают почему так вышло
UFO just landed and posted this here
Примеры кода где? Системы, которые построены по такому принципу где? Требую кода и зрелищ!
Любая программа на Haskell, взаимодействующая с внешним миром через монады.
Макконнелл, «Совершенный код», страницы 200-220 печатного издания. Копипастить сюда 20 килобайт текста не хочу.
Обычно достаточно сделать выжимку из самых ярких примеров данного подхода и опубликовать ее тут. Никому не интересно читать 20 страниц ради понимания того, что же это за зверь такой новомодный, баррикада…
Вот ссылок на соответствующие изречения Макконнелла и Фаулера явно не хватает.
Дык раз уж знаете, что такие есть — так и сослались бы…
В посте написано: "О баррикаде писали в своё время Макконнелл, Фаулер и может быть кто-то еще."
Вот я и хочу, чтобы ссылки были не голословными, а реальными.
Создать защиту от дурака практически невозможно, ведь дураки так гениальны :)
Я бы сказал — совершенно непредсказуемые, на счёт гениальности — не факт.
«Никогда не стоит недооценивать непредсказуемость тупизны» (Большой Куш в переводе Пучкова)
Вспомнилось изречение Бисмарка

«Никогда не замышляйте ничего против России! На любую вашу хитрость Россия ответит своей непредсказуемой глупостью»
В своё время тоже задавался таким вопросом. Правда в разрезе: должны ли protected и private методы проверять входные аргументы как это делают public методы или можно ограничиться Assert'ом. На практике пришлось задуматься о рефакторинге — теперь это непросто замена модификатора доступа, а переписывание всех Assert'ов на нормальные проверки. В итоге победила лень — практически не использую Assert'ы)
у меня местами ассерты по объему сравнимы с кодом
Подход интересный ) Кстати, в web-приложениях как правило есть очевидное место для построения баррикады — прием данных из запросов — и там она обычно и формируется)
Плюс не очевидное для проектировщика (или программиста) поведение пользователя: нажал кнопку Назад или обновил страницу «не вовремя» или внезапно несколько раз и прочее.
Ну да, т.е. проверка состояния тоже важна.
Я бы не сказал, что настолько оно очевидное. В MVC приложениях куда логичнее, по-моему, проверять данные при помещении в модель.
По сути это примерно тоже самое, что я и имел ввиду.
Или я вас неверно понял, или сам не точно выразился :) Я имел в виду, что сами методы проверки находятся в классах модели (или тесно с ними связаны). Что контроллер анализом запроса на корректность не занимается, а просто преобразует HTTP-запрос в понятный для модели вид, в параметры методов и передаёт ей, а уж модель может сообщить контроллеру, что данные некорректны. Не
if ($_GET['param'] > 0) {
  $model->setParam($_GET['param']);
  render('Success');
}
else {
  render('Error', 'Param is not positive');
}
, а, например,
if ($model->setParam($_GET['param'])) {
  render('Success');
}
else {
  render('Error', $model->getErrorMessage());
}

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

P.S. Кстати, «баррикада» — ещё один довод в пользу использования сеттеров/геттеров в модели, а не public свойств.
А если у вас три классических слоя и каждый разрабатывает независимый человек,
или еще интереснее, во втором слое 10 сервисов, каждый из которых разрабатывает независимый человек (который еще и работает на субподряде и контракт у него заканчивается) где тут провести баррикадную черту?

Если не проверять параметры траблшутинг превратится в настоящий ночной кошмар.

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

Что может быть проще и быстрее проверки на null, или на >0. Если от такого ваша «программа» тормозит у вас серьёзные проблемы ;)
>Что может быть проще и быстрее проверки на null, или на >0. Если от такого ваша «программа» тормозит у вас серьёзные проблемы ;)

а две проверки?
а если функция вызывается в цикле?
а в цикле по битмап-буферу?
Можно делить float на float, любой на любой. В случае N/0.0 получается ± INF, если N != 0.
Сколько можно эту чушь про исключения писать.
а ничего, что кроме JS есть другие языки?

In [2]: float(0)/float(0)
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)

/home/muromec/ in ()

ZeroDivisionError: float division
Я на C++ проверял.
Впрочем я немного погорячился. Деление на ноль выставляет флаги, которые можно проверить и бросить исключение. Возможно некоторые языки так делают.
Что за язык кстати?
Статья не про деление на ноль, а про валидацию данных.
Да, оффтоп конечно. Но после таких примеров люди вставляют проверки там, где они не нужны.
Что значит не нужны? В подавляющем большинстве случаев деление на нуль — не валидная операция.
если формально подходить, то 9mm прав, функция делает ровно то, что от нее ожидают — делит. :) вот только пример был чисто для абстракции, а в этом оффтопе комменты уже до 5 уровня дошли.
А по-моему, пример как раз очень удачный. Ведь если функция ничего кроме деления не делает, то почему бы не использовать стандартный оператор деления?
А вот если функция понадобилась, значит, это не просто деление, а и дополнительные проверки, которые, если их делать каждый раз при выполнении операции деления в коде, — получится куча дублированного кода.
Например, при делении на нуль хочется всегда выкидывать какой-то свой exception, потому что в нашем приложении такая операция не валидна.
В подавляющем большинстве случаев как раз валидная.

Невалидной она становится если важен порядок бесконечности, что редкость.
Собственно «оперирование двоичными числами почти так-же как простыми вещественными» и было одний из целей стандарта IEEE 754.

UFO just landed and posted this here
Сразу вспомнилась цитата из книги Programming Perl:

The trend over the last 25 years or so has been to design computer languages that enforce a state of paranoia. You're expected to program every module as if it were in a state of siege.


Я уж надеялся, что в конце статьи будет какой-то полезный совет, как избежать превращения кода в баррикаду. Жаль.
Такое чувство, что мы с вами читали разные статьи. Вся статья один большой совет: чтобы избежать превращения всего кода в баррикаду разделите код на обособленную баррикаду и код, которые работает с гарантированно безопасными данными.
… гарантированно безопасными данными, которые все равно проверяются ASSERT'ами потому, что ошибка может быть в самой баррикаде. Я так и не понял, чем это отличается от повсеместного баррикадирования. Если вы об экономии нескольких процессорных инструкций с помощью использования препроцессора, то это применимо только к семейству C, в большинстве же остальных языков разницы не будет никакой.
отличие в том, что все ошибки и некорректности в потоках данных внутри баррикады должны быть выявлены на этапах тестирования, после этого все ошибки это исключительные ситуации, требующие исправления
Тогда уж лучше сразу воспользоваться модульным тестированием:
1. Тестовый код будет заведомо отделен от логики приложения в любом языке
2. Проверка результатов будет на выходе тестируемой функции, а не внутри вызывающей ее — если функция вызывается в 10 местах, не нужно писать 10 ассертов
3. Одинаковый формат тестирования что для «внешних», что для «внутренних» функций
Вы правы, «Во всех внутрибаррикадных методах обязаны стоять ASSERTы» — очевидно эта фраза не подходит для каждого языка и не совсем корректна, правильнее будет сказать, что единственная проверка корректности данных в «безопасной» зоне должна осуществляются в процессе отладки «баррикады». В глобальном смысле на саму концепцию это не влияет на мой взгляд.
поправьте пожалуйста если не прав,
MVC является реализацией данной концепции, если абстрагироваться от назначения компонентов?
Нет, дело не в этом.
Вы можете проверять во View, пришли ли правильные данные, а можете построить баррикаду в контроллере или модели и во View не делать никаких проверок.
Client-side и server-side валидации как раз вписываются в эту концепцию — во View по-быстрому проверяем данные на JavaScript'е, в контроллере уже по-взрослому, ну а всякие сервисы/бизнес-логика/репозитории — это уже на «нашей» стороне баррикады.
Вспомнил цитату из одного руководства:
Ограничивайте, отклоняйте и очищайте пользовательский ввод. Иначе говоря, предполагайте, что весь пользовательский ввод является злонамеренным. Проводите проверку длины, диапазона, формата и типа вводимых данных.
Всегда проверяйте ввод на наличие неверных или злонамеренных данных на стороне сервера,
поскольку проверка на стороне клиента может быть без труда подделана.

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

Здесь хотелось бы немного дополнить. Не для опровержения ваших слов, а для более полного раскрытия темы.

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

Всегда уведомляйте пользователя, что что-то пошло не так, давая нужные ему подробности и советы по исправлению входных данных.
В свою очередь хочу добавить: я по возможности стараюсь делать так — сам барьер работает на стороне сервера, но он особо не заморачивается с объяснениями в чем именно проблема, его задача — проверить данные на 100% и при этом потратить минимум ресурсов (соответственно, за этим барьером проверки уже практически не производятся). А вот на клиентской стороне делаются дублирующие проверки, но более конкретные, которые подскажут что именно не так: то ли длина строки, то ли неверный первый символ, то ли недопустимый символ во всей строке в принципе и т.п.

Таким образом мы снижаем нагрузку на сервер/барьер, сохраняем безопасность, но при этом повышаем информирование пользователя до максимума.

На практике конечно есть свои сложности, особенно в отдельных ситуацих (в идеале нужно обеспечить полное соответствие проверок клиента проверкам сервера/барьера, иначе нужно описывать поведение клиента для ошибки, которую клиент пропустил, но поймал барьер, плюс изменение в спецификации требует изменений в двух местах, плюс не все проверки можно сделать на стороне клиента и т.п.), но в большинстве случаев все отлично работает.
Ну если без фанатизма.
Потому что конструкция on error resume next высмеивает весь этот пост!
Я проводя собеседования на вакансию программера С++ сильно удивился, что про исключения знает менее чем каждый второй соискатель.
эмм. А как эти соискатели говорят, как обычно они управляют обработкой ошибок?
вопрос звучал так: как без анализа содержимого делителя обработать ошибку деления на ноль:

предполагается ответ с try...catch

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

Множество раз замечал, как собеседуемые не отвечают на простейшие вопросы только потому, что считают: «здесь серьёзные люди и серьёзное собеседование — значит вопрос с подвохом. Ну не могут же они такую элементарную вещь спрашивать»…

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

предполагается ответ с try...catch


Неправильный ответ. Правильный: средствами C++ это сделать невозможно.
ISO/IEC 14882, глава 5 Expressions [expr], параграф 4 говорит нам следующее:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of
representable values for its type, the behavior is undefined. [ Note: most existing implementations of C++
ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all
floating point exceptions vary among machines, and is usually adjustable by a library function. —end note ]


Это UB.

В разных РЕАЛИЗАЦИЯХ C++ это можно сделать при помощи implementation-specific функционала. Более того, такие ошибки практически никогда не транслируются в C++ исключения автоматом.

В Windows будет сгенерировано SEH исключение, которое можно словить через __try/__except либо с помощью _set_se_translator сконвертировать в C++ исключение. Кроме того, если включить обработку асинхронных исключений catch-all блок будет ловить и SEH исключения (примечательно, что именно divide-by-zero использовано в качестве примера в статье про разные типы обработки исключений).

В POSIX системах будет сгенерирован сигнал (SIGFPE), который можно обработать предварительной установкой хендлера.

В общем, задавать на собеседовании вопросы про UB можно только если принимаете на какую нибудь senior/principal должность. Новички этого знать не обязаны — им бы с defined поведением разобраться.
спасибо за развернутый комментарий. К сожалению НИ ОДИН из соискателей не упомянул, что с исключениями не все так гладко. Либо пишут try… catch, либо пишут какую-то ерунду.

Вообще, открою секрет, тест изначально создавался для соискателей на Delphi, там с исключениями все на порядок прозрачнее.
Вопрос «как без анализа содержимого делителя обработать ошибку деления на ноль:
предполагается ответ с try...catch» совершенно не подразумевает, что «про исключения знает менее чем каждый второй соискатель.»

Я думаю, что если бы вы захотели поговорить конкретно об исключениях, их преимуществах и недостатках, то процент ответивших подскочил бы, скажем, до 90%.

A так вы задали вопрос, на который можно ответить, например так:
double foo (int a, int b)
{
if (a — b == a)
{
ProcessError(«ololo»);
return 0;
}
return a/b;
}
и это тоже будет являться приемлимым решением, но неверным с вашей точки зрения.

Вообще, реально напрягают интервьеверы, которые задают вопрос не,- «Как ты использовал исключения в своем последнем проекте», а в стиле,- «Ну-ка, попробуй прочитать мои мысли, сынок!»
Какие бы преобразования Вы ни делали над b — это все равно остается «анализом делителя».
обработка исключения при делении на ноль описана почти в каждой книжке по программированию как академический пример
Обработка исключения при делении на ноль — это ОТВРАТИТЕЛЬНО. Это как в Java unchecked exception — ошибка программирования. Это все равно что контроль выхода за границы массива перехватом ArrayIndexOutOfBoundsException.
А если у вас десяток параметров, которые на ноль надо проверить? тоже каждый анализировать? бред
А если у вас десяток коллекций, по которым надо итерировать, вы тоже не пишете цикл от 0 до collection.size(), а ждете исключения?
не понимаю, что общего у деления на ноль и OutOfBounds?

PS: кстати цикл до .size()-1
Ну в Java например, традиционно считается, что runtime (unchecked) exceptions типа обращения по нулевой ссылке, арифметические ошибки типа деления на ноль, выход за границы массива перехватывать не надо — это дурной тон использовать исключения для управления логикой программы. Эти исключения — ошибки программирования. Если вы обращаетесь файла, а его там нет или у вас нет прав — тут можно выбросить исключение, т.к. вы не можете никогда заранее знать, есть файл или нет, и это нельзя заранее проверить.

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

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

Это неправильный ответ. Для usage-ошибок не надо использовать try...catch.

> более половины пишут что всегда анализируют делитель

И правильно делают. Это не от незнания про исключения, а от умения ими не пользоваться в неподходящих местах.

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

Вы и сами бы не прошли собеседования на джуниора.

> предполагается ответ с try...catch

Правильный ответ (с учётом рассуждений про баррикады) может быть примерно таким (на C#).

Вызываемый код:
private double DivUnsafe(double a, double b)
{
Debug.Assert(!/*корректное сравнение b с 0.0*/);
return a / b;
}

public double Div(double a, double b)
{
// Не допускается выбрасывание из библиотеки ZeroDivisionException,
// NullReferenceException и тому подобных исключений.
if (/*корректное сравнение b с 0.0*/)
{
throw new ArgumentException(...);
}
return DivUnsafe(a, b);
}

В библиотечном коде (по «нашу сторону» баррикад) всегда вызываем DivUnsafe, контекст вызова всегда можем контролировать. Пользовательскому коду (на корректность которого мы не можем прямо влиять) предоставляется Div.
Неправильный вызывающий код:
try
{
c = Div(a, b);
}
cactch (ArgumentException ex)
{
// Обработка ошибки, например:
throw new MyBusinessLogicException(..., a, b, ex);
}

Правильный вызывающий код:
if (/*корректное сравнение b с 0.0*/)
{
// Обработка ошибки, например:
throw new MyBusinessLogicException(..., a, b);
}
с = Div(a, b);
статью не хотите написать на эту тему?
Да просто вопросы соискателям нормальные задавать надо.
Позвольте пофантазировать в php на эту тему:
 class ArgumentException extends Exception {
  public function __construct($message, $code, $previous) {
   parent::__construct($message, $code, $previous);
  }
  
  public function __toString() {
   parent::__toString();
  }
 }
 
 interface Ifoo {
  /**
   *
   * @param mixed $a
   * @param mixed $b
   * @return double
   */
  public function Test($a, $b);
 }

 class fooProtected implements Ifoo {
  
  /**
   *
   * @param mixed $a
   * @param mixed $b
   * @return double
   */
  public function Test($a, $b){
   return $this->_Test();
  }
  
  /**
   *
   * @access protected
   * @param double $a
   * @param double $b
   * @throws divide
   * @return double
   */
  protected function _Test($a, $b) {
   return $a / $b;
  }
 }
 
 class barUnProtected extends fooProtected {
  
  /**
   *
   * @access protected
   * @param mixed $a
   * @param mixed $b
   * @return double
   */
  protected function _Test($a, $b) {
   
   //valid
   if(!is_double($a) || !is_double($b))
    throw new ArgumentException('Some variables not convertible to double!');
   
   //user rule
   if($b == 0)
    return 0.0;
   
   return parent::_Test($a, $b);
  }
 }
 
 /**
  *
  * @param Ifoo $dividor
  * @param mixed $a
  * @param mixed $b
  * @throws ArgumentException on un protected
  * @throws Exception
  * @return double
  */
 function dividePleaseTwoStrangeParams($dividor, $a, $b) {
  try
  {
   if($dividor instanceof Ifoo)
   {
    return $dividor->Test($a, $b);
   }
  }
  catch (ArgumentException $exc)
  {
   echo $exc->getTraceAsString();
   return 0.0;
  }
  catch(Exception $exc)
  {
   echo $exc->getTraceAsString();
   return 0.0;
  }
 }


* This source code was highlighted with Source Code Highlighter.


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

По хорошему придется делать dividePleaseTwoStrangeParams() методом класса, и в нем хранить ссылки на Ifoo обьекты и выбирать по ключу, опять же переданному в качестве аргумента функции… Замкнутый круг какой-то получается…

Подскажите, как научить dividePleaseTwoStrangeParams() самому определять, в каком контексте (в смысле с каким $dividor) выполняться?
Какую проблему вы хотите решить, используя этот класс?
Разделение доступа к методу (читать самого метода) для «Своих» и «Чужих».
Для «Чужих» осуществляются различного рода козни проверок, для «Своих» — сразу результат.
*Пример притянут для функций, пользуются которыми по обе стороны*

Почитав комментарии другое направление: вначале отфильтровали, внутрь пустили только валидные данные.
а раньше он бы свернулся…
Поздравляю вас, граданин!
Вы придумали «чистое программирование». Теперь осталось декларировать иммутабельность и реализрватть саму бариикаду на монадах.

Ждем новых, потрясаающих идей -)
Я себе чужих достижений не присваивал и честно признался, что вычитал идею в книгах других авторов. Просто там, чтобы её поймать нужно прочитать 2 книги по 800 страниц, а тут — 3 абзаца по 100 слов. Кому-то может сэкономить время.
Достаточно прочитать статью в википедии о Функциональном Программировании: там даже больше, шире, лучше и разнообразнее.

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

Но вы в любом случае делаете хорошее дело, просвещая общественность -)
я согласен про википедию, но не согласен, что «кому надо — знают». На Хабре много студентов, которые еще учатся. Если не сама статья, то мои ссылки на Макконнелла и Ваши на википедию могут быть полезными.
Есть другой подход, который мне видится более удобный и перспективным.
Можно назвать его «Let It Crash».

Любая сущность обрабатывающая данные может рухнуть при обработке данных когда хочет. Собственно это и будет сообщением о ошибке, ибо ее падение будет отслежено и залогинованно. Сущность будет перезапущена при получении новых данных, или по мониторингу… Остальные сущности (того же типа) опять же будут продолжать работать параллельно.

Собственно подход Erlang, название оттуда же.
UFO just landed and posted this here
LetItCrash не спасет от SQL инъекции или XSS уязвимости например…
Данная проблема, как мне кажется, решается не очень сложно.
Пишем код с использованием контрактов вроде Code Contracts. Компилятор проверяет, что внутри библиотеки/баррикады контракты не нарушены. После этого, можно [будет] сказать компилятору убрать код проверок, для которых доказано, что они всегда верны. Таким образом, реальные проверки останутся только по периметру баррикады, а корректность баррикады гарантируется компилятором, анализирующим контракты.

В Code Contracts можно задать (Requires) условия для аргументов метода. Также можно пообещать (Ensures) что для при выходе из метода будут выполнены определённые условия для результата и конечных значений аргументов (да аргументы могут меняться внутри процедуры). Также можно пообещать, что аргументы не изменятся если в методе возникнет исключение. Инварианты пишутся в отдельном методе.
При компиляции с соответствующими опциями, все утверждения проверяются и выводятся ворнинги, причём довольно осмысленные. Например, увидев код вроде int a = 1 << (arg1.Prop — 8), компилятор предложил добавить проверки на то, чтоm 7 < arg1.Prop < 39.
а как доказать, что контракты всегда соблюдаются и соответствующие проверки можно убрать? просто проанализировать граф вызовов и сопоставить requires и ensures — мало…
Компилятор анализирует сам код. Если ты написал Ensures, то он проверит, что ты в самом коде метода не нарушил это условие. Многие стандартные функции помечены как [Pure] — это означает, что они никак не меняют состояние объекта. Ещё есть случай совсем внешних библиотек. У них нет контрактов и система, отдавая им переменную, или принимая от них значение, не знает, что произойдёт. Для таких случаев есть Assume. Assume похож на Requires, но если Requires находится в начале метода и проверяет добросовестность вызывающего метода, то Assume просто делает недоказуемое утверждение (сопровождающееся проверкой в Debug версии). Вообще, задача, конечно, непростая. Как я понимаю, Code Contracts дествуют по принципу «виновен, пока не доказано обратное».

У меня был совершенно глупый случай, когда я не мог понять почему контракт считается недоказаным. У меня в программе был набор списков. Был метод, позволяющий добавить новый список в набор. При этом было условие, что длина нового списка должна быть такая же, как и длины уже существующих (проверялось при помощи Requires). Но инвариант, который постулировал, что длины всех списков равны, оказывался недоказуем. Причина банальна: тот, кто передал мне список для добавления, может всего добавить в него элемент, изменив длину и нарушив мой инвариант.
Если будет интересно, то основной сайт сейчас — research.microsoft.com/en-us/projects/contracts/
Вот тут (http://rise4fun.com/CodeContracts и pexforfun.com/) можно поиграться прямо в браузере — пишется код функции, а Code Contracts и PEX проверяют валидность (первая ссылка) и находят аргументы при которых возникают проблемы (вторая ссылка).
Они и есть темто, ради чего вся эта затея начиналась.

Все это красивые слова и прекрасно в теории, но сделать хорошую работающую программу по такому принципы задача очень сложная. К тому же за счет баррикады можно нехило потерять в производительности.
так утверждается же что наоборот, «баррикада» как раз уменьшает количество мест, где понатыканы тормозящие проверки?
найти бы теперь руководство, в котором учат где конкретно в коде лучше выстраивать эту «баррикаду» :)
В случае Java многие уже используют аннотации типа @NotNull и @Nullable, а IDE проверяет, чтобы это было корректно. В итоге — делаем в одном месте проверку, возвращаем @NotNull — и в остальных местах уже можно не проверять.
Я думаю, через пару-тройку лет подобная фича будет реализована на уровне синтаксиса, причем не только для not-null, а также для range-check и тому подобных условий.
Sign up to leave a comment.

Articles