Не верь, не бойся, не проси - [entries|archive|friends|userinfo]
phantom

[ website | My Website ]
[ userinfo | ljr userinfo ]
[ archive | journal archive ]

[Jan. 12th, 2017|09:39 pm]
Previous Entry Add to Memories Tell A Friend Next Entry
Какой я всё-таки охуительный программер

Наконец-то наблатыкался в макросах достаточно, чтобы залабать всё круто. Давно хотел, да не мог... а теперь смог, я круть.

Была у меня прога, чтоб учить числительные. Одна функция берёт число, например, 17, и выдаёт его написание словами, а другая вызывает её и в бесконечном цикле просит юзера написать число. Сравнивает их, и говорит: "симнадцать неправильно, правильно семнадцать". Ну, и генератор случайных чисел - чуть хитровыебанный.

Однако, бесконечный цикл это скучно, да и до каких пор трахать юзера им? Когда наступает тот момент, когда уже можно сказать, что он выучил уже числительные?

И меня посетила гениальная мысль: надо вхуярить туда code coverage и следить за ним динамически!

Долго тренировался, с макросами ебался, код инструментировал. Сначала реализовал простейшее профилирование и через него - покрытие функций. Потом вглубь по коду - с трудом, через несколько промежуточных задач, - но написал что-то вроде операторного покрытия: каждый узел в AST зарегил в хэше и проинструментировал. Посещение каждого узла увеличивает свой счётчик визитов.

И хотя это не новость, а обычный statement coverage, но в динамике его, кажется, не использовал никто. Так, чтобы прога следила за собой же и принимала решения в зависимости от того, на сколько процентов своих нутрей она уже выполнилась. Ну, точнее, я не слышал таких громких примеров.

Теперь прогу не в бесконечной рекурсии запускаю, а только до тех пор, пока control flow не обойдёт все узлы. А если юзер ошибается, сбрасываю счётчики, и игра сначала.

Более того, таким финтом достигнут двойной выигрыш. Теперь на каждом шаге мурыжу генератор, пока новое число не добавит хоть чуть-чуть к метрике, и тогда только выдаю его юзеру. Это значит, что новый инпут всегда проводит выполнение по какому-то ещё непокрытому пути.

Общий эффект такого изъёба в данной педагогической программе офигенный. Кроме того, это и пример правильного education gamification'а. Играть просто приятно. Кажется, что прога ровно столько задаёт числительных, сколько нужно, чтобы их выучить (точнее, проверить, что они полностью выучены), причём в достаточно интересной случайной последовательности (ну, это с помощью неравномерного распределения в генераторе достигается).

Потом, однако, понял, что в чистой функции, вообще-то, не нужно все узлы инструментировать, а только ветвление. В eager ФП под ветками всё вызовется с неизбежностью, если отбросить возможность эксепшенов и забыть про short-curcuiting. Перелабал под branch coverage, стало эквивалентно круто, но генерированный код чище.

Ещё была проблема со статически определёнными списками. Они-то по-любому проходятся, и какой-нибудь a = { "нуль", "один", "два", ... } считается за один нод. Разные такие строки лучше через юзера прогнать отдельно. Теоретически, этот дефинишен и обращения a[i] можно в метафазе перехуярить на вызов инструментированной индексирующей функции (типа data coverage), но это нужно коданализ продвинутый. Потому пока что вручную разбранчил через паттернматчинг - каждая константа становится своей веткой в функции get-a(i).

Генератор - отдельная тема. К слову, созреть моей гениальной идее, наверное, помогло знание о quick check'e в haskell'е, где тестирование инвариантов через случайный генератор инпута. Однако, если он не порождает все варианты для обхода, то метрика, равная ста процентам, выродится в бесконечный цикл. Типа, нод с константой "триллион" не достигнуть, если генератор в пределах миллионов работает. То же и с мёртвым кодом (к слову, можно это использовать как dead-код-детектор).

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

Попытка открыть глаза и окинуть этот ландшафт взглядом - сродни, в нашем случае, кодоанализу продвинутому. И следующий вопрос, над которым стоит поразмышлять, - это как генерить такой инпут, зная AST, чтобы control flow скатывался в заданную наперёд ветку. Типа, как кидать шары прицельно.

Вот так вот, покрытие кода - но не для тестирования, а в рантайме. Точнее для тестирования, но не регрессионного тестирования программ. А как бы прогрессионное тестирование человека - human in the loop.
LinkLeave a comment

Comments:
[User Picture]
From:[info]lenkasm
Date:January 12th, 2017 - 11:45 pm
(Link)
Да, интересная идея повернуть тестинг в сторону человека.

А ты в свободное от работы время программированием занимаешься?
From:[info]phantom
Date:January 13th, 2017 - 12:00 am
(Link)
Спасибо на добром слове, однако.

Часто на работе прожу для себя вот такое, например. Ну, такой подход известные недостатки имеет.
From:(Anonymous)
Date:January 13th, 2017 - 12:12 am
(Link)
Какой ты всё-таки охуительный программер
From:[info]phantom
Date:January 13th, 2017 - 12:24 am
(Link)
Доброе слово и кошке приятно...