Pull to refresh
236
0
Сергей Тепляков @SergeyT

Пользователь

Send message
Я не совсем понял мысль. О какой «неподдержке» идет речь? Тул этот асболютно standalone, тут нет разницы между MS и non-MS assemblies.
Сейчас это — by design. Ну и с альтернативами ведь довольно плохо все. Ведь основная фишка контрактов (ладно, одна из основных) — это возможность включать/отключать некоторые утверждения по требованию. Вот одна сборка, там нужно все — включая инварианты, а вот эта — тут оставляем только предусловия. А вот здесь мы вообще хотим положить контракты рядом, чтобы они никак не влияли на эффективность нашего кода, но использовались нашими клиантами, если они захотят. В общем, вменяемая альтернатива — это впиливать это на уровне компилятора. И недавно даже proposal появился, хотя ХЗ, что из него выйдет и выйдет ли вообще что-то.
Там нужно вначале выяснить, что именно тормозит. Есть все шансы, что проблемы не в самом rewriter-е, а в CCI — туле, который используется для анализа и генерации IL-а. Если это так, то решать глобальную проблему с производительностью нужно будет глобальным образом — переходом на CCI2 (есть и такое) или на Розлиновские потроха для чтения/генерации IL-а. Но такие радикальные шаги будет довольно сложно реализовать.
Да, rewriting имеет сложность O(N) от количества зависимостей переписываемой сборки (нужно получить полное транзитивное замыкание всех зависимостей прежде чем можно будет вычислить контракты класса). Это одна из причин существенного замедления ccrewrite при увеличении числа проектов (поскольку обычно при том растет и число зависимостей каждого из них).
Да, исходники открыты с начала года. Но там довольно высокий порог вхождения, ИМХО. В статическом анализаторе вообще без бутылки и PhD не раобраться. В rewriter-е разобраться можно, но тоже довольно трудоемко.

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

З.Ы. У rewriter-а есть очень нехорошее поведение. Когда есть анонимный метод, проверяющий постусловие и захватывающий только Contract.Result, то в этом случае ccrewrite генерирует некорректный с точки зрения многопоточности код, протаскивая результат через статическое свойство.
У меня в текущем проекте (200К строк где-то) контракты используются сверх активно. Да, билд тормозится сильно. Но теперь есть планы и возможности по устранению этих проблем.

Так что я все еще советую;)
Название статьи показалось до боли знакомым.
Не вот эта ли статья была источником вдохновения: How to receive a million packets per second?
Небольшой опыт написания книг показал, что этот процесс достаточно сложен и очень сильно напоминает разработку софта. Нужно найти определенный балланс между тем, что давать, о чем упоминать и на что забить. Молодые авторы (книг, блогов, тренингов) обычно стараются впихнуть максимальный объем материала по принципу — чем больше, тем лучше. Подход хорош для автора, но не всегда подходит читателю, которому теперь придется перескакивать с темы на тему (получаем low cohesion и нарушение SRP). С другой стороны, забить совсем — тоже не хорошо, поскольку связь у двух тем все же присутствует. В результате и появляется большое число перекрестных ссылок:)
Несколько замечаний:

1. StackoverflowExcpetion невозможно обработать. Пруф:

Starting with the .NET Framework version 2.0, a StackOverflowException object cannot be caught by a try-catch block and the corresponding process is terminated by default. Consequently, users are advised to write their code to detect and prevent a stack overflow. For example, if your application depends on recursion, use a counter or a state condition to terminate the recursive loop. Note that an application that hosts the common language runtime (CLR) can specify that the CLR unload the application domain where the stack overflow exception occurs and let the corresponding process continue.


2. OutOfMemoryException — тоже обработать сложно. Точнее, существует две версии OOM — синхронная, которая бросается при выделении большого куска, который GC выделить не может, и асинхронная — которое может сгенерировать CLR, например, при попытке выделить новый сегмент для GC0.

3. Метод FullMessage является адским велосипедом, который не понятно зачем нужен. Вы же выводите в builder целиком объект исключения на каждой итерации. A ex.ToString будет выводить каждый раз полное исключение, включая все вложенные. Просто создайте исключение с пятком вложенных и вызовите этот метод.

