Нужно дело делать, а не языком трепаться

Из ленты: OpenQuality.ru | Качество программного обеспечения | Опыт экспертов

В послужном списке Алексея Пахунова, известного как «Not a kernel guy», более семи лет работы в трех подразделениях Microsoft (Office, OSD и MSR), участие в двух крупных проектах (Axapta и Windows) и нескольких мелких. Текущее место работы – Google. Низкоуровневая разработка, “серьезность” разработчика, тестирование Windows и Chrome, “шерифы” в Google, TDD “по-взрослому”, время-качество-деньги, импровизация как причина авралов – вот некоторые темы нашей беседы.

Алексей, каковы истоки выражения «Not a kernel guy»? Почему именно оно дало название вашему блогу? Что подразумевалось под таким позиционированием?

Полностью эта фраза звучит как «Not a kernel guy in the Windows kernel team», что было, фактически, буквальным изложением той любопытной ситуации, в которой я оказался. В 2006 году я перешел из Копенгагенского центра разработки Microsoft в команду, работавшую над ядром Windows. Это было большой удачей, так как мне всегда нравилось работать с низкоуровневым кодом. В тот момент, однако, мой опыт низкоуровневой разработки был достаточно скромным. Пришлось переучиваться из прикладного разработчика в системного, открывая для себя много нового и интересного по ходу дела. Где-то в этот момент у меня «дозрело» желание завести свой собственный блог. Осталось только придумать название. «Not a kernel guy…» лучше всего отражало тематику блога – я писал о том, что сам недавно узнал, понял и прочувствовал в процессе превращения в тыкву системного разработчика.

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

Гм. Я долго пытался сформулировать что же не так с этой фразой. На первый взгляд, вроде все верно. Ядро – это ключевой компонент любой операционной системы (или, по крайней мере, большинства их них). Кроме того, это довольно сложный компонент, включающий заметное количество нетривиального кода. Но, тем не менее, эта фраза определенно режет мне слух. В этой фразе слишком много эмоций и почти нет фактов. И огромный простор для фантазий о том, как может выглядеть “святая святых” или “сердце системы”. На самом деле все немного проще и совсем по другому. Важных и сложных компонент намного больше, чем пальцев на руках. JIT компилятор .NET, DirectX и драйвера графических адаптеров, подсистема совместимости с предыдущими версиями системы, драйвера файловых систем, гипервизоры и мониторы виртуальных машин, инструменты разработки, сборки и профилирования, тестовая инфраструктура. Ядро – просто еще один элемент в этом списке. Многие из этих компонентов даже не входят в состав системы, но, тем не менее, без них система была бы совсем другой.

Если не вдаваться в детали, то фундаментальной разницы между техпроцессом разработки прикладных программ и процессом низкоуровневой разработки, как мне кажется, нет. Вернее, разница диктуется не типом разрабатываемого кода, а разной степенью “серьезности” (за неимением лучшего слова) разработчиков.

Скажем, цена ошибки в коде ядра или драйвера выше, чем в прикладном коде, так как первая может “уронить” всю систему, в то время как сфера действия второй ограничена рамками приложения. Чтобы получить успешно работающий у пользователей ядерный код, нужно, чтобы в нем было меньше ошибок, чем в прикладном. Как этого добиться – дело десятое. Понятное дело, будут применяться методики, улучшаюшие качество кода: более разносторонее и формальное тестирование, статический анализ, рецензирование кода, методики, позволяющие улучшить исходный дизайн, формальное доказательство корректности кода и т.д. Выбор конкретных методик зависит от команды, доступного бюджета и прочих обстоятельств.

Да, “святая святых” и “сердце системы” – слишком пафосно и эмоционально. Спасибо, что спустили с небес на землю. Пишут ли системные разработчики псевдокод, создают ли прототипы или сразу же приступают к созданию «боевого» кода?

Системные разработчики делают все то же самое, что и прикладные: пишут псевдокод и рисуют коробочки на доске, создают прототипы, пишут тесты и, бывает, правят “по-живому”. Просто более тщательно. Если же, конечно, они ставят перед собой задачу написать качественный код.

