Артём Н. -> [email protected] @ 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 нишевый по назначению, для >> программирования общего назначения на нем писать неудобно. АН> Вполне универсальный язык, насколько я знаю. А насколько я знаю - не очень. Начиная с того, что на нем текст обрабатывать неудобно. Можно, но неудобно. А удобно, насколько я понимаю, делать распределенные системы, которые занимаются больше аккуратной передачей данных, чем их продвинутой обработкой.

