Re: Стабильная система?
Артём Н. -> debian-russian@lists.debian.org @ Thu, 01 Oct 2015 23:12:30 +0300:
>>> Как измеряется надёжность?
>> Количеством, гм, ВНЕЗАПНЫХ глюков в единицу времени.
АН> Если серьёзно говорить об измерении, это не является показателем.
Если серьезно говорить об измерении, то показатель - это количество
времени, которое приходится тратить на поиск и починку проблем.
На этой задаче. Если бы это была реальная авионика,
>>> не исключена просто логическая ошибка в алгоритме, которую без тестов вы не
>>> сможете отловить (да знаю, это очевидно).
>>
>> Ну да, естественно. Вот это как раз обычно лечится ручным прогоном.
АН> Либо вы несколько противоречите сами себе, либо кода не так много.
АН> В таком случае, чтобы проверить все пути выполнения, вам надо выполнить столько же
АН> операций, сколько выполнят тесты. Только вручную.
Я ел этих устриц.
Тут есть три момента.
Первый - это то, что ручной прогон по каждому месту делается один раз
(ну, пять раз, но в один промежуток времени - пока пишется и проверяется
это место в коде). В смысле, если с умом писать на хаскеле, этого, как
правило, достаточно, потому что куски достаточно хорошо изолированы. В
парадигме с mutable state, конечно, дело обстоит намного хуже.
Второй - это то, что написать тесты, имитирующие ручной прогон, зачастую
задача более сложная, чем написать собственно код. Во всяком случае, я
еще ни разу не видел, чтобы такие тесты реально были написаны и
работали.
И третий - когда тестов много, ошибок в них тоже хватает. Мне более чем
неоднократно попадались тесты, которые не ловили ошибку в коде из-за
ошибки в тесте. И в случае автоматизированного теста проконтролировать
этот факт (что ошибка есть, но тест ее не видит) некому. То есть
уровень уверенности, который обеспечивают тесты, тоже имеет верхний
предел заметно меньше единицы.
>> Кстати, в оригинальном приборе (который стоит в реальных вертолетах)
>> таки благополучно перепутали в одном месте плюс с минусом, и отловлено
>> это было (мной) на стенде при ручном прогоне.
АН> Фактически, в данном случае стенд являлся тестом для реальной системы,
АН> а реальная система тестом для стенда.
Учитывая, что реальная система - это вертолет в воздухе, то тестирование
таким образом стенда, особенно на критических и закритических режимах,
дело такое...
>> Тесты, кстати, могли и не поймать - написать тест, который это ловит, весьма нетривиально.
АН> И вручную, простым прогоном без дополнительных данных, я так понимаю, вы тоже не поймали ошибку?
Я как раз поймал. Другое дело, что я ее поймал случайно, потому что
занимался в этот момент не поиском ошибки, а изучением работы прибора.
Я, собственно, увидел, что построенный маршрут вместо прямоугольника с
закругленными углами, который я ожидал увидеть, имеет форму
прямоугольника с петлями на углах. Пересмотр ожиданий (кто неправ - я
или прибор) занял две минуты. Так, на глазок, тест на это дело (в
предположении, что необходимые данные можно из прибора вытащить, что без
работы над кодом прибора вообще-то неправда) пришлось бы писать пару
дней. А тест, который работает как с черным ящиком (т.е. имитирует ввод
данных, делает снимок экрана и анализирует изображение) - так не меньше
месяца, и с кучей ручных прогонов.
Да, там это, возмоно, имело бы смысл, потому что реальный прибор писан,
вероятно, на C++, в лучшем случае на C, с mutable state, и гонять потом
этот тест следовало бы регулярно. Угадайте с трех раз, стали ли это
делать разработчики прибора?
>> Вообще мне приходилось работать в режиме TDD, когда я не знал про
>> хаскель. Там, собственно, я и узнал на практике, что прекрасная в
>> теории идея TDD на практике выражается в квадратичном количестве тестов
>> и многочасовом их прогоне.
АН> Да, я тоже это знаю.
АН> Ну, по факту, странный "спор". По-моему, вы сами прекрасно знаете, что
АН> без тестов никогда нельзя гарантированно сказать, что система не работает
АН> на некотором подмножестве случаев после её изменения.
АН> И тесты нужны. Хотя писать их лень и оверхед.
Видите ли, информация о том, что система НЕ работает - это не та
информация, которая нужна. Нужна достаточная степень уверенности, что
система РАБОТАЕТ. Эту уверенность можно получить разными способами.
Тесты, как мы прекрасно понимаем, гарантию работоспособности дать не
могут. Я утверждаю более конкретно - что тесты дают уверенность заметно
меньше единицы, и с возрастанием сложности системы предельная
уверенность, достигаемая тестами, падает. И начиная с некоторого
уровня, если программа не mission-critical, может оказаться, что при
грамотном выборе инструмента для создания рабочего кода написание
автоматизированных тестов вообще не окупается. В смысле, добавляемая
ими уверенность не стоит усилий по ее достижению.
>> Позже, уже в Транзасе, мне коллеги рассказывали, что они изначально тоже
>> писали тесты, и автоматически прогоняли их каждое утро, когда приходили
>> на работу и запускали smalltalk. Профессиональных параноиков среди низ
>> не было, с количеством тестов они не перенапрягались, но все равно через
>> несколько месяцев прогон тестов стал занимать столько времени, что был
>> отключен.
АН> Может проблема в Smalltalk?
АН> Я представил себя на месте пилота (если это не тренажёр).
АН> Наверное, если бы летал я, для себя я бы не стал отключать тесты...
У Лема, кажется, есть рассказ на эту тему. Как искусственный интеллект
космического корабля, обученный несколько параноидальным пилотом,
настолько погряз в проверках, все ли хорошо, что разбил корабль при
посадке.
Аналогично, если предполетные тесты занимают три часа, то за время их
выполнения что-то может снова испортиться. Например, погода. Поэтому в
реальной авиации никто и никогда не прогоняет полную проверку всех
систем самолета во всех режимах перед каждым вылетом (она, кстати,
занимает куда больше, чем три часа). Делают это хорошо если раз в год.
Перед вылетом проводят частичную, а в воздухе перед посадкой - еще более
частичную.
АН> Да, и может проблема не в тестах?
АН> Если изменяется кусок, который сильно изолирован, так ли часто надо прогонять _все_ тесты?
Проблема выяснения, насколько сильно изолирован кусок, в общем виде
неразрешима. Поэтому автоматизированная система не может решить, все ли
тесты надо прогонять.
>> Благодаря иммутабельности блокировки надо делать не на каждый чих, а
>> только при _явной_ операции записи, которых в иммутабельной парадигме
>> немного.
АН> Всё-таки принципиально ничего не меняется, и для записи они нужны...
Уменьшение количества блокировок (на глаз) на порядок на некоторых
задачах дает принципиальный прирост скорости. Если это задача на уровне
предела возможностей имеющейся техники, в результате можно из нерешаемой
задачи получить решаемую.
Мне рассказывали про обратную ситуацию, очень поучительная была история.
Меняли несколько лет назад в ЦБ систему межбанковских платежей. Старая
система, написанная еще программистами мейнфреймов, с кастомным
хранилищем данных, работала по схеме "оптимистичной блокировки" - она
сначала проводила все транзакции, потом подсчитывала балансы, и если в
результате на каком-то счету баланс оказывался отрицательным, начинала
откатывать транзакции, в которых с него что-то перечислялось. Ситуация
редкая, и система работала. Новая была написана с современной БД,
программистами, обученными по современным стилям, и не особо думавшими
над границами их применения. Она проверяла баланс (вероятно,
триггерами) при каждой транзакции, и откатывала транзакцию, если в ее
результате баланс оказывался отрицательным. Если транзакцию провести не
удавалось, она переставлялась в конец очереди, и т.д. А вот эта
ситуация, как ВНЕЗАПНО выяснилось, частая, и в результате новая система
тупо не успевала провести все транзакции за день. То есть в теории
работала, а на практике - нет.
АН> Но да, согласен, мало - меньший оверхед и меньше вероятность получить побочный эффект.
АН> Выше изоляция - лучше, мне тоже нравится (а многопоточность нравится не особенно
АН> по сравнению с многопроцессностью).
На некоторых задачах, опять же, многопоточность оказывается
принципиально (в смысле разницы успевает - не успевает) лучше
многопроцессности. Ибо нет переключения контекстов - нет сброса кэша
процессора. Ну, при условии, что блокировки ставятся не на каждый чих.
Сам я такого не писал, а вот тот знакомый, который мерил, как раз писал.
Он не просто так взялся мерить.
>> >> В смысле, человека берут решать сложную
>> >> задачу, а на чем он ее решает - это уже его личное дело.
>> >> Задача сложная, "программист на XXX" ее не решит.
>> АН> Как правило, всё-таки декларируют определённый язык.
>> АН> Решите вы задачу и уйдёте.
>> АН> А кто будет поддерживать ваш код затем?
>>
>> Поскольку задача сложная, то поддерживать будет человек со сравнимым
>> интеллектом. Собственно, я не единственный сотрудник Транзаса,
>> способный поддерживать код на хаскеле.
АН> Но есть ещё "умение разбираться в чужом коде".
АН> Хотя, в данном случае, наверное Хаскелю будет плюс, в целом.
АН> Сложно найти людей, которые с ним работают, чтобы поручить
АН> им поддерживать кусочки задачи (не думаю, что интеллект измеряется знанием или незнанием
АН> Haskell, я вот его не знаю, так что, я автоматически попадаю у вас в категорию "тупой"?).
Я же сказал, "будет дешевле выучить". Умный человек отличается от
тупого не тем, что он уже знает, а тем, что он может узнать, когда
понадобится.
>> Человеку со сравнимым интеллектом будет дешевле выучить хаскель и
>> разобраться в хаскельном коде, чем разобраться в коде того же
>> назначения, написанном на C++, который он, предположим, уже знает.
>>
АН> Вероятно. Это зависит от того, кем, как и с какой целью так написан C++ код.
АН> Есть C++ код, в некоторой степени написанный для того, чтобы показать "какая
АН> крутая у меня квалификация".
На сложной задаче кода на С++ неизбежно будет много, даже если он
написан хорошо. Соответственно, сильно возрастают расходы на
"разобраться в коде". Язык более высокого уровня позволяет выразить ту
же мысль ближе к собственно мысли, соответственно, разобраться проще.
>> А если учитывать, что "поддерживать" часто начинается с "рефакторить",
>> то тут преимущество хаскеля с его "как только после рефакторинга
>> скомпилировалось, то почти наверняка работает правильно" становится
>> заметным.
АН> Странная поддержка...
Поддержка кода, который работает, чаще сводится к тому, что нужно
приделать дополнительную функциональность, не сломав имеющуюся. И
реже — к исправлению вылезших ошибок. Приделывание дополнительной
функциональности довольно часто начинается с рефакторинга, потому что
закладок именно под нее в изначальном коде нет.
>> Если задача простая, то ситуация, разумеется, иная.
АН> На C++ любую задачу возможно решить сложно.
Да, но хороший программист так делать не будет. Плохой, возможно,
будет, но хороший тупо не захочет поддерживать такой код - либо
перепишет (на чем сочтет нужным), либо пойдет на другую работу.
>> Я пробовал, довольно много. ООП как парадигма - это duck typing
АН> Да не обязательно. Это выражение больше напоминает Python.
АН> И всё вытекающее: нестрогую типизации прежде всего, прототипное "наследование" и т.п..
В парадигме там в чистом виде duck typing - есть объект, который
абсолютный черный ящик, и все, что ты можешь с ним сделать, это послать
ему сообщение. Если не ошибаюсь, то строго говоря, там даже ответ на
сообщение как отдельная сущность не предусмотрен - если на сообщение
надо ответить, получатель посылает сообщение отправителю.
Реализации в языках со статической типизацией добавляют туда проверку на
уровне компилятора - он не согласится скомпилировать посылку сообщения,
если не будет уверен, что получатель сообщения такого типа принимает.
Но это уже вне парадигмы ООП.
В хаскеле, кстати, элемент ООП тоже есть, в языке называется class, а в
описаниях чаще - typeclass, чтобы отличать от понятия class в
традиционных ООП-языках, а на практике соответствует тому, что в Java
называется interface. (А в парадигме, насколько я понимаю, под классом
понимается именно интерфейс.) Но в хаскеле оно применяется чаще
все-таки с подразумеванием нижележащей математической модели, в то время
как в ООП скорее с подразумеванием, гм, некоего представления в голове
конкретного программиста, из-за чего интерфейсы бывают странны.
>> следствие, вышеупомянутое квадратичное количество тестов. Собственно,
>> это оно у хорошо примененной ООП квадратичное, у спагетти-кода оно
>> экспоненциальное.
АН> И на хаскеле возможно так написать. :-\
Не вопрос. Просто хаскель провоцирует более аккуратный стиль.
>> А вот
>> функциональная парадигма с богатой системой типов позволяет поднять
>> удерживаемый в голове уровень сложности задачи на следующую ступень.
АН> Разве не проще оперировать объектами в терминах какого-нибудь DSL?
Так объектами или DSL? :)
DSL - это в первую очередь L, что сразу заменяет одну проблему на две:
во-первых, придумать такой язык, на котором удобно выражать решение
задачи, а во-вторых, реализовать его парсер на языке программирования.
Это нередко имеет смысл, но и в этом случае - угадайте, на каком языке
удобнее преобразовывать грамматику из головы в парсер?
Но чаще делают не так. Чаще делают EDSL (embedded DSL), т.е. строят DSL
поверх базового языка. Если не надо парсить внешние файлы, то это
намного дешевле. И вот тут богатая система типов позволяет сделать
более выразительный EDSL.
>>> Ещё тут рядом на Erlang пытались что-то делать, не пошло.
>>> Некому это поддерживать.
>>> Просто людей не найти, кто станет на этом писать.
>>
>> У меня коллега уехал в Швецию как раз на позицию, где был нужен Erlang.
>> Идет.
АН> Не именно потому он уехал в Швецию, что специалистов фиг найдёшь? :-)
Нет. Он вообще-то до того как раз на хаскеле и смоллтоке писал, а
Erlang изучал уже на месте.
>> Но насколько я его понял, Erlang нишевый по назначению, для
>> программирования общего назначения на нем писать неудобно.
АН> Вполне универсальный язык, насколько я знаю.
А насколько я знаю - не очень. Начиная с того, что на нем текст
обрабатывать неудобно. Можно, но неудобно. А удобно, насколько я
понимаю, делать распределенные системы, которые занимаются больше
аккуратной передачей данных, чем их продвинутой обработкой.
Reply to: