Последние две мои статьи — интервью со спикерами одной прошедшей конференции. Мне показалось интересным поговорить с человеком, который в свое время отказался выступать на этой конференции, “из за одного маленького семейного обстоятельства”. Этот человек — Сергей SergeyT Тепляков, MVP, автор отличной книги про паттерны проектирования, адепт TDD, ныне разработчик Tools for Software Engineers в Microsoft и мейнтейнер библиотеки Code Contracts.
Под катом много текста про конференции, TDD, парное программирование, архитектуру Code Contracts, Хабру.
Сергей, добрый день. Зачем люди посещают конференции? Зачем их стоит посещать?
Я, честно признаться, толком и не посещал конференций, если не являлся там спикером. Но вот каждый раз, когда я там был, то главное, что выносил оттуда – это вдохновение. Да, может быть это звучит завышено, но конференции и общение с умными коллегами для меня всегда были лучшей мотивацией: изучить что-то новое, поделиться чем-то старым или просто продолжать свое развитие, как специалиста и как человека.
Ты прочел и дал обзоры на очень много книг. Что можешь посоветовать в плане конференций?
Довольно сложно дать конкретный вопрос на довольно общий вопрос. Тут важно понимать, что именно вам там нужно: зарядиться энергией? Познакомиться и/или пообщаться с интересными людьми? Пополнить какие-то конкретные знания? Если последнее, то вместо конференции нужно взять пару толковых книг, а вот если один из первых пунктов, то опять-таки нужно выяснить качество и интересность конференции и самостоятельно решить: идти или нет.
Как оценить конференцию по программе?
Конференцию нужно оценивать по спикерам и идти нужно не на тему, а именно на человека.
Если выступает Андрей Акиньшин, то я пойду его послушать не зависимо от того, о чем он будет говорить, об устройстве Stopwatch-а или о теме, вообще не связанной с производительностью. Поскольку я уверен, что качество будет высоким и я узнаю из него довольно много нового.
Ок, а как оценивать спикеров?
Довольно просто, на самом деле. Если спикер известен хорошими выступлениями — идешь. Если спикер не знаком, но беглый гуглеж выводит тебя на его блог, github или хабру — оцениваешь материал. Если спикер не знаком и не гуглится — лучше пойти на что-то другое.
С посещением понятно. Можешь рассказать, зачем люди выступают на конференциях?
Думаю, это вопрос самореализации или подтверждения определенного уровня развития: «Да, я дорос до определенного уровня, когда мне не страшно выходить перед многими людьми и делиться своим опытом: как положительным, так и отрицательным. Мне не стыдно признаться в том, чего я не знаю, поскольку я знаю достаточно полезных вещей которыми интересно поделиться». Для меня выступление вообще, и на конференциях в частности, было таким себе показателем развития. Начиналось все с разговором за чашкой кофе, или в процессе вливания в новый коллектив, когда ты начинаешь более уверенно общаться с коллегами, делясь опытом, отстаивая свое мнение. После этого идут локальные выступления на всяких митапах и юзер-группах, после чего уже не страшно выступить и перед несколькими сотнями.
Отдельный плюс: валидация знаний в процессе подготовки. Знаешь фразу: хочешь чему-нибудь научиться — научи другого. Подготовка к выступлению может занять в 10-20 раз больше времени, чем само выступление, и все это время ты целенаправленно копаешь одну тему. Бывают конечно эксперты, рассказывающие\пишущие что-то интересное без плана, но таких мало. С ходу могу вспомнить разве что Криса Брума, одного из архитекторов CLR. Он большинство постов писал прямо из головы, а как-то раз сделал вступление “Раньше я поднимал темы которые не требовали от меня дополнительных исследований. Эту статью я отдал на предварительное ревью, ибо на некоторые вопросы у меня не было ответов”. Так что конференция — это еще мотиватор изучить что-то подробнее, структурировать знания.
А как понять, что “сейчас я знаю достаточно полезных вещей, и мне действительно есть о чем рассказать”?
Как я уже говорил, проще всего обкатать материал «на кошечках». Это могут быть посиделки за кофе, корпоративные выступления, заметки в блоге или в любом другом виде.
Тут очень важно провалидировать свои знания на ком-то, чтобы не оказалось, что твои знания не сильно соответствуют реальности или не являются такими уж глубокими и/или интересными. Есть вариант валидации своего опыта с помощью гитхабов и прочих опенсорсов. Если ты пофиксил пару багов в Розлине и предложил вариант дизайна, который одобрили старожилы проекта или другие участники, то это должно быть достаточным свидетельством твоего опыта и зрелости.
То есть, детально ты не готовишься? Лишь канва?
Канва, обязательный подстрочник (хоть я его и не смотрю в процессе выступления), и все. Разница между выступлением и написанием поста не так и велика: вступление, рассказ, заключение; тебе нужно подать мысль, ты прикидываешь, что знает читатель, какие места нужно пояснить, какие просто упомянуть. В посте, правда, можно на что-то сослаться, а вот на конференции сказать “ребята, сначала прочитайте вот эти три статейки, а потом возвращайтесь” уже не получится.
Ты выступаешь в Штатах?
Пока нет. Приоритеты иные. Плюс, уровень разговорного английского не позволяет свободно разговаривать. Как я уже говорил, я обычно не проговариваю все заранее, а просто готовлю план и рассказываю по нему, а в таких вещах язык очень важен. Трудно выступать, когда не можешь высказать ничего сверх подготовленного, когда даже пошутить не в состоянии. А еще уверенность нужна. У нас в MS есть традиция: иногда ребята собираются командой или проектом в комнате с ланчем, и там кто-то рассказывает коллегам об интересных технических штуках. Пока я выступаю только там.
Какая аудитория в твоих MS посиделках? По сравнению с конференцией?
Среди слушателей иногда встречаются люди, которые приходят померяться паттернами. Среди коллег такого обычно не бывает, а вот на конференциях случается. С такими товарищами надо уметь работать.
Стыдно признать, но один раз я выступил в роли такого товарища. Как с такими работать?
Тут надо понять, чего хочет слушатель; возможно, задавая вопрос, человек действительно пытается что-то понять, например он спец-смежник, или просто коллега с богатым опытом в другом языке. Если ты думаешь, что можешь ответить на вопрос, но это долго\сложно — предложи обсудить после выступления. Если он тебя подводит к ответу “я не знаю” — говори “я не знаю, погуглю, свяжусь с вами после”. Бывают, ты ссылаешься на Рихтера, а с тобой начинают споры типа “Рихтер говорил не так...”, “этого не было...”, такого рода споры обычно неконструктивны и достаточно сослаться на “после выступления”, вежливо.
Классическая схема: тест-код-рефакторинг. Насколько я понял из твоих статей, ты ей следуешь постольку-поскольку. Как определить, когда нужно ей следовать, а когда нет? Как удерживаться от скатывания к “да ладно, весь проект уже без тестов, и к этой фиче ничего писать не стану”?
Честно сказать, я практически не использую TDD в его классическом представлении. Я пишу много тестов, но не пишу их до кода. Мой цикл выглядит примерно таким образом:
Получается совсем не test-first, но мне удобнее «видеть» дизайн в коде и уже валидировать его корректность с помощью тестов. Мне не удобно «выращивать» дизайн с помощью тестов, для меня такое переключение «тест»-«код» сильно отвлекает.
Теперь давай перейдем ко второму вопросу: «как удержаться от скатывания». Тут все проще.
Несколько лет назад я занимался продвижением («продаванием») юнит-тестирования заказчику. И я ни тогда, не сейчас не акцентировал внимание на регрессионной составляющей, на качестве дизайна и т.п. Я старался показать, как тесты помогают здесь и сейчас: «ок, нам нужно добавить новую фичу. Но вместо того, чтобы проверять ее вменяемость путем деплоя сервака с последующим проклацыванием 73 пунктов в UI-е, почему бы не добавить простой юнит-тест, который проверит наиболее сложную функциональность? Да, придется чутка подумать, как выделить эту возможность в отдельный компонент, но ведь это быстрее, чем вся эта муть с ручным тестированием».
Другими словами, если мне нужно сделать минимально вменяемое дополнение нетривиальной сложности, я буду думать о том, как вынести новую логику в отдельный класс/метод, чтобы его можно было легко протестировать. Когда все сильно запущено, то это не всегда сделать легко и не всегда это работает, но, как это не странно, для меня это работает в большинстве случаев.
Бывает, что все сильно запущено и новый функционал – это вариации старого, когда изменения вносятся в группу существующих классов. В этом случае думаем, как покрыть все это добро интеграционными тестами, после чего решаем, будет ли оправданным клин-ап существующего кода, будут ли существенные изменения в этих кусках или нет? Если речь идет о плохом коде, который мы собираемся активно развивать, то я попробую обосновать более вменяемые изменения перед внесением существенных функциональных изменений. Если же код мертв и толком не будет развиваться, то тут придется тестировать ручками.
Когда тесты писать не нужно? Или ты тестируешь все, вплоть до PS скриптов?
Есть ряд вещей, покрывать тестами которые нет никакого смысла. PS-скрипты – это один из них. Поскольку PowerShell по своей природе плотно работает с окружением, то тестировать скрипты изолированно – очень сложно. Кроме того, PowerShell – это немного безумная штука, и без реальных данных ты просто не будешь знать, чего от него ожидать и работает ли твой скрипт или нет.
Есть довольно много вещей, которые я не тестирую напрямую. Если много легаси кода, то тут нужны несколько интеграционных тестов, а юнит-тесты с высоким покрытием стоит уже писать для новых компонентов. Есть вещи, которые должны быть покрыты тестами, но это будут не модульные тесты, а интеграционные, которые скорее проверять базовую валидность. К этому относятся тесты базы данных и коммуникационных вещей, типа WCF.
Как и с многими другими вещами, нужно понимать, что тесты – это инструмент, не самоцель. При этом нужно менять отношение к инструменту в процессе работы. Если тестов много, и они ловят постоянные регрессии – это хорошо. Если тестов много и они постоянно ломаются – то это плохо. Но тут важно не кидаться из крайности в крайность, не прибивать все тесты одним махом, считая их бесполезными. Тут нужно подумать, как мы тестируем, что мы тестируем, хорош ли наш дизайн? Обычно хрупкие тесты – это симптом более общей проблемы – проблемы с дизайном, высокой связностью и с тестами, которые проверяют детали реализации, а не поведение абстракции.
Как написание тестов сочетается с парным программированием? У тебя было пару статей на тему парного программирования, но прямой связи с тестами я не заметил.
Теоретически, работа в паре подразумевает разделение ролей, когда один лабает тесты, а второй – логику. К сожалению, у меня не слишком много опыта классического парного программирования, при которой мы бы следовали этому принципу.
В результате я всегда работал в паре так, чтобы было комфортно обоим. Это значит, что тесты писались так, как мы считали нужным. Часто это были интеграционные тесты, которые покрывали довольно крупные куски, после чего мы вместе работали над реализацией. Полноценные тесты мы же потом добавляли по одному.
Но тут нужно понимать, что если TDD – это дело в некоторой степени личное, то TDD в паре – это дело этой самой пары. Пара должна определиться с тем, как ей быть эффективной. В моей работе мы не стеснялись говорить о том, что сейчас стоит делать, а что нет. Тут догм быть никаких не должно.
Как договариваться с агрессивными напарниками, как со стеснительными, закрытыми?
У меня опыта не особо много, все-таки парное программирование — подход довольно редкий в индустрии, как в Штатах, так и на Украине. У нас есть немало умных людей на проектах, но я не готов прогать с партнером “с потолка”. На мой взгляд, парное программирование неэффективно если один человек аккуратен в коде, другой нет, или один быстро пишет, другой вдумчиво, или один агрессивен другой стеснителен. Будь я в такой паре — попытался бы избежать такого. Очень мало людей мне подойдут чисто психологически. Это я именно про парное программирование говорю. Если нужно менторить — думаю, процентов 90 меня устроят, я найду к ним подход.
Как ты сам начал кодить в паре?
Я участвовал в хакатоне, и заджойнился к одному из коллег для создания следующей версии Code Search, это поисковый движок для referencesource.microsoft.com — предыдущая версия много лишних данных держала в памяти. У меня был опыт подходящий в Elastic Search, я заджойнился. Ты знаешь хакатоны: времени мало, сделать надо много, времени и возможности разделить работу не было, и мы сидели за одним компом набирая один код. Получилось удачно, и с тех пор некоторые задачи кодим парой, благо через несколько месяцев оказались на одном проекте. Но еще раз повторюсь, это редкость. На Украине я про такое вообще не слышал, в MS слышал про одну команду практикующую регулярное парное программирование, но это капля в море.
У нас бывало с коллегами, что мы спорим по какому-то поводу чуть дольше чем надо, слишком неуступчивы. Такие обсуждения можно считать ПП?
Это всегда так, особенно если кто-то в команде новичок. Когда притретесь, пободаетесь — можете попробовать покодить в паре. Когда уже выясните, кто чего стоит, когда поймете что “ну да, человек упрямый, но мысли дельные, ну да, я тоже не подарок, но тоже привношу пользу”, тогда можно пробовать. До этого будет довольно сложно коммуницировать, да и пользы будет мало. Если бы ты работал с профи парного программирования, который очень быстро адаптируется — можно было бы попробовать, но это явление еще более редкое чем обычный “парник”.
Как готовишься к парному программированию?
Никак. Вообще. Разве что, стоит в контекст вникнуть до начала работы, а так вообще ничего не нужно.
Как мешает языковой барьер?
Вообще не мешает. Общий проект, общий язык программирования, общая предметная область. В самом крайнем случае можно взять бумажку и начертить на ней что-то.
Инструментальный барьер?
Да, это классика. Мой приятель R#-хейтер, я же скорее наоборот. Иногда мы ржем с этого. То есть кодишь ты такой, ставишь скобку фигурную, тут у тебя все выравнивание с непривычки летит, и ты такой, “стоп, горшочек, не вари, Ctrl+Z, все отменяется...”. Вроде мелочи, иные переносы, скобки, хоткеи, но наблюдать за процессом забавно. С другой стороны, неплохой способ перенять навыки использования тулзы. В последнее время я слышу фразы типа “не хочу признаваться, но вот эта фича R# мне нравится”. То есть, сначала человек не любил R# из-за тормозов на больших проектах, а теперь начинает ценить. Плюс, у нас были различия в тулзах для diff-merge, интересно было посмотреть за аналогом. Мне вообще наблюдение нравится больше всего в процессе, начиная от подхода к дизайну, заканчивая используемыми тулами. То есть, ты не мануал читаешь, а смотришь как живой человек справляется с задачей “на лету”. Очень удобно.
Ты один из разработчиков Code Contracts. Как ты начал работать с проектом?
Я являюсь давним поклонником контрактного программирования. Началась моя любовь после знакомства с книгой Бертрана Мейера “Объектно-ориентированное построение программных систем”. Обдумывание дизайна в терминах ответственности позволяет существенно упростить разработку, позволяет сделать его более чистым.
На платформе .NET для этих целей давно существует библиотека CodeContracts, и я её использую уже давно. Где-то за год до перехода в MS я написал плагин для R# для упрощения контрактного программирования, и использования этой либы. Плагин отлавливает типичные ошибки Code Contracts, позволяет добавлять Contract.Requires(arg != null) контракты и делает ряд других полезных вещей. В первый год работы в MS я сделал небольшую тулзу для Applications Insights. Она оказалось полезной для Майка Барнета, одного из авторов Code Contracts. Потом я перешел в другую команду, и так вышло, что эта команда вовсю использует Code Contracts и это один из самых крупных пользователей Code Contracts в MS. После выхода VS 2015 мы могли отказаться от Code Contracts, или исправить библиотеку, чтобы она могла поддерживать C# 6.0. В результате я полтора-два месяца активно занимался доработкой Code Contracts. Именно в этот период времени я был активнее всего на github. Помимо добавления новых возможностей были починены ряд фич, которые Code Contracts никогда нормально не поддерживал, в частности, постусловия в асинхронных методах. После этого я стал одним из мейнтейнеров библиотеки. Сейчас я занимаюсь проектом в свободное от работы время, но занимаюсь значительно меньше времени чем хотелось бы.
Почему?
Другие приоритеты.
Новый проект?
Новый, но связанный с Code Contracts.
Не понимаю.
У Code Contracts есть несколько серьезных проблем, которые сильно затрудняют развитие. Проект большой, сложный, сотни тысяч строк местами запутанного кода. Кроме того, библиотека предназначена для анализа и изменения IL кода, без привязки к конкретному .NET языку или компилятору.
Библиотека содержит три основных компонента.
У этого подхода есть важная особенность: разные компиляторы и даже разные версии одного компилятора, генерируют разный IL-код для одних и тех же синтаксических конструкций. А CCI предоставляет лишь тонкую обертку над IL: там нет лябда-выражений, итераторов или асинхронных методов. Ты видишь, что у тебя создается объект некоторого класса, но ты не знаешь, это реальный класс, созданный программистом, или сгенерированный компиляторов в результате использования лямбда-выражения. Точнее ты можешь об этом узнать, поскольку компиляторы используют специальные паттерны генерации кода. Поэтому ccrewrite анализирует код, смотрит на имена типов и уже по ним определяет, является ли это класс-замыкания, класс для итератора или асинхронного метода или что-то еще.
При этом паттерны меняются в разных версиях компилятора. Для тех же лямбд C# 5.0 генерировал статическое поле с делегатом, а в C# 6.0 появился новый тип классов-замыканий. Разработчики языка C# выяснили, что экземплярные методы дешевле статических, поэтому теперь для «незахватывающих» лямбда-выражений генерируется синглтон вместо статической переменной.
Совершенно безумный пример можно привести для асинхронных методов. Компилятор C# 6.0 генерирует разный код в зависимости от количества await-операторов в методе: если await-ов нет получится один код, если await только один — другой, и так далее.
И мы должны учитывать все эти детали. Каждый раз когда компилятор начинает иначе обрабатывает языковую конструкцию — нужно менять и верификатор, и Rewriter. Каждое изменение языка — это большая и серьезная работа: нужно не только внести поддержку новых языковых конструкций, но и но и проверить, что все старые конструкции также продолжают работать, да еще и обратную совместимость сохранить.
Отдельная проблема: очень тяжело чинить баги. Можно просидеть 8 часов ради двух строчек фикса. Так я бился с одним из багов, когда в сгенерированном обобщенном методе внутри обобщенного класса в определенном случае тип аргумента был !T, а не !!T (это значит, что использовался обобщенный параметр типа, а не обобщенный параметр метода).
В общем, AST генерируемое CCI слишком низкоуровневое, что выливается в слишком сложную поддержку. С каждой новой версией языка\компилятора приходится проделывать все больше тяжелой и неблагодарной работы.
Еще одна проблема: негативное влияние CC Rewrite на время билда. CC Rewriter недетерминирован. То есть, ты берешь одну DLL, дважды вызываешь CC Rewriter — и гарантированно получаешь два разных бинарника. Для современных билд систем это очень большая проблема; фактически теряется возможность кеширования и инкрементальных билдов. То есть, если ты используешь CC Rewrite и внес изменение в один файл — ты гарантированно должен перебилдить все решение. Если же ты не используешь CC Rewrite — ты можешь перебилдить только один проект и всех его прямых клиентов. На сотне проектов использование CC Rewrite увеличивает время билда в разы.
Плюс, CC Rewriter при перезаписи хачит pdb, и не всегда это делает корректно, что негативно сказывается на дебаге. Из недавнего — мой коллега просто не мог заниматься дебагом без отключения CC Rewriter, у него съезжает call stack, неправильные показывались локальные переменные, весь debug experience был потерян.
Из-за этих проблем мы с коллегой сейчас работаем над Source Level Rewriter (SL Rewriter). То есть, мы не оперируем IL, мы изменяем высокоуровневый код. Из недостатков — это только утилита только для C#.
То есть, вы берете Roslyn-дерево, изменяете его, и отдаете компилятору? Обычный фиксер?
Именно. Roslyn предоставляет ровно тот механизм который нам нужен. Мы берем наш Workspace, ищем все обращения к контрактам, и вставляем нужные нам фрагменты.
Если вы используете Roslyn, вы ограничены С# 6.0?
Почти. На самом деле, нас больше волнует интеграция с компилятором, но это тема для отдельного разговора.
Где можно пощупать Source Level Rewriter?
Сейчас это внутренний продукт, который пилят 2 человека. Пока что он не готов, но как только он будет корректно работать у нас в команде — мы его расшарим.
Будут прогнозы про быстродействие? Сейчас Code Contracts часто ругают за “тормознутость”.
Смотри, CC Check очень медленный. Для корректной работы ты должен расставить очень много аннотаций, и билд ста тысяч строк кода будет занимать минуты, а то и десятки минут. Потом еще надо вывод проанализировать. Короче, медленный и неэффективный инструмент с точки зрения разработчика. Точнее, идея хорошая, но задача очень сложная и медленно решаемая. Я знаю буквально несколько команд активно его использующих. Моя команда отказалась от него года два назад и никто не стремится снова его использовать.
Теперь CC Rewrite. Как я уже говорил, он существенно влияет на билд-процесс. Есть конечны трюки, например отключить его локально и использовать только на билд-машине, но замедление все-таки есть.
Гораздо важнее влияние на рантайм. С контрактами очень легко расплодить лишние проверки. Так, если у тебя есть тяжелые инварианты на классе — в начало и конец каждого открытого метода добавятся проверки. Или контракты на коллекцию — ты должен проитерировать всю коллекцию. Особенно это может быть заметно если у тебя есть цепочка из пяти методов пробегающих по коллекции. Или проверка рекурсии, на ограничение вызова контрактов в глубину. Она использует вызов к thread local, что добавляет тормозов. Одним только отключением этой проверки мы повысили end-to-end производительность на 15%. Подобных деталей не сильно много, но про них стоит знать.
С другой стороны, мы сейчас пилим билд-движок, это системное ПО и немалая кодовая база. Нам приходится настраивать контракты для минимально влияния на производительность в рантайме, но отказываться от контрактов мы не хотим: сообщения от контрактов в получаемых нами креш-дампах дают очень много полезной информации, добиться этого другими стандартными средствами — значительно сложнее.
Кроме того, надо сказать что для большинства приложение накладные расходы на рантайм будут минимальны.
То есть, для малых проектов инструмент можно использовать без настроек?
Для малых — да. Для средних можно поставить только проверку предусловий и открытых методов, тогда импакт будет минимален. Ключевую функциональность, тщательно покрытую тестами, можно настроить на удаление всех контрактов из рантайма. Инструмент настраиваемый — им вполне можно (и стоит) пользоваться. Тюнить его придется только на крупных проектах.
Почему ты на нее пришел?
Ух… давно это было. Я пришел на Хабр в 2010-м, когда rsdn уже умирал, dou.ua уже был, но не был столь популярным. Хабр в то время активно развивался, с множеством глубоких технических постов и, самое главное, с высоким уровнем сигнал-шума. Его было интересно читать, в него было интересно писать. Публикации там – это был самый простой способ обсудить какую-то техническую проблему. Да, не все комментарии были одинаково полезны, но в тот момент это было мне интересно.
Почему ты ушел с Хабра?
Прежде всего изменился я, изменились мои интересы. Я продолжаю писать в своем блоге, часто выбирая более философские темы, и там уже сложилась некоторая аудитория, с которой интересно, которая знает меня и мои интересы. Писать же параллельно в блоге и на Хабре — не совсем «правильно». После того как мои последние “параллельные” посты собрали больше комментариев в блоге — я практически перестал писать на Хабр.
Я все еще читаю Хабр, но чаще благодаря соц-сетям и другим способам. Уровень же шума путем чтения через RSS для меня слишком высок: видимо, я не интересуюсь всем, что интересно аудитории Хабра, которая стала еще более разнообразной. При этом я часто попадаю на отличные статьи, вдумчивые, глубокие и интересные.
Кроме того, Хабр – это крупнейший IT-портал, с помощью которого можно почерпнуть много полезной информации. Изучая конкретную тему я все еще часто там оказываюсь, если ищу в русскоязычном сегменте.
От автора: наш разговор с Сергеем вышел несколько сумбурным и затронул сразу несколько несвязанных тем. Если я что-то упустил — Сергей доступен в личке и комментариях. Ответ может идти больше обычного, учитывайте часовой пояс: время Сиэтла отличается от московского на 11 часов.
Под катом много текста про конференции, TDD, парное программирование, архитектуру Code Contracts, Хабру.
О конференциях с точки зрения Слушателя
Сергей, добрый день. Зачем люди посещают конференции? Зачем их стоит посещать?
Я, честно признаться, толком и не посещал конференций, если не являлся там спикером. Но вот каждый раз, когда я там был, то главное, что выносил оттуда – это вдохновение. Да, может быть это звучит завышено, но конференции и общение с умными коллегами для меня всегда были лучшей мотивацией: изучить что-то новое, поделиться чем-то старым или просто продолжать свое развитие, как специалиста и как человека.
Ты прочел и дал обзоры на очень много книг. Что можешь посоветовать в плане конференций?
Довольно сложно дать конкретный вопрос на довольно общий вопрос. Тут важно понимать, что именно вам там нужно: зарядиться энергией? Познакомиться и/или пообщаться с интересными людьми? Пополнить какие-то конкретные знания? Если последнее, то вместо конференции нужно взять пару толковых книг, а вот если один из первых пунктов, то опять-таки нужно выяснить качество и интересность конференции и самостоятельно решить: идти или нет.
Как оценить конференцию по программе?
Конференцию нужно оценивать по спикерам и идти нужно не на тему, а именно на человека.
Если выступает Андрей Акиньшин, то я пойду его послушать не зависимо от того, о чем он будет говорить, об устройстве Stopwatch-а или о теме, вообще не связанной с производительностью. Поскольку я уверен, что качество будет высоким и я узнаю из него довольно много нового.
Ок, а как оценивать спикеров?
Довольно просто, на самом деле. Если спикер известен хорошими выступлениями — идешь. Если спикер не знаком, но беглый гуглеж выводит тебя на его блог, github или хабру — оцениваешь материал. Если спикер не знаком и не гуглится — лучше пойти на что-то другое.
О конференциях с точки зрения Спикера
С посещением понятно. Можешь рассказать, зачем люди выступают на конференциях?
Думаю, это вопрос самореализации или подтверждения определенного уровня развития: «Да, я дорос до определенного уровня, когда мне не страшно выходить перед многими людьми и делиться своим опытом: как положительным, так и отрицательным. Мне не стыдно признаться в том, чего я не знаю, поскольку я знаю достаточно полезных вещей которыми интересно поделиться». Для меня выступление вообще, и на конференциях в частности, было таким себе показателем развития. Начиналось все с разговором за чашкой кофе, или в процессе вливания в новый коллектив, когда ты начинаешь более уверенно общаться с коллегами, делясь опытом, отстаивая свое мнение. После этого идут локальные выступления на всяких митапах и юзер-группах, после чего уже не страшно выступить и перед несколькими сотнями.
Отдельный плюс: валидация знаний в процессе подготовки. Знаешь фразу: хочешь чему-нибудь научиться — научи другого. Подготовка к выступлению может занять в 10-20 раз больше времени, чем само выступление, и все это время ты целенаправленно копаешь одну тему. Бывают конечно эксперты, рассказывающие\пишущие что-то интересное без плана, но таких мало. С ходу могу вспомнить разве что Криса Брума, одного из архитекторов CLR. Он большинство постов писал прямо из головы, а как-то раз сделал вступление “Раньше я поднимал темы которые не требовали от меня дополнительных исследований. Эту статью я отдал на предварительное ревью, ибо на некоторые вопросы у меня не было ответов”. Так что конференция — это еще мотиватор изучить что-то подробнее, структурировать знания.
А как понять, что “сейчас я знаю достаточно полезных вещей, и мне действительно есть о чем рассказать”?
Как я уже говорил, проще всего обкатать материал «на кошечках». Это могут быть посиделки за кофе, корпоративные выступления, заметки в блоге или в любом другом виде.
Тут очень важно провалидировать свои знания на ком-то, чтобы не оказалось, что твои знания не сильно соответствуют реальности или не являются такими уж глубокими и/или интересными. Есть вариант валидации своего опыта с помощью гитхабов и прочих опенсорсов. Если ты пофиксил пару багов в Розлине и предложил вариант дизайна, который одобрили старожилы проекта или другие участники, то это должно быть достаточным свидетельством твоего опыта и зрелости.
То есть, детально ты не готовишься? Лишь канва?
Канва, обязательный подстрочник (хоть я его и не смотрю в процессе выступления), и все. Разница между выступлением и написанием поста не так и велика: вступление, рассказ, заключение; тебе нужно подать мысль, ты прикидываешь, что знает читатель, какие места нужно пояснить, какие просто упомянуть. В посте, правда, можно на что-то сослаться, а вот на конференции сказать “ребята, сначала прочитайте вот эти три статейки, а потом возвращайтесь” уже не получится.
Ты выступаешь в Штатах?
Пока нет. Приоритеты иные. Плюс, уровень разговорного английского не позволяет свободно разговаривать. Как я уже говорил, я обычно не проговариваю все заранее, а просто готовлю план и рассказываю по нему, а в таких вещах язык очень важен. Трудно выступать, когда не можешь высказать ничего сверх подготовленного, когда даже пошутить не в состоянии. А еще уверенность нужна. У нас в MS есть традиция: иногда ребята собираются командой или проектом в комнате с ланчем, и там кто-то рассказывает коллегам об интересных технических штуках. Пока я выступаю только там.
Какая аудитория в твоих MS посиделках? По сравнению с конференцией?
Среди слушателей иногда встречаются люди, которые приходят померяться паттернами. Среди коллег такого обычно не бывает, а вот на конференциях случается. С такими товарищами надо уметь работать.
Стыдно признать, но один раз я выступил в роли такого товарища. Как с такими работать?
Тут надо понять, чего хочет слушатель; возможно, задавая вопрос, человек действительно пытается что-то понять, например он спец-смежник, или просто коллега с богатым опытом в другом языке. Если ты думаешь, что можешь ответить на вопрос, но это долго\сложно — предложи обсудить после выступления. Если он тебя подводит к ответу “я не знаю” — говори “я не знаю, погуглю, свяжусь с вами после”. Бывают, ты ссылаешься на Рихтера, а с тобой начинают споры типа “Рихтер говорил не так...”, “этого не было...”, такого рода споры обычно неконструктивны и достаточно сослаться на “после выступления”, вежливо.
TDD
Классическая схема: тест-код-рефакторинг. Насколько я понял из твоих статей, ты ей следуешь постольку-поскольку. Как определить, когда нужно ей следовать, а когда нет? Как удерживаться от скатывания к “да ладно, весь проект уже без тестов, и к этой фиче ничего писать не стану”?
Честно сказать, я практически не использую TDD в его классическом представлении. Я пишу много тестов, но не пишу их до кода. Мой цикл выглядит примерно таким образом:
- Смотрим на проблему и пробуем увидеть высокоуровневые компоненты. Возможно, берем карандаш и рисуем квадратики с отношениями. Это позволяет увидеть картину целиком и то, какие подкомпоненты в нее входят. После этого можно набросать высокоуровневый тест кейс, но обычно я обхожусь без этого.
- После этого начинаем декомпозировать систему снизу-вверх (до этого мы попробовали разобрать/разрисовать проблему по принципу сверху вниз). Берем один из листовых классов и начинаем думать, чем он является.
- После этого я перехожу к реализации, набрасываю каркасы функций, возможно расставляя контракты.
- Затем перехожу к реализации определенной функции и только после ее написания добавляю тест.
Получается совсем не test-first, но мне удобнее «видеть» дизайн в коде и уже валидировать его корректность с помощью тестов. Мне не удобно «выращивать» дизайн с помощью тестов, для меня такое переключение «тест»-«код» сильно отвлекает.
Теперь давай перейдем ко второму вопросу: «как удержаться от скатывания». Тут все проще.
Несколько лет назад я занимался продвижением («продаванием») юнит-тестирования заказчику. И я ни тогда, не сейчас не акцентировал внимание на регрессионной составляющей, на качестве дизайна и т.п. Я старался показать, как тесты помогают здесь и сейчас: «ок, нам нужно добавить новую фичу. Но вместо того, чтобы проверять ее вменяемость путем деплоя сервака с последующим проклацыванием 73 пунктов в UI-е, почему бы не добавить простой юнит-тест, который проверит наиболее сложную функциональность? Да, придется чутка подумать, как выделить эту возможность в отдельный компонент, но ведь это быстрее, чем вся эта муть с ручным тестированием».
Другими словами, если мне нужно сделать минимально вменяемое дополнение нетривиальной сложности, я буду думать о том, как вынести новую логику в отдельный класс/метод, чтобы его можно было легко протестировать. Когда все сильно запущено, то это не всегда сделать легко и не всегда это работает, но, как это не странно, для меня это работает в большинстве случаев.
Бывает, что все сильно запущено и новый функционал – это вариации старого, когда изменения вносятся в группу существующих классов. В этом случае думаем, как покрыть все это добро интеграционными тестами, после чего решаем, будет ли оправданным клин-ап существующего кода, будут ли существенные изменения в этих кусках или нет? Если речь идет о плохом коде, который мы собираемся активно развивать, то я попробую обосновать более вменяемые изменения перед внесением существенных функциональных изменений. Если же код мертв и толком не будет развиваться, то тут придется тестировать ручками.
Когда тесты писать не нужно? Или ты тестируешь все, вплоть до PS скриптов?
Есть ряд вещей, покрывать тестами которые нет никакого смысла. PS-скрипты – это один из них. Поскольку PowerShell по своей природе плотно работает с окружением, то тестировать скрипты изолированно – очень сложно. Кроме того, PowerShell – это немного безумная штука, и без реальных данных ты просто не будешь знать, чего от него ожидать и работает ли твой скрипт или нет.
Есть довольно много вещей, которые я не тестирую напрямую. Если много легаси кода, то тут нужны несколько интеграционных тестов, а юнит-тесты с высоким покрытием стоит уже писать для новых компонентов. Есть вещи, которые должны быть покрыты тестами, но это будут не модульные тесты, а интеграционные, которые скорее проверять базовую валидность. К этому относятся тесты базы данных и коммуникационных вещей, типа WCF.
Как и с многими другими вещами, нужно понимать, что тесты – это инструмент, не самоцель. При этом нужно менять отношение к инструменту в процессе работы. Если тестов много, и они ловят постоянные регрессии – это хорошо. Если тестов много и они постоянно ломаются – то это плохо. Но тут важно не кидаться из крайности в крайность, не прибивать все тесты одним махом, считая их бесполезными. Тут нужно подумать, как мы тестируем, что мы тестируем, хорош ли наш дизайн? Обычно хрупкие тесты – это симптом более общей проблемы – проблемы с дизайном, высокой связностью и с тестами, которые проверяют детали реализации, а не поведение абстракции.
Парное программирование
Как написание тестов сочетается с парным программированием? У тебя было пару статей на тему парного программирования, но прямой связи с тестами я не заметил.
Теоретически, работа в паре подразумевает разделение ролей, когда один лабает тесты, а второй – логику. К сожалению, у меня не слишком много опыта классического парного программирования, при которой мы бы следовали этому принципу.
В результате я всегда работал в паре так, чтобы было комфортно обоим. Это значит, что тесты писались так, как мы считали нужным. Часто это были интеграционные тесты, которые покрывали довольно крупные куски, после чего мы вместе работали над реализацией. Полноценные тесты мы же потом добавляли по одному.
Но тут нужно понимать, что если TDD – это дело в некоторой степени личное, то TDD в паре – это дело этой самой пары. Пара должна определиться с тем, как ей быть эффективной. В моей работе мы не стеснялись говорить о том, что сейчас стоит делать, а что нет. Тут догм быть никаких не должно.
Как договариваться с агрессивными напарниками, как со стеснительными, закрытыми?
У меня опыта не особо много, все-таки парное программирование — подход довольно редкий в индустрии, как в Штатах, так и на Украине. У нас есть немало умных людей на проектах, но я не готов прогать с партнером “с потолка”. На мой взгляд, парное программирование неэффективно если один человек аккуратен в коде, другой нет, или один быстро пишет, другой вдумчиво, или один агрессивен другой стеснителен. Будь я в такой паре — попытался бы избежать такого. Очень мало людей мне подойдут чисто психологически. Это я именно про парное программирование говорю. Если нужно менторить — думаю, процентов 90 меня устроят, я найду к ним подход.
Как ты сам начал кодить в паре?
Я участвовал в хакатоне, и заджойнился к одному из коллег для создания следующей версии Code Search, это поисковый движок для referencesource.microsoft.com — предыдущая версия много лишних данных держала в памяти. У меня был опыт подходящий в Elastic Search, я заджойнился. Ты знаешь хакатоны: времени мало, сделать надо много, времени и возможности разделить работу не было, и мы сидели за одним компом набирая один код. Получилось удачно, и с тех пор некоторые задачи кодим парой, благо через несколько месяцев оказались на одном проекте. Но еще раз повторюсь, это редкость. На Украине я про такое вообще не слышал, в MS слышал про одну команду практикующую регулярное парное программирование, но это капля в море.
У нас бывало с коллегами, что мы спорим по какому-то поводу чуть дольше чем надо, слишком неуступчивы. Такие обсуждения можно считать ПП?
Это всегда так, особенно если кто-то в команде новичок. Когда притретесь, пободаетесь — можете попробовать покодить в паре. Когда уже выясните, кто чего стоит, когда поймете что “ну да, человек упрямый, но мысли дельные, ну да, я тоже не подарок, но тоже привношу пользу”, тогда можно пробовать. До этого будет довольно сложно коммуницировать, да и пользы будет мало. Если бы ты работал с профи парного программирования, который очень быстро адаптируется — можно было бы попробовать, но это явление еще более редкое чем обычный “парник”.
Как готовишься к парному программированию?
Никак. Вообще. Разве что, стоит в контекст вникнуть до начала работы, а так вообще ничего не нужно.
Как мешает языковой барьер?
Вообще не мешает. Общий проект, общий язык программирования, общая предметная область. В самом крайнем случае можно взять бумажку и начертить на ней что-то.
Инструментальный барьер?
Да, это классика. Мой приятель R#-хейтер, я же скорее наоборот. Иногда мы ржем с этого. То есть кодишь ты такой, ставишь скобку фигурную, тут у тебя все выравнивание с непривычки летит, и ты такой, “стоп, горшочек, не вари, Ctrl+Z, все отменяется...”. Вроде мелочи, иные переносы, скобки, хоткеи, но наблюдать за процессом забавно. С другой стороны, неплохой способ перенять навыки использования тулзы. В последнее время я слышу фразы типа “не хочу признаваться, но вот эта фича R# мне нравится”. То есть, сначала человек не любил R# из-за тормозов на больших проектах, а теперь начинает ценить. Плюс, у нас были различия в тулзах для diff-merge, интересно было посмотреть за аналогом. Мне вообще наблюдение нравится больше всего в процессе, начиная от подхода к дизайну, заканчивая используемыми тулами. То есть, ты не мануал читаешь, а смотришь как живой человек справляется с задачей “на лету”. Очень удобно.
Code Contracts
Ты один из разработчиков Code Contracts. Как ты начал работать с проектом?
Я являюсь давним поклонником контрактного программирования. Началась моя любовь после знакомства с книгой Бертрана Мейера “Объектно-ориентированное построение программных систем”. Обдумывание дизайна в терминах ответственности позволяет существенно упростить разработку, позволяет сделать его более чистым.
На платформе .NET для этих целей давно существует библиотека CodeContracts, и я её использую уже давно. Где-то за год до перехода в MS я написал плагин для R# для упрощения контрактного программирования, и использования этой либы. Плагин отлавливает типичные ошибки Code Contracts, позволяет добавлять Contract.Requires(arg != null) контракты и делает ряд других полезных вещей. В первый год работы в MS я сделал небольшую тулзу для Applications Insights. Она оказалось полезной для Майка Барнета, одного из авторов Code Contracts. Потом я перешел в другую команду, и так вышло, что эта команда вовсю использует Code Contracts и это один из самых крупных пользователей Code Contracts в MS. После выхода VS 2015 мы могли отказаться от Code Contracts, или исправить библиотеку, чтобы она могла поддерживать C# 6.0. В результате я полтора-два месяца активно занимался доработкой Code Contracts. Именно в этот период времени я был активнее всего на github. Помимо добавления новых возможностей были починены ряд фич, которые Code Contracts никогда нормально не поддерживал, в частности, постусловия в асинхронных методах. После этого я стал одним из мейнтейнеров библиотеки. Сейчас я занимаюсь проектом в свободное от работы время, но занимаюсь значительно меньше времени чем хотелось бы.
Почему?
Другие приоритеты.
Новый проект?
Новый, но связанный с Code Contracts.
Не понимаю.
У Code Contracts есть несколько серьезных проблем, которые сильно затрудняют развитие. Проект большой, сложный, сотни тысяч строк местами запутанного кода. Кроме того, библиотека предназначена для анализа и изменения IL кода, без привязки к конкретному .NET языку или компилятору.
Библиотека содержит три основных компонента.
- Инфраструктура (Common Compler Infrastrucutre, CCI) — декомпилит IL в объектную модель (Abstract Syntax Tree, AST). AST на выходе получается мутабельное, низкоуровневое. Замыканий нет, async нет, блоков итераторов нет.
- Статический верификатор CC Check, проверяющий валидность контрактов во время билда. То есть, она парсит AST, и если видит метод с NotNull “аннотацией” пытается доказать, что Null в этот метод никогда не придет.
- Runtime IL Rewriter (CC Rewrite). Занимается трансформацией dll файлов во время билда. Преобразует обращения к классу Contract в различные конструкции: в генерацию исключения, в Debug.Assert или просто удаляет утверждение целиком. Это отдельная утилита, не связанная с верификатором.
У этого подхода есть важная особенность: разные компиляторы и даже разные версии одного компилятора, генерируют разный IL-код для одних и тех же синтаксических конструкций. А CCI предоставляет лишь тонкую обертку над IL: там нет лябда-выражений, итераторов или асинхронных методов. Ты видишь, что у тебя создается объект некоторого класса, но ты не знаешь, это реальный класс, созданный программистом, или сгенерированный компиляторов в результате использования лямбда-выражения. Точнее ты можешь об этом узнать, поскольку компиляторы используют специальные паттерны генерации кода. Поэтому ccrewrite анализирует код, смотрит на имена типов и уже по ним определяет, является ли это класс-замыкания, класс для итератора или асинхронного метода или что-то еще.
При этом паттерны меняются в разных версиях компилятора. Для тех же лямбд C# 5.0 генерировал статическое поле с делегатом, а в C# 6.0 появился новый тип классов-замыканий. Разработчики языка C# выяснили, что экземплярные методы дешевле статических, поэтому теперь для «незахватывающих» лямбда-выражений генерируется синглтон вместо статической переменной.
Совершенно безумный пример можно привести для асинхронных методов. Компилятор C# 6.0 генерирует разный код в зависимости от количества await-операторов в методе: если await-ов нет получится один код, если await только один — другой, и так далее.
И мы должны учитывать все эти детали. Каждый раз когда компилятор начинает иначе обрабатывает языковую конструкцию — нужно менять и верификатор, и Rewriter. Каждое изменение языка — это большая и серьезная работа: нужно не только внести поддержку новых языковых конструкций, но и но и проверить, что все старые конструкции также продолжают работать, да еще и обратную совместимость сохранить.
Отдельная проблема: очень тяжело чинить баги. Можно просидеть 8 часов ради двух строчек фикса. Так я бился с одним из багов, когда в сгенерированном обобщенном методе внутри обобщенного класса в определенном случае тип аргумента был !T, а не !!T (это значит, что использовался обобщенный параметр типа, а не обобщенный параметр метода).
В общем, AST генерируемое CCI слишком низкоуровневое, что выливается в слишком сложную поддержку. С каждой новой версией языка\компилятора приходится проделывать все больше тяжелой и неблагодарной работы.
Еще одна проблема: негативное влияние CC Rewrite на время билда. CC Rewriter недетерминирован. То есть, ты берешь одну DLL, дважды вызываешь CC Rewriter — и гарантированно получаешь два разных бинарника. Для современных билд систем это очень большая проблема; фактически теряется возможность кеширования и инкрементальных билдов. То есть, если ты используешь CC Rewrite и внес изменение в один файл — ты гарантированно должен перебилдить все решение. Если же ты не используешь CC Rewrite — ты можешь перебилдить только один проект и всех его прямых клиентов. На сотне проектов использование CC Rewrite увеличивает время билда в разы.
Плюс, CC Rewriter при перезаписи хачит pdb, и не всегда это делает корректно, что негативно сказывается на дебаге. Из недавнего — мой коллега просто не мог заниматься дебагом без отключения CC Rewriter, у него съезжает call stack, неправильные показывались локальные переменные, весь debug experience был потерян.
Из-за этих проблем мы с коллегой сейчас работаем над Source Level Rewriter (SL Rewriter). То есть, мы не оперируем IL, мы изменяем высокоуровневый код. Из недостатков — это только утилита только для C#.
То есть, вы берете Roslyn-дерево, изменяете его, и отдаете компилятору? Обычный фиксер?
Именно. Roslyn предоставляет ровно тот механизм который нам нужен. Мы берем наш Workspace, ищем все обращения к контрактам, и вставляем нужные нам фрагменты.
Если вы используете Roslyn, вы ограничены С# 6.0?
Почти. На самом деле, нас больше волнует интеграция с компилятором, но это тема для отдельного разговора.
Где можно пощупать Source Level Rewriter?
Сейчас это внутренний продукт, который пилят 2 человека. Пока что он не готов, но как только он будет корректно работать у нас в команде — мы его расшарим.
Будут прогнозы про быстродействие? Сейчас Code Contracts часто ругают за “тормознутость”.
Смотри, CC Check очень медленный. Для корректной работы ты должен расставить очень много аннотаций, и билд ста тысяч строк кода будет занимать минуты, а то и десятки минут. Потом еще надо вывод проанализировать. Короче, медленный и неэффективный инструмент с точки зрения разработчика. Точнее, идея хорошая, но задача очень сложная и медленно решаемая. Я знаю буквально несколько команд активно его использующих. Моя команда отказалась от него года два назад и никто не стремится снова его использовать.
Теперь CC Rewrite. Как я уже говорил, он существенно влияет на билд-процесс. Есть конечны трюки, например отключить его локально и использовать только на билд-машине, но замедление все-таки есть.
Гораздо важнее влияние на рантайм. С контрактами очень легко расплодить лишние проверки. Так, если у тебя есть тяжелые инварианты на классе — в начало и конец каждого открытого метода добавятся проверки. Или контракты на коллекцию — ты должен проитерировать всю коллекцию. Особенно это может быть заметно если у тебя есть цепочка из пяти методов пробегающих по коллекции. Или проверка рекурсии, на ограничение вызова контрактов в глубину. Она использует вызов к thread local, что добавляет тормозов. Одним только отключением этой проверки мы повысили end-to-end производительность на 15%. Подобных деталей не сильно много, но про них стоит знать.
С другой стороны, мы сейчас пилим билд-движок, это системное ПО и немалая кодовая база. Нам приходится настраивать контракты для минимально влияния на производительность в рантайме, но отказываться от контрактов мы не хотим: сообщения от контрактов в получаемых нами креш-дампах дают очень много полезной информации, добиться этого другими стандартными средствами — значительно сложнее.
Кроме того, надо сказать что для большинства приложение накладные расходы на рантайм будут минимальны.
То есть, для малых проектов инструмент можно использовать без настроек?
Для малых — да. Для средних можно поставить только проверку предусловий и открытых методов, тогда импакт будет минимален. Ключевую функциональность, тщательно покрытую тестами, можно настроить на удаление всех контрактов из рантайма. Инструмент настраиваемый — им вполне можно (и стоит) пользоваться. Тюнить его придется только на крупных проектах.
Вопросы про Хабру.
Почему ты на нее пришел?
Ух… давно это было. Я пришел на Хабр в 2010-м, когда rsdn уже умирал, dou.ua уже был, но не был столь популярным. Хабр в то время активно развивался, с множеством глубоких технических постов и, самое главное, с высоким уровнем сигнал-шума. Его было интересно читать, в него было интересно писать. Публикации там – это был самый простой способ обсудить какую-то техническую проблему. Да, не все комментарии были одинаково полезны, но в тот момент это было мне интересно.
Почему ты ушел с Хабра?
Прежде всего изменился я, изменились мои интересы. Я продолжаю писать в своем блоге, часто выбирая более философские темы, и там уже сложилась некоторая аудитория, с которой интересно, которая знает меня и мои интересы. Писать же параллельно в блоге и на Хабре — не совсем «правильно». После того как мои последние “параллельные” посты собрали больше комментариев в блоге — я практически перестал писать на Хабр.
Я все еще читаю Хабр, но чаще благодаря соц-сетям и другим способам. Уровень же шума путем чтения через RSS для меня слишком высок: видимо, я не интересуюсь всем, что интересно аудитории Хабра, которая стала еще более разнообразной. При этом я часто попадаю на отличные статьи, вдумчивые, глубокие и интересные.
Кроме того, Хабр – это крупнейший IT-портал, с помощью которого можно почерпнуть много полезной информации. Изучая конкретную тему я все еще часто там оказываюсь, если ищу в русскоязычном сегменте.
От автора: наш разговор с Сергеем вышел несколько сумбурным и затронул сразу несколько несвязанных тем. Если я что-то упустил — Сергей доступен в личке и комментариях. Ответ может идти больше обычного, учитывайте часовой пояс: время Сиэтла отличается от московского на 11 часов.