Вопрос о «серьезности». Алексей Пахунов в своем первом проекте и сейчас: что потребовалось изучить/осознать на пути к задачам, которые поручают «серьезным» разработчикам?

Наиболее важными мне кажутся несколько вещей. Во-первых, опыт – в смысле, багаж шишек, набитых как на собственном лбу, так и на лбах коллег. Знания вида “если делать так, то получится эдак”. Наличие опыта позволяет быстрее приходить к верным решениям. В категорию опыта можно отнести и широту кругозора. В какой-то момент становится полезно знать, что происходит в индустрии, какие исследования проводятся в данный момент, текущие тренды и прочее. Такие поверхностные знания помогают быстрее разобраться в новой предметной области, если возникнет такая необходимость.

Во-вторых, важна репутация. Я это хорошо почувствовал при переходе в Google. На новом месте зарабатывать репутацию приходится почти с нуля. Репутация, зачастую, – основной критерий оценки, “сдюжит или нет”. “Стоит ли поручать эту задачу подчиненному? До сих пор он справлялся, каждый раз – со все более сложными задачами. Однозначно стоит!” “Стоит ли тратить время на этот проект? Они уже два раза начинали что-то похожее и бросали на полпути. Пожалуй, не стоит.”

В категорию репутации, вернее, методов её создания, можно отнести умение говорить на одном языке с собеседником. Инженеры, средний менеджмент, менеджеры проектов, шишки из руководства – все разговаривают на собственном диалекте. Бывает забавно наблюдать, как на совещании кто-нибудь из среднего менеджмента “переводит” вице-президенту то, что сказал инженер.

В-третьих, крайне важно уметь работать с людьми. Уметь договариваться и донести свою мысль. Уметь привлечь на свою сторону. Уметь понять собеседника. Не просто понять, что он говорит, но заодно и понять, почему и чем вызвана такая точка зрения. Уметь не принимать близко к сердцу рабочие конфликты.

И, наконец, нужно дело делать, а не языком трепаться. 🙂

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

Про свой подход к созданию приложений ничего внятного, откровенного говоря, сказать не могу. Я просто опираюсь на свой опыт и стараюсь не наступить повторно на одни и те же грабли. Чуть менее успешно, стараюсь не наступать на грабли, ранее разминированные коллегами. Понятно, почему – свои шишки болят больнее. Но, как вы понимаете, суммарный опыт нескольких людей больше чем опыт одного человека. Поэтому, например, следует всегда помнить, что если коллега несет пургу, то это еще не значит, что он не прав. 🙂

И, раз мы об этом заговорили, про стиль. Использование единого стиля кодирования в команде/проекте крайне полезно. Единый стиль облегчает чтение и сопровождение кода, уменьшает число ошибок в коде. Особенно если это хорошо продуманный, проверенный практикой стиль кодирования.

Вместе с тем обсуждение деталей стиля – это самый бесполезный способ потратить время. Стоит только собрать больше пяти разработчиков вместе и предложить поговорить про целесообразность спорного пункта из правил. Если через час они не перессорятся, это можно считать большой удачей. Самое обидное, что если бы они потратили это время на написание юнит-тестов результат с хорошей вероятностью был бы лучше.

В связи с этим мне начинают импонировать команды, где эталонный стиль задан “указом сверху”. Кто-то мне рассказывал, что в некоей команде мерилом правильности стиля был стиль, в котором пишет наиболее старший (по должностной иерархии) разработчик. Стиль, наверное, был ужасен, но зато никаких споров. 🙂

Я сам обычно просто перенимаю стиль, который используется в проекте (или в конкретном файле) и избегаю участия в обсуждениях стиля. Ну, разве что, если за обедом не о чем поговорить.

Сможете ли вы рассказать о наиболее значимых ошибках, с которыми вам довелось столкнуться в проектах на тех или иных этапах? Какие выводы из них удалось извлечь?

Расскажу то, что пришло в голову.