4. Существует два стандартных способа фильтрации исключений: по типу в блоке catch, с помощью фильтров исключений (начиная с C# 6.0), зачем делать еще один обобщенный обработчик на основе Func-ов и как он помогает решать проблему обработки исключений, мне не ясно.
> Можете прокомментировать этот комментарий на схожу тему? Это второй вопрос. Тема крайне интересная и до конца не понятая, как мне кажется.

Очень толковый комментарий. Возразить нечего.
Действительно, проблема с исключениями в том, что одна и та же концепция (исключения) несет разную информацию: это могут быть баги (ArgumentException и наследники, NullReferenceException, InvalidOperationException), критические невостановливаемые системные ошибки (Critical System Faults) (OutOfMemoryException, StackOverflowException, ExecutionEngineExcpetion, ThreadAbortException и, возможно, одно-два других), ошибки окружения (environmental faulures или recoverable system failrues: IOException, DBException etc), и логические ошибки (DatabaseRecordNotFound, EmployeeAlreadyExistsException etc).

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

Вот пример. Насколько логично, что контроллер или модель, которые используют DAL будут бросать DbException? не логично, поскольку для пользователя модели или контроллера наличие базы данных вообще спрятано. Это деталь реализации модели или контроллера. Поэтому, когда происходит ошибка в DAL-е, у вызывающего кода есть два варианта: обработать исключение самостоятельно (подавить, вернуть значение по умолнчанию, попробовать трижды и закрыть принудительно приложение), или сообщить вызывающему коду о неудаче, при этом использовать термины, понятные для него (это значит, что нужно бросать PersistentStoreUnavaliable или InfrastructureFailure, завернув во внутрь нового исключения исходное).

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

При этом что делать моделе/контроллеру, если при вызове DAL-а вылетает что-то отличное от DbExcpetion? Можно ли при этом сказать, что PersistentStoreUnavailable? не совсем! Ведь смысл PersistentStoreUnavailable в том, что персистент стор недоступен сейчас, но будет доступен в будущем. Но если мы хватаем System.Exception, то причина может быть не во временной недоступности хранилища, а в баге в реализации слоя доступа к данным, который никогда не будет исправлен. Поэтому оборачивать что-то, что мы не ожидали в PesistentStoreUnavailable опасно, поскольку мы можем сокрыть ошибки!

С другой стороны, генерируемые исключения редко описываются достаточно четко, чтобы модель или контроллер понимали, какие исключения ожидаемые, а какие нет. Вот именно поэтому мы обычно приходим к стратегии catch all — хватаем все, показываем MessageBox и все. Но последнее замечание не означает, что хватать все стоит. Гораздо разумнее следовать такому подходу: хватаем то, что знаем, оборачиваем в более высокоуровневое исключение или пытаемся восстановиться самостоятельно, а все остальные исключения пробрасываем дальше. Так доходит до самого верха стека вызовов, где мы уже решаем, что делать с неизвестными исключениями — показывать сообщение и закрываться, просить пользователя попробовать позже или что-то еще.

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

Для этого команда должна перехватывать понятные ей исключения оборачивать их в более высокоуровневые исключение (throw ФайлоОченьНужноеДляЭтогоПриложенияНеНайденоИлиИспорчено), которое затем будет уже перехватываться на верхнем уронве. Но показывать сообщения об ошибках в команде, если они не являются рутами — не хорошо.
> согласны ли вы с тем, что в таких случаях можно поймать все исключения, профильтровать на АДСКИЕ и просто вывести сообщение об ошибке?

Не совсем:)

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

Плюс этого подхода в том, что обработка ожидаемых и неожидаемых исключений четко разнесена. В этом случае ваша вью-модель не подавит баги (например, ContractException не должны перехватываться никогда, это баги, аналогично не должны подавляться ArgumentExceptions). При этом решение о поведении при встрече необработанных исключений будет в одном месте (глобальном обработчике, которые есть для Windows Forms и WPF), а не размазаны по всем командам.
ну так я ж и пишу, что в общем случае на уровне статического анализа эта задача не решается.
Этот пример относительно простой, поскольку можно посмотреть секцию exceptions документации. Но это не будет работать со сторонним, но не стандартным кодом. Можно попробовать прикрутить анализатор, аналогичный приведенному в предыдущем комментарии по ссылке Вывод NotNull-аннотаций по байткоду Java. Но это будет весьма затратная операция.

Именно по этой причине, данная проблема на 100% не решаема.
Да, и мысль дельная. Тот же R# именно этот подход используется для анализа nullable/not-nullable типов.
По идее, эти все вещи можно даже на лету выводить. Вот, народ делает такое для вывода not-nullable аннотаций в IntelliJ IDEA — Вывод NotNull-аннотаций по байткоду Java. Теперь дело за малым, скрестить это дело с Розлином и впихнуть в анализатор!:)
Дельный совет! Но для реализации этого дела нужно решить более общую проблему: нужно выяснить, какие исключения может генерировать метод. А вот это задача весьма непростая. Точнее в общем случае она вообще не решается, поскольку является разновидностью проблемы под названием Halting Problem.

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

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

Ведь никто не отменял правила из Framework Design Guidelines, которые говорят, что все статические методы должны быть потокобезопасными. Причем сделать это довольно просто, достаточно перенести статическое состояние во вновь выделенный объект, а старый метод сделать фасадным методом. Так бы автор показал, что он знает и уважает принципы той платформы, на которой он пишет примеры, да еще бы и один дополнительный паттерн показал бы в придачу.
Книга Head First Design Patterns действительно хороша, но это скорее учебник для новичка. Как уже сказали, там примеры с пиццами, которые сложно встретить в реальных приложениях.
Я думаю, что сегодня выпускать книгу без электронной версии смысла нет. Я сам когда-то большой любитель бумажных книг, уже давно пересел на электронные версии. Так что в том или ином виде, но электронная версия точно будет.

Information

Rating
Does not participate
Location
Washington, США
Registered
Activity