пятница, 11 июля 2014 г.

[management] Хорошая статья "О здравом смысле и руководстве компаний"

Как ни странно, на Хабре, но статья, действительно, хорошая: О здравом смысле и руководстве компаний.

PS. Для моих бывших коллег/подчиненных очень актуально сейчас. Часть описанного в статье маразма на них уже свалилась. Да и все остальное, подозреваю, не заставит себя долго ждать.

среда, 9 июля 2014 г.

[prog.process] О полезности написания тестов до изменения кода

Сегодня в очередной раз удалось убедится в разумности очень простой вещи. Но зайду, как положено, издалека :)

Когда-то давным-давно, в тогда еще совсем молодой компании Интервэйл, принимал участие в выборе системы контроля версий для управления исходным кодом. В рамках этого процесса лично мне довелось познакомиться с несколькими представителями этого класса продуктов. Если не ошибаюсь, там были 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. Кстати, далеко не для всех ошибок, о которых подозреваешь, можно легко создать условия воспроизведения -- вот один пример из своей практики.

вторник, 8 июля 2014 г.

[management] О чем может поведать статья под названием "Как создать инновационную культуру в банке?"

Особенно читателю, который знаком с работами Адизиса и Минцберга и понимает, что банк является одной из форм механистической бюрократии, а механистические бюрократии в принципе не приспособлены для инноваций? И что такой вопрос в заголовке статьи ничем не отличается от вопроса "Как создать инновационную культуру в больнице?" или "...на вагоно-ремонтном заводе?" или "...на фанеро-спичечном комбинате?"

Собственно, ответ я знал еще до прочтения: никак, если только речь не идет об одном маленьком подразделении внутри банка. Именно это там и написано:
...чтобы совершить подрыв в банковской сфере – необходима выделенная (отдельная, не загруженная текущими операционными задачами) команда с широкими полномочиями: где работать, какие эксперименты ставить, и т.д. Эксперименты необходимые для подрывных инноваций невозможны в приказной или вертикально-иерархизированной модели управления. Необходима корпоративная культура, управление процессами и задачами через ценности, живая конструкция. В нашей группе такой командой является венчурный фонд «Лайф.Среда». Такая команда должна быть независимой, т.к. подрывные инновации – самая тяжелая вещь, – и если такого рода команды пытаться интегрировать в банках в существующие системы, то они будут пытаться втиснуть команду в свои правила и нормы, в старую систему ценностей...
Правда, вокруг этого абзаца налито много воды и маркетингового булшита. Но, может это и не так уж плохо, т.к. в "сопровождающем тексте" есть россыпь интересных и любопытных фактов, которые могут дать читателю больше пищи для размышлений, чем основной вопрос, вынесенный в заголовок. Вот, например, упоминание об интересной практике в компании Zappos.com:
...каждому сотруднику предлагают 3000$ (раньше – 2000$) за уход сразу после испытательного срока (чтобы отсеять сомневающихся)...
Вообще, материал статьи очень сильно напоминает мне манеру поведения выходцев из банковской сферы: очень много правильных, но совершенно бесполезных слов вокруг одной простой идеи. По моим впечатлениям, технари (пусть даже и бывшие), в большинстве своем, ограничились бы самой сутью, не тратя ни свое время, ни время читателей/слушателей на дополнительную лабуду. Ибо впадая в пространные повествования можно договориться до очень неоднозначных вещей, вроде вот таких:
Основную цель, которую мы сейчас видим перед собой, – это как масштабировать подбор людей, способных к состоянию «потока», и выстроить им еще более благоприятную среду внутри. Мы ищем себе неэгоцентрически уверенных в себе людей (см. «парадокс Стокдейла»), открытых к окружающему миру (не занимающихся самокопанием) и вовлеченных в него (не отвлекающихся). Типичная эволюция, которую должен проходить сотрудник у нас в группе – это, после того, как он оторвется от контекста «жить от зарплаты до зарплаты», научиться самому себе и своим коллегам самостоятельно ставить стратегические цели, потом начать обучать лидерству других, и в итоге придти к партнерству. В итоге, для таких людей мы предлагаем самое ценное, что для них в их системе ценностей может быть – партнерство (у нас в Группе одна из самых широких партнерских программ в России) как воплощение «работы на себя».

понедельник, 7 июля 2014 г.

[prog.c++] О скорости и стоимости ACE_RW_Thread_Mutex

Начал работать над SObjectizer-5.4.0. В процессе переделок попробовал выбросить пул мутексов для того, чтобы каждый ресурс, которому нужна защита от параллельного доступа, создавал себе собственный мутекс. При прогоне набора unit-тестов выяснилось, что один из них стал работать невообразимо долго: вместо 1.8 секунды теперь время его работы составляло 1 минуту и 38 секунд. Отличительной особенностью теста являлось то, что в процессе работы создавалось и уничтожалось порядка 87K агентов, с каждым из которых было связано два мутекса. Ранее ссылки на эти мутексы выделялись из небольшого пула (т.е. несколько агентов использовали один общий мутекс), а сейчас стали персональной собственностью каждого агента.

Собака оказалась зарыта в ACE_RW_Thread_Mutex-е, который является реализацией идиомы single-writer/multi-reader lock. Мало того, что размер ACE_RW_Thread_Mutex составляет почти 300 байт, так еще, похоже, он выделяет для себя какие-то объекты ядра Windows (полагаю, создают Event-ы и получат от системы HANDLE для них). Думаю, что именно с использованием ресурсов Windows и связанно такое увеличение времени работы при создании в приложении нескольких десятков тысяч объектов ACE_RW_Thread_Mutex. Эту же гипотезу подтверждает и простейший автономный тест, который в цикле создает 100K объектов ACE_RW_Thread_Mutex -- его время работы на порядки больше, чем аналогичного теста, создающего такое же количество объектов std::mutex.

Печальная новость, т.к. стандартный для C++ std::shared_mutex появится только в C++14.

Сам же ACE_RW_Thread_Mutex работает весьма шустро. На тех задачах, где он нам нужен, т.е. на одновременном доступе к общему ресурсу на чтение, он оказывается от 1.5 до 2 раз быстрее, чем std::mutex (по результатам нескольких тестов, имитирующих реальную работу, тесты проводились на 4-х одновременно работающих потоках на двух ядрах с гипертрейдингом, т.е. на четырех аппаратных потоках).

Под катом исходный код одного из тестов. Там в качестве альтернативы ACE_RW_Thread_Mutex был проверен класс shared_mutex от автора вот этой большой статьи. Данная реализация у меня работала раза в два медленнее, чем обычный std::mutex и где-то в четыре раза медленнее, чем ACE_RW_Thread_Mutex. Я это могу объяснить только тем, что упрятанный внутри shared_mutex реальный std::mutex захватывается и освобождается два раза: сначала в shared_mutex::lock_shared, затем в shared_mutex::unlock_shared. Для той короткой операции над разделяемым ресурсом, которая используется в тесте, это оказывается фатально. Для проверки своей гипотезы я сделал там еще простенькие имитаторы dummy_shared_mutex и dummy_shared_lock. В целом, они работают быстрее, чем shared_mutex, но все равно проигрывают std::mutex-у.

В общем, будем ждать включения shared_mutex-а в стандарт C++14 и появления его реализаций в мейнстримовых компиляторах. Пока же продолжим использовать ACE_RW_Thread_Mutex, но понимая, что больше пары-тройки тысяч таких объектов в программе лучше не создавать.