Сегодня в очередной раз удалось убедится в разумности очень простой вещи. Но зайду, как положено, издалека :)
Когда-то давным-давно, в тогда еще совсем молодой компании Интервэйл, принимал участие в выборе системы контроля версий для управления исходным кодом. В рамках этого процесса лично мне довелось познакомиться с несколькими представителями этого класса продуктов. Если не ошибаюсь, там были PVCS, CVS, Svn, ClearCase, Perforce, Darcs, SourceSafe, AccuRev SCM, Code Co-Op и еще какая-то экзотика. Вот из этой экзотики запомнилась такая штука, как Aegis. Штука сильно специфическая, да еще, как мне помнится, сильно заточенная под Unix. В short-list, естественно, не попавшая.
Но вот один из аспектов подхода к процессу внесения изменений в кодовую базу в Aegis-е мне буквально врезался в память. Дело в том, что если нужно было внести изменения, исправляющие какой-то баг, то разработчик должен был снабдить изменения еще и кодом теста. Сам Aegis сначала запускал этот тест со старой кодовой базой и тест должен был "сломаться". Если это происходило, то Aegis запускал тест на новой кодовой базе. И сейчас тест должен был отработать успешно. Только если оба эти условия выполнялись, Aegis коммитил изменения в репозиторий.
Очень логичный подход -- если знаешь или думаешь, что знаешь про наличие проблемы в коде -- сначала напиши тест, который это демонстрирует. И только потом уже вноси изменения.
Вчера вечером, размышляя о добавлении очередной нужной фичи в старый код вдруг пришел к выводу, что в старом коде есть проблема, которая проявляется в очень специфической ситуации. Ситуация из разряда гипотетических, но какие фокусы могут происходить в многопоточных программах -- это просто фантастика. Тут в прямом смысле лучше перебдеть, чем... :) Поэтому стал придумывать решение.
Придумался красивый вариант. Трудоемкий правда, но красивый. Придумался более-менее подробно, пусть не до последней точки с запятой, но первый вариант плана по его реализации из восьми пунктов был написан сходу. Одним из пунктов в этом плане был unit-тест, который должен был продемонстрировать, что тот самый хитрый сценарий отрабатывает без проблем.
Вынужден признать, этот пункт с unit-тестом был не первым в списке :) Но начать решил все-таки с него, т.к. он выглядел самым сложным пунктом, а именно самые сложные задачи лучше решать "на свежую голову".
Написал, запустил. Получил совсем не тот результат, который ожидал.
Стал разбираться. Оказалось, что вчера вечером я круто ошибся. Запутался в моментах захвата и освобождения нескольких мутексов. И хоть и просматривал код вдоль и поперек несколько раз, все равно почему-то считал, что часть операции происходит без захвата одного из них. Ан нет. Мутекс все это время захвачен, у гипотетического сценария нет шансов сработать. Все нормально. Необходимости в переделках нет. Придуманное решение, хоть и красивое, не нужно. Что не может не радовать, т.к. и так есть что делать, без переделки старого и давно работающего кода.
Вот так, несколько часов, потраченных на предварительное создание unit-теста позволили сэкономить день или два работы.
Понятное дело, в данной заметке я выступил как КО. Но баги бывают разных типов: есть те, которые уже проявились и про которые известно как их повторить. В таких случаях можно сразу внести изменения в код и затем написать unit-тест, проверяющий корректность исправления. Собственно, когда разработчики работают по заявкам из баг-трекера, так и происходит (за исключением того, что некоторых программистов нужно заставлять и приучать писать unit-тесты или хотя бы самому проверять работоспособность изменений в условиях, описанных в баг-трекере, а не перекладывать все это на девушек из QA).
А бывают и баги, о которых лишь подозревают, пусть и сильно подозревают. Вот как в моем вчерашнем случае. Тут уж нужно бороться с соблазном сначала внести изменения, а потом уже написать тесты. Не всегда находятся силы принять правильное решение :( В этот раз повезло :)
PS. Кстати, далеко не для всех ошибок, о которых подозреваешь, можно легко создать условия воспроизведения -- вот один пример из своей практики.
Когда-то давным-давно, в тогда еще совсем молодой компании Интервэйл, принимал участие в выборе системы контроля версий для управления исходным кодом. В рамках этого процесса лично мне довелось познакомиться с несколькими представителями этого класса продуктов. Если не ошибаюсь, там были PVCS, CVS, Svn, ClearCase, Perforce, Darcs, SourceSafe, AccuRev SCM, Code Co-Op и еще какая-то экзотика. Вот из этой экзотики запомнилась такая штука, как Aegis. Штука сильно специфическая, да еще, как мне помнится, сильно заточенная под Unix. В short-list, естественно, не попавшая.
Но вот один из аспектов подхода к процессу внесения изменений в кодовую базу в Aegis-е мне буквально врезался в память. Дело в том, что если нужно было внести изменения, исправляющие какой-то баг, то разработчик должен был снабдить изменения еще и кодом теста. Сам Aegis сначала запускал этот тест со старой кодовой базой и тест должен был "сломаться". Если это происходило, то Aegis запускал тест на новой кодовой базе. И сейчас тест должен был отработать успешно. Только если оба эти условия выполнялись, Aegis коммитил изменения в репозиторий.
Очень логичный подход -- если знаешь или думаешь, что знаешь про наличие проблемы в коде -- сначала напиши тест, который это демонстрирует. И только потом уже вноси изменения.
Вчера вечером, размышляя о добавлении очередной нужной фичи в старый код вдруг пришел к выводу, что в старом коде есть проблема, которая проявляется в очень специфической ситуации. Ситуация из разряда гипотетических, но какие фокусы могут происходить в многопоточных программах -- это просто фантастика. Тут в прямом смысле лучше перебдеть, чем... :) Поэтому стал придумывать решение.
Придумался красивый вариант. Трудоемкий правда, но красивый. Придумался более-менее подробно, пусть не до последней точки с запятой, но первый вариант плана по его реализации из восьми пунктов был написан сходу. Одним из пунктов в этом плане был unit-тест, который должен был продемонстрировать, что тот самый хитрый сценарий отрабатывает без проблем.
Вынужден признать, этот пункт с unit-тестом был не первым в списке :) Но начать решил все-таки с него, т.к. он выглядел самым сложным пунктом, а именно самые сложные задачи лучше решать "на свежую голову".
Написал, запустил. Получил совсем не тот результат, который ожидал.
Стал разбираться. Оказалось, что вчера вечером я круто ошибся. Запутался в моментах захвата и освобождения нескольких мутексов. И хоть и просматривал код вдоль и поперек несколько раз, все равно почему-то считал, что часть операции происходит без захвата одного из них. Ан нет. Мутекс все это время захвачен, у гипотетического сценария нет шансов сработать. Все нормально. Необходимости в переделках нет. Придуманное решение, хоть и красивое, не нужно. Что не может не радовать, т.к. и так есть что делать, без переделки старого и давно работающего кода.
Вот так, несколько часов, потраченных на предварительное создание unit-теста позволили сэкономить день или два работы.
Понятное дело, в данной заметке я выступил как КО. Но баги бывают разных типов: есть те, которые уже проявились и про которые известно как их повторить. В таких случаях можно сразу внести изменения в код и затем написать unit-тест, проверяющий корректность исправления. Собственно, когда разработчики работают по заявкам из баг-трекера, так и происходит (за исключением того, что некоторых программистов нужно заставлять и приучать писать unit-тесты или хотя бы самому проверять работоспособность изменений в условиях, описанных в баг-трекере, а не перекладывать все это на девушек из QA).
А бывают и баги, о которых лишь подозревают, пусть и сильно подозревают. Вот как в моем вчерашнем случае. Тут уж нужно бороться с соблазном сначала внести изменения, а потом уже написать тесты. Не всегда находятся силы принять правильное решение :( В этот раз повезло :)
PS. Кстати, далеко не для всех ошибок, о которых подозреваешь, можно легко создать условия воспроизведения -- вот один пример из своей практики.
Комментариев нет:
Отправить комментарий