Для надежного кода, NModel, Spec Explorer, F # или других? - PullRequest
4 голосов
/ 04 мая 2010

У меня есть бизнес-приложение на C # с юнит-тестами. Могу ли я повысить надежность и сократить время и затраты на тестирование с помощью NModel или Spec Explorer? С другой стороны, если бы я переписал его на F # (или даже на Haskell), какие виды (если они есть) повышения надежности я мог бы увидеть?

Кодовые контракты? ASML

Я понимаю, что это субъективно, и, возможно, аргументированный, поэтому сделайте резервную копию ваших ответов с данными, если это возможно. :) Или, может быть, удачный пример, например, система грузовых перевозок Эрика Эванса?

Если мы рассмотрим

Модульные тесты, чтобы быть конкретными и сильные теоремы, проверено квазистатически для конкретных «интересных случаев» и «Типы» являются общими, но слабыми теоремами (обычно проверяются статически), а контракты являются общими и сильными теоремами, динамически проверяются на конкретные случаи, возникающие во время обычной работы программы. (из Б. Пирса, считающихся вредными ),

где подходят эти другие инструменты?

Мы могли бы задать аналогичный вопрос для Java, используя Java PathFinder, Scala и т. Д.

Ответы [ 2 ]

5 голосов
/ 05 мая 2010

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

Сказав это, язык, безусловно, оказывает существенное влияние.При прочих равных условиях:

  • Дефекты примерно пропорциональны количеству SLOC.Языки, которые являются более краткими, видят меньше ошибок кодирования.Похоже, что для Haskell требуется около 10% SLOC, требуемого для C ++, Erlang - около 14%, Java - около 50%.Я думаю, что C #, вероятно, подходит для Java в этом масштабе.
  • Системы типов не равны.Языки с выводом типа (например, Haskell и, в меньшей степени, O'Caml) будут иметь меньше дефектов.В частности, Haskell позволит вам кодировать инварианты в системе типов, так что программа будет компилироваться, только если они могут быть подтверждены.Это требует дополнительной работы, поэтому рассмотрите компромисс в каждом конкретном случае.
  • Управление состоянием является источником многих дефектов.Функциональные языки и особенно чистые функциональные языки избегают этой проблемы.
  • QuickCheck и его родственники позволяют вам писать модульные и системные тесты, которые проверяют общие свойства, а не отдельные тестовые случаи.Это может значительно сократить объем работы, необходимой для тестирования кода, особенно если вы стремитесь к высоким показателям охвата тестирования.Набор свойств QuickCheck напоминает формальную спецификацию, и эта концепция прекрасно вписывается в разработку через тестирование (сначала пишите свои тесты, и когда код проходит их, все готово).

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

1 голос
/ 17 февраля 2011

Некоторые комментарии к цитате в контексте C #, который является моим «первым» языком:

Юнит-тесты должны быть конкретными и сильными теоремы,

Да, но они могут не дать вам проверки логики первого порядка, например, «для всех x существует ay, где f (y)», больше похоже на «существует ay, здесь это (!), F (y)» , ака настройка, действовать, утверждать. ;) *

включен квазистатически конкретные «интересные случаи» и Типы должны быть общими, но слабыми теоремами (обычно проверяется статически),

Типы не обязательно такие слабые **.

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


Модульное тестирование

Pex + Moles. Я думаю, что это приближается к логическому типу проверки первого порядка, поскольку он генерирует крайние случаи и использует решатель C9 для работы с целочисленным решением ограничений. Мне бы очень хотелось увидеть больше руководств по Moles (moles для замены реализаций), в частности, вместе с некоторой инверсией контейнера управления, которая может использовать уже существующие и абстрактные реализации абстрактных классов и интерфейсов.

Слабые типы

В C # они довольно слабые, конечно: универсальная типизация / типы позволяет вам добавлять семантику протокола для одной операции - т.е. ограничения типов для интерфейсов, которые находятся в некоторые смысловые протоколы, с которыми согласны реализующие классы. Однако статическая типизация протокола предназначена только для одной операции .

Пример: API Reactive Extensions

Давайте возьмем Reactive Extensions в качестве темы для обсуждения.

Контракт, требуемый потребителем, реализуется наблюдаемым.

interface IObserver<in T> : IDisposable {
  void OnNext(T);
  void OnCompleted();
  void OnError(System.Exception);
}

Протокол содержит больше, чем показывает этот интерфейс: методы, вызываемые на экземпляре IObserver <в T>, должны следовать этому протоколу:

Сортировка:

OnNext {0, n} (OnCompleted | OnError) {0, 1}

Кроме того, на другой оси; времени измерения:

Время:

for all t|-> t:(method -> time). t(OnNext) < t(OnCompleted)
for all t|-> t:(method -> time). t(OnNext) < t(OnError)

т.е. после OnCompleted xor OnError нельзя вызывать OnNext.

Кроме того, ось параллелизма:

Параллелизм:

no invocation to OnNext may be done in parallel

т.е. Существует ограничение планирования, которому должны следовать разработчики IObservable. Никакая IObservable не может выдвигать из нескольких потоков одновременно, без предварительной синхронизации вызова вокруг контекста.