Случай первый. Жил-был один проект. Состоял он из двух больших кусков работы: 1) спроектировать и произвести нестандартное железо; 2) модифицировать существующий софт и запустить на этом железе. Обе задачи были сложными и масштабными для имевшихся в наличии ресурсов. Естественным образом сформировались две команды: одна, работающая над аппаратной частью, другая – над программной. В ходе работы возникли проблемы взаимной коммуникации между командами. То программные требования не доходили до ведома проектировщиков железа, то, наоборот, программисты узнавали специфические требования, предъявляемые железом, только когда что-то переставало работать. Поначалу это было похоже на обычные человеческие ошибки. Потом это стало походить на замалчивание информации. Контакты с внешними командами и вендорами тоже страдали похожих же проблем.

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

История вторая. Писал я некое приложение, для которого понадобилось хранить настройки. Причем код должен был быть максимально платформо-независимым. “Ну чё тут думать?”, сказал мой шеф. Пусть будут INI файлы и делов-то. И я написал код для разбора INI файлов. Проблема только, что, как позже выяснилось, тогда я не понимал как, собственно, должны разбираться INI файлы. Взглянув на получившееся, шеф тактично промолчал. Не до того было. А вот мой коллега, которому досталось сопровождение этого кода после моего ухода, говорил пространные речи про рак мозга. Совершенно справедливо, кстати. Мораль: изобретая велосипед, изучи аналоги. Иначе потом будет стыдно.

Еще одна история про то, как я ломал официальную сборку Chrome три раза подряд. Проект, над которым я сейчас работаю, живет в том же самом репозитории, что и Chromium – open source версия Chrome. И собирается вместе с ним же. Понадобилось нам сделать так, чтобы наши файлы архивировались вместе с файлами Chome. Ну что может быть проще, скажете вы? Раз наши файлы уже собираются вместе с Chrome, то достаточно сделать то же самое, что делается для других файлов – добавить в какой-нибудь список “архивируемых” файлов или что-то в этом роде. Единственная закавыка – никакой документации, как это сделать, не существует. Вся документация – это код и комментарии в нем. Совершенно обычная история. Тем более, что есть полнотекстовый поиск по исходному коду.

Я нашел нужный список файлов, добавил наши файлы, тщательно имитируя все то, что делается с остальными файлами. Проверил, что все добавленные файлы собираются как надо. Получил “добро” от владельцев соответствующего скрипта. Результат – официальная ночная сборка не собирается. Оказалось, что официальная сборка собирается немного не так, и наши бинарники просто не были скомпилированы.

Второй заход – исправил официальную сборку, проверил что файлы собираются так, как надо, снова получил “добро”. Естественно, на утро получаю раздраженное письмо: “Can you please test it locally before committing?”. Ну я, собственно, так и сделал, но сборка-то сломана! На этот раз причиной была ошибка в одном из скриптов, которая выползла наружу из-за моих правок.

В третий раз пришел невод я умудрился сломать сборку, правя скрипт, который подписывает файлы цифровой подписью. Доступа к машине, где запускается этот скрипт, у меня нет, чтобы избежать утечки приватного ключа подписи. В результате, несмотря на успешное локальное тестирование, не все удалось учесть и сборка, по своему обычаю, сломалась.

Во всех трех случаях причина проблем была в одном и том же – часть логики осталась неоттестированной из-за отсутствия возможности её протестировать без запуска официальной сборки. Те, кто имел дело с большими проектами, знают, что зачастую инфраструктура для сборки проекта выглядит как лоскутное одеяло. Дополнительные ограничения, связанные с безопасностью, только усложняют проблему. А сделать так, чтобы все можно было протестировать заранее, стоит немалых усилий и времени разработчиков и инженеров сопровождения.

Алексей, есть ли какие-то особенности в организации тестирования в Microsoft и Google, на которые вы обратили внимание?

И Microsoft, и Google состоят из отдельных команд, методы и подходы к работе которых могут очень сильно отличаться друг от друга. Скажем, подразделения Bing и Windows в Microsoft выпускают продукты совершенно по-разному. Я могу сравнить то, что видел собственными глазами: как поставлено тестирование Windows в Microsoft и Chrome в Google.

