Некоторые комментарии к цитате в контексте 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) помогают программистам создавать надежные программы.