Как вы можете легко проверить этот контракт? С c # я не знаю.

Потребитель API

Со стороны потребителя приложения могут быть взаимодействия между различными контекстами , такими как Dispatcher, Background / другие потоки, и предпочтительно мы хотим дать гарантии, что мы не закончим в тупике.

Кроме того, существует требование обрабатывать детерминированное удаление наблюдаемых. Это может быть неясно все время, когда возвращенный экземпляр IObservable метода расширения заботится о экземплярах IObservable аргументов метода и располагает их, поэтому необходимо знать о внутренней работе черного ящика (в качестве альтернативы вы можете оставить ссылки «разумным образом», и GC примет их в какой-то момент)

<<< Без реактивных расширений это не обязательно проще: </h3> Существует пул задач поверх TPL реализован. В пуле задач у нас есть очередь для кражи работы делегатов для вызова в рабочих потоках. Использование APM / begin / end или асинхронного шаблона (который ставит в очередь пул задач) может оставить нас открытыми для ошибок упорядочения обратных вызовов, если мы изменяем состояние. Кроме того, протокол begin-invocations и их обратных вызовов может быть слишком запутанным и, следовательно, им невозможно следовать. На днях я прочитал вскрытие о проекте Silverlight, у которого проблемы с просмотром леса бизнес-логики для всех деревьев обратного вызова. Тогда есть возможность реализации асинхронной монады бедняка, IEnumerable с асинхронным «менеджером», выполняющим итерацию по ней и вызывающим MoveNext () каждый раз, когда завершается возвращаемый IAsyncResult. ... и не заставляйте меня начинать с многочисленных скрытых протоколов в IAsyncResult. Еще одна проблема, без использования Reactive extensions, - это проблема с черепахами - как только вы решите, что хотите, чтобы операция блокировки ввода-вывода была асинхронной, нужно, чтобы черепашки были вплоть до вызова p / вызов, который помещает связанный поток Win32 на порт завершения IO! Если у вас есть три уровня, а затем некоторая логика внутри самого верхнего уровня, вам нужно заставить все три уровня реализовать шаблон APM; и выполнить многочисленные контрактные обязательства IAsyncResult (или оставить его частично нарушенным) - и в библиотеке базовых классов нет общедоступной реализации AsyncResult по умолчанию. >>>

Работа с исключениями из интерфейса

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

Контекст

Допустим, мы ловим исключение из контракта / интерфейса (не обязательно из реализаций IObservable реактивных расширений, которые имеют монадическую обработку исключений, а не на основе стекового фрейма).

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

Aim

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

Пример

Реальным примером здесь может быть Spotify, который я использую каждый день.

Мой маршрутизатор / точка доступа за 100 долларов в случайное время выбрасывает полотенце. Я предполагаю, что в нем есть ошибка кеша или какая-то ошибка переполнения стека, как это происходит каждый раз, когда я пропускаю через него данные LAN / WAN со скоростью более 2 МБ / с.

Мне нужны сетевые карты; Wi-Fi и сетевая карта. Ethernet-соединение обрывается. Сокеты цикла обработчика событий Spotify возвращают неверный код (я думаю, что это C или C ++) или генерируют исключения. Spotify должен справиться с этим, но он не знает, как выглядит моя сетевая топология (и нет кода, чтобы попробовать все маршруты / обновить таблицу маршрутизации и, следовательно, используемый интерфейс); У меня все еще есть маршрут к Интернету, но только не на том же интерфейсе. Spotify падает.

Дипломная работа

Исключения просто не достаточно семантические. Я полагаю, что можно взглянуть на исключения с точки зрения монады ошибок в Haskell. Мы либо продолжаем, либо прерываем: разматывая стек, выполняя перехват, выполняя молитву finally, мы не в конечном итоге сталкиваемся с условиями гонки на других обработчиках исключений или с GC, или с асинхронными исключениями для выдающихся портов завершения IO.

Но когда соединение / маршрут одного из моих интерфейсов обрывается, происходит сбой Spotify.

Теперь у нас есть SEH / Структурная обработка исключений, но я думаю, что в будущем у нас будет SEH2, где каждый источник исключения дает, за фактическим исключением, дискриминационное объединение (т.е. оно должно быть статически типизировано для связанной библиотеки сборка) о возможных компенсирующих действиях - в этом примере я мог бы представить сетевой API-интерфейс Windows, указывающий приложению выполнить компенсирующее действие, чтобы открыть тот же сокет на другом интерфейсе или обработать его самостоятельно (как сейчас), или повторить попытку сокета, с некоторой управляемой ядром политикой повторения. Каждый из этих параметров является частью различаемого типа объединения, поэтому разработчик должен использовать один из них.

Я думаю, что когда у нас будет SEH2, он больше не будет называться исключением.

^^

Во всяком случае, я уже слишком отвлекся.

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

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

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

Почему это относится к "слабым типам"?

Потому что я считаю, что система типов будет частью решения; типы не должны быть слабыми! Краткий код и системы строгого типа (например, Haskell) помогают программистам создавать надежные программы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...