В Windows структура команды базируется на тройках: разработчики (Software Development Engineers, SDEs), тестеры (SDEs in Test) и менеджеры проектов (Program Managers, PMs). Численность разработчиков и тестеров примерно равная с перекосом в сторону разработчиков. Задача тестеров состоит в создании такой инфраструктуры автоматического тестирования, чтобы максимально проверить корректность кода. Код должен не только выдавать ожидаемый результат, но и быть быстрым, надежным, совместимым, локализуемым и т.д. Кроме этого, тестовая инфраструктура должна обеспечивать разработчика информативной диагностикой для обнаруженных проблем и давать возможность прогона тестов разработчиком перед тем, как код попадет в репозиторий.

Соответственно, задача разработчиков – написать код, который не только делает то, что нужно, но и позволяет себя проверить с помощью инфраструктуры, созданной тестерами. Сделать это без взаимодействия разработчиков и тестеров нельзя. Или, по крайней мере, сложно. Поэтому обычно разработчики и тестеры совместно работают над созданием новой функциональности. Начиная с написания спецификаций: разработчик пишет design specification, а тестер – test specification. Обе спецификации (на самом деле три – PM-ы пишут functional specification) перекрестно рецензируются и согласовываются. Далее разработчики с тестерами более-менее параллельно пишут код и полируют его до рабочего состояния.

Далее, в разработке Windows интенсивно используются ветки. Новый код сначала попадает в ветку нижнего уровня, а затем постепенно мигрирует выше, попадая со временем в основную ветку. Перед каждой интеграцией изменений в более высокую ветку код должен быть доведен до “зеленого” состояния. За тестерами при этом остается последнее слово – быть интеграции или не быть.

В случае Chrome все сказанное выше про код также верно. Отличие состоит в том, что тестеров гораздо меньше чем разработчиков. Пропорция, на мой взгляд, где-то один тестер к 5-10 разработчикам. Соответственно разработчики обязаны писать тесты сами и всячески тестировать свой код.

Сильный акцент ставится на автоматическое тестирование. Каждый коммит в репозиторий прогоняется через почти все имеющиеся тесты (т.н. commit queue, или CQ – автоматизированная очередь тестов перед коммитом). Часть тестов выполняется перед коммитом, часть (такие как прогон с анализатором памяти и остальные занимающие много времени проверки) – после.

Ветки используются только для release management. Все разработчики, фактически, коммитят код в основную ветку. Стабильность кода при этом обеспечивается тремя вещами. Во-первых, все изменения просматриваются коллегами в обязательном порядке. Во-вторых, значительная часть тестов прогоняется до коммита в репозиторий. В-третьих, коммиты, успешно прошедшие CQ, но сломавшие, тем не менее, сборку или какой-то тест, как правило, сразу откатываются назад. Занимаются этим так называемые “шерифы” – разработчики, назначенные следить за состоянием кода в репозитории. Каждый постоянный разработчик периодически отрабатывает пару дней “шерифом”, а затем возвращается к своим повседневным обязанностям.

Не возьмусь судить, какая из моделей лучше. В каждой есть свои положительные и отрицательные стороны. Наличие отдельной команды тестеров освобождает разработчиков от возни с тестовой инфраструктурой. Скажем, нестабильные тесты меня совсем не волновали, пока я работал над Windows. С другой стороны, обязательное рецензирование всех изменений очень положительно влияет на качество кода. Было бы интересно ввести правило, согласно которому тестер обязательно должен просмотреть коммит разработчика и наоборот. Уверен, что в конечном итоге это улучшило бы взаимодействие тестеров и разработчиков.

Насколько эффективно, на ваш взгляд, вовлечение в техпроцесс выделенных тестировщиков?

Примерное равенство численности разработчиков и тестеров мне кажется очень хорошей идеей. Естественно, если при этом разработчики и тестеры совместно работают над повышением качества кода. Кто-то, наверное, может возразить, что сами разработчики вполне могут справиться с созданием автоматической тестовой инфраструктуры. Мне кажется, однако, что на практике это работает не очень хорошо. Разработчики слишком легко сбиваются на написание кода, откладывая тесты “на потом”. Выделенная QA-команда (даже если набрать её из тех же разработчиков) лучше уже тем, что перед ней стоит другая цель – не написать код, а убедиться в его качестве.

Исходя из вашего опыта, как организовать тестирование продукта наиболее эффективным образом в координатах “время-качество-деньги”?

У меня, конечно же, нет готового рецепта. Я бы выделил несколько вещей:

1.  Качественный код требует больших затрат времени и денег, чем менее качественный.
2.  Качество кода должно быть измеримо.
3.  Компьютер должен работать, а человек – думать.
4.  Наличие обратной связи на всех этапах от разработчика до потребителя кода очень важно.

Мне довольно часто приходилось наблюдать (и быть участником) ситуаций, когда умные, казалось бы, люди не понимают того, что создание более качественного кода требует затрат времени и денег. Если хотите использовать TDD “по-взрослому”, приготовьтесь, что половину времени разработчики будут тратить на написание тестов. Да, это означает, что будет написано вдвое меньше фич [features]. (Здесь сторонники TDD дружно потянулись за гнилыми помидорами 🙂 ). Хотите, чтобы написанные тесты запускались автоматически? Недостаточно купить оборудование. Запаситесь временем на диагностику ошибок, поддержку и сопровождение скриптов, серверов и т.п.

Верно и обратное: “денег в кассе” может и не быть, либо другие факторы могут быть важнее для выживания бизнеса. Может быть и так, что если некачественный продукт не выйдет через месяц, то компания вылетит в трубу, и шанса выпустить качественный продукт когда-либо вообще – просто не будет.

Существует мнение, что TDD и другие методики создания качественного кода требуют лишь начальных затрат на написание тестов и создание инфраструктуры. Утверждается, что как только начальный этап будет преодолен, то созданная инфраструктура начнет приносить дивиденды в виде ошибок, отловленных почти задаром на ранних подступах к коду проекта. Мне кажется, что это утверждение только сбивает с толку.

На практике происходит следующее. По мере развития инфраструктуры на неё возлагается все больше и больше задач, освобождая разработчиков от рутины. Все освободившееся время (и еще чуть-чуть) тут же тратится на дальнейшее улучшение качества кода, совершенствование инфраструктуры, процессов разработки и т.д. Т.е., получается, что времени и ресурсов тратится чем дальше, тем больше, но взамен появляется возможность создавать все более сложные и совершенные продукты.

Второй пункт говорит сам за себя. Мониторинг качества разрабатываемого продукта – необходимая составляющая процесса разработки качественного кода. Иначе откуда вы знаете, что вносимые изменения делают ваш продукт лучше (или хуже)? Качество кода – трудно измеримая величина. Не существует единственного параметра, определяющего качество. Лучшие из известных мне решений отслеживают массу параметров: состояние дел в баг-трекере, результаты прогонов тестов, результаты статического анализа кода, статистику баг-репортов, полученных от пользователей и многое другое.

Большая часть создаваемой инфраструктуры должна работать автоматически и на постоянной основе. Понятно, почему – так дешевле, надежней, а полученные результаты – повторяемы (что абсолютно необходимо для мониторинга качества). На данный момент создана масса удобных и полезных инструментов: статические анализаторы кода, динамические валидаторы кода, инструменты для сбора и анализа логов и т.п. Многие из них свободно доступны и бесплатны. Грех не пользоваться ими. Тем не менее, подобные инструменты используются не так широко, как можно было бы ожидать.

Наличие обратной связи. От момента зарождения идеи и до момента, когда продукт попадает к конечному пользователю, код проходит через множество этапов, видоизменяясь по пути. Это сложный процесс. Как показывает практика, даже самые умные люди не могут точно предсказать как покажет себя только что написанный код. Разработчик не может точно предсказать, где тестер найдет ошибку в его коде. Вся продуктовая команда не может точно предсказать, в каких условиях будет работать выпущенный продукт и где именно пользователи столкнутся с проблемами. Налаженная обратная связь позволит оперативно исправить код, “подогнав” его под реальность.

Также очень важно, чтобы обратная связь была проактивной, а не реактивной. Иными словами, тестер должен иметь возможность вносить коррективы в дизайн до написания кода; пользователи должны иметь возможность влиять на дизайн продукта (например, участвуя в тестировании прототипов).

Авралы: как часто они случаются в вашей практике и что обычно является их причиной?

От проекта зависит. Мне, вроде бы, удается пока держать баланс между авралами и нормальной работой. Помимо всего прочего, объем внеурочной работы напрямую зависит от готовности работника взваливать её на свои плечи. Практика показывает, однако, что многие срочные проблемы успешно решаются откладыванием их на завтра. Осталось только научиться уверенно отличать подобные “срочные” проблемы от действительно срочных… 🙂

Фундаментальная причина авралов – импровизация. Возьмите любую процедуру, для которой существует контрольный список шагов. Любое отклонение от списка – возможная ошибка. Чем больше допущено отклонений, тем выше вероятность проблем. Тем выше вероятность, что их придется исправлять в срочном порядке. Тоже самое с существующими техпроцессами. Любое отклонение – возможная ошибка. Вы закоммитили код без прогона тестов – ждите поломанной сборки. Новый код не покрыт юнит тестами – значит, он сломается в будущем в самый неподходящий момент. Еще хуже ситуация, когда техпроцесса попросту не существует. Скажем первый выпуск продукта на рынок свежеиспеченной компанией. Сплошная импровизация – большое количество ошибок – непрерывный аврал.

Алексей, в «Вестях с полей» вы пишите: «То ли повезло, то ли менеджеры у меня были хорошие (кстати, хорошие менеджеры были, кроме шуток), то ли игры на самом деле не такие ужасные…». Что характеризует хорошего менеджера и чем он отличается от плохого?

“Нормальный” менеджер по крайней мере не мешает работать. Хороший – помогает. Очень хороший – помогает и не держит зла за допущенные ошибки (но, при этом, указывает на них и помогает не допустить их в будущем).

Каких возможностей вам не хватает в современных языках программирования?

Если в двух словах, то не хватает удобных средств борьбы со сложностью кода и возможности аналитически доказать корректность кода. К первым я бы отнес инструменты, помогающие находить сходные куски кода; диагностику, показывающую, как именно выполнялся код, как он может выполниться в похожих/граничных условиях; инструменты для статического и динамического анализа кода; возможность контролировать разрешённые конструкции языка; возможность расширять язык напрямую, вместо костылей типа перегрузки операторов и шаблонов в C++ и т.д.

К второй группе я бы отнес возможность задать (и гарантировать) условия, в которых должен выполнятся кусок кода (входные данные, определенное количество доступной памяти, немодифицируемость кода и данных, другие потоки не касаются той же памяти и т.п.) и возможность формально доказать корректность куска кода в данных условиях. Ну и на сладкое – возможность “сшить” такие формально доказанные куски кода в одну программу.

Что нового, на ваш взгляд, появится в языках и средах разработки в ближайшие 5-10 лет?

Прогнозы – неблагодарное дело. Средства разработки следуют за модными технологиями с задержкой в несколько лет. Видимо, появится больше инструментов для облачных приложений и виртуальных сетей. Что там еще модно на данный момент?

Говоря о нынешней моде, я бы добавил мобильные приложения и социальные сети. Продажа Instagram за 1 млрд долларов вскружила голову многим. 🙂 Что вам хотелось бы изменить в индустрии IT?

В IT, как мне кажется, процветает полнейший дарвинизм. Выживает тот, кто смог заработать. Можно помечтать, что если бы критерием выживания был не коммерческий успех, а то самое декларируемое удобство пользователя и общая эффективность технологических решений, то и индустрия IT занималась бы совсем другими, более интересными вещами. Но сказка она на то и сказка, что там лучше живется. 🙂

Алексей, большое спасибо за интервью. Пусть сказка на вашем пути хоть иногда становится явью!

Источник