<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" version="2.0">

<channel>
	<title>OpenQuality.ru | Качество программного обеспечения | Опыт экспертов</title>
	
	<link>http://experience.openquality.ru</link>
	<description>Что такое качество программного обеспечения и как его улучшить</description>
	<lastBuildDate>Mon, 16 Jan 2012 11:45:36 +0000</lastBuildDate>
	<language>ru</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/OpenQualityExperience" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="openqualityexperience" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:emailServiceId xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">OpenQualityExperience</feedburner:emailServiceId><feedburner:feedburnerHostname xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">http://feedburner.google.com</feedburner:feedburnerHostname><item>
		<title>Верный путь к убыткам от программного обеспечения</title>
		<link>http://experience.openquality.ru/universal-pattern-of-huge-software-losses/</link>
		<comments>http://experience.openquality.ru/universal-pattern-of-huge-software-losses/#comments</comments>
		<pubDate>Sat, 16 Apr 2011 19:19:10 +0000</pubDate>
		<dc:creator>Gerald Weinberg</dc:creator>
				<category><![CDATA[Истории]]></category>
		<category><![CDATA[провалы]]></category>
		<category><![CDATA[уроки]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=495</guid>
		<description><![CDATA[Gerald Marvin (Jerry) Weinberg делится полувековым опытом в области психологии разработки ПО: стопроцентный сценарий возникновения проблем и надежный способ их избежать. Во что обходятся провалы? Часть перфекционистов в индустрии разработки ПО чрезмерно озабочена провалами, а большинство других не подвергают вдумчивому анализу ценность бесперебойной деятельности. Тем не менее, обычно при тщательной оценке стоимости провала мы видим, что повышение надежности программного обеспечения может принести огромную пользу. В книге “Responding to Significant Software &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><em><a href="http://en.wikipedia.org/wiki/Gerald_Weinberg">Gerald Marvin (Jerry) Weinberg</a> делится полувековым опытом в области психологии разработки ПО: стопроцентный сценарий возникновения проблем и надежный способ их избежать.</em></p>
<h3>Во что обходятся провалы?</h3>
<p>Часть перфекционистов в индустрии разработки ПО чрезмерно озабочена провалами, а большинство других не подвергают вдумчивому анализу ценность бесперебойной деятельности. Тем не менее, обычно при тщательной оценке стоимости провала мы видим, что повышение надежности программного обеспечения может принести огромную пользу. В книге <a href="http://www.smashwords.com/books/view/35783">“Responding to Significant Software Events”</a> я представляю пять примеров, которые вас убедят.</p>
<p>Национальный банк государства X выдал займы всем банкам страны. Крошечная ошибка в вычислении процентной ставки стоила более миллиарда долларов, которые национальный банк так и не смог восполнить.</p>
<p>Коммунальное предприятие изменяло алгоритм выставления счетов в соответствии с изменением ставки оплаты труда (эфвемизм коммунальщиков для “повышения тарифов”). Процедура сводилась к корректировке нескольких численных констант в существующей программе биллинга. Незначительная ошибка в одной константе приумножилась миллионами потребителей и вылилась в X долларов, безвозвратно потерянных предприятием. Я говорю “X долларов”, поскольку слышал эту историю от четырех разных клиентов с различными значениями X. Оценка потерь варьировалась от 42 миллионов до 1,1 миллиарда долларов. Учитывая, что такая история произошла с четырьмя моими клиентами, и лишь малая часть коммунальных компаний пользуется моими услугами, я уверен, что на самом деле такие случаи бывали намного чаще.</p>
<p>О следующем случае я узнал из общедоступной прессы, поэтому могу сказать, что речь идет о New York State Lottery. Законодательный орган штата Нью-Йорк разрешил проведение особой лотерии в целях сбора дополнительных средств на одно достойное дело. Поскольку эта лотерея отличалась от  обычной лотереи, в программу для печати лотерейных билетов нужно было внести изменения. К счастью, модификация заключалась в изменении одной цифры в существующей программе. Крошечная ошибка привела к печати билетов-дубликатов, а общественное доверие к лотерее было подорвано и сопровождалось убытками, оцениваемыми в пределах между 44 и 55 миллионами долларов.</p>
<p>Другую историю я знаю со стороны, как клиент одной большой брокерской фирмы. Однажды фиктивная строка с 100.000$ была напечатана в сводках 1500000 аккаунтов, и никто не знал причин ее появления. Суммарная стоимость этого провала составила по меньшей мере 2000000 долларов, а причина заключалась в простейших ошибках при программировании на языке COBOL: не очищалась строка в зоне печати.</p>
<p>А эту историю я знаю как со стороны, будучи клиентом компании, высылающей товары по почте, так и изнутри, как консультант этой компании. Однажды на каждом счете был напечатан новый телефонный номер отдела сервиса для работы с запросами клиентов. К сожалению, одна цифра в телефонном номере оказалась неверной, и в результате получился номер местного врача, а не почтовой компании. Телефон доктора был постоянно занят в течение недели, пока он его не отключил. Пострадало много пациентов, хотя я не знаю, умер ли кто-нибудь из-за невозможности вызвать врача. Подсчитать убытки было бы трудно, если бы доктор не засудил почтовую компанию и не добился большой компенсации.</p>
<h3>Сценарий Больших Провалов</h3>
<p>В каждом из исследованных случаев события развивались согласно универсальному сценарию:</p>
<p style="margin-left:20px">1. &nbsp;&nbsp;Есть работающая система, которая считается надежной и архиважной</p>
<p style="margin-left:20px">2. &nbsp;&nbsp;Запрос на быстрое изменение в системе обычно исходит от высокопоставленного лица в организации.</p>
<p style="margin-left:20px">3. &nbsp;&nbsp;Изменение признается “тривиальным”.</p>
<p style="margin-left:20px">4. &nbsp;&nbsp;Никто не замечает, что пункт 3 говорит о сложности выполнения изменений, а не о последствиях их выполнения или последствиях допущенных при этом ошибок.</p>
<p style="margin-left:20px">5. &nbsp;&nbsp;Изменение проводится без каких-либо мер предосторожности, типичных для разработки ПО и внедренных в организации (пусть в минимальном объеме).</p>
<p style="margin-left:20px">6. &nbsp;&nbsp;Изменение проводится непосредственно в системе, выполняющей повседневные операции.</p>
<p style="margin-left:20px">7. &nbsp;&nbsp;Точечный эффект от изменения невелик, поэтому никто не замечает его сразу.</p>
<p style="margin-left:20px">8. &nbsp;&nbsp;Этот скромный эффект приумножается многократными операциями и приводит к большим последствиям.</p>
<p>Всякий раз когда мне удавалось отследить действия руководства, предпринимаемые вслед за  убытками, я обнаруживал продолжение универсального сценария. После выявления провала:</p>
<p style="margin-left:20px">9. &nbsp;&nbsp;Первая реакция руководства &#8211; преуменьшить значимость провала, поэтому его последствия ощущаются в некоторой степени дольше и без того неизбежного периода.</p>
<p style="margin-left:20px">10. &nbsp;&nbsp;Когда значимость убытка становится неопровержимой, увольняют программиста, который вносил изменения в код, ибо он сделал ровно то, что сказал руководитель группы.</p>
<p style="margin-left:20px">11. &nbsp;&nbsp;Руководитель группы понижается в должности до программиста &#8211; наверное, вследствие продемонстрированного (не)понимания технических аспектов работы.</p>
<p style="margin-left:20px">12. &nbsp;&nbsp;Менеджер, который поручил это дело руководителю группы, ускользает в сторону на штабную должность &#8211; по-видимому, для развития методик разработки программного обеспечения.</p>
<p style="margin-left:20px">13. &nbsp;&nbsp;Вышестоящие менеджеры остаются нетронутыми. В конце концов, что они могли сделать?</p>
<h3>Первое Правило Предотвращения Провала</h3>
<p>Осознав Универсальный Сценарий Гигантских Потерь,  вы знаете как поступать в ответ на высказывания такого рода:</p>
<div style="margin-left:20px">
<ul>
<li>“Это тривиальное изменение”</li>
<li>“И что тут может пойти не так?”</li>
<li>“Ничего не изменится”</li>
</ul>
</div>
<p>Когда вы слышите, как кто-нибудь считает нечто слишком маленьким и недостойным внимания, будьте начеку. Вот Первое Правило Предотвращения Провала:</p>
<p style="margin-left:20px">
<em>Нет ничего слишком маленького, чтобы оно было недостойно внимания.</em>
</p>
<h3>Все может быть иначе</h3>
<p>Истории катастроф всегда служат хорошим предметом новостей, но наблюдение за ними может исказить реальность.  Если мы рассматриваем только катастрофы в разработке ПО, мы упускаем все организации с эффективным управлением. Но хорошее управление так скучно! Не происходит ничего достойного публикации в прессе. Или почти ничего. К счастью, мы изредка встречаем согревающие душу истории, как, например, публикация в Financial World, рассказывающая о Чарлзе T. Фишере III из NBD Corporation, одном из титулованных CEO восьмидесятых:</p>
<p style="margin-left:20px">
<em>“Когда компьютеры Comerica  начали извергать ошибочные отчеты своим клиентам,  Фишер ввел Контроль Гарантированной Производительности, обещая 10$ за каждую ошибку в месячном отчете клиента NBD. В течение двух месяцев корпорация NBD заявила о 15000 новых клиентов и более чем 32 миллионах долларов в новых аккаунтах.”<br />
</em></p>
<p>История умалчивает о том, что происходило в недрах департамента Информационных Систем, когда его сотрудники осознали, что их CEO Чарлз Т. Фишер III установил стоимость их работы. Я не присутствовал при этом, но могу предположить эффект от осознания того, что каждая непредотвращенная ошибка стоила бы 10$ наличными.</p>
<h3>Второе Правило Предотвращения Провала</h3>
<p>Один из уроков истории с NBD: другие организации не знают, как установить значение своих потерь даже когда они в конце концов случились. Как будто они пошли в школу, внесли большую плату за обучение и не усвоили один важный урок &#8211; Первый Принцип Финансового Менеджмента, или Второе Правило Предотвращения Провала:</p>
<p style="margin-left:20px">
<em>Потеря X долларов всегда является ответственностью управленца, чья финансовая ответственность превышает X долларов.<br />
</em></p>
<p>Осознают ли когда-нибудь другие фирмы, что за незащищенность организации от вероятной потери  миллиарда долларов должен отвечать руководитель самого высокого уровня? Программист, который даже не уполномочен позвонить по межгороду, никогда не может быть ответственным за потерю миллиарда долларов. При наличии вероятности потерять миллиард долларов надежное функционирование информационных систем в организации входит в зону ответственности CEO.</p>
<p>Конечно, я не думаю, что Чарлз Т. Фишер III или любой другой CEO приложит руку даже к одной цифре в программе на COBOL. Но я действительно надеюсь, что когда исполнительные директоры  осознают ценнность  бесперебойной деятельности, они совершат правильные управленческие поступки. И тогда это послание просочится на уровни, в которых можно что-то предпринять по его поводу &#8211; наряду с ресурсами для таких действий.</p>
<h3>Учиться у других</h3>
<p>Другой урок из этих историй:  к моменту обнаружения провалов все будет слишком поздно. Будем надеяться, что ваш CEO прочитает о незащищенности в этих учебных примерах, а не в отчете о катастрофе в вашем офисе. Лучше найти способы предотвратить провалы до того как они выйдут из стен офиса.</p>
<p>Вот вопрос на проверку ваших познаний в разработке ПО:</p>
<p style="margin-left:20px">
<em>Каков самый ранний, дешевый, легкий и наиболее практичный способ обнаружить провалы?<br />
</em></p>
<p>И вот ответ, который вы, возможно, не ожидали:</p>
<p style="margin-left:20px">
<em>Самый ранний, дешевый, легкий и наиболее практичный способ обнаружить провалы &#8211; выявить их в другой организации.<br />
</em></p>
<p>За мою полувековую деятельность в области информационных систем было много неразгаданных тайн. К примеру, почему мы не поступаем так, как, по нашему разумению, должны поступать? Почему мы не учимся на наших ошибках? Но одна загадка, которая побивает все другие: почему мы не учимся на чужих ошибках?</p>
<p>Случаи, подобные описанным выше, встречаются в новостях каждую неделю и оказывают сильное влияние на отношение широкой публики к компьютерам. Но, похоже, они не оказывают никакого влияния на позицию профессионалов в области разработки ПО. Не потому ли, что такие чудовищные потери могут вызывать лишь одну безопасную психологическую реакцию: “У нас это не произойдет, потому что если произойдет, то я потеряю свою работу, а я не могу себе этого позволить, поэтому я не буду об этом думать”?</p>
<p>(По материалам книги <a href="http://www.smashwords.com/books/view/35783">Responding to Significant Software Events</a>)<br />
Оригинальная публикация: <a href="http://secretsofconsulting.blogspot.com/2011/01/universal-pattern-of-huge-software.html">http://secretsofconsulting.blogspot.com/2011/01/universal-pattern-of-huge-software.html</a></p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/universal-pattern-of-huge-software-losses/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>20 главных причин краха стартапов</title>
		<link>http://experience.openquality.ru/top-reasons-startups-fail/</link>
		<comments>http://experience.openquality.ru/top-reasons-startups-fail/#comments</comments>
		<pubDate>Sat, 16 Apr 2011 19:00:25 +0000</pubDate>
		<dc:creator>Chubby Team</dc:creator>
				<category><![CDATA[Истории]]></category>
		<category><![CDATA[провалы]]></category>
		<category><![CDATA[стартапы]]></category>
		<category><![CDATA[уроки]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=513</guid>
		<description><![CDATA[Команда проекта ChubbyBrain видит свою задачу в накапливании, анализе и предоставлении информации, полезной для стартапов. Ранее мы выделили 32 прощальных послания (post-mortem), в которых предприниматели анализировали причины провала созданных ими стартапов и были столь любезны поделиться уроками, извлеченными из неудач. Многие читатели заинтересовались наиболее типичными причинами краха, которые можно выделить из представленных публикаций. Что ж, мы подготовили ответы на ваши вопросы. После тщательного анализа 32 прощальных заметок предпринимателей мы выделили &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><em>Команда проекта <a href="http://www.chubbybrain.com/">ChubbyBrain</a> видит свою задачу в накапливании, анализе и предоставлении информации, полезной для стартапов.</em></p>
<p>Ранее мы <a href="http://www.chubbybrain.com/blog/startup-failure-post-mortem/">выделили</a> 32 прощальных послания (post-mortem), в которых предприниматели анализировали причины провала созданных ими стартапов и были столь любезны поделиться уроками,  извлеченными из неудач. Многие читатели заинтересовались наиболее типичными причинами краха, которые можно выделить из представленных публикаций.</p>
<p>Что ж, мы подготовили ответы на ваши вопросы. После тщательного анализа 32 прощальных заметок предпринимателей мы выделили 20 типичных факторов, которые, по мнению предпринимателей, приводят к краху стартапов.</p>
<h3>20. Организация компании в неподходящее время</h3>
<p>Многие компании, потерпевшие неудачу при старте во время недавнего финансового кризиса и продолжающие испытывать трудности, видят причину угасания своей активности в повышенной негативности рынка. Негативность ударила по инвестиционным фондам (венчурный капитал обвалился в 2009 году) либо по намеченным клиентам, как в случае с компанией Untitled Partners, которая создавала платформу для совместного владения предметами изобразительного искусства. В прощальной заметке основатели компании <a href="http://www.fabricegrinda.com/entrepreneurship/lessons-from-a-startup-failure/">говорят</a>:</p>
<p style="margin-left:20px"><em>Наш анализ поддерживался статьями в Wall Street Journal и NYT, а также индексом предметов искусства <a href="http://www.bloggingstocks.com/2009/02/19/what-is-the-mei-moses-art-index/">Mei Moses</a> , согласно которому рынок был антициклическим и имел слабую корреляцию с <a href="http://en.wikipedia.org/wiki/Standard_%26_Poor%27s">S&#038;P</a>. Но мы не учли величину текущих колебаний и их влияние на дискретный уровень затрат на предметы роскоши в кругу клиентов, которым наш продукт по-прежнему был по карману. Очевидно, мы ошиблись в своей возможности расти на фоне грядущего экономического спада.</em></p>
<h3>19. Неполный рабочий день</h3>
<p>Стартапы &#8211; нелегкое дело. Еще тяжелее, когда в течение дня вы вовлечены в два разных направления деятельности. Такое заключение прослеживается в нескольких прощальных заметках. Если на основной работе вы заняты полный день, и никто из вас не посвящает все свое время стартапу, вы рискуете перегореть, двигаться недостаточно оперативно и просто не иметь времени на необходимые действия. Кроме того, при наличии другой работы команда рискует действовать с меньшим подъемом, держа в уме наличие других источников дохода. Команда проекта Overto ощутила, что отсутствие хотя бы одного человека, занимающегося проектом полный день, послужило основной причиной <a href="http://blog.brodzinski.com/2009/01/lessons-learned-startup-failure-part-1.html">неудачи</a>:</p>
<p style="margin-left:20px"><em>Мы думали, что сможем заниматься нашим интернет-сервисом в послерабочее время. До какого-то момента так оно и было. Все шло замечательно, пока с нашими серверами и приложением не происходило ничего плохого. Мы работали над новой функциональностью, когда появлялось свободное время. Трудности начались, когда мы столкнулись с некоторыми проблемами в нашей инфраструктуре. Мы не смогли с ними справиться “на лету”, и сервис был недоступен в течение нескольких периодов времени. Можете себе представить, как это отразилось на пользователях. Неполадки также полыхнули по развитию сервиса, поскольку нам пришлось сфокусироваться на текущих проблемах вместо добавления новой функциональности. Отсутствие человека, работающего полный день и способного справиться с поддержкой сервиса и исправлением багов, стало наиболее важной причиной неудачи.</em></p>
<h3>18. Дислокация, дислокация, дислокация</h3>
<p>Дислокация оказалась важной в двух аспектах. Во-первых, должна быть согласованность между концепцией вашего стартапа и местоположением. Грубый пример: если вы создаете инновационное программное обеспечение для торговли на Wall Street, находитесь рядом со своими клиентами для наилучшего взаимодействия с ними. Местоположение также сыграло свою роль в неудачах распределенных команд. Ключевой момент для распределенной команды: найдите эффективные методы коммуникации, иначе отсутствие командной работы и планирования может привести к провалу. Нюансы местоположения назывались в качестве причины неудач в 6% случаев. В своем прощальном письме создатель сервиса микроблоггинга Nouncer рассматривает решение разместиться в Нью-Йорке как фактор, повредивший его <a href="http://hueniverse.com/2008/04/the-last-announcerment/">компании</a>:</p>
<p style="margin-left:20px"><em>В моем случае в Нью-Йорке не ощущалось нехватки денег, сообщества, достойных сотрудников и умных людей, способных дать хороший совет. Для успеха моего продукта не хватало целевой аудитории &#8211; ранних последователей, веб-хакеров, ищущих новую клевую игрушку. Я мог рассчитывать лишь на малое количество людей, встреченных на редких нью-йоркских тусовках, которые подходили под это описание. В Сан-Франциско такие конференции можно было найти каждый день, не говоря уже о неформальных сборищах, где люди “пачкали руки”, играя с программным кодом. Это очень специфичная аудитория, выбор которой был обусловлен моим решением не создавать продукт для широкого круга клиентов.</em></p>
<h3>17. Неспособность привлечь инвесторов</h3>
<p>Эту причину можно было счесть родственницей причины №20 (организация компании в неподходящее время), если бы не группа основателей стартапов, откровенно заявивших о  неспособности привлечь инвесторов как причине заката своей деятельности. Если у вас нет денег для реализации идеи, пересмотрите состояние рынка и свои подходы.</p>
<h3>16. Вытеснение на второй план</h3>
<p>Вопреки банальным утверждениям, согласно которым стартапам не следует уделять внимание конкуренции, в реальности происходит так: как только свежая идея получает одобрение рынка, на сцене появляется много соперников. И хотя навязчивые мысли о конкуренции не прибавляют здоровья, ее игнорирование привело к неудаче 10% стартапов. <a href="http://www.chubbybrain.com/companies/wesabe/people/marc-hedlund#axzz1AYVxCS7X">Marc Hedlund</a>, руководитель <a href="http://www.chubbybrain.com/companies/wesabe#axzz1AYVxCS7X">Wesabe</a> (сервис для управления персональными финансами) рассказал об этом в своих прощальных заметках:</p>
<p style="margin-left:20px"><em>В <a href="https://www.mint.com/">Mint</a> был худший метод агрегации данных, а в <a href="https://www.wesabe.com/groups">Wesabe</a> требовалось значительно больше усилий для работы в системе. При выборе между ними клиентам оказалось намного легче и удобнее работать в Mint, и положительный опыт накопился довольно быстро. Все, что я упомянул &#8211; независимость от одного провайдера ресурсов, сохранение конфиденциальности пользователей, помощь клиентам в изменении их финансовой жизни в лучшую сторону &#8211; все это замечательные, разумные вещи, и мы  к ним стремились. Но ни одна из них не имеет значения, если с продуктом труднее работать, потому что большинство людей просто не заботятся о том, чтобы извлечь прибыль в долгосрочной перспективе, если доступна краткосрочная.</em></p>
<h3>15. Выгорание</h3>
<p>Основателям стартапов нечасто удается соблюдать баланс работы и личной жизни, поэтому риск сгорания высок. Способность при необходимости урезать свои расходы и перенаправить усилия при попадании в тупик оказалась важным фактором в достижении успеха и предотвращении выгорания &#8211; наряду с наличием крепкой, разноплановой и целеустремленной команды, в которой можно разделить ответственность. Выгорание было названо причиной краха в более чем 12% случаев. В прощальных заметках один из основателей сервиса <a href="http://diffle-history.blogspot.com/">Diffle</a> рассматривает  последствия выгорания и говорит о том, что оно не закончилось с закрытием стартапа и продолжалось в течение некоторого времени:</p>
<div style="margin-left:20px;"><em></p>
<p>Не осознавая это тогда, я буквально летал после закрытия Diffle &#8211; на чистом адреналине. Это объяснялось взаимодействием со стартапом <a href="http://ycombinator.com/">YC</a>, а также тем, что предпринимательство ведет вас к сильносфокусированному состоянию ограниченного видения, кажущемуся лишь чуточку  нереальным. Все обрушилось через месяц, спустя полторы недели после того как я начал поиск работы. Мне отказали в FriendFeed, а затем я сообщил другим компаниям, что не совсем готов вернуться в мир наемных служащих и мне нужно несколько месяцев для того чтобы понять, что я на самом деле хочу делать дальше.</p>
<p>Каждому основателю компании, столкнувшемуся с необходимостью ее закрытия, я настоятельно рекомендую взять паузу перед принятием серьезных решений, и пусть это будет не две с половиной недели как в моем случае. Когда умирает ваш стартап, вы не мыслите здраво. Ваше восприятие может слегка искажаться в течение нескольких месяцев наряду с вашими предпочтениями относительно того, что делать дальше.</em></div>
<h3>14. Потеря фокуса: распыление</h3>
<p>Отвлечение на “а что если” было множество раз названо в качестве фактора, способствовавшего провалу. Важно запустить на рынок одну вещь и сфокусироваться на одном продукте, иначе вы рискуете остаться с большим количеством почти готовых продуктов, которые не представляют ценности для вас или ваших клиентов. В последней заметке о проекте Kiko его основатель Mahesh Piddshetti <a href="http://hitechstartups.wordpress.com/2008/05/23/lessons-from-kiko-web-20-startup-about-its-failure/">пишет</a>:</p>
<p style="margin-left:20px"><em>У большинства предпринимателей есть куча идей. Зачастую многие из них действительно хороши. Я не знаю, как у вас, но мое излюбленное занятие, связанное со  стартапами &#8211; разговор о новых продуктах и бизнес-идеях. Творческой личности легко отвлечься на побочные идеи в то время когда вам реально нужно работать над основной задумкой. Это плохо. Плохо, плохо, плохо. В Kiko такое случалось с нами много раз, и это привело к большим задержкам с выходом продукта.</em></p>
<h3>13. Разногласия с инвесторами/cooснователями</h3>
<p>Разлад с сооснователем стал одним из наиболее фатальных факторов для компаний. Сооснователь <a href="http://bricabox.org/">Bricabox</a> рекомендует: <em>“Когда сооснователь покидает компанию (как в случае со мной), вам уже нанесен сокрушительный удар. Не усугубляйте ситуацию бременем заботы о капитале компании, оставшейся на ваших руках (инвесторам не понравится, что второй акционер отсутствует &#8211; даже если он уже не имеет никакого отношения к компании). Поэтому наилучший вариант в такой ситуации &#8211;  длинный-длинный срок ограничения на обращение акций для всех ключевых сооснователей.”</em></p>
<p>Но напряженность не ограничена командой основателей. Когда ухудшаются отношения с инвестором, все довольно быстро идет под откос, что доказывает случай с <a href="http://en.wikipedia.org/wiki/ArsDigita">ArsDigita</a>. Инвесторы и основатели разошлись во взглядах на светлое будущее компании, и в конце концов инвесторы привели компанию к краху. В своих финальных заметках Phillip Greenspun, один из основателей компании, ярко и откровенно говорит о том, что происходит, когда инвесторы и управляющая команда стартапа находятся в разладе:</p>
<div style="margin-left:20px"><em></p>
<p>В течение года  Peter Bloom (<a href="http://www.chubbybrain.com/investors/general-atlantic#axzz11PLCHQB1">General Atlantic</a>), Chip Hazard (<a href="http://www.chubbybrain.com/investors/greylock-partners#axzz11PLJ4za3">Greylock</a>), and Allen Shaheen (CEO) обладали абсолютной властью в ArsDigita. За это время они:</p>
<ol>
<li>Потратили 20 млн. долларов, чтобы добиться той же выручки, что была в мою бытность CEO.</li>
<li>Отказались от предложения Microsoft (лето 2000 года) стать первой компанией, выпускающей софт для больших предприятий, с продуктом на базе .Net (сотрудник Microsoft после совещания с Allen сказал: “Он напомнил мне других исполнительных директоров впоследствии обанкротившихся компаний”.)</li>
<li>Закрыли старый полнофункциональный продукт (ACS 3.4) до завершения работы над новым продуктом (ACS 4.x). Заметьте, что такой способ уничтожить компанию хорошо известен среди людей с опытом разработки программных продуктов. Informix разрушил себя сам, потому что люди не могли понять, работать ли им со старой-доброй версией 7 или с новой иллюзорной версией 9, поэтому они переметнулись к Oracle.</li>
<li>Создали чрезвычайно дорогостоящую корпоративную структуру. В моем подчинении было 80 человек, большинство из которых сидело на базовых окладах в пределах 100000$ и приносило годовую выручку в размере 20 миллионов долларов. В компании ArsDigita под управлением Greylock, General Atlantic и Allen работало почти 200 человек: много новых управленческих позиций с окладом 200000$ и выше, программистов с базовым окладом 125000$ и т.д. Добавьте к этому новую моду работать с 9 до 5 с понедельника по пятницу. Allen, Greylock и General Atlantic не вышли бы на работу в выходные дни &#8211; равно как и сотрудники компании.</li>
<li>Сдали лидерские позиции на рынке и упустили интеллектуальное лидерство.</li>
</ol>
<p></em></div>
<h3>12. Недооценка круга общения</h3>
<p>Мы часто слышим, как организаторы стартапов сокрушаются о нехватке деловых  контактов. Нас удивило, что в качестве одной из основных причин неудачи предприниматели называют неэффективное использование собственной сети общения. Об этом говорится в 16% прощальных заметок. Чему учит нас этот факт? Если у вас есть сеть общения (она есть у каждого), будьте осмотрительны с этим ресурсом, но ни в коем случае не забывайте о нем.</p>
<h3>11. Вопросы ценообразования</h3>
<p>В ценообразовании одна часть наука и десять частей искусство. И это темное искусство, судя по большому количеству стартапов, которые потерпели неудачу и выставляли слишком большую или слишком маленькую цену за продукт. К примеру, один предприниматель сказал: <em>“Потребовалось немало ключевых цепочек с покупкой за 50 центов и продажей за 1,25$, чтобы оплатить телефонные счета”</em>. Основатель EventVue <a href="http://blog.eventvue.com/post/372936164/post-mortem">заметил</a>, что их смертельной стратегической ошибкой было <em>“следование модели продаж в корпоративном секторе с каждый раз новой маленькой ценой”</em>.</p>
<h3>10. Продукт, недружественный пользователю</h3>
<p>Не уверены, что откроем Америку, но дела обстоят плохо, если вы сознательно или случайно игнорируете пожелания и нужды пользователей.</p>
<h3>9. Запоздалый отказ от убыточного пути</h3>
<p>Одним из чрезмерно используемых стартаповских словосочетаний в 2010 году было &laquo;ключевые предположения модели&raquo; (<a href="http://www.multitran.ru/c/m.exe?l1=1&#038;l2=2&#038;s=Pivot">Pivot</a></em>), но среди причин краха довольно часто назывался недостаточно быстрый отказ от плохого продукта, плохого сотрудника, плохого решения и т.п. Остановки или приверженность плохой идее &#8211; не лучший способ распределить ресурсы. И это не просто идеи. Если вы принимаете неверное решение по найму сотрудника, совершите корректирующий поступок (эвфемизм для его увольнения) &#8211; и чем раньше, тем лучше. Как только вы увидите, что ваш продукт не пользуется спросом на рынке, подумайте о необходимых изменениях. Инерция и упертость, ограничивающие рост и возможность изменить модель бизнеса, назывались среди причин неудач в одном случае из пяти.</p>
<h3>8. Нехватка страсти и специфических знаний</h3>
<p>В мире рождается много хороших идей, но создатели угасших стартапов считают остутствие интереса к выбранной области и нехватку специфических знаний ключевыми причинами провала вне зависимости от того, насколько идея хороша. Сооснователь Untitled Partners заявил: <em>“Я недооценил важность взаимосвязи между нашими корпоративными и личными интересами”</em>. Нехватка страсти как причина краха фигурирует в 18,8% случаев. В своем последнем обращении основатели <a href="http://blog.paulbiggar.com/archive/why-we-shut-newstilt-down/">NewsTilt</a> откровенно говорили об отсутствии интереса к выбранной отрасли:</p>
<div style="margin-left:20px"><em></p>
<p>Я думаю, будет справедливо признаться, что на самом деле нас не заботила журналистика. Мы начали с создания системы комментирования, которая возникла из моего желания обрести совершенную систему для моего блога. Это обернулось в проектирование лучшей из когда-либо существовавших систем комментирования, что в свою очередь помогло обнаружить идеального клиента: газеты. В нашем воображении возник продукт, который должен был прийтись людям по вкусу.</p>
<p>Но мы никогда не интересовались журналистикой и никогда не читали новостей. Нам стоило создать этот продукт, если бы в начале каждого дня мы шли на <a href="http://news.bbc.co.uk">news.bbc.co.uk</a>. Но даже когда у нас появился NewsTilt, он не стал моим излюбленным местом времяпрепровождения. Таким местом по-прежнему были Hacker News и Reddit. И как вы можете создавать продукт, который интересен вам лишь с позиций бизнеса?</p>
<p>Все это сочеталось с нашим незнанием индустрии и желаний читателей.</em></div>
<h3>7. Выпуск продукта в неподходящее время</h3>
<p>Если вы выпускаете продукт слишком рано, пользователи могут сбросить его со счетов как недостаточно хороший. Если первое впечатление пользователей было отрицательным, вернуть их назад будет трудно. Если вы выпускаете продукт слишком поздно, вы можете упустить благоприятное стечение обстоятельств.  <em>“Здесь требуется баланс. Если речь идет о мегапопулярном веб-сайте, от которого зависят пользователи, скажем, Ebay или Mint.com, то его недоступность может обернуться катастрофой. Но если речь идет о сайте, подобном Twitter, то простой могут счесть шуткой. Знайте свой сайт, не затягивайте до бесконечности его выход на рынок. Но если он критически важен для пользователей, обеспечьте надежность его работы&raquo;</em>. Как сказал  Reid Hoffman,<em> “Если вы не испытываете определенных трудностей при запуске версии 1.0 своего продукта, вы выпустили его слишком поздно”</em>. Неподходящее время выхода продукта было названо причиной краха в более чем 20% случаев.</p>
<h3>6. У меня есть продукт, теперь мне нужна бизнес-модель</h3>
<p>Безусловно, Twitter умудряется обходиться без бизнес-модели, но они &#8211; не норма. Возможно, мы придерживаемся старых позиций, но если нет плана по превышению доходов над расходами, то это проблема. Провалившиеся основатели, похоже, признают важность бизнес-модели. К сожалению, в каждом четвертом анализе неудачи отстутствие бизнес-модели названо причиной провала.</p>
<h3>5. Деньги кончились</h3>
<p>Деньги и время заканчиваются и требуют разумного отношения. Задача правильного расходования средств зачастую оказывалась неразрешимой, и ее неверное решение называлось причиной краха угасших стартапов. Стоит ли потратить существенную сумму единовременно для придания импульса продукту или лучше разрабатывать его постепенно? Непросто найти правильное соотношение. Команда YouCastr обозначила денежные проблемы в качестве причины провала, но в то же время подчеркнула существование других причин для закрытия проекта вопреки возможности попытаться найти дополнительные <a href="http://theambitiouslife.com/youcastr-a-post-mortem">деньги</a>:</p>
<p style="margin-left:20px"><em>Единственная важная причина, по которой мы закрываем проект &#8211; у нас кончились деньги. Несмотря на ЧРЕЗВЫЧАЙНО бережливое управление компанией, наличие выручки и готовность держаться до конца, у нас не было денег для продолжения работы. Следующие несколько причин могут пролить больше света на наше решение закрыться вместо того чтобы найти другой источник денежных средств.</em></p>
<h3>4. Слабый маркетинг</h3>
<p>Понимание целевой аудитории, знание способов заполучить внимание пользователей и умение в конце концов превратить их в клиентов &#8211; одни из наиболее важных  навыков в успешном бизнесе. Тем не менее, в 30% провалов неэффективный маркетинг стал главной причиной неудачи. Зачастую неспособность заниматься маркетингом была отличительной чертой  основателей. Им нравилось писать код и строить продукт, но у  них отсутствовала тяга к продвижению продукта. Ребята в проекте Devver <a href="http://devver.wordpress.com/2010/04/26/lessons-learned">подчеркнули</a> крайнюю важность заполучить кого-нибудь, кто любит создавать и находить каналы дистрибуции, развивать деловые связи.</p>
<h3>3. Неподходящая команда</h3>
<p>Наличие разноплановой команды с взаимодополняющими навыками часто выдвигалось в качестве критически важной составляющей успеха стартапа. В прощальных записках зачастую слышались стенания вида <em>“вот если бы у нас был CTO с самого начала”</em> или <em>“основатель, которому бы нравилось заниматься деловыми вопросами”</em>. В некоторых случаях команда основателей желала более глубокого контроля действий друг друга и достижения баланса. Как <a href="http://hueniverse.com/2008/04/the-last-announcerment/">заметил</a> основатель Nouncers, <em>“Теперь мне ясно, что базовой проблемой было отсутствие партнера, который мог меня сбалансировать, а также тщательно анализировать принимаемые деловые и технические решения”</em>. Основатель Wesabe также отмечает, что решения, касаемые деятельности предприятия, он упорно принимал в одиночку и потому в неудаче Wesabe не может обвинить никого кроме себя. Нехватка умений в команде называлась в качестве причины провала почти в каждом третьем случае.</p>
<h3>2. Решение интересной задачи вместо нацеливания на потребности рынка</h3>
<p>Выбор интересной задачи вместо решения задачи, соответствующей требованиям рынка, часто назывался в качестве причины неудачи. Конечно, вы можете создать приложение и посмотреть, что с ним будет, но лучше заранее знать потребности рынка.<em> “Компании должны браться за проблемы рынка, а не за технические задачи”</em>, утверждает основатель BricaBox. Одной из основных причин провала BricaBox было стремление решить техническую <a href="http://innonate.com/2008/06/19/bricabox-goodbye-world/">задачу</a>: <em>“Здорово уступать своим желаниям, но лучше всего если ваши желания находят отклик на широком рынке. Если вы хотите решить техническую проблему, соберите группу и создайте проект с открытым кодом”.</em></p>
<h3>1. Негибкость и отсутствие активного внимания к отзывам клиентов</h3>
<p>Игнорирование своих пользователей &#8211; проверенный способ прийти к неудаче. Несмотря на очевидность этого постулата, отказ от него назывался первейшей причиной краха. Узость взглядов и пренебрежение отзывами  пользователей &#8211; это фатальные упущения для большинства стартапов. Например, основатель компании eCrowds, создавшей систему управления веб-контентом, отметил: <em>“Мы потратили слишком много времени, создавая систему для себя и не получая обратной связи от потенциальных клиентов. Легко оказаться в ситуации с ограниченным  полем зрения. Я рекомендую не затягивать больше чем на два-три месяца получение объективного мнения потенциальных клиентов”</em>.</p>
<p>Итак, отважные стартапы, теперь, благодаря опыту, великодушию и  откровенности  32 ваших собратьев, вы знаете 20 причин краха стартапов. Мы надеемся, что представленный список пригодится на вашем пути, и вы поделитесь этим списком с другими сооснователями стартапов, которые также могут извлечь пользу из его ключевых идей.</p>
<p>Оригинальная публикация: <a href="http://www.chubbybrain.com/blog/top-reasons-startups-fail-analyzing-startup-failure-post-mortem/">http://www.chubbybrain.com/blog/top-reasons-startups-fail-analyzing-startup-failure-post-mortem/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/top-reasons-startups-fail/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>DTrace: технология для ленивых сисадминов и разработчиков</title>
		<link>http://experience.openquality.ru/dtrace-use-cases/</link>
		<comments>http://experience.openquality.ru/dtrace-use-cases/#comments</comments>
		<pubDate>Tue, 15 Mar 2011 21:19:06 +0000</pubDate>
		<dc:creator>Филипп Торчинский</dc:creator>
				<category><![CDATA[Методики]]></category>
		<category><![CDATA[DTrace]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=459</guid>
		<description><![CDATA[Филипп Торчинский, признанный эксперт в администрировании Unix-систем, раскрывает секреты приложений с помощью DTrace. Технология DTrace и подходящие инструменты для ее использования появились в 2005 году, но, несмотря на это, DTrace еще малоизвестна в широких кругах разработчиков и сисадминов. Это тем более удивительно, что за пять с половиной лет, прошедших с выхода системы Solaris 10, в которой она впервые появилась, так и не было придумано более совершенной технологии наблюдения за операционной &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><em><a href="http://voyadger.livejournal.com/">Филипп Торчинский</a>, признанный эксперт в администрировании Unix-систем, раскрывает секреты приложений с помощью DTrace.</em></p>
<p>Технология <a href="http://wikis.sun.com/display/DTrace/Introduction">DTrace</a> и подходящие инструменты для ее использования появились в 2005 году, но, несмотря на это, DTrace еще малоизвестна в широких кругах разработчиков и сисадминов. Это тем более удивительно, что за пять с половиной лет, прошедших с выхода системы Solaris 10, в которой она впервые появилась, так и не было придумано более совершенной технологии наблюдения за операционной системой и приложениями.</p>
<p>DTrace была разработана в компании Sun Microsystems, и открытый код всех ее компонентов был опубликован в том же 2005 году. С тех пор DTrace была перенесена в <strong>Mac OS X</strong>, <strong>QNX</strong> и <strong>FreeBSD</strong>, и появилась также во всех дистрибутивах, унаследовавших код ядра Solaris: <strong>Belenix</strong>, <strong>Korona</strong>, <strong>Nexenta</strong>, <strong>OpenIndiana</strong> и <strong>Milax</strong>. Разумеется, в <strong>Solaris</strong> технология DTrace тоже осталась и даже развилась &#8211; датчиков стало больше. В настоящее время DTrace доступна в самой свежей версии Solaris 11 Express, равно как и в Solaris 10.</p>
<p>По сравнению с другими средствами сбора информации о системе и отладки приложений, DTrace обладает рядом <strong>уникальных свойств</strong>:</p>
<div style="margin-left: 20px">
<ol>
<li>накапливает информацию о системе, работающей под максимальной нагрузкой &#8211; с низкими накладными расходами на сбор информации;</li>
<li>собирает любую информацию из любых уголков системы, позволяя наблюдать как за работой приложений, так и за работой самого ядра системы;</li>
<li>может показать, какие аргументы передаются от одной функции к другой, независимо от того, доступен ли исходный код функций;</li>
<li>собирает информацию о том, как долго исполняются вызываемые функции, какой процент времени занимает исполнение каждой из них, сколько раз каждая из них была вызвана;</li>
<li>фильтрует информацию любым заданным образом &#8211; например, позволяет ограничить область наблюдения одним приложением, одним потоком команд, или определенной областью (скажем, измерять время выполнения только конкретного системного вызова, а об остальном не заботиться);</li>
<li>может реагировать на определенные события (ввод-вывод, вызов заданных функций, завершение программы, запуск нового потока и пр.);</li>
<li>имеет как средства низкоуровнего наблюдения (можно изучать ход работы драйвера устройства), так и средства высокоуровневые, например, позволяет отслеживать определенные события при исполнении скриптов на PHP или вызов методов в приложении, написанном на Java;</li>
<li>позволяет выполнять трассировку вызовов, с одновременным отслеживанием любых параметров &#8211; времени выполнения, переданных аргументов и пр.</li>
</ol>
</div>
<p><strong>Основным компонентом</strong> DTrace является <strong>модуль ядра</strong>, обеспечивающий функционирование подсистемы DTrace, а <strong>основным инструментом</strong> &#8211; приложение <strong>dtrace</strong>, которое воспринимает скрипты, написанные на языке D, созданном специально для работы с DTrace. В этой заметке мы не будем вдаваться в детали реализации DTrace; тем, кто в них заинтересован, я могу посоветовать найти их описание в Google, а также в главах 27 и 28 второго издания книги <a href="http://www.books.ru/shop/books/651013">&laquo;Операционная система Solaris&raquo;</a>, написанной мной в соавторстве с Евгением Ильиным в 2009 году. Кроме того, наблюдение, профилирование и трассировка работы приложений с помощью DTrace подробно рассмотрены <a href="http://www.intuit.ru/department/os/sysadmsolaris10/15/">в одной из лекций </a>по курсу «Системное администрирование ОС Solaris 10».</p>
<p>Сейчас мы рассмотрим несколько примеров, из которых будет ясно, как лучше всего применять функциональность DTrace, и какой прок из нее можно извлечь. Мы увидим, <em>как стремление облегчить себе жизнь и потакать своей лени может заставить сисадмина или разработчика изучить и применять новый инструмент, и как это повышает производительность труда в разы, а производительность приложений &#8211; на порядки</em>.</p>
<p>Однажды на конференции OSDevCon в Дрездене Чад Минхир (Chad Mynhier) вел мастер-класс по DTrace. Интерес к нему был огромный, и вот почему. Чад работает в компании <a href="http://www.forsythe.com/na/">Forsythe</a>,  зарабатывающей миллионы долларов в месяц на оптимизации банковских приложений. Как-то раз у одного из заказчиков он триумфально ускорил работу приложения <em>в тысячу раз</em>, заодно побив рекорд скорости зарабатывания гонорара.</p>
<p>Дело в том, что одно из основных банковских приложений, функциональность которого вполне удовлетворяла всех  раньше, стало работать значительно медленнее с ростом нагрузки. Так как банк планировал еще больший рост нагрузки, опасное замедление становилось неприемлемым. Анализ приложения с помощью DTrace вначале не выявил ничего особенного, однако распределение функций по частоте вызовов показалось Чаду настораживающим: чаще всех вызывалась функция gettimeofday(), возвращающая текущее системное время. При работе с базами данных в вызове этой функции нет ничего подозрительного, так как в БД часто записывается время, в которое произошла транзакция, но эта функция отчего-то доминировала в вызовах&#8230; С помощью DTrace было локализовано место в приложении, где эта функция вызывалась чаще всего. Оказалось, что по недосмотру разработчика она вызывалась в цикле, который, как назло, тоже был задействован почти в каждой операции приложения.</p>
<p><em>Не исключено, что вызов gettimeofday() остался от какого-то отладочного вывода или был помещен внутрь цикла по ошибке, но так или иначе, отловить эту ошибку на этапе тестирования не удалось, и всплыла она только тогда, когда нагрузка на приложение превысила определенный уровень. До того мощный компьютер легко справлялся с сотнями тысяч вызовов gettimeofday() в секунду.</em></p>
<p>Cкрипт, выдающий количество функций, написать легко. В качестве затравки рассмотрим несколько вариантов, а затем перейдем к синтаксису скриптов. Например, вот такой скрипт выдает количество вызовов любых функций при работе программы ls (вначале на экран будет выдан результат ее работы, а потом &#8211; список функций с количеством вызовов для каждой в порядке возрастания):</p>

<div class="wp_syntax"><div class="code"><pre class="d" style="font-family:monospace;">pfexec dtrace <span style="color: #66cc66;">-</span>n <span style="color: #ff0000;">'pid$target:::entry {@funсtions[probefunc]=count();}'</span> <span style="color: #66cc66;">-</span>c ls</pre></div></div>

<p>А вот таким скриптом можно посчитать количество функций, вызванных конкретным процессом с PID=2355; для получения результата надо прервать выполнение скрипта с помощью Ctrl-C:</p>

<div class="wp_syntax"><div class="code"><pre class="d" style="font-family:monospace;">pfexec dtrace <span style="color: #66cc66;">-</span>n <span style="color: #ff0000;">'pid2355:::entry {@funсtions[probefunc]=count();}'</span></pre></div></div>

<p>А вот так можно посчитать количество системных вызовов в том же процессе:</p>

<div class="wp_syntax"><div class="code"><pre class="d" style="font-family:monospace;">pfexec dtrace <span style="color: #66cc66;">-</span>n <span style="color: #ff0000;">'syscall:::entry /pid==2355/ {@funсtions[probefunc]=count();}'</span></pre></div></div>

<p>Итак, в скриптах на языке D всегда используется следующий синтаксис:</p>
<pre>
провайдер:модуль:функция:датчик
/условие/
{
действия
}
</pre>
<p><strong>Провайдер</strong> &#8211; это модуль ядра или модуль приложения, обеспечивающий регистрацию своих датчиков DTrace в системе. Например, провайдер syscall регистрирует датчики, расположенные в системных вызовах, провайдер mysql<PID-mysql-сервера> &#8211; датчики, встроенные в сервер MySQL.</p>
<p><strong>Модуль</strong> &#8211; это название модуля или библиотеки, например, libc.</p>
<p><strong>Функция</strong> &#8211; имя функции, датчик в которой нам интересен, например, fopen.</p>
<p><strong>Датчик</strong> &#8211; название датчика (во многих случаях датчик называется entry или return).</p>
<p><strong>Условие</strong> задает ситуацию, когда следует выполнить действие при срабатывании датчика. Например, надо выполнить его только тогда, когда датчик сработал в приложении top. Тогда условие выглядит так:</p>
<pre>
 /execname == top/
</pre>
<p><strong>Действия</strong> &#8211; это то, что обеспечивает сбор информации и вывод ее на экран. Из примеров ниже будет ясно, какими они бывают.</p>
<p>Использовать DTrace приходится не только в задачах повышения или мониторинга производительности; я сам часто использую DTrace когда мне лень искать и читать документацию, когда в документации нет полной информации или когда документации и исходного кода под рукой нет, а понять, как работает приложение, надо.</p>
<p>Простой пример того, как я использую DTrace в повседневной жизни: после переноса пользовательских настроек с одного компьютера на другой система на не захотела работать с принтером, как раньше. Было ясно, что какой-то из файлов настроек отчего-то не скопирован. Но какой именно? Их же сотни&#8230; Банальные /etc/printers и /etc/lp/* были проверены и разгадки не дали. Тогда пришлось задействовать DTrace. Простой скрипт дает возможность заглянуть в недра программы lpstat (наиболее безвредная программа из тех, что работает с принтером) и посмотреть, какие файлы она пытается открыть:</p>
<pre>
lpstat -s
scheduler is running
system default printer: eaqvap21
aqvap21: unknown printer
aqvap21: unknown printer
</pre>
<p>Чтобы запустить программу и подсунуть ее PID скрипту на DTrace надо использовать ключ -c :</p>

<div class="wp_syntax"><div class="code"><pre class="d" style="font-family:monospace;">dtrace <span style="color: #66cc66;">-</span>n <span style="color: #ff0000;">'pid$target::fopen:entry {printf(&quot;%s&quot;,copyinstr(arg0));}'</span> <span style="color: #66cc66;">-</span>c <span style="color: #ff0000;">&quot;lpstat -s&quot;</span></pre></div></div>

<pre>
dtrace: description 'pid$target::fopen:entry ' matched 1 probe
scheduler is running
system default printer: eaqvap21
aqvap21: unknown printer
aqvap21: unknown printer
dtrace: pid 11156 has exited
CPU     ID                    FUNCTION:NAME
 0  59882                      fopen:entry /etc/default/init
 0  59882                      fopen:entry /etc/lp/ppd/eaqvap21.ppd
 0  59882                      fopen:entry /export/home/filip/.printers
 0  59882                      fopen:entry /export/home/filip/.printers
 0  59882                      fopen:entry /export/home/filip/.printers
 0  59882                      fopen:entry /etc/printers.conf
 0  59882                      fopen:entry /export/home/filip/.printers
 0  59882                      fopen:entry /etc/printers.conf
 1  59882                      fopen:entry /etc/nsswitch.conf
 1  59882                      fopen:entry /export/home/filip/.printers
 1  59882                      fopen:entry /export/home/filip/.printers
 1  59882                      fopen:entry /etc/printers.conf
</pre>
<p>Вот и все: я забыл, что надо почистить файл .printers в домашнем каталоге:</p>
<pre>
rm /export/home/filip/.printers
</pre>
<p>Готово! Все работает на ура!</p>
<p><em>Замечание: подсовывать PID программы скрипту надо потому, что провайдер pid требует указания PID изучаемого с помощью DTrace процесса, а знакомый нам всем вызов fopen как раз относится к этому провайдеру DTrace.</em></p>
<p>Вообще, сбор информации о том, какие файлы открываются в системе, часто дает разгадку сисадмину. Поэтому иногда я использую и такую модификацию скрипта:</p>

<div class="wp_syntax"><div class="code"><pre class="d" style="font-family:monospace;">pfexec dtrace <span style="color: #66cc66;">-</span>n <span style="color: #ff0000;">'syscall::open*:entry {printf(&quot;%s<span style="color: #000099; font-weight: bold;">\n</span>&quot;,copyinstr(arg0));}'</span></pre></div></div>

<p>Этот скрипт валит в кучу данные от всех системных вызовов, имена которых начинаются на open, что неудобно, но зато его легче всего вспомнить и проще всего применить к уже запущенным приложениям, а полученной информации может хватить для анализа.</p>
<p>Наконец, для тех, кто отлаживает веб-приложения, может пригодиться скрипт, который вылавливает SQL-операторы из сервера БД перед тем, как сервер их запустит. Это позволяет выяснить, какие именно операторы исполняются; такая информация поможет, если кажется, что скрипт, работающий с БД, все делает верно, а база данных возвращает неожиданный результат: может оказаться, что либо SQL-выражение формируется неверно, либо соединние происходит не с той базой данных, что надо, либо какой-то параметр передается СУБД без должного оформления (например, без кавычек или с лишними кавычками).</p>
<p>Вот этот скрипт:</p>

<div class="wp_syntax"><div class="code"><pre class="d" style="font-family:monospace;"><span style="color: #0040ff;">#!/usr/sbin/dtrace -s</span>
#pragma D option quiet
dtrace<span style="color: #66cc66;">:::</span>BEGIN
<span style="color: #66cc66;">&#123;</span>
  printf<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">&quot;%-20s %-20s %-40s %-9s<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;Who&quot;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;Database&quot;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;Query&quot;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;Time(ms)&quot;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">;</span>
<span style="color: #66cc66;">&#125;</span>
mysql<span style="color: #66cc66;">*:::</span>query<span style="color: #66cc66;">-</span>start
<span style="color: #66cc66;">&#123;</span>
  self<span style="color: #66cc66;">-&gt;</span>query <span style="color: #66cc66;">=</span> copyinstr<span style="color: #66cc66;">&#40;</span>arg0<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">;</span>
  self<span style="color: #66cc66;">-&gt;</span>connid <span style="color: #66cc66;">=</span> arg1<span style="color: #66cc66;">;</span>
  self<span style="color: #66cc66;">-&gt;</span>db    <span style="color: #66cc66;">=</span> copyinstr<span style="color: #66cc66;">&#40;</span>arg2<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">;</span>
  self<span style="color: #66cc66;">-&gt;</span>who   <span style="color: #66cc66;">=</span> strjoin<span style="color: #66cc66;">&#40;</span>copyinstr<span style="color: #66cc66;">&#40;</span>arg3<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span>strjoin<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">&quot;@&quot;</span><span style="color: #66cc66;">,</span>copyinstr<span style="color: #66cc66;">&#40;</span>arg4<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">;</span>
  self<span style="color: #66cc66;">-&gt;</span>querystart <span style="color: #66cc66;">=</span> timestamp<span style="color: #66cc66;">;</span>
<span style="color: #66cc66;">&#125;</span>
&nbsp;
mysql<span style="color: #66cc66;">*:::</span>query<span style="color: #66cc66;">-</span>done
<span style="color: #66cc66;">&#123;</span>
  printf<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">&quot;%-20s %-20s %-40s %-9d<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #66cc66;">,</span>self<span style="color: #66cc66;">-&gt;</span>who<span style="color: #66cc66;">,</span>self<span style="color: #66cc66;">-&gt;</span>db<span style="color: #66cc66;">,</span>self<span style="color: #66cc66;">-&gt;</span>query<span style="color: #66cc66;">,</span>
         <span style="color: #66cc66;">&#40;</span>timestamp <span style="color: #66cc66;">-</span> self<span style="color: #66cc66;">-&gt;</span>querystart<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">/</span> <span style="color: #0000dd;">1000000</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">;</span>
<span style="color: #66cc66;">&#125;</span></pre></div></div>

<p>Запустив его, можно наблюдать картину работы сервера (осторожно! там может быть очень много информации, если сервер сильно нагружен запросами!):</p>
<pre>
$ pfexec ./mysql.d
Who                  Database             Query                                    Time(ms)
root@localhost       data                 SELECT DATABASE()                        0
root@localhost       test                 show tables                              0
root@localhost       test                 SELECT DATABASE()                        0
root@localhost       mysql                select * from users                      0
root@localhost       mysql                select * from host                       0
</pre>
<p>C помощью измерения времени между срабатываниями датчиков query-start и query-done вычисляется время исполнения SQL-оператора, и DTrace позволяет строить графики распределения времени исполнения по операторам. Так можно найти те обращения к базе данных, которые отнимают много времени при выполнении конкретных приложений. Большое преимущество DTrace в том, что все эти измерения можно производить в условиях реальной нагрузки на сервер, так как накладные расходы на работу самого DTrace минимальны и не будут мешать работе сервера.</p>
<p><em>Замечание: время выполнения запросов меньше 1 ms, а в скрипте выполняется целочисленное деление, поэтому результат получается 0. Можно считать в микросекундах, а не в миллисекундах, тогда будет какое-то небольшое значение.<br />
</em></p>
<p>Из приведенных примеров видно, что датчики DTrace расставлены в системе и приложениях Solaris повсюду. Изучить, какие аргументы, связанные с датчиками, можно использовать так, как показано выше, можно в документации по системе (man) и в документации по конкретным приложениям (например, по серверу MySQL на сайте <a href="mysql.com">mysql.com</a>).</p>
<p>Значительно больше примеров скриптов для dtrace можно найти в следующих источниках:</p>
<div style="margin-left: 20px">
<ul>
<li>каталог /opt/DTT/ (Solaris)</li>
<li><a href="http://blogs.sun.com/brendan/category/DTrace">http://blogs.sun.com/brendan/category/DTrace</a></li>
<li><a href="http://www.brendangregg.com/DTrace/dtrace_oneliners.txt">http://www.brendangregg.com/DTrace/dtrace_oneliners.txt</a></li>
</ul>
</div>
<p>Тем, кто захочет попробовать DTrace на практике, важно помнить, что для использования dtrace надо обладать правами администратора системы; обычному пользователю dtrace недоступна из соображений безопасности.</p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/dtrace-use-cases/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Типичные сценарии распространения и обработки исключений</title>
		<link>http://experience.openquality.ru/exception-handling/</link>
		<comments>http://experience.openquality.ru/exception-handling/#comments</comments>
		<pubDate>Mon, 14 Feb 2011 10:16:37 +0000</pubDate>
		<dc:creator>Артур Бакиев</dc:creator>
				<category><![CDATA[Методики]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[исключения]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=345</guid>
		<description><![CDATA[Предисловие Статья рассматривает вопросы, относящиеся к обработке исключительных ситуаций [exception handling] в языках, поддерживающих соответствующий механизм. В статье обсуждаются наиболее распространённые проблемы, с которыми сталкивается разработчик, применяя обработку исключений, а также возможные способы решения этих проблем. Основной акцент в статье делается на примерах и прецедентах использования исключений. Статья предназначена для разработчиков, знакомых с объектно-ориентированными языками и будет полезна при освоении этих языков. Основные примеры в статье приведены на C++. Введение &#160;[...]]]></description>
			<content:encoded><![CDATA[<h2>Предисловие</h2>
<p>Статья рассматривает вопросы, относящиеся к обработке исключительных ситуаций [exception handling] в языках, поддерживающих соответствующий механизм. В статье обсуждаются наиболее распространённые проблемы, с которыми сталкивается разработчик, применяя обработку исключений, а также возможные способы решения этих проблем. Основной акцент в статье делается на примерах и прецедентах использования исключений. Статья предназначена для разработчиков, знакомых с объектно-ориентированными языками и будет полезна при освоении этих языков.</p>
<p>Основные примеры в статье приведены на C++.</p>
<h2>Введение</h2>
<p>Идеализированный подход, использующий обработку исключений подразумевает следующее. Разработчик пишет код так, как если бы в нём не случались ошибки. За обработку же ошибок отвечает некоторый заранее определённый код, который знает, как обработать исключительную ситуацию. Подобное разделение между кодом, выполняющим основную работу, и кодом обработки ошибок должно упрощать взаимодействие отдельных компонент между собой, позволяя строить изолированные/слабо-связанные уровни абстракций, и как следствие, уменьшать стоимость разработки и поддержки.</p>
<p>Понятия “ошибка” и “исключение” в статье, в основном, рассматриваются как синонимы. Везде, где не оговорено особо, фразу “функция генерирует исключение” можно трактовать, как “функция возвращает ошибку”, и наоборот. Либо, переиначив, функция извещает об ошибке, генерируя исключение.</p>
<p>В тех местах, где термины “ошибка” и “исключение” трактуются по-разному, различие очевидно из контекста. В этом случае, “ошибка” рассматривается как ситуация, сигнализирующая о том, что некоторая часть системы не смогла справиться с поставленной задачей. “Исключение” же рассматривается как транспортный механизм, позволяющий доставить по назначению информацию об ошибке.</p>
<p>Под термин “исключительная ситуация” не подводится теоретической базы &#8211; ситуация считается “исключительной”, если таковой её считает разработчик.</p>
<div style="margin-left: 20px"><em></p>
<p>Видимо, определение того, что является “исключительной ситуацией”, в общем случае, является непростой задачей. Определение будет зависеть от явной (и неявной) семантики интерфейсов, от деталей реализации и пристрастий разработчика.</p>
<p>Как простой пример, можно рассмотреть разыменование недействительного указателя. Является ли подобная ситуация “исключительной”? На первый взгляд, да. С другой стороны, намерение разработчика могло состоять в локальном перехвате нарушения доступа [access violation] используя структурную обработку исключений [<a href="http://msdn.microsoft.com/en-us/library/ms680657(v=vs.85).aspx">structured exception handling</a> - <a href="http://msdn.microsoft.com/en-us/library/ms680657(v=vs.85).aspx">SEH</a>] для выделения очередной страницы памяти. А это, скорее, попадает в категорию “алгоритм”.</p>
<p></em></div>
<p>Помимо этих понятий, в статье широко используется термин “компонент”. Термин следует трактовать в самом широком смысле &#8211; он может ссылаться на класс, подсистему, программный продукт и т.п.</p>
<h2>1 Стратегия</h2>
<p>Мы будем исходить из того, что язык позволяет нам генерировать и перехватывать исключения. Это необходимо и, в принципе, достаточно для реализации единого – последовательного и согласованного – подхода к обработке ошибок, о котором говорилось выше. Утверждение вытекает из уверенности в том, что подход реализуем, в рамках одного проекта, поддерживаемого одним человеком.</p>
<p>Посмотрим, жизнеспособна ли идея, если речь идёт о промышленном применении.</p>
<p>Для того, чтобы решение выглядело чем-то большим, нежели совет “пишите хороший код”, начнём со списка вопросов, которые встают перед разработчиком в момент обработки ошибочной ситуации:</p>
<div style="margin-left: 20px">
<ol>
<li>Прежде всего, необходимо определиться – следует ли возбуждать исключительную ситуацию в данной конкретной точке или можно воспользоваться традиционным возвращаемым значением.</li>
<li>Если решение о генерации исключения принято – необходимо решить, понадобится ли нам идентифицировать ошибку либо достаточно факта существования исключения и ошибка может быть анонимной.</li>
<li>Далее, нужно определиться с тем, где исключение будет перехвачено.</li>
<li>Необходимо убедиться в корректной обработке исключения.</li>
<li>Следует “автоматизировать” генерацию и обработку исключений. Давать ответы на предыдущие 4 вопроса в каждой точке, где может быть инициировано исключение &#8211; занятие утомительное и потому, подвержено ошибкам (кодирования).</li>
<li>Нельзя упустить вопрос эффективности работы с исключениями &#8211; мы не хотим платить за то, что мы не заказывали.</li>
</ol>
</div>
<p>Вопросы перечислены приблизительно в том порядке, в каком они возникают перед разработчиками. Способ описания, выбранный в статье, согласуется с этим порядком. Далее будет подробно рассмотрено каждое из требований и сформулирован общий подход и рекомендации.</p>
<h3>1.1 Когда следует генерировать исключения</h3>
<p>Начнём с первого вопроса в нашем списке &#8211; следует ли в данной точке воспользоваться исключением или достаточно будет возвращаемого функцией значения. Сам по себе вопрос не сложен, но в реальных проектах решение о том, следует ли генерировать исключение, приходится принимать чаще, чем можно было бы ожидать, поэтому остановимся на нём подробно.</p>
<p>Два фактора влияют на выбор того или иного способа:</p>
<div style="margin-left: 20px">
<ol>
<li>Ограничения, которые накладываются внешними условиями.</li>
<li>Ограничения, которые определяются назначением компонента.</li>
</ol>
</div>
<p>Первый не зависит от разработчика и, порой, не оставляет ему выбора, заставляя использовать традиционные методы оповещения об ошибках. Тогда как второй определятся прецедентами использования [use cases] разрабатываемого компонента.<br />
Ниже мы подробно рассмотрим оба фактора.</p>
<h4>1.1.1 Внешние ограничения</h4>
<p>Начнём с рассмотрения внешних ограничений. Подобные ограничения актуальны для языка C++, но подчас их приходится учитывать и для языков, для которых работа с исключениями является “врождённым” свойством. Вот основные случаи, в которых нам приходится использовать традиционные способы оповещения об ошибках.</p>
<div style="margin-left: 20px">
<ol>
<li>Создание функции (обычно C++), вызываемой из другого языка программирования (экспортируемая функция). Клиентами могут выступать Visual Basic, C, C++, Java и т.п.</li>
<li>Создание функции обратного вызова (callback функции). Функция может вызываться как операционной системой, так и библиотечным кодом. Оконная процедура для платформы Windows может служить примером подобной функции. Функцию main языка C++ также можно рассматривать как специальную форму функции обратного вызова. Для платформы Android примерами будут являться callback-интерфейсы, широко используемые в пространстве имён android.</li>
<li>Использование определённой технологии, запрещающей использование исключений. Например при создании модулей COM на языке C++ у разработчика не существует альтернативы: спецификация требует, чтобы ошибка поставлялась единственным способом – через результирующее значение функции, HRESULT.</li>
</ol>
</div>
<p>Хотя перечисленные случаи, не разрешают использовать генерацию исключений для извещения об ошибках, внутри самих функций может, а, возможно, даже должен, производиться перехват исключений.</p>
<div style="margin-left: 20px">
<em></p>
<p>Изредка разработчики склонны забывать о соглашениях, принятых для функций обратного вызова. Подобные функции возвращают своё управление в библиотечный код/код операционной системы. Генерация исключения внутри такой функции может привести к неожиданным последствиям.<br />
Хотя мы и можем ожидать, что разработчик библиотеки будет защищаться от подобного поведения клиентского кода, в общем случае следует исходить из того, что мы не имеем доступа к исходному коду библиотеки/операционной системы и не можем его контролировать. В качестве примера, рассмотрим следующий псевдокод (С++ и WinAPI).</em></p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
LRESULT CALLBACK MyCallbackMethod<span style="color: #008000;">&#40;</span>API_STRUCTURE<span style="color: #000040;">*</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #0000ff;">void</span> DoSomething<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    API_STRUCTURE s <span style="color: #000080;">=</span> <span style="color: #008000;">&#123;</span> <span style="color: #0000dd;">0</span> <span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">try</span>
    <span style="color: #008000;">&#123;</span>
        API_Method<span style="color: #008000;">&#40;</span><span style="color: #000040;">&amp;</span>s, MyCallbackMethod <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
    <span style="color: #0000ff;">catch</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> MyError<span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        <span style="color: #666666;">// #1</span>
        Another_API_Method<span style="color: #008000;">&#40;</span><span style="color: #000040;">&amp;</span>s <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
LRESULT CALLBACK MyCallbackMethod<span style="color: #008000;">&#40;</span>API_STRUCTURE<span style="color: #000040;">*</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">throw</span> MyError<span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p><em>Точка #1 отчётливо демонстрирует неоднозначность возникающей ситуации в случае, когда MyError покидает пределы метода MyCallbackMethod:</p>
<div style="margin-left: 30px">
<ul>
<li>В первую очередь нужно отметить, что, генерируя исключения внутри функции MyCallbackMethod, мы не можем быть уверены, что будет достигнута точка #1.</li>
<li>Кроме этого нужно добавить, что если точка #1 всё же будет достигнута, мы не можем полагаться на то, что данные &#8211; как те, что передаются через параметры, так и внутренние данные библиотеки &#8211; остались в согласованном состоянии.</li>
</ul>
</div>
<p></em></div>
<h4>1.1.2 Выбери меня</h4>
<p>Теперь, предположив, что внешние ограничения отсутствуют, давайте рассмотрим, что может повлиять на наше решение &#8211; воспользоваться исключением или вернуть из метода значение?</p>
<p>Обсуждение полезно будет провести на примере небольшого класса. Подобное обсуждение даст нам возможность поставить “правильные” вопросы, задав которые мы в значительной мере приблизимся к решению. Начнём с рассмотрения примера совместно используемого кода (для краткости назовём такой код “библиотечным”). Это позволит нам в дальнейшем упростить обсуждение “обычного” небиблиотечного кода.</p>
<h5>1.1.2.1 Создание библиотеки</h5>
<p>Написание совместно используемого кода возлагает определённые обязательства на разработчика. Здесь мы коснёмся лишь тех, что относятся непосредственно к теме нашего обсуждения.</p>
<p>Как правило, в отсутствии внешних ограничений, разработчик волен сам выбирать способ оповещения об ошибках. Рассмотрим факторы, которые могут повлиять на этот выбор:</p>
<div style="margin-left: 20px">
<ol>
<li>Функциональная и логическая совместимость библиотеки (“похожесть”).</li>
<li>Способ распространения библиотеки.</li>
</ol>
</div>
<h6>1.1.2.1.1 Функционально совместимый код</h6>
<p>Под функциональной совместимостью здесь следует понимать совместимость создаваемого совместно используемого кода с базовым кодом, на котором построена библиотека (если таковой имеется) или кодом, который она призвана заместить.</p>
<p>Одним из примеров функционально совместимой библиотеки может послужить набор классов, обеспечивающих тонкую (а иногда и не очень тонкую) обёртку вокруг функций API. Здесь уместно вспомнить Windows Template Library (WTL).</p>
<p>Другой пример – дополнение или расширение хорошо известной библиотеки. Здесь знакомой иллюстрацией может быть библиотека boost.</p>
<p>В случаях, подобным упомянутым выше, наилучшей стратегией является соблюдение <em>“принципа наименьшего удивления”</em> [principle of least astonishment] [principle of least surprise] (<a href="http://books.google.com/books?id=H4q1t-jAcBIC&#038;lpg=PP1&#038;ots=-c7AMnBe4v&#038;dq=E.S.%20Raymond%2C%20The%20Art%20of%20Unix%20Programming&#038;pg=PA20#v=onepage&#038;q&#038;f=true">E.S. Raymond, The Art of Unix Programming, 1.6.10 Rule of Least Surprise</a>). Сейчас мы ведём речь лишь об обработке исключений, но, в общем случае, этот подход хорошо работает и при реализации библиотеки в целом.</p>
<p><em>Класс CFile</em></p>
<p>В качестве примера обратимся к классу, который работает с описателем файла &#8211; объектом ядра операционной системы Windows. И попытаемся на этом примере проиллюстрировать условия, которые могут повлиять на выбор определённой стратегии распространения ошибок.<br />
Объявив класс в некотором пространстве имён, для того чтобы минимизировать конфликты с другими классами “файл”, в дальнейшем будем опускать это пространство имён. Выдвинем основное требование к данному классу – облегчить, насколько возможно, жизнь разработчикам-пользователям данного класса.</p>
<p>Первое, что мы можем потребовать от класса &#8211; это взять на себя скучную работу по закрытию описателя объекта ядра. Всё, что нам понадобится &#8211; это пара функций: конструктор-деструктор.</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #666666;">// пространство имён опущено</span>
<span style="color: #0000ff;">class</span> CFile
<span style="color: #008000;">&#123;</span>
<span style="color: #0000ff;">public</span><span style="color: #008080;">:</span>
    <span style="color: #0000ff;">explicit</span> CFile<span style="color: #008000;">&#40;</span>HANDLE h<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    ~CFile<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    operator HANDLE<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span><span style="color: #008080;">;</span>
<span style="color: #0000ff;">private</span><span style="color: #008080;">:</span>
    CFile<span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> CFile<span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    CFile<span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">=</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> CFile<span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #0000ff;">private</span><span style="color: #008080;">:</span>
    HANDLE m_h<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span></pre></div></div>

<p>Рассмотрим каждую из функций класса с целью выяснить наиболее пригодный в данном случае способ распространения ошибок.</p>
<p><em>CFile::operator HANDLE()</em></p>
<p>Выше я немного слукавил. Сказав, что нам понадобится лишь пара функций &#8211; конструктор и деструктор, я, на самом деле, описал в классе 5 методов. Закрытые конструктор копирования [copy constructor] и оператор присваивания [assignment operator] мы ещё обсудим. Начнём же мы, пожалуй, с метода CFile::operator HANDLE().</p>
<p>Поскольку в классе не определено ни одной функции, через которую можно оперировать с описателем, нам понадобился метод CFile::operator HANDLE(). Метод позволит нам свободно передавать объект CFile в функции, ожидающие описатель. Также это позволит расширять класс постепенно, без необходимости реализовывать все аналоги функций API в момент создания класса.</p>
<div style="margin-left: 20px">
<p><em>Подобное раскрытие реализации (посредством функции CFile::operator HANDLE()), в общем случае, нежелательно, поскольку оно обеспечивает пользователю класса полный доступ к внутренней структуре класса, и не скрывает того, что данный класс является обёрткой описателя. Хотя от класса CFile именно это и требуется, такой подход накладывает и определённые обязательства. В данном случае, класс берёт на себя обязательство обеспечить семантическую эквивалентность поведения функций класса поведению “чистых” функций API, оперирующих с объектом операционной системы – “файл”. Ниже мы увидим о какой эквивалентности идёт речь.</p>
<p>С другой стороны, мы могли бы избежать объявления метода CFile::operator HANDLE(), реализовав в классе все аналоги API функций, предназначенных для работы с файлом (CreateFile etc.), а также аналоги всех функций, работающих с описателями объекта ядра “полиморфно”, без учёта их типа (таких как WaitForMultipleObjects). Но и в этом случае решение нельзя было бы назвать достаточным &#8211; следующая версия операционной системы может добавить новую функцию API, работающую с описателем ядра, и это заставит нас изменить определение класса (возможно, добавив метод CFile::operator HANDLE()).</em></p>
</div>
<p>С функцией CFile::operator HANDLE() дело обстоит проще всего. Имея приведённое выше описание класса, можно предположить, что функция не должна генерировать исключение.</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">inline</span>
CFile<span style="color: #008080;">::</span><span style="color: #007788;">operator</span> HANDLE<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">return</span> m_h<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>И это действительно так, поскольку единственное место, где m_h может быть присвоено недействительное значение – это конструктор класса. И потому выглядит разумно, что именно этот метод (конструктор) должен нести ответственность за то, чтобы генерировать исключение (если будет выбран этот путь распространения ошибок).</p>
<p>Либо, давая более развёрнутое пояснение, предположим, что в конструктор класса было передано недействительное значение описателя ядра. В этом случае конструктор может как возбудить исключительную ситуацию, так и инициализировать член класса m_h недействительным значением. Но какой бы из вариантов ни был выбран при реализации конструктора, это не должно повлиять на реализацию метода CFile::operator HANDLE() и вот почему:</p>
<div style="margin-left: 20px">
<ol>
<li>Если при конструировании объекта будет возбуждена исключительная ситуация, то объект не будет создан и функцию CFile::operator HANDLE() вызывать будет не у кого.</li>
<li>Если исключение не было инициировано в теле конструктора, но мы решим сгенерировать его внутри метода CFile::operator HANDLE(), мы, по всей видимости, усложним жизнь пользователям класса. Поскольку, вместо того, чтобы информировать их об ошибке в той точке, где она произошла (инициализация переменной недействительным значением в конструкторе класса), мы отсрочим диагностику, заставив их иметь дело с методом, который лишь читает неверное значение.</li>
</div>
<p>Таким образом метод CFile::operator HANDLE()  может быть написан так, как если бы приватный член m_h был проинициализирован должным образом.</p>
<p><em>CFile::~CFile()</em></p>
<p>Перейдём к рассмотрению деструктора. Наверное, не ошибусь, сказав, что нас приучили не инициировать исключение в деструкторе и с подозрением смотреть на классы, практикующие данный подход.</p>
<p>Деструктор класс CFile, не генерирующий искючений, мог бы выглядеть следующим образом:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #339900;">#ifdef _DEBUG</span>
<span style="color: #0000ff;">inline</span>
BOOL Verify<span style="color: #008000;">&#40;</span>BOOL b<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> <span style="color: #000040;">!</span>b <span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        DWORD nErr <span style="color: #000080;">=</span> <span style="color: #008080;">::</span><span style="color: #007788;">GetLastError</span><span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span> nErr<span style="color: #008080;">;</span>
        <span style="color: #008080;">::</span><span style="color: #007788;">DebugBreak</span><span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
    <span style="color: #0000ff;">return</span> b<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span>
<span style="color: #339900;">#else</span>
<span style="color: #0000ff;">inline</span>
BOOL Verify<span style="color: #008000;">&#40;</span>BOOL b<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#123;</span> <span style="color: #0000ff;">return</span> b<span style="color: #008080;">;</span> <span style="color: #008000;">&#125;</span>
<span style="color: #339900;">#endif // _DEBUG</span>
&nbsp;
<span style="color: #0000ff;">inline</span>
CFile<span style="color: #008080;">::</span>~CFile<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    Verify<span style="color: #008000;">&#40;</span> <span style="color: #008080;">::</span><span style="color: #007788;">CloseHandle</span><span style="color: #008000;">&#40;</span> m_h <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Тем не менее, мы отвлечёмся на время от общей рекомендации не генерировать исключение в теле деструктора и внимательно рассмотрим причины, по которым не ст<i>о</i>ит этого делать.</p>
<p>Деструктор класса CFile мог бы возбудить исключительную ситуацию лишь в случае, если бы метод ::CloseHandle вернул ошибку. Вызов же функции ::CloseHandle может вернуть ошибку по одной из следующих причин.</p>
<p>Причина первая &#8211; в конструктор CFile был передан недействительный описатель. В этом случае нам необходимо повторить ту же цепь рассуждений, что мы провели для метода CFile::operator HANDLE() и прийти к выводу, что если исключение и должно возникнуть, то в конструкторе CFile:CFile, но никак не в деструкторе CFile::~CFile.</p>
<p>Причина вторая. Объект CFile был создан с использованием действительного описателя, но ::CloseHandle, тем не менее, вернул ошибку (например, по причине нарушения прав доступа). В этом случае следует руководствоваться следующим практическим соображением &#8211; выполнение кода деструктора означает, что переменная покидает область видимости, и в большинстве случаев не имеет значения, успешно или неуспешно завершился вызов ::CloseHandle. Безусловно, нельзя полагаться на то, что всех разработчиков устроит подобное поведение (о тех, кому важны результаты закрытия описателя мы поговорим ниже). Нельзя, также, рассчитывать на то, что код, который будет исполняться в случае ошибки в отладочной версии приложения, исправит ситуацию.</p>
<p>Но давайте на секунду представим деструктор объекта CFile, который генерирует исключение. Код должен был бы выглядеть примерно следующим образом.</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">inline</span>
CFile<span style="color: #008080;">::</span>~CFile<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span>CFileError<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> Verify<span style="color: #008000;">&#40;</span> <span style="color: #008080;">::</span><span style="color: #007788;">CloseHandle</span><span style="color: #008000;">&#40;</span> m_h <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span>
        <span style="color: #0000ff;">return</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> std<span style="color: #008080;">::</span><span style="color: #007788;">uncaught_exception</span><span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span>
        <span style="color: #0000ff;">return</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">const</span> DWORD nErr <span style="color: #000080;">=</span> <span style="color: #008080;">::</span><span style="color: #007788;">GetLastError</span><span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">throw</span> CFileError<span style="color: #008000;">&#40;</span> nErr <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Прежде всего, следует отметить, что и в этом случае нельзя гарантировать того, что пользователь сможет получить оповещение о неуспешной попытке закрыть объект ядра. </p>
<p>Проверка результата вызова std::uncaught_exception() необходима: она защищает код от попытки вызвать повторную генерацию исключения в процессе раскрутки стека, когда исключение уже существует. Если же позволить повторную генерацию, это будет считаться ошибкой механизма обработки исключений (<a href="http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=38110">ISO/IEC 14882:2003</a> раздел 15.5.1) и, по умолчанию, будет вызвана функция std::terminate(), которая, в свою очередь, по умолчанию, вызовет std::abort(). </p>
<p>Нельзя назвать это и самым изящным способом оповещения об ошибке. Корень неоднозначности кроется в том, что в общем случае невозможно определить, можно ли проигнорировать одно исключение ради обработки другого. Именно поэтому стандарт настоятельно не рекомендует генерировать исключения в деструкторе объекта (<a href="http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=38110">ISO/IEC 14882:2003</a> раздел 15.2.3).</p>
<p>Таким образом, можно утверждать, что отсутствует основная предпосылка для использования исключения в деструкторе &#8211; повышение надёжности кода путём своевременного оповещения об ошибке. Или, другими словами, поскольку существует ситуация, при возникновении которой ошибка не может быть донесена до пользователя, постольку необходим другой, надёжный способ донести ошибку.</p>
<p>От вопроса о необходимости генерировать исключение перейдём к минусам, получаемым от использования деструктора, возбуждающего исключительную ситуацию.</p>
<div style="margin-left: 20px">
<ol>
<li>В первую очередь замечу, что не приходится говорить о том, что подобная модель распространения ошибок облегчает жизнь разработчикам. Любой, кто пожелает использовать класс CFile для автоматического закрытия описателя, будет вынужден обрабатывать исключения, которые могут возникнуть в деструкторе объекта, независимо от того, важно ему это или нет.</li>
<li>Далее, необходимо упомянуть о потенциальной утечке памяти в operator delete [ ]. Если при разрушении объекта, размещённого в динамически выделенном массиве, возникнет исключение, то, скорее всего, это выльется в невозможность вызова деструкторов для объектов с меньшими значениями индексов, а память, выделенная под массив, не будет возвращена системе.</li>
<li>Кроме этого, использование исключений в деструкторе заставит отказаться от использования определённых техник программирования. Например, техника безопасного с точки зрения исключений конструктора, описанная H.Sutter’ом (H.Sutter “Exceptional C++ and More Exceptional C++”), исходит из того, что деструктор объекта не распространяет исключений.</li>
<li>Также объект класса CFile не сможет быть использован в качестве глобального статического объекта [non-local object with static storage duration] (<a href="http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=38110">ISO/IEC 14882:2003</a> всё тот же раздел 15.5.1).</li>
<li>Ко всему прочему, могут оказаться важными особенности используемого компилятора. Например, при использовании среды разработки Visual Studio 6.0 последний вариант деструктора всегда будет генерировать исключение (в случае, если ::CloseHandle() вернёт NULL) по той причине, что функция std::uncaught_exception() в данной реализации всегда возвращает false.</li>
</ol>
</div>
<p>Подводя итог, можно сказать, что вариант деструктора, не использующего исключения – это наилучший вариант деструктора. Представляется, что приведённые выше доводы остаются верными при обсуждении любого класса. Потому, в дальнейшем, политика распространения исключений деструкторами не обсуждается и молчаливо предполагается, что все описанные в статье классы используют пустую спецификацию исключений для деструкторов.</p>
<p><em>CFile::CFile(HANDLE h)</em></p>
<p>По всей видимости, политику распространения исключений будет определять реализация конструктора, поскольку именно она влияет на поведение функций, обсуждавшихся выше. В предложенном описании класса это единственная функция, модифицирующая приватный член класса.</p>
<p>Рассматривая конструктор, невозможно сослаться на то, что обработка ошибки должна вестись где-то в другом месте (как в случае с CFile::operator HANDLE()). Также нельзя привести пример неоднозначного, или вводящего в заблуждение кода (как в случае с деструктором).</p>
<p>Таким образом мы стоим перед выбором:</p>
<div style="margin-left: 20px">
<ol>
<li>Объявив конструктор с явной спецификацией throw(CFileError), мы укажем на то, что входной параметр проверяется, и, в случае его недействительности, будет возбужена исключительная ситуация.</li>
<li>Объявив же конструктор с пустой спецификацией throw(), мы возложим всю ответственность за передачу неверного параметра на пользователя.</li>
</ol>
</div>
<p>Оба варианта имеют право на существование. Вариант (1) – по причине того, что генерация исключения – чуть ли не единственный “легальный” способ уведомить об ошибке из конструктора. Вариант (2) возможен из-за неформального правила, позволяющего библиотеке не обрабатывать ошибки, которые могут быть обработаны в коде пользователя. При выборе той или иной стратегии приходится пользоваться общими соображениями.</p>
<p>Решая вопрос о способе распространения ошибки из конструктора, необходимо помнить о требованиях, выдвинутых к классу. Предназначение класса – облегчить жизнь тем разработчикам, которым приходится оперировать с объектом операционной системы – “файл”. С этой точки зрения и рассмотрим плюсы обеих реализаций. Плюс одного подхода является ничем иным, как минусом другого. Поэтому наборы минусов из перечисления исключим.</p>
<p>Плюсом первого подхода видится единственный пункт:</p>
<div style="margin-left: 20px">
<ul>
<li>Пользователь класса CFile избавлен от необходимости явно проверять допустимость входного параметра.</li>
</ul>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;"><span style="color: #0000ff;">try</span>
<span style="color: #008000;">&#123;</span>
    CFile file<span style="color: #008000;">&#40;</span> <span style="color: #008080;">::</span><span style="color: #007788;">CreateFile</span><span style="color: #008000;">&#40;</span> <span style="color: #ff0000; font-style: italic;">/*...*/</span> <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span>
<span style="color: #0000ff;">catch</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> CFileError<span style="color: #000040;">&amp;</span> e<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

</div>
<p>Плюсов у второго подхода, как видится, больше:</p>
<div style="margin-left: 20px">
<ul>
<li>Отсутствует необходимость проверять входной параметр в библиотечном коде. В общем случае неясно, что считать неверным входным парамером – только ли значение INVALID_HANDLE_VALUE, или нулевой указатель также следует отнести к этому набору? А как насчёт неинициализированной локальной переменной &#8211; следует ли её рассматривать как некорректный входной параметр? Последний вариант, видимо, не может быть реализован без включения платформенно-зависимого кода и/или добавления дополнительных расходов времени выполнения. В обоих случаях, решение может рассматриваться как неудовлетворительное.</li>
<li>Нет необходимости вводить в код пользователя обработку исключений. Это даёт простоту использования класса в “смешанном”, или унаследованном коде. Под “смешанным” кодом здесь подразумевается код, в котором отсутствует регулярный подход к обработке ошибок. Исключения в подобном коде генерируются, в основном, случайным образом. Для сигнализации об ошибке может использоваться как возвращаемое функцией значение (в обычном значении), так и генерация исключения. Такая ситуация возникает, обычно, в результате долгой поддержки унаследованного кода, но нередко может определяться общей (неудачной) архитектурой приложения. Если обработка ошибок не производилась систематически, использование класса, инициирующего исключения может существенным образом сказаться на поведении приложения.</li>
<li>Облегчение рефакторинга. По сути, этот пункт, является ничем иным, как следствием предыдущего. Отсутствие необходимости вводить поддержку обработки исключений в код, который изначально не был рассчитан на это, минимизирует количество ошибок, которые могут возникнуть при рефакторинге.</li>
</ul>
</div>
<p>Следующий пример поможет проиллюстрировать проблему, описанную в двух последних пунктах.</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;"><span style="color: #0000ff;">bool</span> CMyClass<span style="color: #008080;">::</span><span style="color: #007788;">Foo</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    HANDLE h <span style="color: #000080;">=</span> <span style="color: #008080;">::</span><span style="color: #007788;">CreateFile</span><span style="color: #008000;">&#40;</span> <span style="color: #ff0000; font-style: italic;">/*...*/</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>        <span style="color: #666666;">// #1 CFile file( ::CreateFile )</span>
&nbsp;
    ToDoSomethingUnrelatedWithCreatedFile<span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>  <span style="color: #666666;">// #2, эта строка</span>
                                               <span style="color: #666666;">// выполняется всегда</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> INVALID_HANDLE_VALUE <span style="color: #000080;">==</span> h <span style="color: #008000;">&#41;</span>
        <span style="color: #0000ff;">return</span> <span style="color: #0000ff;">false</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">bool</span> bRet <span style="color: #000080;">=</span> ToDoSomethingWithCreatedFile<span style="color: #008000;">&#40;</span> h <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008080;">::</span><span style="color: #007788;">CloseHandle</span><span style="color: #008000;">&#40;</span> h <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">return</span> bRet<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Предположим, что в приведённом выше коде мы заменили вызов функции ::CreateFile() (строка #1) созданием объекта типа CFile, который генерирует исключение в конструкторе.</p>
<p>В этому случае, функция CMyClass::ToDoSomethingUnrelatedWithCreatedFile() (строка #2) будет вызвана лишь в случае успешного открытия файла, что, в свою очередь, может неявным образом изменить результат действия функции CMyClass::Foo(). Данный код, безусловно, является результатом небрежного программирования, но, к сожалению, в “смешанном” коде могут встречаться подобные примеры.</p>
<p>Создатель класса, рассматривая общий случай использования, обычно может оперировать контекстом, в котором, предположительно, класс будет использоваться наиболее часто. И потому видится, что, хотя проектировщик библиотеки и не может знать все места, где будет использоваться его код, он может сформулировать разумные предположения, в том числе, касающиеся обработки исключений.</p>
<p>Подведём итог. Плюсы второго подхода можно кратко сформулировать следующим образом – класс не навязывает обработку исключений. Подобное поведение хорошо согласуется с тем, что класс представляется ничем иным, как объектной заменой обычному описателю. Объект CFile может использоваться в любом месте, где может использоваться HANDLE, попутно предоставляя удобный сервис, которым славится объектный подход. И потому окончательный выбор будет сделан в пользу версии, не генерирующей исключений &#8211; класс CFile будет содержать конструктор CFile::CFile(HANDLE h) с пустой спецификацией throw().</p>
<p><em>CFile::CFile(const CFile&#038;) и CFile&#038; CFile::operator =(const &#038;)</em></p>
<p>Нам осталось рассмотреть два последних метода – конструктор копирования и оператор присваивания. Функции настолько сильно семантически связаны между собой, что о них необходимо говорить совместно, что мы и будем делать.</p>
<p>Оба метода объявлены как закрытые и с пустой спецификацией throw(). Таким образом, мы подразумеваем, что:</p>
<div style="margin-left: 20px">
<ol>
<li>Методы могут быть вызваны лишь функцией-членом данного класса, и</li>
<li>Методы не генерируют исключений.</li>
</ol>
</div>
<p>Подобное объявление сделано по одной причине &#8211; у данных функций отсутствует реализация, поэтому они не могут быть вызваны, и, следовательно, они не могут инициировать исключение. А реализация, в свою очередь, у них отсутствует, потому что мы хотим избавиться от вопроса о том, что должно происходить при копировании объекта типа CFile. Данный вопрос не представляется тривиальным &#8211; как разработчики класса мы могли бы использовать любую из перечисленных ниже возможностей:</p>
<div style="margin-left: 20px">
<ol>
<li>Не объявлять функции. Код для обеих функций, при необходимости, будет создан компилятором.</li>
<li>Объявить функции закрытыми и оставить их без реализации.</li>
<li>Реализовать семантику владения описателем каждым из объектов класса CFile (a-la std::auto_ptr).</li>
<li>Реализовать семантику разделения описателя между объектами класса CFile (a-la boost::shared_ptr).</li>
<li>Использовать вызов ::DuplicateHandle() для создания дубликата описателя.</li>
<li>Вынести всю семантику в какое-то другое место (параметризовать решение).</li>
</ol>
</div>
<p>Выбор одной из этих линий поведения – отдельный вопрос. Для наших целей достаточно остановиться на втором пункте, т.к. реализация остальных только усложнит пример, не добавив ничего нового к теме обсуждения.</p>
<div style="margin-left: 20px">
<em></p>
<p>Этот интересный вопрос о том, какую из упомянутых возможностей следует выбрать, заслуживает отдельной темы. Здесь можно лишь заметить, что отсутствие реализации конструктора копирования и оператора присваивания хорошо подходит для случаев, когда объект класса CFile является локальной переменной или членом класса. (Необходимо однако помнить, что для последнего случая в описании класса CFile понадобится пара функций имеющих семантику Attach()/Detach() для того, чтобы разрешить копирование охватывающего класса.). Поместить объект класса CFile в STL’ный контейнер при отсутствии конструктора копирования, к сожалению, не удастся.</p>
<p>Кстати, конструкцию “приватность + отсутствие реализации” можно считать идиомой языка. Человеку, читающему код, становятся понятны намерения разработчика, описывающего конструктор копирования и оператор присваивания таким образом – класс не способен предоставить реализацию по умолчанию, приемлемую для всех пользователей данного класса. Подобную неоднозначность поведения иногда лучше описывать посредством специализированных функций – членов класса, имеющих хорошо подобранные имена (например, вышеприведённая пара &#8211; Attach()/Detach()). Но может статься и так, что семантику копирования и присваивания удобнее будет описывать, агрегируя и параметризуя примитивные классы.<br />
</em></div>
<p>Необходимо заметить, что в реальном проекте при выборе стратегии распространения ошибок двумя этими функциями (при условии, что реализация этих функций оказалось востребованной), потребуется повторить те же доводы, которые приводились при обсуждении конструктора. Таким образом, можно заключить, что в общем случае, для конструктора копирования и оператора присваивания следует выбирать такую же стратегию распространения исключений, как и для семейства обычных конструкторов, что, по крайней мере, будет наиболее близко соответствовать ожиданиям конечного пользователя класса.</p>
<p>Если подвести итог, то определение класса теперь выглядит следующим образом (все функции объявлены с пустой спецификацией throw()).</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">class</span> CFile
<span style="color: #008000;">&#123;</span>
<span style="color: #0000ff;">public</span><span style="color: #008080;">:</span>
    <span style="color: #0000ff;">explicit</span> CFile<span style="color: #008000;">&#40;</span>HANDLE h<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    ~CFile<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    operator HANDLE<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #0000ff;">private</span><span style="color: #008080;">:</span>
    CFile<span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> CFile<span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    CFile<span style="color: #000040;">&amp;</span> operator <span style="color: #000080;">=</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> CFile<span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #0000ff;">private</span><span style="color: #008080;">:</span>
    HANDLE m_h<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span></pre></div></div>

<div style="margin-left: 20px">
<em></p>
<p>Можно заметить, что у деструктора отсутствует спецификатор виртуальности. Здесь подразумевается, что данный примитивный класс будет листовым в дереве наследования (что, в принципе, не сможет удержать разработчика от использования данного класса в качестве базового).</p>
<p></em></div>
<p><em>Семейство классов</em></p>
<p>Конкретную задачу закрытия описателя можно решить гораздо более простым и элегантным способом – определив шаблонный класс, единственная задача которого – вызвать определённую функцию в своём деструкторе. Создание выделенного класса для объекта ядра может быть оправдано лишь в том случае, если класс предоставляет дополнительный сервис по сравнению с сервисом, что предоставляет пара “описатель и шаблон, закрывающий описатель”.</p>
<p>Потому, если речь идёт о промышленном применении, то данная реализация выглядит несколько наивно. Очевидно, что она определяет политику распространения ошибок ключевыми функциями класса внутри реализации данного класса. Как только возникнет необходимость определить класс, обладающей подобной семантикой для другого объекта ядра, нам понадобится повторить всю цепь рассуждений. Или воспользоваться распространённым инструментом разработчиков – механизмом “copy-paste”. По-видимому, наиболее перспективным решением для долгосрочных проектов было бы решение, параметризующее политику распространения исключений (в статье мы данное решение рассматривать не будем).</p>
<p><em>CFile::Create(/*…*/)</em></p>
<p>Заговорив о дополнительных сервисах, давайте оценим, как добавление новой функциональности повлияет на существующую стратегию распространения ошибок. Также интересно понаблюдать за тем, какие изменения придётся вносить в уже описанные функции.</p>
<p>Для начала, для уменьшения количества кода, необходимого для открытия файла, добавим в класс функцию CFile::Create().</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">class</span> CFile
<span style="color: #008000;">&#123;</span>
<span style="color: #0000ff;">public</span><span style="color: #008080;">:</span>
    <span style="color: #666666;">// новые методы</span>
    CFile<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    HANDLE Create<span style="color: #008000;">&#40;</span>LPCTSTR lpFileName,
        DWORD dwDesiredAccess,
        DWORD dwShareMode,
        LPSECURITY_ATTRIBUTES lpSecurityAttributes <span style="color: #000080;">=</span> <span style="color: #0000dd;">0</span>,
        DWORD dwCreationDisposition <span style="color: #000080;">=</span> CREATE_ALWAYS,
        DWORD dwFlagsAndAttributes <span style="color: #000080;">=</span> FILE_ATTRIBUTE_NORMAL,
        HANDLE hTemplateFile <span style="color: #000080;">=</span> <span style="color: #0000dd;">0</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #666666;">// объявления “старых” методов остались без изменений</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #0000ff;">inline</span>
CFile<span style="color: #008080;">::</span><span style="color: #007788;">CFile</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #008080;">:</span> m_h<span style="color: #008000;">&#40;</span> INVALID_HANDLE_VALUE <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#123;</span><span style="color: #008000;">&#125;</span>
&nbsp;
<span style="color: #0000ff;">inline</span>
HANDLE CFile<span style="color: #008080;">::</span><span style="color: #007788;">Create</span><span style="color: #008000;">&#40;</span>LPCTSTR lpFileName,
                     DWORD dwDesiredAccess,
                     DWORD dwShareMode,
                     LPSECURITY_ATTRIBUTES lpSecurityAttributes<span style="color: #ff0000; font-style: italic;">/* = 0*/</span>,
                     DWORD dwCreationDisposition<span style="color: #ff0000; font-style: italic;">/* = CREATE_ALWAYS*/</span>,
                     DWORD dwFlagsAndAttributes<span style="color: #ff0000; font-style: italic;">/* = FILE_ATTRIBUTE_NORMAL*/</span>,
                     HANDLE hTemplateFile<span style="color: #ff0000; font-style: italic;">/* = 0*/</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000dd;">assert</span><span style="color: #008000;">&#40;</span> INVALID_HANDLE_VALUE <span style="color: #000080;">==</span> m_h <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">return</span> m_h <span style="color: #000080;">=</span> <span style="color: #008080;">::</span><span style="color: #007788;">CreateFile</span><span style="color: #008000;">&#40;</span> lpFileName, dwDesiredAccess, dwShareMode,
        lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes,
        hTemplateFile <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Заметим, что добавление функции CFile::Create() заставляет нас добавить конструктор CFile::CFile() (к которому мы ещё вернёмся).</p>
<p>Из приведённого выше описания видно, что CFile::Create() объявлена с пустой спецификацией throw(). Обсудим это решение. Фактически, выбор в пользу распространения ошибок традиционным способом для всех функций класса был уже сделан при обсуждении конструктора. Описав функцию CFile::Create() как генерирующую исключение, мы оставили бы пользователя с несогласованным описанием класса:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
HANDLE h <span style="color: #000080;">=</span> <span style="color: #008080;">::</span><span style="color: #007788;">CreateFile</span><span style="color: #008000;">&#40;</span> “file1.<span style="color: #007788;">dat</span>”, <span style="color: #ff0000; font-style: italic;">/*...*/</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
CFile file1<span style="color: #008000;">&#40;</span> h <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>                                 <span style="color: #666666;">// CFile::CFile(HANDLE h)</span>
                                                  <span style="color: #666666;">// не генерирует исключений</span>
CFile file2<span style="color: #008080;">;</span>
File2.<span style="color: #007788;">Create</span><span style="color: #008000;">&#40;</span> “file2.<span style="color: #007788;">dat</span>”, <span style="color: #ff0000; font-style: italic;">/*...*/</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>             <span style="color: #666666;">// предположим, что</span>
                                                  <span style="color: #666666;">// CFile::Create()</span>
                                                  <span style="color: #666666;">// может инициировать</span>
                                                  <span style="color: #666666;">// исключительную ситуацию</span></pre></div></div>

<p>В данном случае семантически эквивалентный код даёт разный результат и демонстрирует указанную неоднозначность. Предположив, что приведённый выше код присутствует в одной функции, будет трудно обосновать перед конечным пользователем класса разницу в поведении.</p>
<p><em>CFile::CFile()</em></p>
<p>Как было отмечено выше, добавление функции CFile::Create() приводит к необходимости описать конструктор по умолчанию [default constructor]. Это, в свою очередь, приводит к тому, что приватный член класса получает инициализирующее значение, которое остальные функции могли и не ожидать. В общем случае, добавление (или изменение) инициализирующего значения приватного члена приводит к пересмотру и, возможно, модификации всех функций, использующих этот член класса. В данном случае, член m_h инициализируется значением INVALID_HANDLE_VALUE, что необходимо приводит к изменению реализации деструктора. После модификации он должен выглядеть примерно так:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
CFile<span style="color: #008080;">::</span>~CFile<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> INVALID_HANDLE_VALUE <span style="color: #000080;">==</span> m_h <span style="color: #008000;">&#41;</span>
        <span style="color: #0000ff;">return</span><span style="color: #008080;">;</span>
    Verify<span style="color: #008000;">&#40;</span> <span style="color: #008080;">::</span><span style="color: #007788;">CloseHandle</span><span style="color: #008000;">&#40;</span> m_h <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>До введения конструктора умолчания предполагалось, что приватный член m_h не может получить значения INVALID_HANDLE_VALUE иначе, как по невнимательности пользователя класса. Теперь же это значение становится разрешённым в контексте объявления конструктора по умолчанию. Более тщательный, можно даже сказать “маниакальный”, подход подразумевает добавление ещё одного члена класса, который указывал бы на то, был ли член m_h инициализирован в результате вызова конструктора по умолчанию, либо был изменён внутри какой-либо другой функции. Это дало бы нам возможность в деструкторе провести различие между разрушением переменной, созданной конструктором по умолчанию, и неверно инициализированной переменной (имеющей то же значение INVALID_HANDLE_VALUE).</p>
<p>Но, кажется, что подобная тщательность выглядит излишней. Поскольку у деструктора нет способа сообщить о неудачном закрытии описателя (кроме как в отладочной версии приложения), постольку пользователи, которым важно знать о такой ситуации, будут вынуждены явно закрывать описатель, не полагаясь на деструктор.</p>
<p>Можно заметить, что после добавления конструктора по умолчанию, нет необходимости модифицировать функцию CFile::operator HANDLE(). Причины здесь те же, что приводились и раньше при обсуждении этой функции.</p>
<p><em>CFile::Close()</em></p>
<p>Вернёмся к нуждам тех, кому необходимо знать о неуспешном закрытии описателя объекта ядра “файл”. Для них необходимо ввести явную операцию, закрывающую описатель. И класс теперь будет выглядеть так (код деструктора продублирован для сравнения).</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">class</span> CFile
<span style="color: #008000;">&#123;</span>
<span style="color: #0000ff;">public</span><span style="color: #008080;">:</span>
    <span style="color: #666666;">// новый метод</span>
    BOOL Close<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #666666;">// объявления “старых” методов остались без изменений</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
&nbsp;
CFile<span style="color: #008080;">::</span>~CFile<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> INVALID_HANDLE_VALUE <span style="color: #000080;">==</span> m_h <span style="color: #008000;">&#41;</span>
        <span style="color: #0000ff;">return</span><span style="color: #008080;">;</span>
    Verify<span style="color: #008000;">&#40;</span> <span style="color: #008080;">::</span><span style="color: #007788;">CloseHandle</span><span style="color: #008000;">&#40;</span> m_h <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
BOOL CFile<span style="color: #008080;">::</span><span style="color: #007788;">Close</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">throw</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #666666;">// #1</span>
    <span style="color: #666666;">// if( INVALID_HANDLE_VALUE == m_h )</span>
    <span style="color: #666666;">//     return true;</span>
    <span style="color: #0000dd;">assert</span><span style="color: #008000;">&#40;</span> INVALID_HANDLE_VALUE <span style="color: #000040;">!</span><span style="color: #000080;">=</span> m_h <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    BOOL bRes <span style="color: #000080;">=</span> <span style="color: #008080;">::</span><span style="color: #007788;">CloseHandle</span><span style="color: #008000;">&#40;</span> m_h <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    Verify<span style="color: #008000;">&#40;</span> bRes <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> bRes <span style="color: #008000;">&#41;</span>
        m_h <span style="color: #000080;">=</span> INVALID_HANDLE_VALUE<span style="color: #008080;">;</span>
    <span style="color: #0000ff;">return</span> bRes<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Обратите внимание, что следует противостоять искушению объединить “общий” код в деструкторе и в функции CFile::Close. Задача деструктора – попытаться закрыть описатель и позволить дальнейшее исполнение. Задача явного вызова CFile::Close – закрыть описатель и сигнализировать об ошибке, если закрытие прошло неуспешно. Отсутствие проверки на допустимость описателя при входе в функцию CFile::Close() – отнюдь не оплошность. Разработчик, который не поленился и пожелал явно закрыть файл, либо занимался оптимизацией (освобождение ресурса до выхода из области видимости), либо для него важен результат закрытия. В этом смысле не должно быть никакой разницы между вызовом CFile::Close() и непосредственным вызовом ::CloseHandle().</p>
<p>Если бы мы проверяли значение описателя при входе в функцию CFile::Close() (раскомментировав две сточки, ниже #1), пользователь мог бы наблюдать разницу в побочных эффектах. А именно, вызов CFile::Close(), для неинициализированного объекта, не модифицировал бы значение, которое возвращает ::GetLastError(), а вызов ::CloseHandle() с тем же неинициализированным объектом в качестве параметра заносил бы в thread local storage (TLS) значение 6 (The handle is invalid).</p>
<div style="margin-left: 20px">
<p><em>Здесь также можно отметить схожесть объявлений неинициализированной переменной типа HANDLE и переменной типа CFile. Если обе переменные в дальнейшем нигде не используется, то единственный побочный эффект от их объявления – используемая память (возможно, на стеке). Для обеих переменных функция ::CloseHandle() вызвана не будет. Попытка же явно вызвать CFile::Close(), так же, как и попытка вызвать ::CloseHandle() для неинициализированной переменной, приводит к тому, что в TLS заносится информация об ошибке.</em>
</div>
<p><em>Общие замечания</em></p>
<p>На этом можно и остановиться. Описание класса, как примера, демонстрирующего конкретную стратегию распространения ошибок, выглядит логически законченным. Дальнейшее расширение функциональности класса потребует от разработчика только аккуратности, для обеспечения совместимого, с уже описанными функциями, поведения.</p>
<p>Следует отметить, что вопросы, связанные со способом распространения ошибок при написании класса CFile, возникли по той причине, что класс не скрывает своей реализации и пытается поддержать согласованное поведение с определённым семейством функций API. Эти дополнительные требования, накладываемые на реализацию класса, привносят описанную сложность. Далее мы увидим, что таких вопросов не возникает при создании класса, который не озабочен поддержкой функциональной совместимости.</p>
<p>Общая “проблема” подобных библиотек-обёрток состоит в том, что в подавляющем количестве случаев, они будут служить чисто утилитарной цели – уменьшение объёма кода в одной конкретной функции (классе, компоненте). И у библиотеки будет гораздо больше шансов быть повторно используемой, если пользователю не придётся изучать её структуру и сопоставлять поведение библиотеки с поведением “чистых” функций API. Генерация исключения и, соответственно, его перехват, могут рассматриваться как попытка навязать дополнительную и, возможно, ненужную пользователю работу.</p>
<div style="margin-left: 20px">
<em></p>
<p>В заключение можно заметить, что приведённый пример следует принципу наименьшего удивления:</p>
<div style="margin-left: 30px">
<ul>
<li>Функции не замещают результаты вызовов (относительно функций API). Например, могло бы возникнуть желание возвращать из функции CFile::Create() значение BOOL, вместо HANDLE.</li>
<li>Функции не изменяют порядок параметров. В той же функции CFile::Create() порядок аргументов не отличается от порядка аргументов функции ::CreateFile(). Хотя, казалось бы, логично поменять местами параметры lpSecurityAttributes и dwCreationDisposition, так как значение dwCreationDisposition, возможно, будет меняться чаще, чем lpSecurityAttributes (который, обычно, будет равен нулю).</li>
<li>Вызов функций не приводит к появлению новых побочных эффектов, по сравнению с вызовом функций API, и не приводит к потере побочных эффектов, описанных в документации.</li>
</ul></div>
<p>Смысл подобной пунктуальности, опять же, состоит в том, чтобы облегчить использование данного класса. Разработчик, который будет пользоваться этим классом, как представляется, чаще будет заглядывать в справочное руководство по конкретной, интересующей его, функции API, нежели в файл, содержащий описание класса. И он вправе ожидать похожего, если не идентичного, поведения от функций-обёрток.<br />
</em></div>
<p>Продолжение следует&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/exception-handling/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Управление изменениями и кессонная болезнь проектов</title>
		<link>http://experience.openquality.ru/software-configuration-management/</link>
		<comments>http://experience.openquality.ru/software-configuration-management/#comments</comments>
		<pubDate>Sun, 13 Feb 2011 14:29:34 +0000</pubDate>
		<dc:creator>Юрий Удовиченко</dc:creator>
				<category><![CDATA[Методики]]></category>
		<category><![CDATA[SCM]]></category>
		<category><![CDATA[конфигурации]]></category>
		<category><![CDATA[техпроцесс]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=390</guid>
		<description><![CDATA[Юрий Удовиченко делится практическим опытом внедрения систем управления конфигурациями ПО. Какие виды деятельности относятся к &#171;управлению изменениями&#187;, как выбирать и внедрять соответствующие инструменты, каких ошибок следует избегать &#8211; темы сегодняшней публикации. - Видишь суслика? - Нет&#8230; - И я не вижу. А он есть! (с) ДМБ Вступление В рамках любого проекта особняком стоит управление конфигурацией продукта (Software Configuration Management, SCM). С одной стороны, такое управление есть, с другой стороны, немалая &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://scm-notes.blogspot.com/">Юрий Удовиченко</a> делится практическим опытом внедрения систем управления конфигурациями ПО. Какие виды деятельности относятся к &laquo;управлению изменениями&raquo;, как выбирать и внедрять соответствующие инструменты, каких ошибок следует избегать &#8211; темы сегодняшней публикации.</p>
<div style="text-align: left; margin-left: 250px;">
<p>- Видишь суслика?<br />
- Нет&#8230;<br />
- И я не вижу. А он есть! (с) <a href="http://ru.wikiquote.org/wiki/%D0%94%D0%9C%D0%91_(%D1%84%D0%B8%D0%BB%D1%8C%D0%BC)">ДМБ</a>
</div>
<h2>Вступление</h2>
<p>В рамках любого проекта особняком стоит управление конфигурацией продукта (<strong>Software Configuration Management</strong>, <a href="http://en.wikipedia.org/wiki/Software_configuration_management">SCM</a>). С одной стороны, такое управление есть, с другой стороны, немалая часть разработчиков не выделяет его как отдельную &laquo;дисциплину&raquo;. Иными словами, управление незримо присутствует и в то же время не существует. Между тем, практики SCM используются повсеместно. Иногда сообща, иногда разрозненно, но &#8211; используются. Присутствует ли SCM в вашем проекте? Попробуем предотвратить метания на тему &laquo;да или нет&raquo; и перечислим основные направления деятельности под общим названием «<strong>Управление конфигурацией продукта</strong>». К этому списку мы будем возвращаться на протяжении всей статьи.</p>
<div style="margin-left: 20px">
<ol>
<li>Управление запросами на изменение (bug tracking)</li>
<li>Управление исходным кодом (другое название: &laquo;контроль версий&raquo;)</li>
<li>Работа по сборке продукта (и это не только ваш любимый компилятор)</li>
<li>Управление рабочей средой: настройка окружения, его переменных и установленных библиотек &#8211; мало кого обошла стороной эта деятельность.</li>
<li>Работа по выпуску продукта: строгий техпроцесс и учёт выпускаемых &laquo;наружу&raquo; артефактов. Это важный отрезок, на котором не имеет значения, кому выдается продукт &#8211; своим тестировщикам или отделу SQA заказчика.</li>
<li>Развёртывание продуктов: на стороне клиента может быть множество подводных камней, их надо уметь обойти.</li>
</ol>
</div>
<p>Знакомые слова, не правда ли? Пусть вас не вводит в заблуждение слово &laquo;продукт&raquo;. Продукт &#8211; это не только дистрибутив, за который компания-разработчик получит деньги. Это любой артефакт, создаваемый командой на всём протяжении жизненного цикла продукта. Файл с исходниками, документ со спецификацией требований, тесты, бинарники для установки у клиента &#8211; всё это продукты. А под <strong>конфигурацией</strong> понимается набор версий, то есть, “срез” состояния всего продукта.</p>
<p>В общем и целом, главное назначение SCM &#8211; это <strong>управление изменениями</strong>, поступающими в проект, забота о качестве приложений. При чём здесь качество? Если перечисленные функции свести воедино, то образуется &laquo;подложка&raquo;, фундамент, лежащий под всеми видами деятельности проекта. Соответственно, если он будет непрочным, здание проекта рухнет. Хорошо, если фундамент просядет где-нибудь в начале строительства, когда мы не приступали к возведению стен. А если мы уже людей туда заселяем? Вот и получается, что обеспечить качество продукта без продуманной организации техпроцесса не получится.</p>
<h2>Выбор инструмента</h2>
<p>Как и в любом процессе, здесь важны две равновеликие составляющие &#8211; <strong>методики работы и инструменты для поддержки этих методик</strong>. Одно без другого не имеет смысла. </p>
<p>Скажем, многие разработчики стоят перед выбором: &laquo;Какую систему контроля версий использовать &#8211; <a href="http://git-scm.com/">git</a> или <a href="http://www.mercurial-scm.org/">Mercurial</a>?&raquo; При детальном рассмотрении выясняется, что речь идёт о команде, в которой распределённая модель работы просто не нужна. К примеру, условия работы с кодом диктуют постоянную синхронизацию результатов работы между всей командой. Или же команда продолжает покупать лицензии на <a href="http://www.perforce.com/">Perforce</a>, хотя его использование выродилось в линейное складирование версий, без ветвления. </p>
<p>Порой разработчики  начинают выбирать систему отслеживания ошибок, требуя гибкость в настройке жизненного цикла, максимум возможностей по разделению прав, встроенную Вики и т.п. А потом выясняется, что в команде два человека, заводятся только простые тикеты (tickets) из 3-х состояний, разделение прав ещё несколько лет не понадобится, а документированием своей работы они как не занимались, так и не занимаются (так что Вики ни к чему). </p>
<p>В общем, выбор порой надуман, особенно на первых этапах работы. Однако чаще всего бывает обратная ситуация &#8211; внедрение методик и инструментов просто не поспевает за потребностями.</p>
<p><img src="http://openquality.ru/images/experience/diver1.gif" style="float:right; margin: 5px 7px 5px 5px; border: 0px" alt="Водолаз 1" />При выборе инструментов и политик, а также правильного времени их внедрения, уместна аналогия с <strong>кессонной болезнью</strong> &#8211; <em>эффектом, который возникает при слишком быстром подъеме на поверхность с большой глубины. Из-за быстрой смены давления из крови и тканей ныряльщика начинают выделяться мелкие пузырьки газов, обычно в них растворённых. Это приводит к серьезным последствиям для всего организма. То есть, всплыть получится, однако человек рискует просто погибнуть. Известны только два решения &#8211; постепенный многочасовой подъем или быстрый подъём с последующей акклиматизацией на поверхности, но уже в барокамере</em>. Поскольку обычные проекты не избалованы бюджетом для покупки “барокамер”, будем рассматривать только первый рецепт.</p>
<p>Предположим, некоторый проект с серверной и клиентской частями начинается &laquo;на коленке&raquo;, в глубине гаража или квартиры основателя проекта. Выбираются простые инструменты: <strong>багтрекером</strong> (bug tracker) служит лист в Excel, контроль версий реализован в традиционном <a href="http://subversion.tigris.org/">Subversion</a>, сборка проекта идёт простым нажатием кнопки в любимой среде разработки, а дистрибутив выдается в виде архива или заливается на сервер по FTP. Проходит время, проект зреет, появляются новые люди. Листик в Excel ещё удается расшарить по сетке, SVN (Subversion) пока хорошо справляется с <strong>коммитами</strong> (commit) в <strong>транк</strong> (trunk, по умолчанию &#8211; главная ветка в Subversion), ветки пока не нужны. Поскольку продукт теперь собирается под 2-3 разные  операционные системы (клиентура растёт), приходится использовать пару разных сред разработки и на каждой новой машине тратить немало времени на настройку всех библиотек. Да и сама компиляция и линковка теперь &#8211; задача непростая, каждый раз вылазят специфичные для среды ошибки. При наличии нескольких человек трудно понять, кто и когда залил на сервер новый релиз и кто готовит инсталляционный пакет для клиентов. Проблемы пока решаются, ибо команда всё ещё невелика, и все находятся рядом. Однако давление уже изменяется: проект “всплывает”, последствия начального выбора процессов и инструментов скоро начнут &laquo;пузыриться&raquo;.</p>
<p><img src="http://openquality.ru/images/experience/diver3.gif" style="float:left; margin: 5px 7px 5px 5px; border: 0px" alt="Водолаз 3" />Первый пузырёк появляется в системах контроля версий и багтрекинга. Всё чаще люди начинают непродуктивно тратить время на коммит новой версии на транк &#8211; ведь приходится по 2-3 раза делать update перед очередным коммитом. Классическая ситуация:  три человека меняют один файл, первый делает commit, второй и третий, прежде чем сделать то же самое, вынуждены делать update. Далее кто-то из них двоих делает свой коммит, в результате чего третий вынужден опять упражняться в слиянии (merge) исходников. Начинается использование веток: сначала робко, для больших подсистем, потом всё чаще. При этом кто-то продолжает скидывать <strong>дельту</strong> (изменения в рабочем продукте) прямо в транк &#8211; ведь единой договорённости об использовании веток пока нет. Всё чаще возникают конфликты слияния, потеря дельты &#8211; сказывается разница в подходах между участниками команды. Всё больше ошибок при компиляции и тестировании появляется из-за несогласованности внесения изменений &#8211; слабо контролировалось вхождение новой дельты (новой функциональности или багфиксов). Листик Excel разросся, стало сложно  управлять поступающими запросами, уже непонятно, кто за какие изменения отвечает. К примеру, кто-то решил вместе с очередным багфиксом сделать рефакторинг и &laquo;размазать&raquo; его на 4 последовательных коммита. Разумеется, это переплелось с плановыми изменениями &#8211; в итоге билд поломан, пытаемся выяснить, на каком основании что было сделано. Иными словами, “пузырёк” появляется там, где происходит наибольшая активность, а стало быть &#8211; наибольший перепад давлений.</p>
<p>Следующий пузырь появляется при сборке проекта и работе с настройками среды. Разные среды, компиляторы, настройки окружения &#8211; всё чаще появляются специфичные ошибки компиляции и линковки. Да тут ещё и одна из сторонних библиотек поменялась &#8211; всем разработчикам нужно ее установить и проверить на совместимость с их модулями. При этом текущая стабильная версия системы (она стоит у заказчика), использующая предыдущую версию библиотеки, требует сопровождения. А значит, приходится её переустанавливать и менять ключи компиляции каждый раз, когда делаются багфиксы. А если учесть, что теперь клиенту, работающую под Windows, надо устанавливать новую dll и регистрировать дополнительный COM-объект, становится понятен нервный смех того, кто делает инсталляционные пакеты. И вот уже очередной перепад давлений &#8211; политики и инструменты для сборки проекта и управления рабочей средой просто не справляются. Под словом “<strong>политика</strong>” (policy) в этом контексте обычно понимают свод договорённостей, правил и используемых практик, которые принимаются всей командой.</p>
<p>Худо-бедно, учась на своих ошибках, сменили часть инструментария (в первую очередь, систему отслеживания запросов), договорились о правильном взаимодействии при работе с ветками, написали скрипты для запуска отстройки, начали использовать CMake для поддержания кроссплатформенности. Но продукт не стоит на месте, он растёт, а значит продолжает увеличиваться перепад давлений. Вот уже к работе подключилась удаленная команда тестировщиков. Возникла необходимость разделения прав в системе отслеживания ошибок, нужны новые способы быстрой настройки рабочего окружения, надо куда-то и в каком-то порядке выкладывать релизы для прогона тестов разной интенсивности и глубины. И снова “пузыри”&#8230;</p>
<p><img src="http://openquality.ru/images/experience/diver5.gif" style="float:right; margin: 5px 7px 5px 5px; border: 0px" alt="Водолаз 5" />В нашем примере очень показательно и естественно добавление удаленной команды разработчиков. Это большое испытание для всего проекта, не говоря уже о его SCM-инфраструктуре. Надо решить вопрос распределенной работы с исходниками, понять кто, где и в какой момент интегрирует изменения, выпускает очередной релиз, куда его положить, как отдать на тесты и получить их результаты для дальнейшего закрытия &laquo;тикетов&raquo;. В общем, очередной “подъём наверх” ставит всё новые задачи с точки зрения организации инфраструктуры. И редко кто бывает к ним заранее готов &#8211; всё меняется под нужны момента и редко кто заранее планирует все изменения.</p>
<p>Однако, предугадать это безобразие вполне возможно. Главное &#8211; нужно понимать, что все используемые инструменты и практики между собой увязаны, хотя и ортогональны друг другу. <em>Всё как в физике &#8211; есть векторы сил, которые действуют в разных направлениях и с разной величиной, однако при сложении получается конкретный вектор, который направлен вперёд</em>. Напомним, что есть 6 основных направлений работы (рассмотрены выше), с которыми совершенно точно предстоит столкнуться. Предупреждён &#8211; значит вооружён.</p>
<p>Нельзя отвергать и использование “барокамеры”, а именно &#8211; единовременных вложений ресурсов и времени на улучшение инфраструктуры. В этом случае команда целенаправленно вкладывается в то, чтобы улучшить свою работу. На это требуются деньги, однако результат можно получить относительно безболезненно и, если постараться, то быстро. Поэтому можно считать подобное решение альтернативой “постепенному подъёму”.</p>
<p><strong>На что ориентироваться при выборе инструмента?</strong> Итак, начался проект. Первое, с чем придётся столкнуться ещё до начала написания кода &#8211; запросы на изменения. Это <strong>пункт 1</strong> в  списке, рассмотренном в начале статьи. Даже когда единственным рабочим продуктом будет спецификация требований, её разработчикам уже нужен инструмент для отслеживания всего того, что происходит с требованиями, какие правки в них вносятся и зачем. Что уж говорить об исходном коде. Отслеживать изменения нужно научиться сразу &#8211; и научиться делать это качественно. Так что не экономим ресурсы, а постоянно оцениваем: хватает ли нам системы трекинга изменений и хватит ли на ближайшие пару месяцев. </p>
<p>Тут возникает проблема выбора, ведь система bug- или task-трекинга &#8211; это один из любимейших велосипедов программистов. За такие системы берутся многие, при этом доводят до презентабельного вида &#8211; сотни, и наибольшей популярностью пользуются десятки систем &#8211; от простых todo lists с галочками до сложных и безумно больших и гибких систем управления проектами. К слову сказать, разделение между bug-, task- и issue-tracking достаточно условно, однако task-трекеры ориентированы на более широкую аудиторию, не только разработчиков. Здесь очень велик риск внедрить одно, а через несколько месяцев захотеть уже сильно другое. Поэтому надо или быть заранее готовым сменить через год-два систему трекинга, или каким-то образом предвидеть направление роста команды и выбрать именно то, что не помешает маленькой команде и поможет &#8211; команде большой. </p>
<p>Не стоит забывать и о политиках использования. Ведь можно бесконтрольно плодить и закрывать запросы на изменения, а можно создать рабочую группу (change control board, группа контроля за изменениями), которая будет вести работу по приоритезации и отслеживанию выполнения этих запросов.</p>
<p>На данный момент мои фавориты &#8211; <a href="http://www.redmine.org/">Redmine</a>, <a href="http://code.google.com/p/etraxis/">eTraxis</a>, <a href="http://basecamphq.com/">Basecamp</a> &#8211; каждый для своих классов задач. Basecamp, по сути, это сильно расширенный todo list. eTraxis &#8211; гибкая система трекинга чего угодно. В нём открытый код, и я, участвуя в его внедрении на крупном проекте, даже написал несколько улучшений и исправлений кода. Вообще, очень гибкая штука &#8211; горячо рекомендую. Ну а на Redmine смотрите, если нужна целая система управления проектами &#8211; для этих глобальных целей он хорошо подходит.</p>
<p>Следующий по значимости класс систем &#8211; контроль версий. Это <strong>пункт 2</strong> в нашем списке SCM-задач. Такая система также выбирается в самом начале, поскольку рабочие продукты начинают появляться практически с первого дня работы. Тут ситуация отличается от систем трекинга. С одной стороны, инструментов не так много. Из бесплатных (open source) систем лидируют Subversion (SVN), git, Mercurial, среди платных &#8211; <a href="http://www.microsoft.com/business/smb/ru-ru/servers-and-tools/visual-studio.mspx">MS TFS</a>, Perforce, <a href="http://www-01.ibm.com/software/awdtools/clearcase/">IBM Rational ClearCase</a> (и его потомки). Небольшой выбор компенсируется тем, что этими инструментами можно пользоваться по-разному.<img src="http://openquality.ru/images/experience/diver2.gif" style="float:left; margin: 5px 7px 5px 5px; border: 0px" alt="Водолаз 2" /> Можно использовать один и тот же SVN годами, однако в начале проекта работать только на транке, а уже через год вырастить увесистое дерево веток и меток. Так что с этим классом инструментов просто необходимо периодически пересматривать политики и практики их использования. При этом ничто не является догмой &#8211; вы можете сейчас активно ветвиться, а потом постепенно перейти к системе непрерывной интеграции (continuous integration), исповедующей частую интеграцию на транке. К примеру, немало копий, поломано при обсуждении того, как надо ветвиться (и надо ли вообще). Или священная война последних 3-4 лет &#8211; какие системы лучше, централизованные или распределенные. Что тут сказать, надо исходить из потребностей. Моя слабость &#8211; это IBM Rational ClearCase, но только не UCM, а его базовый функционал. Мощнейшая штука, несправедливо загубленная маркетологами IBM. Кстати, не так давно появился неплохой продукт <a href="http://www.plasticscm.com/">PlasticSCM</a>, одновременно имеющий распределённую природу и частично использующий идеологию ClearCase. С интересом <a href="http://scm-notes.blogspot.com/search/label/Plastic">слежу</a> за его развитием.</p>
<p>Следующую немаловажную нишу занимают системы поддержки построения продуктов &#8211; это <strong>третий пункт</strong> нашего “хит-парада”. Ниша эта небольшая, однако без неё многие проекты немыслимы. Сюда входят как системы для описания кроссплатформенной компиляции и линковки (<a href="http://www.cmake.org/">CMake</a>), так и системы полного цикла сборки (<a href="http://maven.apache.org/">Maven</a>, <a href="http://ant.apache.org/">ant</a>). Общая цель &#8211; сделать построение продукта как можно более прозрачным. Зачастую такие системы специфичны для конкретного языка или технологии, а их выбор приходится на более поздние этапы развития проекта. Чаще всего их начинают внедрять когда ресурсы ручной сборки уже выработаны. Однако правильные архитекторы и СМ-инженеры думают о них уже в самом начале проекта :)</p>
<p>Это же описание справедливо и для систем управления рабочим окружением, выпуском и развертыванием продуктов. В нашем списке они выделены под <strong>пунктами 4, 5 и 6</strong>, однако потребность в них возникает практически в одно и то же время. Зачастую продукт сильно эволюционирует до того как доходит очередь до этого класса систем. Это правильно &#8211; только реальные потребности покажут, что и когда надо внедрять для подобных задач. Мало научиться строить продукт &#8211; надо его правильно выдать пользователям (в том числе, тестировщикам), убедиться, что при использовании или тестировании будет всё настроено так, как задумывалось разработчиком, и нам не будут лететь сообщения об одних и тех же ошибках каждый раз после выхода нового релиза. Подобные инструменты эволюционируют обособленно и, как правило, по времени ближе к середине проекта, когда есть уже что показать тестировщикам. Обычно же начинают с того, что просто выкладывают бинарники в расшаренную папку в сети, приложив release notes, где описано, какие изменения внесены и как настраивать окружение. Тем и ограничиваются долгое время.</p>
<p>Несколько слов о литературе по SCM. Книг на русском языке практически нет. Есть масса онлайн-материалов по конкретным инструментам, много HowTo, руководств, но не книг. Обзор имеющейся литературы можно найти <a href="http://scm-notes.blogspot.com/2010/10/books-about-configuration-management.html">здесь</a>. </p>
<p><em>В целом, важно не какой инструмент выберешь, а как его будешь использовать. То есть, первичны методы, практики, политики, а не инструменты. Инструмент выбирается под задачу, и не грех менять инструмент по мере необходимости.</em></p>
<h2>Типичные ошибки и заблуждения</h2>
<p>Первая типичная ошибка &#8211; не использовать контроль версий. Лечится быстро, но болезненно. Без контроля живут до первой ошибки, которую не смогли поправить откатом к предыдущей работающей версии системы. Никакие бэкапы не сравнятся с системой контроля версий.</p>
<p>Аналогичная ситуация &#8211; отслеживание запросов на изменение. Кто-то ограничивается перепиской по email, и долгое время тем и счаслив. Лечится не очень быстро. Скачок происходит, когда количество обозримых задач и контроль за их исполнением просто не умещается в голове, а поиск по почте мало помогает. Вот тогда и начинаются поиски подходящей системы. Впрочем, тут появляется другая ошибка &#8211; как я уже писал, начинают писать что-то своё, тогда как готового &#8211; просто навалом. Но тут уж трудно кого-то в чём-то убедить.</p>
<p><img src="http://openquality.ru/images/experience/diver4.gif" style="float:right; margin: 5px 7px 5px 5px; border: 0px" alt="Водолаз 4" />Ещё одно заблуждение (скорее, позиция в священной войне), состоит в том, что распределённая модель может полностью заменить модель централизованную. Рискую навлечь на себя гнев апологетов git и Mercurial, однако считаю, что это далеко не так. Будучи активистом антиброуновского движения :), полагаю, что централизованная модель может и даже должна использоваться в проектах, имеющих жёсткую иерархию работы с изменениями. Перед глазами собственный пример. Я имел удовольствие работать в крупном проекте, распределённом между странами и часовыми поясами. У нас, бывало, проходила не одна неделя, прежде чем изменения попадали от разработчика в продукт, отгруженный “наружу”. При этом участники команды должны были иметь возможность смотреть изменения от любого разработчика  вне зависимости  от его местонахождения, времени суток и доступности его копии изменений. У нас использовался ClearCase с надстройкой Multisite, позволявшей централизованным хранилищам в локальных командах реплицировать между собой все нужные изменения. Таким образом я, находясь в России или США, мог видеть код, написанный в Индии или Италии &#8211; он становился доступным в течение получаса (максимум). С децентрализованной моделью это было бы трудно.</p>
<p>К заблуждениям отнесу и уже упоминавшийся отказ от использования веток. Я не имею виду непрерывную интеграцию, которая принципиально строится на использовании единой ветки для разработки &#8211; есть аргументы как “за”, так и “против” этого метода.  Речь идет о непонимании механизма работы. Кто-то считает, что наличие веток отнимает кучу дискового пространства &#8211; это неверно, так как внутреннее представление большинства систем достаточно оптимально и там, как правило, хранятся только изменения, но никак не полная копия исходников для каждой ветки. Кого-то пугает процесс слияния изменений после окончания работы на ветке. Страх этот идёт отчасти из-за того, что в самой популярной на сегодняшней день системе контроля версий &#8211; Subversion &#8211; ветвление и слияние действительно сделаны&#8230; скажем так, не совсем хорошо :) В других системах это делается значительно проще, так что страхи пора забывать.</p>
<p>Кстати, встречал упоминания о том, что кто-то просто удаляет ветки после того, как дельта использована для интеграции, и ветка уже не нужна. Объясняется это какими-то полурелигиозными сображениями вида “она будет мешать” или “она же занимает место”. Система контроля версий изначально создана для того, чтобы хранить все версии, сколько бы их не было &#8211; под это ветки “заточены”. Нередки случаи, когда вернуться к старым изменениям приходится и через пару месяцев. Если база разрастается совсем уж до неприличия и тормозит всю работу, можно слить старые изменения в архив, но ни в коем случае их не удалять.</p>
<p>Нельзя не сказать и о метках &#8211; незаслуженно мало используемой возможности систем контроля версий. Метки позволяют работать с именованными версиями. Например, интегратор проекта слил воедино все изменения пары команд, которые параллельно работали над изменениями, получил тот срез, который можно считать стабильным. Он вешает на него метку, и теперь любой участник команды может взять этот срез путём извлечения одной-единственной метки. Чем это отличается от обычного номера ревизии? Тем, что метку могут “перевесить”, если вдруг выяснится, что произошел сбой и стабильной считается уже другая ревизия. Кроме того, не во всех системах контроля есть наборы изменений (changesets), и для них метки &#8211; это единственный способ каким-либо образом идентифицировать конфигурацию без перечисления всех версий всех элементов. В общем и целом, метки &#8211; полезная штука, от которой не надо отказываться.</p>
<h2>Будущее SCM</h2>
<p>Судя по англоязычным публикациям, будущее за системами класса Application Lifecycle Management (ALM). Это инструменты, соединяющие в себе функции контроля версий,  поддержки билда, и, самое главное, сбора требований, отслеживания запросов на изменения, поддержки тестирования и оценки его результатов. Этакие швейцарские ножи. Уже сейчас существует некоторое количество таких продуктов. К примеру, Microsoft Team Foundation Server (MS TFS) &#8211; думаю, он в представлениях не нуждается. Чуть менее известно семейство <a href="http://www.ibm.com/developerworks/rational/products/rtc/">IBM Rational Team Concert</a>, в частности IBM Rational Jazz. Судя по описаниям и отзывам, команда IBM критически перетрясла разработки прошлых лет и оставила самое лучшее. Также на слуху <a href="http://www.accurev.com/">AccuRev</a> и <a href="http://www.neuma.com/">Neuma</a>.</p>
<p>Заметьте, все решения платные. Единственная альтернатива, которая приходит в голову &#8211; это сборка воедино бесплатных решений с открытым кодом. Многие системы  багтрекинга имеют возможность интеграции с системами контроля версий. В общем, есть все предпосылки для объединения, однако, как правило, дальше объединения трекеров и контроля версий дело не заходит. Полные интегрированные системы если и существуют, то, как правило, это результат работы отдельных компаний или коллективов: и дальше внутреннего использования они не выходят. Думаю, здесь мы ещё увидим немало интересного.</p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/software-configuration-management/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Индустрию спасут массовые расстрелы</title>
		<link>http://experience.openquality.ru/maxim-krentovskiy-interview/</link>
		<comments>http://experience.openquality.ru/maxim-krentovskiy-interview/#comments</comments>
		<pubDate>Fri, 28 Jan 2011 06:53:45 +0000</pubDate>
		<dc:creator>Максим Крентовский</dc:creator>
				<category><![CDATA[Интервью]]></category>
		<category><![CDATA[LAMP]]></category>
		<category><![CDATA[архитектура]]></category>
		<category><![CDATA[спагетти]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=321</guid>
		<description><![CDATA[За плечами системного архитектора Максима Крентовского десятки завершенных проектов, опыт создания приложений различной тематики и сложности. Ночные кошмары и риски, спагетти-код и баги-кровопийцы, “время-качество-деньги” и взаимодействие с заказчиком, идеальный код и программистский коммунизм, “тяп-ляпы” и “кризис перепроектирования” &#8211; вот далеко не полный перечень вопросов, затронутых в беседе. Максим, сможете ли вы выделить факторы, которые влияют на успех/провал разработки и внедрения любого продукта (вне зависимости от его предметной области)? На мой &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><em>За плечами системного архитектора <a href="http://devimpress.com/">Максима Крентовского</a> десятки завершенных проектов, <a href="http://devimpress.com/portfolio">опыт</a> создания приложений различной тематики и сложности. Ночные кошмары и риски, спагетти-код и баги-кровопийцы, “время-качество-деньги” и взаимодействие с заказчиком, идеальный код и программистский коммунизм, “тяп-ляпы” и “кризис перепроектирования” &#8211; вот далеко не полный перечень вопросов, затронутых в беседе.</em></p>
<p><strong>Максим, сможете ли вы выделить факторы, которые влияют на успех/провал разработки и внедрения любого продукта (вне зависимости от его предметной области)?</strong></p>
<p>На мой взгляд не существует однозначных факторов, которые могут привести к определенному результату.  Как правило, проекту свойственны индивидуальные риски, часть из которых хоть и можно классифицировать, но смысла особого не имеет, потому что методология работы их учитывает. Другая часть, напротив, уникальна для каждого проекта, и, подчас, играет гораздо большую роль в его успешном завершении. Беда в том, что очень часто распознать эти риски и подстраховать их не представляется возможным. Особенно часто это бывает в случае выполнения нетривиальных, новых, но одновременно и очень интересных проектов. С типовыми решениями все гораздо проще: сценарии нарабатываются с опытом, риски выверяются и страхуются, в договорах появляются новые формальности.</p>
<p>Помимо рисков огромное влияние оказывает мотивация участников процесса. В случае отдельной незаинтересованности (неважно с чьей стороны &#8211; заказчика или исполнителя) велика вероятность краха проекта.</p>
<p><strong>Каким образом методология работы позволяет учитывать риски?</strong></p>
<p>Как правило, это может происходить в двух направлениях &#8211; либо в виде жестком регламентировании отношений, либо, наоборот, в фиксации ключевых моментов, коротких итерациях и плотном взаимодействии.</p>
<p>Когда мы формализуем взаимодействие, то снижаем риски неправильной оценки проекта по времени и финансам, получаем лучшую предсказуемость процесса и определенные преимущества в случае развития конфликтных ситуаций.</p>
<p>Когда, наоборот, определяем только ключевые моменты, но упрощаем взаимодействие и всячески его стимулируем, стараясь максимально быстро получить обратную связь &#8211; уменьшаются риски конфликтов из-за неоправданных ожиданий и недостаточного взаимопонимания.</p>
<p><strong>Что нужно учесть с момента первого разговора с заказчиком (внешним или внутренним) и до выхода продукта в свет?</strong></p>
<p>Ну, первый разговор &#8211; самый главный, на нем обе стороны определяют, смогут ли они сотрудничать или лучше не надо. Очень много проектов провалилось только потому, что заказчику не понравился исполнитель или наоборот, но решили все-таки попробовать. Если на первой, второй и последующих встречах есть хоть единое сомнение в надежности делового партнера, такие сомнения лучше разрешить в ближайшее время, желательно &#8211; в тот же миг. Потом это, как правило, обходится много дороже.</p>
<p>Другой момент  &#8211; степень информированности. Не секрет, что очень часто стороны не обладают полной информацией о действиях противоположной стороны. Это вносит неопределенность, которая обычно трактуется с позиции ожиданий. Если ожидается провал, то начинает копиться негатив, а если успех, то мечты. Поэтому чем больше информации о деятельности по проекту доступно, тем лучше. Разумеется, это не следует утрировать до сообщения каждого шага противоположной стороне.</p>
<p>Третий аспект &#8211; целеполагание. Если цель проекта недостаточно четко определена хотя бы на уровне менеджеров проекта, то в итоге и результатом будет непонятно что. Цель, безусловно, должна быть достижима в разумные сроки и может быть пересмотрена в процессе проекта. Последнее часто является необходимой процедурой, поскольку локальные цели проекта (написание CRM-системы, например) могут быть следствием более глобальных целей (устранение бардака в клиентском отделе), и ожидания заказчика строятся именно относительно глобальных, а не локальных целей.</p>
<p>При всем этом всегда нужно ясно представлять равенство сторон в процессе взаимодействия. Наличие денег с одной стороны и обширных знаний с другой не может быть причиной доминации в любом партнерстве.</p>
<p><strong>Как происходит разработка приложения после того как техзадание согласовано с заказчиком? Пишите ли вы псевдокод, создаете ли прототипы или сразу же приступаете к реализации &laquo;боевой&raquo; системы?</strong></p>
<p>Это обычно зависит от количества имеющихся наработок и/или готовых решений, которые применяются в проекте. Если количество разработки незначительно относительно настройки уже имеющегося кода, можно сразу в бой. Если же система требует значительных усилий по разработке, пишется &laquo;с нуля&raquo; или в дальнейшем может серьезно видоизмениться, то первым этапом обязательно будет продумывание архитектуры, затем прототипирование (отдельно &#8211; пользовательского интерфейса, отдельно &#8211; программных механизмов), затем можно приступать к реализации.</p>
<p><strong>В разрезе создания большой системы: какой стиль разработки &#8211; нисходящий или восходящий &#8211; вы предпочитаете и почему?</strong></p>
<p>Для любой большой системы интуитивно кажется, что нисходящий стиль лучше и правильнее. Однако в реальной жизни идеализация очень часто наступает на &laquo;надо завтра что-то показать и это не диаграмма классов&raquo;. Поэтому очень часто приходится комбинировать, например, реализацию интерфейса пользователя с продумыванием архитектуры всего проекта. Сказать, что это плохо, однозначно нельзя &#8211; полученные отзывы по интерфейсу могут внести коррективы и в архитектуру, и в последовательность взаимодействия элементов.</p>
<p>Тут надо упомянуть, что разные языки по-разному способствуют тому или иному стилю. Например, в ОО-языках можно писать боевой код прямо сразу, рассчитывая потом распихать его по классам и произвести улучшения &#8211; допустим, вводя дополнительные абстракции. Функциональные и декларативные языки, на мой взгляд, предполагают, что разработка будет строиться в основном &laquo;сверху вниз&raquo;.</p>
<p><strong>Максим, какая из реализованных систем была наиболее интересной и технически сложной? Сможете ли вы описать ее жизненный путь с точки зрения выбора архитектуры и средств разработки?</strong> </p>
<p>Обычно при ответе на такой вопрос я подробно рассказываю о проекте создания системы управления <a href="http://devimpress.com/archives/440">театральными подъемами</a>, но в данный момент, думаю, это не самый сложный проект. Хотя и не самый простой.</p>
<p>Но в этот раз, пожалуй, имеет смысл упомянуть более свежий проект &#8211; систему учета взаимодействия с клиентами в рамках одного из направлений деятельности компании, в которой я сейчас работаю. Проект создания <a href="http://en.wikipedia.org/wiki/Customer_relationship_management">CRM-системы</a> (возможно, это слишком громкое название, но короче и точнее термина подобрать сложно) примечателен тем, что успешно работает и продолжает развиваться, при этом будучи выполненным в рамках абсолютно неправильного процесса: не было ни ТЗ, ни фиксированных сроков, ни фиксирования пожеланий и требований, ни долгих споров на тему того, как должна выглядеть грядущая победа над энтропией. Это давало мне, по сути, основному разработчику системы, простор для фантазии.</p>
<p>Изначально наша компания &#8211; это типичный представитель малого бизнеса, численность которого не превышает 40 человек. При этом по направлению, которое подлежало частичной автоматизации, работала примерно половина сотрудников компании. Процедура была формализована, пользователи предоставляли в организацию бланки-заявки, в которых описаны типовые  действия, требуемые к выполнению в соответствии с договором. Заявка перемещалась между отделами, так же выполняющими вполне регламентные процедуры. Но одно дело, когда клиентов десять и поток заявок относительно небольшой. Совсем другое &#8211; когда клиентов за тысячу, а появление шквала заявок носит весьма нерегулярный, подчас лавинообразный характер. В этих случаях были нередки потери заявок, невыполнения обязательств, ошибки в работе и, как следствие, конфликты между сотрудниками. Поэтому возникла вполне очевидная необходимость в информационной системе, которая позволяла бы: </p>
<div style="margin-left: 20px;">
<p>а) объединить данные по всем клиентам и выполняемым для них операциям,<br />
б) отследить сроки выполнения обязательств с выявлением узких мест,<br />
в) установить возможности сообщения сотрудникам о просроченных операциях,<br />
г) всячески способствовать снижению хаоса и анархии на выбранном направлении деятельности.</p>
</div>
<p>Изначально задача была поставлена в виде &laquo;нам нужна база по клиентам с отслеживанием сроков исполнения&raquo;. Вооруженный ею, а так же кратким описанием процесса, я попытался продумать интерфейс системы. Поскольку единственное ограничение от внутреннего заказчика состояло в том, что система должна быть веб-приложением, доступным из любой точки с интернетом, в качестве инструмента был выбран комплект <a href="http://ru.wikipedia.org/wiki/LAMP">LAMP</a>, в рамках которого у меня был большой опыт и неплохое количество наработок. Была еще идея реализовать все в рамках Google Apps, но этот вариант был отвергнут, поскольку я не знал Python, а мой коллега, хоть и знал, никогда не работал с Google Apps API. В свете этого разработка могла значительно затянуться, а нужно было, как всегда, &laquo;еще вчера&raquo;. Поэтому выбор инструмента был определен, на вооружение взята типовая архитектура веб-приложения (которой я придерживался уже несколько лет при реализации подобного рода проектов), а я занялся проектированием пользовательского интерфейса.</p>
<p>Пользовательский интерфейс во всех приложениях является бичем разработчика. Как ни странно, но почти во всех встречающихся мне задачах (в т.ч. и в задаче с театральными подъемами), на интерфейс, его реализация и связанные с ним вещи занимали до 75-80% времени проекта. Поэтому при создании приложений необходимо уделять максимальное внимание простоте и удобству пользовательского интерфейса. Это в дальнейшем сократит как нервы пользователя, так и временные затраты разработчика. Предполагалось, что в системе будет веб-интерефейс, основанный на HTML4 совместно с применением библиотеки jQuery. В качестве шаблонизатора использовалась технология XSLT: бэкэнд генерировал XML с данными, на который затем одевался XSL-шаблон, который мог быть разным в зависимости от условий, например, предназначены ли данные для передачи на сторону клиента посредством AJAX или же, наоборот, должны быть отправлены на печать.</p>
<p>После проектирования интерфейса была спроектирована и реализована базовая структура БД, в которой предполагалось хранить основные сущности. Второстепенные были добавлены позднее. Как правило, это оказывались всякого рода справочники и шаблоны.</p>
<p>Затем в течение порядка трех-четырех месяцев был реализован основной функционал. Поначалу он представлял собой базу данных по клиентам и смежным сущностям плюс систему назначения и отслеживания задач. В таком виде система и была пущена в бой. Однако последующие два месяца эксплуатации выявили нежелание сотрудников работать в рамках системы. Основой по-прежнему оставались бумажные заявки, а конфликты продолжали возникать. В свете этого было решено модифицировать систему, приведя ее к более привычному для участников процесса виду &#8211; в CRM была встроена сущность &laquo;Заявки&raquo;, каждое внесение которой порождало сверку с уже имеющимися данными по клиенту (для исключения операторских ошибок), а так же инициировало последовательное выполнение задач согласно бизнес-процессу со всеми вытекающими бонусами.</p>
<p>В модернизированном виде система была опробована, проверена боем и, наконец, начала оправдывать свое назначение: заявки из бумажной формы получали порядковый номер, обрабатывались в рамках поставленных задач и успешно закрывались по мере их возникновения. Работать с данным инструментом стало удобнее, почти все отделы, участвующие в процессе, использовали этот инструмент как основной. По мере наработки базы и прецендентов использования пользователи начали вносить конструктивные предложения о незначительных доработках системы: у руководства, в основном, пожелания сводились к отчетам по текущей деятельности, у рядовых сотрудников &#8211; по ведению вспомогательного учета и автоматизации типовых операций. Даже сейчас, спустя год вполне успешного функционирования CRM, появляются новые задачи и задумки, позволяющие развивать систему дальше. Внедрение автоматизации позволило не только повысить качество услуг, но и внедрить некоторые дополнительные процедурные вещи, пошедшие на пользу как нам, так и нашим контрагентам.</p>
<p>На моей памяти это один из немногих случаев, когда внедрение информационной системы не усложнило повседневный труд сотрудников организации, а, наоборот, сделало его более эффективным, как это изначально и планировалось. Однако надо понимать, что данный успех немного притеняет тот факт, что компания занимается деятельностью, связанной с информационными технологиями, т.е. использование информационных систем и компьютерной техники для нее рядовое событие (а в предприятии с многолетним стажем отсутствие этого может стать серьезным препятствием).</p>
<p><strong>Максим, расскажите, пожалуйста, о наиболее интересных багах в вашей карьере. Какой баг был самым забавным? Какой баг проявлял себя самым загадочным образом, и докопаться до его сути было особенно трудно?</strong></p>
<p>О, баги! :) Вообще с возрастом начинаешь замечать, что есть много различных типов багов. </p>
<p>Первый &#8211; синтаксический, когда программист ошибается в названии оператора или забывает поставить разделитель. Это неизбежное зло &#8211; программисту как оператору свойственно ошибаться при вводе текста, и тут обычно только незамыленный взгляд способен найти ошибку (ну и вменяемое сообщение о ней от компилятора/интерпретатора). </p>
<p>Второй тип &#8211; логический, когда надо было одно, а получается совсем другое. По мере приобретения опыта и познания инструмента таких ошибок становится все меньше и меньше. </p>
<p>И третий, самый злостный тип &#8211; ошибки, порождаемые сторонним кодом или сложным взаимодействием. Эти крови пьют больше всего и о них я как раз расскажу.</p>
<p>Первая история произошла как раз при внедрении системы управления театральными подъемами. По заданию требовалось, чтобы запуск двигателей подъемов контролировался не только сенсорным экраном, но и отдельными кнопками, вмонтированными в пульт, плюс присутствовала индикация в виде загорающегося светодиода над нажатой кнопкой. А это означало, что мне в процессе работы надо опрашивать дополнительный контроллер и посылать ему команды на индикацию. Собрали прототип, я протестировал программу &#8211; все работает. На следующий день время теста на конечном оборудовании. Включаем&#8230; и получаем цветомузыку &#8211; светодиоды горят произвольно &#8211; вне какой-либо зависимости от нажимаемых кнопок. Вот захотелось так и все. В панике перепроверяю код: все правильно, но такое дурацкое поведение не исчезает. Коллеги хмурят брови и просят проверить еще раз. После обеда. </p>
<p>Разумеется, весь обед голова забита только вопросом &laquo;Какого &#8230; ?&raquo;. Возвращаемся. И тут в голову приходит глупая идея &#8211; за этот проект мы уже несколько раз наступали в электрические наводки на информационные линии, а с учетом того, что силовые щиты тоже рядом&#8230; в общем, прошу коллег заменить кабель от компьютера до контроллера на экранированный. Заменяем. Все начинает работать как и работало на тестовом пульте. Выдыхаю, перехожу к следующему этапу.</p>
<p>Вторая история случилась совсем недавно. Я разрабатывал простенький веб-видеочат, для создания клиентской части использовал открытый Flex вместо традиционного Adobe Flash, а, поскольку знания платформы были недостаточно глубоки, всячески подглядывал в размещенный в сети код и исходники, идущие вместе с сервером для RTMP-трансляций erlyvideo. Чат в итоге был реализован и даже работал, но при тестировании обнаружился очень странный эффект &#8211; при ожидаемом поведении в браузерах ОС Windows и Mac OS X ответственный за вещание Flash-элемент категорически отказывался работать в Linux, даже не выдавая предупреждения на захват камеры. Попытки неоднократно переписать код, консультации с опытными Flex-разработчиками, один из которых и был автором того кода, на базе которого я строил тот элемент, не увенчались успехом. Окончательно разочаровавшись во всех испытываемых средствах я полез как можно глубже в документацию. Оказалось, что выдача окошка предупреждения безопасности &#8211; это свойство исключительно Flash-плагина, Air-приложения же сразу обломаются. Запустив отладку, я обнаружил &#8211; да, действительно, обламывается. Но у меня было не Air-приложение! Сразу пришло на ум, что, возможно, это указывается в параметрах компиляции. Прошерстив все параметры и вдоволь ими наигравшись до такой степени, что даже работающая компиляция перестала функционировать, я сдался, решив проверить последний шанс. Дело в том, что для Linux есть альфа-версия Flex Builder-а, продукта Adobe для удобной работы с Flex-ом. Создав на удачу в нем проект, я скопировал туда прошедший неоднократную переделку код и запустил. Каково было мое удивление, что все заработало! Появилась панель безопасности, заработал захват видео-потока с камеры. А вместе с удивлением пришло и озарение. Я начал проверять, как ролик вставляется в страницу и опасения подтвердились &#8211; библиотека jQuery <a href="http://jquery.thewikies.com/swfobject/">SWFObject</a> откровенно халтурила, пытаясь вставить Flash-элементы в Linux как и на других платформах через тег object, в то время как правильно это делать через тег embed (что и делает оригинальный SWFObject, который занял вакантное место библиотеки для встраивания). В результате чего браузер трактовал ролик как встроенное Air-приложение и применял соответствующую настройку безопасности. Для проигрывания было все равно, а вот с захватом и трансляцией получилось неудобно. В результате, стыдно признаться, но на этот вполне тривиальный баг я потратил почти неделю времени, правда, неполных рабочих дней, потому как делалось это параллельно остальным задачам.</p>
<p>Мораль обоих историй проста &#8211; &laquo;баги коварны&raquo; и &laquo;никому нельзя верить&raquo;. :)</p>
<p><strong>Максим, есть три базовых сценария  приемочных испытаний: проверку системы осуществляет разработчик, выделенный тестировщик или вменяемый заказчик. При создании каких приложений/систем оптимален тот или иной сценарий? Исходя из вашего опыта, как организовать тестирование продукта наиболее эффективным образом в координатах  “время-качество-деньги”?</strong></p>
<p>Для выбора из предложенных варинтов, на мой взгляд, главное &#8211; представлять стоимость сбоя и отказа.</p>
<p>Если эти суммы незначительны, не приводят к финансовым потерям и человеческим жертвам &#8211; вполне можно обойтись тестированием при разработке плюс надеждой на вменяемого заказчика. Кроме того, на моей памяти было такое, что люди обходились только последним, выкатывая в релиз весьма сырые и непроверенные версии. Надо, правда, понимать, что сбои и отказы ситемы позитива не добавляют, и после очередного падения можно будет узнать много нового о себе и своих родственниках.</p>
<p>Если последствия ошибок весьма значительны (для определенных ситуаций и вполне ничтожная сумма может быть серьезной), то да, имеет смысл проводить выходное тестирование. Однако, на мой взгляд, впадать в крайности (как это делают в TDD) не стоит, лучше уделить внимание проработке модели и алгоритма системы, нежели соревноваться в методах доведения ее до граничных состояний, а потом героически эти самые состояния преодолевать, внося дополнительную энтропию в пусть и проходящий тесты код.</p>
<p><strong>Каковы признаки плохого кода? Можете ли вы привести примеры?</strong></p>
<p>Ну, тут, наверное, стоит произвести небольшую классификацию потенциальных ошибок. Подозреваю, что она будет далеко неполной, но послужит хорошей отправной точкой.</p>
<p>Технические ошибки на проекте можно подразделить на следующие:</p>
<p><em>&bull; Неправильный выбор платформы</em></p>
<p>Здесь, скорее всего, будет все плохо: начиная от архитектуры и заканчивая локальными фрагментами кода, все будет сосредоточено на героическое преодоление нюансов разработки под выбранную платформу. В результате этого скорее всего получится дорогой в разработке и поддержке, но абсолютно неэффективный продукт. К счастью, в наши дни подобные ошибки встречаются все реже и реже. Наиболее абсурдный пример подобного решения &#8211; написание интернет-форума на Си в виде CGI-приложения (зачем, когда есть такие языки, как Python, Ruby, PHP, явно больше подходящие для веб-разработки).</p>
<p><em>&bull; Ошибки архитектуры</em></p>
<p>Под ошибками архитектуры подразумевают неоправданные приемы, которые разработчики бездумно копируют из других проектов, не особо задумываясь о последствиях. Простейшим примером подобного может быть, например, хранение картинок для небольшого сайта внутри СУБД. При этом при запросе картинки веб-приложение должно будет запросить СУБД на предмет картинки, сформировать правильно заголовки и только затем отдать картинку пользователю, иными словами &#8211; это чревато неоправданной нагрузкой по сравнению с вариантом хранения этой же самой картинки как обычного файла (хотя стоит отметить, что иногда это решение тоже приемлемо).</p>
<p>Другой пример архитектурных ошибок &#8211; закон больших чисел. Тот или иной прием может быть хорош для небольших сайтов, но для ресурсов с посещаемостью в десятки тысяч человек в день может превратиться в один большой кошмар. Простейший пример такого просчета &#8211; хранение фотографий пользователей в одном каталоге. В результате, когда пользователей на ресурсе становится много, а количество фотографий становится еще больше &#8211; файловая система начинает весьма медленно работать, что не добавляет позитива ни администратору сервера, ни пользователям ресурса.</p>
<p>Также сюда можно отнести проблемы неравномерности кода, когда существует множество мелких файлов, являющихся промежуточными звеньями, а основная бизнес-логика сосредоточена в отдельном большом файле, что часто приводит к возникновению т.н. &laquo;спагетти-кода&raquo; (о нем поговорим далее). Стыдно признаться, но именно такую ошибку совершил и я, посчитав, что выделение каждой функции в отдельный файл будет хорошим решением. Правда, несколько проектов с последующими внесениями изменений заставили меня передумать на этот счет, поэтому как именно модификации потребовали значительных телодвижений по обеспечению инфраструктуры, нежели написанию самого кода.</p>
<p><em>&bull; Неряшливое кодирование</em></p>
<p>Под неряшливым кодированием я обычно подразумеваю два явления &#8211; несоблюдение стиля кодирования и спагетти-код.</p>
<p>Первое является прямым следствием неорганизованности и внутреннего беспорядка разработчика или отсутствием единого представления стиля (по крайней мере, в его голове). Полученный таким образом код очень тяжело читать, не говоря о внесении в него каких-либо изменений. Пример подобного кода &#8211; несоблюдение отступов, разнообразие в написании типовых конструкций языка, непонятное или неприятное формирование имен переменных и функций.</p>
<p><em>Спагетти-код</em> &#8211; это код, в котором встречаются фрагменты, написанные как в разном стиле, так и с разными подходами к разработке. Такое обычно бывает когда над проектом успело поработать несколько разработчиков, причем некоторые из них внедряли не свои разработки, а код третьих товарищей. В результате получается каша, в которой очень сложно понять, какая строчка за что отвечает, какие части значащие, а какие инфраструктурные. Как следствие, с каждой итерацией вносить изменения в этот код становится все сложнее и сложнее, в умах разработчиков все чаще возникают пролетарские идеи о рефакторинге или &laquo;все взять и переписать&raquo;. Стоит отметить, что бороться со спагетти-кодом (иное название &#8211; <em>&laquo;лоскутное одеяло&raquo;</em>) весьма сложно как на архитектурном, так и на организационном уровне.</p>
<p><em>&bull; Незнание алгоритмов и/или принципов функционирования инструмента</em></p>
<p>Думаю, тут особых пояснений не требуется. Файловая система плохо уживается с тысячами файлов в одном каталоге. Сортировка пузырьком простая в реализации, но не самая быстрая. Возможность создания огромного числа подключений к серверу легко может упереться в нехватку пропускной способности. Неучтенный неунифицированный доступ к памяти приводит к значительным потерям производительности при массовых параллельных вычислениях. Рукопашная реализация функции вряд ли будет много эффективнее уже имеющейся встроенной.</p>
<p><em>&bull; Недостаточная или излишняя документация</em></p>
<p>Тут обычно вспоминают классическую фразу<em> &laquo;Документация как секс &#8211; когда она плохая, это все же лучше, чем ничего&raquo;</em>. На деле можно столкнутся как с очень плохой документацией (крайний случай &#8211; когда она откровенно лжива), так и с излишне хорошей, когда на метод из четырех строк приходится 20 строк описания.</p>
<p><strong>Как сделать код более читабельным? Как обеспечить возможность его легкого изменения в будущем?<br />
</strong></p>
<p>Это очень непростой вопрос, куча умного народу сломало не одну тысячу копий в попытках определить, какая методика разработки позволит реализовать подобный программистский коммунизм. Пока однозначного ответа, увы, нет.</p>
<p>Попробую со своей дилетантской стороны предположить, что наиболее читабельный код &#8211; это код, которого мало. В результате <em>идеальный код &#8211; код, которого нет</em>. Но, поскольку на данном этапе развития технологий к идеалу можно только стремиться, то следует предположить, что код, содержащий меньшее число строк и есть лучший. К сожалению, тут вскрывается другой неприятный факт: чтобы читать и изменять код с минимальным числом строк, нужно обладать хорошей компетенцией в программировании и в языке, в частности. Получается, что <em>&laquo;количество ума на Земле постоянное, а население растет&raquo;</em>.</p>
<p>Если кратко, советы для достижения читабельного кода, думаю, можно свести к следующим:</p>
<p>&bull; Изучайте новые языки, парадигмы, принципы и методики разработки. Наверняка сложная задача в одном языке дается малой кровью в другом.</p>
<p>&bull; Используйте стили написания кода, такой проще читать.</p>
<p>&bull; Как минимум, подписывайте назначение функций/методов (особенно если их назначение неочевидно из названия) и коммиты в систему контроля версий.</p>
<p>&bull; Прежде чем реализовывать какую-либо функцию, проверяйте десять раз, не сделал ли это кто-либо до вас, и как хорошо он это сделал.</p>
<p>&bull; Помните: возможно ваш код будете читать вы сами (лет через пять и бить себя по голове при этом), а возможно &#8211; психопат-убийца-маньяк. И не факт, что он не найдет, где вы живете. <em>(&laquo;Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.&raquo; &#8211; Martin Golding)</em></p>
<p><strong>Максим, каковы признаки хорошего кода?</strong></p>
<p>Их не так много:</p>
<div style="margin-left: 20px;">
<p>1. Такой код не вызывает настойчивого желания его переписать у разработчика аналогичной или более высокой квалификации.<br />
2. Такой код вызывает острое желание записаться в ученики у разработчика более низкой квалификации.</p>
</div>
<p>На самом деле все гораздо проще:</p>
<div style="margin-left: 20px;">
<p>1. Код должен стабильно реализовывать необходимый функционал  без сторонних эффектов.<br />
2. Код должен быть понятен для чтения и изменения.<br />
3. Код можно повторно использовать в других проектах.</p>
</div>
<p>Наверное такой код можно назвать хорошим кодом. Беда в том, что если пункт один можно обеспечить тестированием, а пункт три &#8211; грамотным проектированием и репроектированием, то пункт два исключительно субъективен. На практике хороший код написать с первого раза практически малореально. Хорошим получается код четвертой, пятой и последующих итераций (об этом писал Ф.Брукс в своей эпической книге &laquo;Мифический человеко-месяц&raquo;).</p>
<p><strong>Табу для разработчика: каких ошибок не стоит допускать при создании программных продуктов?<br />
</strong></p>
<p>При создании программных продуктов не нужно допускать двух ошибок:</p>
<div style="margin-left: 20px;">
<p>1. бояться делать ошибки<br />
2. бояться потом их радикально исправлять</p>
</div>
<p>К сожалению, программная инженерия сейчас все еще многогранна и сложна. Программисту приходится учитывать множество факторов (нет, фазы Луны уже учитывать необязательно) и нюансов в своей повседневной работе, поэтому вполне нормально, что вся картина будет выпадать из поля зрения. Конечно, говорят, что можно расширять сознание при помощи <a href="http://ru.wikipedia.org/wiki/%D0%9B%D0%A1%D0%94">LSD</a> и аналогичных веществ, но, боюсь, это порочная и не несущая в конечном итоге значимой прибыли практика.</p>
<p>Самое большое зло в разработке &#8211; делать &laquo;тяп-ляп&raquo;, мол, потом перепишем все правильно. Практика показывает, что все временное старается существовать как можно дольше, а с наколеночными решениями рано или поздно приходится иметь дело. Другая крайность &#8211; &laquo;кризис перепроектирования&raquo; &#8211; не менее ужасна: архитектор не может остановится на конкретном инструменте и пробует все вокруг для получения сферического идеального решения в вакууме.</p>
<p><strong>Каковы, на ваш взгляд, наиболее важные навыки для эффективного программиста?</strong></p>
<p>Наиболее важные навыки эффективного программиста:</p>
<div style="margin-left: 20px;">
<p>1. лень &#8211; он не делает по десять раз скучную и неинтересную работу, для этого есть компьютеры и программы.<br />
2. аккуратность и педантичность &#8211; в противном случае системы будут рассыпаться на глазах.<br />
3. флегматичность и интровертность &#8211; ничего не должно отвлекать.</p>
</div>
<p>Ну а отличный программист должен иметь всего в три раза больше и при этом быть тем самым маньяком-психопатом, который расчленяет по ночам плохих программистов.</p>
<p><strong>Можно ли после этого спокойно спать? :)</strong></p>
<p>Можно. Такие маньяки водятся только в телевизионных сериалах. :)</p>
<p><strong>Максим, каких возможностей вам не хватает в современных языках программирования?</strong></p>
<p>Очень сложно сказать, чего не хватает в современных языках программирования, поскольку языков много, все развиваются, каждый имеет ряд плюсов и минусов и, как правило, уже нет амбиций реализовывать задачу непременно одним языком. Насколько я знаю, опытные разработчики используют в своих проектах разные языки и разные парадигмы в зависимости от поставленных задач и достигают в этом значительного успеха.</p>
<p>Другое дело, что объем знаний для освоения любого из языков программирования на сегодняшний день требует предварительной подготовки, а само изучение языка, если брать доскональную работу, а не поверхностное чтение справочника с последующим ввязыванием в бой, тоже занимает достаточно много времени. Исключения есть (например, Эрланг учится весьма быстро), но их мало. А помимо языков существуют дополнительные инструменты, практики, методики построения приложений&#8230; В результате сложность разработки растет геометрически и под грузом данных разработчику порой сложно написать просто и изящно, как это было бы в случае, если бы не было излишних нагромождений. С другой стороны, отказаться от всего наследия было бы тоже неразумно. Требования к функционалу систем растут, и в этом свете было бы неправильно изобретать велосипед, когда можно подключить огромную подсистему парой строчек, не затратив на это значительных усилий, времени и, что немаловажно, денег заказчика. Се ля ви.</p>
<p><strong>Что нового, на ваш взгляд, появится в языках и средах разработки?</strong></p>
<p>Я думаю, что в ближайшее время в широкую практику войдет создание и использование специализированных языков (DSL), и языки общего назначения будут включать в себя механизмы для упрощения подобных практик. </p>
<p>В то же время на уровне генерации машинного кода, наоборот, произойдет интеграция всех платформ и систем под знамя <a href="http://ru.wikipedia.org/wiki/Low_Level_Virtual_Machine">LLVM</a>, т.е. исходный и/или промежуточный код (CIL, байткод JVM) будут транслироваться в представление LLVM, а только затем &#8211; в бинарный код целевой платформы. Это упростит переносимость продуктов, позволяя модернизировать интерфейсы в зависимости от конечного устройства, оставляя код бизнес-логики неизменным. Впрочем, это весьма оптимистичный прогноз.</p>
<p>В средствах разработки появятся в обязательном порядке статические и динамические анализаторы кода, позволяющие еще до этапа компиляции выявить потенциально узкие места или отловить их в процессе выполнения и предложить варианты исправления ошибок.</p>
<p><strong>Максим, что вам хотелось бы изменить в развитии IT?</strong></p>
<p>ИТ, на мой взгляд, слишком быстро превратилось из академического направления в промышленную индустрию. В результате этого множество хороших и правильных решений было погребено в угоду коммерческой эффективности. Возможно, если бы процесс был не столь скоротечным, а алчущие до денежных компенсаций маркетологи не столь прыткими, мы бы сейчас имели совсем другие информационные технологии, в которых каждый первый не говорил бы крамольное &laquo;Индустрию спасут только массовые расстрелы&raquo;.</p>
<p><strong>Максим, спасибо за интервью. Хочется пожелать вам новых интересных проектов, развивающихся эволюционным, а не революционным путем! :)</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/maxim-krentovskiy-interview/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Тише едешь, дальше будешь</title>
		<link>http://experience.openquality.ru/maxim-kramarenko-interview/</link>
		<comments>http://experience.openquality.ru/maxim-kramarenko-interview/#comments</comments>
		<pubDate>Tue, 14 Dec 2010 18:21:13 +0000</pubDate>
		<dc:creator>Максим Крамаренко</dc:creator>
				<category><![CDATA[Интервью]]></category>
		<category><![CDATA[СУБД]]></category>
		<category><![CDATA[техпроцесс]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=307</guid>
		<description><![CDATA[Максим Крамаренко руководит командой разработки TrackStudio &#8211; иерархической системы управления задачами, которую внедрили сотни клиентов в 33 странах мира. История продукта, извлеченные уроки, техпроцесс &#8211; русло нашей сегодняшней беседы. Максим, расскажите, пожалуйста, о рождении TrackStudio. Как возникла идея, каковы были первоначальные замыслы? Что представляла собой первая версия, и насколько она была успешна? В 2001 году мы занимались заказной разработкой для крупного иностранного заказчика. После 11 сентября заказчик закрыл проект, часть &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><em><a href="http://maximkr.livejournal.com/">Максим Крамаренко</a> руководит командой разработки <a href="http://www.trackstudio.ru/">TrackStudio</a> &#8211; иерархической системы управления задачами, которую внедрили сотни клиентов в 33 странах мира. История продукта, извлеченные уроки, техпроцесс &#8211; русло нашей сегодняшней беседы. </p>
<p>Максим, расскажите, пожалуйста, о рождении TrackStudio. Как возникла идея, каковы были первоначальные замыслы? Что представляла собой первая версия, и насколько она была успешна?<br />
</em></p>
<p>В 2001 году мы занимались заказной разработкой для крупного иностранного заказчика. После 11 сентября заказчик закрыл проект, часть сотрудников освободилась, и мы решили начать проект внутренней автоматизации и SaaS-сервис для других организаций на его основе &#8211; тогда это было очень популярным направлением.</p>
<p>На тот момент трекеры в виде сервисов были 2-х типов:</p>
<div style="margin-left: 20px;">
<p>- все пользователи используют один экземпляр системы и очень похожую конфигурацию. Возможности настройки системы в данном случае минимальны.</p>
<p>- для каждого пользователя настраивается свой индивидуальный экземпляр системы, конфигурация его может быть любой. Этот подход часто сложнее в администрировании и требует больше оперативной памяти.
</p></div>
<p>Мы решили сделать такой SaaS-трекер, который бы позволял хранить информацию разных клиентов в единой БД, но при этом клиенты должны были иметь возможность полностью конфигурировать систему под свои задачи.</p>
<p>Первая версия была реализована в марте 2002 года. Из функционала тогда существовала иерархия задач, разграничение прав доступа, фильтры, оповещение по e-mail.  В течение еще года после этого мы занимались доработками, улучшали функционал и интерфейс, но продаж не было совсем.</p>
<p>Основных проблем было две:</p>
<div style="margin-left: 20px;">
<p>- для крупных компаний была важна интеграция с внутренними сервисами (LDAP, SCM и т.п.) и контроль над данными, реализовать это в SaaS довольно сложно.</p>
<p>- для мелких компаний требовался очень простой продукт, они были в принципе не готовы что-то конфигурировать, пусть даже и через web-интерфейс. Если конфигурация по-умолчанию клиенту не подходила, то он уходил искать другой продукт.
</p></div>
<p>К концу 2002 года стало понятно, что возможности TrackStudio в плане поддержки иерархии задач и особенно независимая конфигурация системы для разных отделов и проектов интересны для крупных компаний со сложной организационной структурой, поэтому мы решили переориентироваться на эту группу клиентов и делать коробочный продукт.</p>
<p><em>Рынок крупных компаний &#8211; лакомый кусок для многих разработчиков. Какой вам представлялась ниша для будущего продукта?</em> </p>
<p>Для популярных в то время трекеров большой проблемой была нехватка гибкости. Традиционно проблемы с гибкостью решались путем инсталляции еще одного экземпляра трекера. Если система не позволяла иметь разные настройки для разных проектов &#8211; заводили отдельный экземпляр для каждого проекта. Если система не позволяла закрыть доступ клиентов компании к важной внутренней информации &#8211; для общения с клиентами также выделялся отдельный экземпляр.</p>
<p>Обычно вначале отсутствие гибкости не кажется большой проблемой, но со временем количество проектов и инсталляций растет, значительно усложняется администрирование трекеров, появляется необходимость синхронизировать конфигурацию и настройки пользователей. Становится сложнее поиск информации для обычных пользователей и анализ информации для менеджеров.</p>
<p>Наша архитектура позволяла решить эту проблему, возможности системы в плане организации задач и управления правами доступа были уникальными. Фактически, TrackStudio &#8211; это не трекер, а что-то вроде &laquo;виртуальной машины для трекеров&raquo;, которая позволяет реализовать множество самых разных процессов в рамках одной системы. Сейчас клиенты используют TrackStudio не только для управления ошибками, но и для смежных областей (управление документаций, требованиями) и даже для управления процессами, не имеющими прямого отношения к IT (обработка заказов, слияние компаний).</p>
<p><em>Развитие продукта: c какими трудностями вы столкнулись при работе над первыми коробочными версиями? Какие уроки вы извлекли в тот период?</em></p>
<p>Главная техническая сложность &#8211; реляционные СУБД довольно плохо работают с ситуации, когда</p>
<div style="margin-left: 20px;">
<p>1) Требуется не обработка множеств, а навигация по дереву.</p>
<p>2) Права доступа настраиваются для каждого узла дерева.</p>
<p>3) Структура дерева часто меняется.
</p></div>
<p>Первые 2 условия означают, что СУБД должна уметь очень быстро обрабатывать большое количество простых запросов (десятки тысяч запросов в секунду), а третье &#8211; что у нас есть серьезные ограничения на выбор алгоритма хранения дерева задач и кеширования информации. Если первые версии TrackStudio целиком использовали возможности СУБД, то в настоящий момент большая часть информации постоянно хранится в памяти и записывается в БД только для долговременного хранения.</p>
<p>Уже после этого мы узнали, что у Microsoft были аналогичные проблемы при попытке реализовать файловую систему поверх реляционной БД (проект WinFS, был закрыт). Попытка переноса Exchange на реляционную СУБД так же закончилась ничем.</p>
<p>Если бы мы начали сейчас писать TrackStudio, то наверняка использовали бы какую-нибудь NoSQL СУБД.</p>
<p><em>Каких подводных камней следует избегать в процессе создания коробочного продукта?</em></p>
<p>Нашим самым большим открытием в первые годы работы над проектом было то, что стандарты де-факто гораздо важнее, чем официальные стандарты. Аналитика сильно преувеличивает важность и популярность дорогих коммерческих продуктов (ORACLE, DB2, WebLogic и т.п.).</p>
<p>Самый важный урок: не нужно ориентироваться на официальную статистику популярности платформ, серверов БД и приложений. Поддержка MySQL может сказаться на продажах больше, чем поддержка ORACLE и DB2 вместе взятых.</p>
<p><em>Самый необычный пользователь: чем он запомнился?</em></p>
<p>Это был директор городского парка, бывший программист, который использовал SaaS-версию TrackStudio для задач вроде &laquo;Нужно покрасить скамейку&raquo;.</p>
<p><em>Самый тяжелый пользователь?</em></p>
<p>Клиент из Австрии, который очень плохо понимал и писал по-английски. За несколько месяцев он написал более 600 писем с вопросами.</p>
<p><em>Как вы отлаживаете код? Что представляет собой техпроцесс работы над продуктом?</em></p>
<p>Из инструментов используем средства статического анализа кода, профайлеры, логи. В общем, с технической точки зрения &#8211; ничего особенного.</p>
<p>С организационной точки зрения исправление багов для нас стоит на первом месте, обычно мы не выпускаем продукт с известными багами. Это не значит, что в TrackStudio нет глюков (конечно, есть), но найденные глюки обычно исправляются уже в следующей минорной версии. Для версии 3.5 таких минорных версий было 77, для 4.0 &#8211; уже 9. Образцом для подражания для меня является OS/2 2.1, для которой было выпущено больше 100 fixpack-ов.</p>
<p>Причем мы стараемся не столько найти больше глюков, сколько ускорить устранение найденных. Со временем такой подход позволяет значительно увеличить надежность приложения: если сразу после выпуска TrackStudio 3.5 пользователи присылали 6-8 багрепортов в неделю, то через несколько лет мы стали получать 1 багрепорт в 3-6 месяцев.</p>
<p>Наш процесс разработки выглядит примерно так:</p>
<div style="margin-left: 20px;">
<p>1) Мы делаем какую-то версию TrackStudio (например, TrackStudio 4), тестируем, исправляем ошибки. Если мы все сделали, известных ошибок больше нет, сами мы найти ничего не можем, то мы выпускаем бета-версию.</p>
<p>2) Пользователи скачивают бету, находят какие-то ошибки, мы их исправляем. Когда все найденные ошибки исправлены, мы выпускаем следующую бету. Для нас нет особого смысла выпускать бету раньше &#8211; ведь к уже найденным ошибкам и запросам добавятся новые, в итоге время нашей реакции на багрепорты пользователей вырастет.</p>
<p>3) Если пользователи бета-версий устойчиво (несколько недель) не могут &laquo;загрузить&raquo; нашу команду &#8211; мы выпускаем релиз. Релиз скачивают большее количество пользователей, количество багрепортов опять увеличивается, мы исправляем проблемы и выпускаем минорные версии (4.0.1, 4.0.2 и т.п.)</p>
<p>4) Когда становится очевидно, что исправлением ошибок и мелкими доделками команду занять не получится &#8211; мы делаем branch и начинаем разработку следующей версии (например, TrackStudio 5.0). При этом все ошибки продолжают исправляться и в старой, и в новой версии.</p>
<p>5) Когда новая версия нам кажется &laquo;готовой&raquo; (обычно это занимает несколько лет), мы выпускаем бету и процесс повторяется.
</p></div>
<p>Т.е. мы не привязываемся заранее ни к конкретным срокам (&laquo;нужно выпустить релиз 1 марта&raquo;), ни к надежности системы (&laquo;если ни одной критичной проблемы, можно выпускать&raquo;). Определяющий для нас фактор &#8211; скорость реакции на запросы пользователей. Одновременно этот подход позволяет выровнять загрузку команды и исключить авралы.</p>
<p><em>Маленькая команда и большой продукт: каким образом осуществляется тестирование TrackStudio и его саппорт?</em></p>
<p>У нас нет своей команды тестировщиков. Перед выпуском бета-версии мы инсталлируем новую версию системы на свой сервер и активно используем несколько недель. Параллельно разработчики занимаются тестированием с применением средств покрытия кода (code coverage), мы устраиваем соревнования &laquo;кто быстрее достигнет заданного уровня покрытия&raquo;.</p>
<p>Когда становится понятно, что мы сами в разумное время больше находить ошибки не можем, мы выпускаем бета-версию. После этого помощь пользователей в нахождении ошибок становится определяющей.</p>
<p>Особенность нашей ситуации в том, что</p>
<div style="margin-left: 20px;">
<p>- с одной стороны, у нас самих плохо получается находить ошибки, так как обычно они специфичны для конкретной конфигураций TrackStudio. Клиенты разрабатывают конфигурации самостоятельно, проверить поведение системы на всех возможных конфигурациях мы физически не можем.</p>
<p>- с другой стороны, если клиент обнаруживает ошибку, то часто она носит локальный характер и проявляется только у этого клиента.
</p></div>
<p>Несколько раз мы сталкивались со следующим явлением: крупная компания-клиент перед внедрением TrackStudio решает провести полное тестирование системы силами своего отдела тестирования. В результате они находят несколько ошибок, мы их исправляем и в дальнейшем у этого клиента все работает стабильно. В то же время на интенсивность потока ошибок со стороны других клиентов это тестирование практически никак не влияет &#8211; у них другие конфигурации и другие проблемы.</p>
<p>Саппортом и общением с клиентами обычно занимаюсь я сам, это позволяет лучше понимать запросы пользователей. Технически у нас ничего интересного нет &#8211; форум, e-mail, телефон. Прямой доступ в нашу TrackStudio мы пользователям не даем по идейным соображениям.</p>
<p><em>ТDD и автотесты: оправданы ли они в работе над TrackStudio?</em></p>
<p>Нет, автоматические тесты не применяем. Пробовали, но ничего хорошего из этого не вышло. Причины такие:</p>
<p>Разные части TrackStudio очень сильно сильно взаимосвязаны. Например, работа правил оповещения по e-mail сильно зависит от работы фильтров, настроек правил доступа, пользовательских скриптов. Ситуации когда что-то перестает работать &laquo;совсем&raquo; у нас возникают редко (т.к. даже обычное создание задачи затрагивает работу значительной части кода TrackStudio), а вот проблемы только на какой-то конкретной конфигурации пользователя бывают часто. Моделировать такие ситуации в тестах довольно трудно, а поддерживать эти тесты в актуальном состоянии &#8211; еще труднее.</p>
<p>Мне кажется, TDD хорошо работает в проектах, реализующих какой-то стандарт (XML-парсер, HTTP-сервер), когда спецификации достаточно жесткие и редко меняются. В нашем случае никаких жестких спецификаций нет, по согласованию с пользователями правила поведение системы может периодически меняться, а небольшие изменения кода могут повлечь очень значительные изменения в тестах.</p>
<p><em>Вопрос по этому <a href="http://maximkr.livejournal.com/11192.html">фрагменту</a>: &laquo;У нас код &#8211; общий, программисты заранее не знают какие из этих задач они будут делать через месяц.&raquo; Общее владение кодом: каковы преимущества такого подхода с точки зрения руководителя проекта? Страховка от кадровых потерь или другие причины?</em></p>
<p>В какой-то мере да, но главное даже не это. В TrackStudio нет изолированных модулей, таких как багтрекер, CRM, форум. Вместо этого, у нас есть модули для работы с правами, с процессами, с почтой или отчетами. Если программисту требуется добавить какое-то поле или исправить форматирование, то это нужно будет сделать во всем приложении. Если программист не знает про оповещения по e-mail, то он даже не догадается, что его задача подразумевает исправления и там тоже.</p>
<p>Также важна унификация приложения: если мы реализуем какую-то возможность, то очень часто требуется реализовать ее во всем приложении. Если это сделает один человек (а не каждый в &laquo;своем&raquo; модуле), степень унификации кода будет выше.</p>
<p>Кроме того, при таком подходе code review происходит автоматически.</p>
<p><em>Есть ли у вас внутренний Coding Style (документ или &laquo;устное&raquo; коллективное знание, которое определяет принятый стиль кодирования)? Сможете ли вы привести  примеры хорошего и плохого стиля?</em></p>
<p>В TrackStudio довольно редко приходится делать что-то совершенно новое, в основном происходит улучшение и переписывание старого кода. Мы стараемся придерживаться следующих правил:</p>
<div style="margin-left: 20px;">
<p>1) Если такое уже где-то было сделано, то нужно посмотреть как это сделано там и сделать тут точно так же.</p>
<p>2) Если есть метод лучше, то старый код нужно тоже исправить.
</p></div>
<p>Что касается форматирования, то никаких особых требований нет, используем &laquo;стандартное&raquo; форматирование средствами IDE.</p>
<p><em>Каковы признаки хорошего кода? Как сделать код более читабельным? Как обеспечить возможность его легкого изменения в будущем?</em> </p>
<p>Сложно сказать, для нас проблема качества кода не является слишком уж актуальной проблемой. Правда, у нас есть несколько особенностей организационного характера:</p>
<div style="margin-left: 20px;">
<p>1) Мы пишем TrackStudio исходя в первую очередь из собственных представлений о том, каким должен быть трекер. Если какое-то изменение не может быть сделано &laquo;красиво&raquo;, то мы скорее всего его вообще делать не будем. Когда таких изменений накапливается критическое количество, мы меняем архитектуру системы. Подозреваю, что при заказной разработке сопротивляться желанию единственного клиента гораздо сложнее.</p>
<p>2) У нас клиенты не могут ставить задачи непосредственно программистам. У клиентов вообще нет доступа к трекеру, так как такой подход стимулирует программистов делать &laquo;что сказал клиент&raquo; и неявно перекладывает ответственность за продукт на клиентов. Обычно задача попадает в трекер, когда становится понятно что именно и как нужно делать.
</p></div>
<p><em>Каковы, на ваш взгляд, наиболее важные навыки, знания для эффективного программиста?</em></p>
<p>Умение и желание разбираться с кодом, наверное. Могу точно сказать, что знание конкретных фреймворков и библиотек для нас не очень важно, т.к. из-за размера проекта его сложность почти наверняка будет больше, чем сложность API всех используемых библиотек.</p>
<p>Еще важно умение подавлять в себе желание переписать старый код просто потому, что он “кривой” и не очень понятно как работает. Сначала хорошо бы понять не только как работает старый код, но и почему он так был написан &#8211; этим мы убережем себя от ошибок предшественников при переписывании.</p>
<p><em>Расскажите, пожалуйста, о наиболее интересных багах, на которые довелось наткнуться. Какой баг был самым забавным? Какой баг проявлял себя самым непредсказуемым образом, и докопаться до его сути было наиболее трудно?</em></p>
<p>У нас в TrackStudio 3.0 была возможность создавать иерархию ролей пользователей. Причем у пользователей и задач, на которые эти роли накладывались тоже были свои иерархии, код там был весьма сложный (особенно разбор ситуаций, которые могли происходить при перемещении задач и пользователей по иерархии).</p>
<p>Как-то раз пользователь прислал свою базу с простым вопросом &laquo;почему в этом списке не видно вот этой роли?&raquo;. Поиск ответа занял 2 дня :) Как выяснилось, никакой ошибки там не было, просто поведение программы было совсем неочевидно. В результате мы полностью убрали наследование ролей и с тех пор при реализации функциональности обращаем особое внимание на то, насколько просто эту функциональность поддерживать.</p>
<p>А самые сложные баги &#8211; это разного рода утечки (памяти, соединений, блокировок).</p>
<p><em>Каких возможностей вам не хватает в современных языках программирования? Что нового, на ваш взгляд, появится в языках и средах разработки?</em></p>
<p>Я жду прогресса в области средств разработки параллельного кода, сейчас в этой области очень просто совершать ошибки и сложно их искать.</p>
<p><em>Что вам хотелось бы изменить в развитии IT?</em></p>
<p>Не нравится, что слишком часто в борьбе за популярность побеждают более примитивные и проблемные продукты, заведомо имеющие серьезные проблемы в архитектуре.</p>
<p>Например, одной из наиболее популярных СУБД сейчас является MySQL. За счет простоты первых версий эта СУБД выиграла у PostgreSQL, а инновации последних лет заключались в реализации функциональности, которая была сделана конкурентами много лет назад.</p>
<p>HTML/CSS стали популярными именно за счет своей простоты, на многие проблемы технологии изначально просто закрыли глаза, в итоге интенсивное &laquo;допиливание&raquo; этих инструментов продолжается до сих пор, а web-разработка стала неоправданно сложным делом.</p>
<p>Тенденции последнего времени скорее не ослабляют, а усиливают этот эффект. Agile-практики фокусируются на получении мгновенного результата в ущерб архитектуре. Считается, что наличие тестов позволит относительно безболезненно поменять архитектуру системы потом, но это далеко не всегда получается. В популярной книге <a href="http://gettingreal.37signals.com/">Getting Real</a> от 37signals также предлагается сфокусироваться на решении простых проблем, оставив сложные на потом.</p>
<p>В итоге, у нас получается видимость прогресса вместо прогресса. Слишком уж примитивные технологии захватывают внимание основной массы пользователей своей простотой и перехватывают инициативу у существующих &laquo;монстров&raquo;. Постепенно пользователи набираются опыта, их требования растут и они сталкиваются с ограничениями новой технологии, которые являются очевидными следствиями проблем в архитектуре системы. Попытка разработчиков обойти эти ограничения рождает очередного монстра. Круг замкнулся.</p>
<p><em>Максим, большое спасибо за интервью. Успехов вам, системе TrackStudio и вашим клиентам вне зависимости от конъюнктуры в IT!</em></p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/maxim-kramarenko-interview/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Разработка без побочных эффектов</title>
		<link>http://experience.openquality.ru/alex-ott-interview/</link>
		<comments>http://experience.openquality.ru/alex-ott-interview/#comments</comments>
		<pubDate>Thu, 02 Dec 2010 17:18:55 +0000</pubDate>
		<dc:creator>Алекс Отт</dc:creator>
				<category><![CDATA[Интервью]]></category>
		<category><![CDATA[безопасность]]></category>
		<category><![CDATA[ФП]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=273</guid>
		<description><![CDATA[Из личного дела Алекса Отта: характер общительный, владеет языками ФП, экспертные знания в сфере информационной безопасности. Активен в проектах с открытым исходным кодом. Алекс, как вы пришли в мир функционального программирования? Что послужило отправной точкой для изучения теоретических основ ФП и первых практических шагов? Я пришел к ФП с практической стороны &#8211; теория до сих пор не самая моя сильная часть :-) Первый раз я познакомился с функциональными языками когда &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><em>Из личного дела Алекса Отта: характер <a href="http://alexott-ru.blogspot.com/">общительный</a>,  владеет <a href="http://alexott.net/ru/fp/">языками ФП</a>,  экспертные знания в сфере <a href="http://alexott.net/ru/cf/">информационной безопасности</a>. Активен в проектах с <a href="http://alexott.net/ru/oss/">открытым</a> исходным кодом.</em></p>
<p><em>Алекс, как вы пришли в мир функционального программирования? Что послужило отправной точкой для изучения теоретических основ ФП и первых практических шагов?</em></p>
<p>Я пришел к ФП с практической стороны &#8211; теория до сих пор не самая моя сильная часть :-)</p>
<p>Первый раз я познакомился с функциональными языками когда нашел свежеизданный двухтомник &laquo;Мир Лиспа&raquo; &#8211; это был 91-й год, и я получил доступ в большую библиотеку томского политеха. Лисп мне почему-то сразу понравился, и я даже эксперементировал с одной из реализаций Лиспа. Хорошим добавлением также было изучение Пролога, хоть он и не относится напрямую к функциональным языкам.</p>
<p>Затем, на несколько лет, примерно до 96-го я занимался только императивными языками &#8211; различные ассемблеры, Fortran (IV &#038; 77), C, C++, немного Pascal (хотя я его не особо люблю), а в 96-м на кафедре появился интернет, и я получил доступ к разнообразию открытого ПО, включая Guile &#8211; особенно были интересны эксперименты в GIMP&#8217;е, хотя я пытался использовать guile и для обработки текста и подобных задач. Продолжались и эксперименты с Common Lisp. Где-то в 98 или в 99-м году я познакомился со статически типизированными функциональными языками в виде Caml Light/OCaml, примерно тогда и начал интересоваться теоретическими постулатами ФП. Хорошим стартом был <a href="http://www.cl.cam.ac.uk/Teaching/Lectures/funprog-jrh-1996/index.html">курс</a> Джона Харрисона &laquo;Введение в функциональное программирование&raquo;, который актуален и сейчас и даже был сделан <a href="http://code.google.com/p/funprog-ru/">перевод</a> этого курса на русский язык.</p>
<p>В 2001-м я перебрался в Москву и начал работать в компании Инфосистемы Джет в качестве одного из разработчиков продукта <a href="http://jetsoft.ru/product/dozor/dlp.html">Дозор-Джет</a>, который разрабатывался на языке Scheme (PLT Scheme), так что мои знания очень <a href="http://fprog.ru/2009/issue2/alex-ott-using-scheme-in-dozor-jet/">пригодились</a>. В Джете собрался очень хороший коллектив, поэтому часто возникали дискуссии о языках программирования, теоретических основах и т.п.  Я продолжал более серьезно заниматься основами функциональных языков, деталями их реализаций, читал много теоретической литературы и продолжал эксперементировать с разными языками &#8211; OCaml, Haskell, Common Lisp, ну и Scheme была для работы :-)</p>
<p>С 2007-го года я работаю в компании Secure Computing (потом она была куплена McAfee), и хотя основным рабочим языком является С++, я применяю  функциональные языки для прототипирования. Большей частью задействованы Lisp&#8217;ообразные языки (раньше это был Common Lisp, теперь большей частью Clojure), хотя иногда используется и Haskell.</p>
<p>Одна из интересных особенностей, связанных с функциональным программированием: даже если ты не программируешь на языках ФП, концепции ФП позволяют улучшить твою работу на императивных языках. Например, отсутствие изменяемых глобальных состояний позволяет делать меньше трудноуловимых ошибок, да и тестировать такой код намного легче. А как показала практика, наличие read-only данных в некоторых случаях позволяет упростить код и улучшить производительность высоконагруженных систем.  Про такое применение идей ФП в императивных языках очень хорошо пишет Евгений Кирпичёв, статьи которого можно найти в журнале <a href="http://fprog.ru/">Практика функционального программирования</a> (ПФП). У него есть много хороших <a href="http://fprog.ru/2009/issue1/eugene-kirpichov-fighting-mutable-state/">примеров</a>.</p>
<p>В последнии годы я принимаю участие в различных проектах, связанных с популяризацией функциональных языков.  Это перевод курса Джона Харрисона, <a href="http://pcl.catap.ru/">перевод</a> книги Practical Common Lisp и последние два года &#8211; журнал &laquo;Практика функционального программирования&raquo;, в котором мы стараемся давать и теоретическую и практическую информацию о функциональном программировании, как оно может применяться и т.п. Также я стараюсь поддерживать в актуальном состоянии <a href="http://alexott.net/ru/fp/books">обзор</a> существующей литературы по функциональным языкам программирования, который первоначально был опубликован в первом номере ПФП, а сейчас выложен на моем сайте со множеством дополнений.  Я также несколько раз <a href="http://alexott.net/ru/clojure/clojure-intro/">рассказывал</a> о языке программирования Clojure &#8211; на конференции <a href="http://www.slideshare.net/alexott/clojure-margincon-2010">MarginCon 2010</a> (через skype), и тут, в Германии, на митингах для программистов на Java.</p>
<p><em>Алекс, в чем удобство применения функционального языка для прототипирования?</em></p>
<p>Для меня ФЯ удобны следующим:</p>
<div style="margin-left: 20px;">
<p>•	Интерактивная разработка кода, особенно при использовании Лиспов &#8211; код функций пишется, сразу вычисляется (evaluated), тут же проверяется и при   необходимости вносятся изменения только в одну из функций, и тестирование продолжается, без перезагрузки модуля(-ей).  Поскольку на этапе прототипирования, необходимо свести к минимуму задержку между написанием кода и получением результата, то такой подход дает большой выигрыш.  К тому же, при реализации какой-либо нестандартной функциональности, основное время затрачивается на нахождение правильного подхода, а не на его написание на конкретном языке.</p>
<p>•	Чистые функциональные языки, не допускающие <a href="http://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B1%D0%BE%D1%87%D0%BD%D1%8B%D0%B9_%D1%8D%D1%84%D1%84%D0%B5%D0%BA%D1%82_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)">побочных эффектов</a>, или   скрывающие их в какой-либо абстракции, позволяют писать функции которые делают что-то одно, не затрагивая остальных объектов программы.  И из-за того, что функции не имеют побочных эффектов, их потом достаточно просто переносить в код на другом языке.</p>
<p>•	Сейчас я в основном делаю прототипы используя язык Clojure &#8211; с одной стороны это Lisp, с его удобством интерактивной работы (я пользуюсь Emacs + SLIME), а с другой стороны я имею возможность использовать огромный набор библиотек для JVM &#8211; я использую много разных вещей &#8211; Hadoop, Lucene, Tika, различные библиотеки для machine learning, численных рассчетов (в том числе, и Incnater, написанный на Clojure), natural language processing и т.п.
</p></div>
<p>Из собственного опыта могу сказать, что нормальные программисты достаточно быстро осваивают новые языки программирования &#8211; за время, сравнимое со стандартным временем вливания в проект &#8211; до месяца. Могу сослаться на <a href="http://www.slideshare.net/j2a/ss-4625844">лекцию</a> Льва Валкина о том, как они используют функциональные языки (Erlang и OCaml) в компании JS-Kit.</p>
<p><em>Алекс, каковы самые распространенные мифы, связанные с ФП? Что этим заблуждениям можно противопоставить?</em></p>
<div style="margin-left: 20px;">
<p>•	Миф &laquo;Тяжело/нельзя найти программистов на ФЯ&raquo;. Практика показывает, что вменяемые программисты достаточно быстро осваивают ФЯ, даже если они с ними до этого никогда не работали. На первых этапах сложность составляет не сам язык, а непривычность концепций. Но есть гибридные языки, например, <a href="http://fprog.ru/2010/issue6/vlad-patryshev-why-scala/">Scala</a>, где можно начать программировать почти как на Java, но постепенно смещаться в сторону функциональных подходов. Да и другие языки имеют такие возможности в той или иной мере, просто может потребоваться больше знаний чтобы писать &laquo;императивный код&raquo;, а когда такие знания получаются, человек уже видит, что можно сделать и по другому.  Лев Валкин приводит в слайдкасте данные об обучении программистов функциональным языкам. Да и мой опыт общения с разными командами разработчиков говорит о том, что эта проблема преувеличена.</p>
<p>•	Миф &laquo;Функциональные языки очень медленные&raquo;. Это старый миф, еще со времен когда Лисп работал на больших машинах. Нынешние реализации функциональных языков очень близки по производительности скомпилированных программ к программам на С. Например, <a href="http://fprog.ru/2010/issue4/vitaly-mayatskikh-lisp-abstractions-on-steroids/">Common Lisp</a>, OCaml и т.д.</p>
<p>•	Миф &laquo;Все ФЯ &#8211; академические, не применимые на практике&raquo;. Ну это тоже распространенный миф. Язык Erlang создавался как язык, ориентированный на бесперебойную работу в телефонных коммутаторах, и хорошо себя зарекомендовал при создании высоконагруженных сетевых систем.  Common Lisp <a href="http://wiki.alu.org/Industry%20Application">применяется</a> во многих организациях, например, ITA Software. OCaml и Haskell достаточно активно используются в банковской сфере, особенно при трейдинге (и вакансии в этой области регулярно <a href="http://alexott-ru.blogspot.com/search/label/job">появляются</a>). Haskell часто используют в тех случаях, когда необходимо проводить доказательство корректности программ и т.п. вещах (вот один <a href="http://www.galois.com/">пример</a>). Clojure и Scala активно внедряются в качестве замены Java (Twitter, Linkedin и т.д.). F# вообще выпущен Microsoft в качестве одного из основных языков в составе MS Visual Studio.
</div>
<p><em>Типичные ошибки программистов, начинающих осваивать и практиковать ФП? Что им можно посоветовать?</em></p>
<p>Ну ошибки, наверное, такие же как и в остальных языках &#8211; overdesign; попытка использовать язык в той области, для которой он не предназначен; попытка писать в несвойственном для языка стиле.  Совет достаточно универсальный &#8211; при получении задачи оценить насколько для нее подходит тот или иной язык. Может быть, необходимо воспользоваться другим языком.</p>
<p><em>Расскажите, пожалуйста, о наиболее интересных багах в вашей карьере. Какой баг имел самые катастрофичные последствия? Какой баг был самым забавным?</em></p>
<p>Из катастрофичных навскидку вспоминается баг в базе сигнатур для определения типов файлов, который приводил к блокировке всего почтового трафика в достаточно большой организации. Но этот баг был быстро исправлен, пришлось только потом отправлять почту вручную из почтового архива.</p>
<p>А забавных даже не припомню &#8211; я обычно работаю с серверными решениями, и если там доходит до каких-то видимых эффектов, то дело плохо :-)</p>
<p>Я стараюсь иметь достаточно много тестов, которые бы обеспечивали быстрое определение наличия ошибки, если что-то сделано неправильно во время рефакторинга и/или доработки кода. Но это не чистый TDD, а лишь те вещи, которые подходят к моему стилю работы. Ну и конечно, перед коммитом больших изменений делается прогон по всем имеющимся тестовым файлам, с проверкой результатов.</p>
<p><em>Какой баг проявлял себя самым непредсказуемым образом, и докопаться до его сути было наиболее трудно?</em></p>
<p>В основном это баги связанные с утечками и повреждениями памяти (я после очередного поиска даже написал небольшую серию <a href="http://alexott.net/ru/writings/prog-checking/">статей</a> на эту тему). Такие ошибки крайне тяжело искать, особенно если они проявляются только при высоких нагрузках и/или после долгой работы (больше 2-3 недель). Мы столкнулись один раз с ошибкой в сторонней библиотеке (open source), которая не обращала внимания на системные лимиты, а всегда выделяла массив фиксированного размера, но использовала максимальное кол-во file handles, полученное от системы. В итоге, при большом количестве подключений библиотека затирала память в произвольном месте, так что ошибки возникали в самых разных частях системы.</p>
<p>Также достаточно тяжело искать баги, связанные с ошибками реализации различных методов сжатия данных, когда в середине гигабайтного файла битовый поток начинает интерпретироваться неправильно (особенно когда достаточно плохо с документацией на форматы файлов и алгоритмы).</p>
<p><em>Как вы тестируете приложения? В чем особенности вашего стиля работы и каким образом они определяют методику тестирования?</em></p>
<p>Я стараюсь писать код таким образом, чтобы он имел как можно меньше зависимостей. Это сильно облегчает написание автоматизированных тестов. Для базовых библиотек пишется большое количество тестов для разных случаев (code coverage порядка 90-95%, не покрытыми остаются в основном такие вещи, как обработка не-выделения памяти или обработка ошибок чтения с диска), что позволяет минимизировать кол-во ошибок при внесении изменений.</p>
<p>Для более сложных вещей также пишутся автоматизированные тесты, которые гоняются перед каждым коммитом. Кроме того, я пишу достаточно большое кол-во коммандно-строковых утилит, которые позволяют выполнить конкретную задачу &#8211; распаковать/запаковать данные и т.п. Применение таких утилит вместе с использованием существующих программ, например, unzip, etc, позволяет проверять корректность разбора данных (с проверкой контрольных сумм и т.п.). Эти утилиты используются при еженедельном прогоне относительно больших объемов данных (десятки гигабайт архивов и документов), а также перед коммитом после большого рефакторинга. Эти же утилиты применяются при профилировании кода.</p>
<p>Перед написанием кода я стараюсь собрать как можно больше данных о задаче, составить список требований, какие есть ограничения по скорости, доступной памяти и т.п. После этого идет этап проектирования, тогда же начинает собираться список test cases, файлы для тестов и т.п. И уже после проектирования идет программирование, практически набело, с очень небольшим объемом рефакторинга.</p>
<p><em>Каковы признаки плохого и хорошего кода? Можете ли вы привести примеры? Как сделать код более читабельным? Как обеспечить возможность его легкого изменения в будущем?</em></p>
<p>Признаки плохого кода:</p>
<div style="margin-left: 20px;">
<p>•	Много межмодульных зависимостей</p>
<p>•	Требование фиксированного порядка вызова процедур и функций</p>
<p>•	Зависимость от глобального состояния, результаты разные в зависимости от предыдущих вызовов &#8211; часто ведет к предыдущему пункту</p>
<p>•	Функция делает много вещей одновременно. Аналогично для классов &#8211; когда класс содержит в себе всю функциональность библиотеки и/или приложения</p>
<p>•	Дублирование достаточно больших кусков кода (часто как результат copy/paste)</p>
<p>•	Длинные функции, не вмещающиеся в экран (максимум два экрана кода)</p>
<p>•	Классы с десятками методов и переменных</p>
<p>•	Втягивание функций в классы, хотя можно обойтись свободной функцией
</p></div>
<p>Хороший код:</p>
<div style="margin-left: 20px;">
<p>•	Достаточно короткие функции, выполняющие одну задачу</p>
<p>•	Мало зависимостей между классами/модулями</p>
<p>•	Одинаковые ответы при одних и тех же входных данных</p>
<p>•	Неизменяемость глобальных переменных и независимость от них
</p></div>
<p>Такой код очень легко тестировать, использовать в других проектах и т.д.</p>
<p>Чтобы код было достаточно легко развивать я использую несколько подходов в зависимости от назначения кода:</p>
<div style="margin-left: 20px;">
<p>•	Если код относится к базовым библиотекам, то я стараюсь по максимуму собрать все требования к библиотеке до начала проектирования и реализации, это позволяет избежать массивного рефакторинга кода, зависящего от таких библиотек</p>
<p>•	Свожу зависимости между модулями к минимуму, общение в основном идет через четко определенные интерфейсы</p>
<p>•	Пишу тесты, особенно много для базовых библиотек</p>
<p>•	&laquo;Изобретение собственных велосипедов&raquo; только в случае проблем с производительностью, использование стандартных библиотек по максимуму</p>
<p>•	Документирование кода, вместе с отсылками к внешней документации (обычно это требования, собранные в wiki, etc)</p>
<p>•	Следование одним и тем же стандартам оформления кода для всех компонент</p>
<p>•	Использование подходов, перечисленных выше для хорошего кода
</p></div>
<p><em>Алекс, вопрос по пройденному пути. Сможете ли вы рассказать о наиболее значимых ошибках, с которыми вам довелось столкнуться в проектах на тех или иных этапах? Какие выводы из них удалось извлечь?</em></p>
<p>Самые большие/тяжелые ошибки, которые я видел &#8211; ошибки сделанные на начальном этапе проекта, например, неправильный сбор требований, неправильная<br />
архитектура (как следствие отсутствия требований). Такие ошибки очень тяжело исправлять когда проект уже вовсю разрабатывается, плюс, часто сталкиваешься с тем, что люди не могут отказаться от написанного кода и пытаются вытянуть систему с помощью различных костылей, локальных переделок и т.д.  Даже если удается &laquo;выкатить&raquo; систему в production, ее тяжело менять &#8211; исправлять ошибки, добавлять новый функционал. Поэтому я сейчас стараюсь увеличить стадию сбора требований и проектирования, чтобы убедиться, что система будет отвечать всем заданным требованиям.</p>
<p>Еще одна из ошибок с которыми я встречался &#8211; неправильная оценка сроков разработки + ошибки в отслеживании работ по проекту. Оценка сроков &#8211; вообще сложная тема, особенно при разработке нетиповых проектов, которые часто требуют больших временных затрат на research. Тут помогает только практический опыт, и все равно необходимо закладывать достаточно большие риски. Плюс многие программисты &#8211; оптимисты по натуре, и могут все сделать &laquo;за неделю&raquo; :-)</p>
<p>Отслеживание хода работ по проекту &#8211; также необходимая часть работы, поскольку понятие &laquo;готово&raquo; у разработчика и у project manager/team leader может сильно отличаться, и крайне важно не допустить &laquo;запущенных&raquo; случаев, когда к релизу система (или конкретная подсистема) очень сырая, или вообще не работает. И тут многое зависит от конкретных людей. Некоторые дают правильные оценки состояния своих частей, а за некоторыми надо следить внимательно и вовремя принять конкретные меры &#8211; обсудить возникшие проблемы, понять что надо для их преодоления &#8211; добавить времени на реализацию, добавить ресурсов (не всегда наилучший выбор) или вообще отложить данную функциональность до следующего релиза.</p>
<p><em>Алекс, в статье <a href="http://alexott.net/ru/writings/cf/index.html">Современные тенденции в области контентной фильтрации</a> есть такие строки: &laquo;Развитие  информационных систем приводит к возникновению все новых и новых угроз. Поэтому развитие продуктов контентной фильтрации не только не отстает, но  иногда даже и предвосхищает возникновение новых угроз, уменьшая риски для защищаемых информационных систем.&raquo;  В связи с этим вопрос: с какими новыми  угрозами мы можем столкнуться в ближайшие годы? Каковы потенциально слабые места нынешних информационных систем? Что вы посоветуете разработчикам,  начинающим проектировать новую систему?</em></p>
<p>Насчет новых угроз &#8211; тяжелый вопрос. Мне кажется, что будет продолжаться слияние различных &laquo;подходов&raquo; &#8211; социальной инженерии, поиска уязвимостей в коде и т.п.</p>
<p>Разработчикам я могу посоветовать только одно: безопасность системы должна быть заложена в начальную архитектуру системы, поэтому необходимо оценить потенциальные угрозы/атаки на систему и постараться избежать типовых ошибок. Ну и в процессе разработки должен регулярно выполняться анализ кода, как автоматический, так и ручной(мы, например, проводим регулярное code review и выполняем автоматическую проверку кода с помощью нескольких систем статического анализа кода).</p>
<p>Вообще существует достаточно много хороших книг, посвященных разработке программных систем с учетом защиты и т.п. вещей.  Например,</p>
<div style="margin-left: 20px;">
<p> &#8211; Software Security: Building Security In<br />
 &#8211; Security Engineering: A Guide to Building Dependable Distributed Systems<br />
 &#8211; Beautiful Security: Leading Security Experts Explain How They Think<br />
 &#8211; 24 Deadly Sins of Software Security: Programming Flaws and How to Fix Them<br />
 &#8211; Software Security Engineering: A Guide for Project Managers<br />
 &#8211; The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities</p>
</div>
<p>В Microsoft&#8217;овской серии книг Best Practices несколько книг также посвящены этой тематике, начиная от моделирования угроз, и заканчивая советами по организации разработки на конкретных языках программирования:</p>
<div style="margin-left: 20px;">
<p> &#8211; Threat Modeling<br />
 &#8211; The Security Development Lifecycle: SDL: A Process for Developing Demonstrably More Secure Software<br />
 &#8211; Writing Secure Code: Practical Strategies and Proven Techniques for Building Secure Applications in a Networked World</p>
</div>
<p>Тестирование &#8211; также важная часть процесса разработки, и тут можно посмотреть на следующую литературу:</p>
<div style="margin-left: 20px;">
<p> &#8211; The Art of Software Security Testing: Identifying Software Security Flaws<br />
 &#8211; Fuzzing: Brute Force Vulnerability Discovery
</div>
<p>Ну и программистам на C &#038; C++ можно посоветовать следующие книги (особенно первые две):</p>
<div style="margin-left: 20px;">
<p> &#8211; Secure Coding in C and C++<br />
 &#8211; The CERT C Secure Coding Standard<br />
 &#8211; Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation &#038; More
</div>
<p><em>Алекс, наиболее значимое достижение в разработке на текущий момент? Что сделало вас счастливым и гордым за свою работу?</em></p>
<p>В настоящее время: проектирование и реализация подсистемы анализа данных &#8211; точное и быстрое определение типов (я не вижу сейчас особых альтернатив на рынке), извлечение данных из разнородных файлов, анализ неструктурированных данных с целью поиска &laquo;чувствительной&raquo; информации.</p>
<p>Из предыдущих проектов: разработка &laquo;Дозоров&raquo;, проект BeepayXP. Вообще, больше всего гордишься когда видишь, что проект/продукт, в который ты вложил свой труд, становится полезен людям.</p>
<p><em>Алекс, большое спасибо за ваш труд. Хочется пожелать вам не сбавлять обороты и продолжать радовать коллег-разработчиков и пользователей!</em></p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/alex-ott-interview/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Когда backup не впрок. Истории успехов и провалов</title>
		<link>http://experience.openquality.ru/backup-lessons-learned/</link>
		<comments>http://experience.openquality.ru/backup-lessons-learned/#comments</comments>
		<pubDate>Thu, 02 Dec 2010 17:07:14 +0000</pubDate>
		<dc:creator>Филипп Торчинский</dc:creator>
				<category><![CDATA[Истории]]></category>
		<category><![CDATA[Методики]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[уроки]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=269</guid>
		<description><![CDATA[Филипп Торчинский работает в компании Oracle. Признанный эксперт в администрировании Unix-систем, автор книг "UNIX. Практическое пособие администратора" и "Операционная система Solaris". Если вы сегодня еще не сделали резервную копию важных данных на вашем ноутбуке и на сервере, за который отвечаете, сделайте, пожалуйста, а потом вернитесь к чтению этой заметки.]]></description>
			<content:encoded><![CDATA[<p><em><a href="http://voyadger.livejournal.com">Филипп Торчинский</a> работает в компании Oracle. Признанный эксперт в администрировании Unix-систем, автор книг &laquo;UNIX. Практическое пособие администратора&raquo; и &laquo;Операционная система Solaris&raquo;.</em></p>
<p><strong>I. Регулярное резервное копирование &#8211; штука для всех полезная</strong></p>
<p>Если вы сегодня еще не сделали резервную копию важных данных на вашем ноутбуке и на сервере, за который отвечаете, сделайте, пожалуйста, а потом вернитесь к чтению этой заметки. Заметка никуда не денется, в отличие от данных.</p>
<p>Постепенно в современный мир приходит понимание, что резервное копирование важно для всех &#8211; и для крупного бизнеса с терабайтами информации, обновляемой ежедневно, и для среднего бизнеса, и для мелкого, и для частного лица, вообще никаким бизнесом не занятого. Если у вас есть любые данные &#8211; тексты, фотографии, фильмы, музыка, таблицы и базы данных, которые вам хоть немного дороги, вы должны делать их резервные копии. Нет в мире вечных и стопроцентно надежных хранилищ информации, какими бы дорогими они ни были.</p>
<p>Среди самых драматичных историй, с которыми мне довелось познакомиться, есть полная и безвозвратная потеря человеком результата его трехлетних трудов (диссертация хранилась в единственном экземпляре на диске ноутбука, диск физически разрушился из-за перегрева). Есть куда более частая и банальная трата нескольких дней и расставание с несколькими тысячами рублей &#8211; из-за восстановления вышедшего из строя внешнего диска с документами. Дело осложнялось еще и тем, что документов там были тысячи, и точно неизвестно, какие документы копировались раньше на другой диск, а какие &#8211; нет. Еще одна история случилась с моим близким другом, который семь лет подряд делал копии всех важных документов и дистрибутивов на внешний диск, и в результате этот диск оказался единственным хранилищем большого архива, в том числе и тех файлов, которые были списаны с давно выброшенных или проданных ноутбуков. Беду, как всегда, не ждали: включенный диск случайно уронили на пол, и весь архив пропал без шансов на восстановление.</p>
<p>Мои коллеги из компании &laquo;Софтджойс&raquo; в Петербурге уже много лет подряд трудятся ежедневно не покладая рук над восстановлением дисков и флэшек &#8211; и поток их клиентов не иссякает. Между тем, их услуги стоят от 5000 до 30000 рублей в самых простых случаях, а цена еще одного резервного диска или флэшки обычно не превышает 3000 рублей. Это если не считать время и нервы, которые страдают из-за аварий.</p>
<p>Думаете, резервным копированием пренебрегают только частные лица? Как бы не так: в малых и больших организациях резервное копирование часто организовано плохо, бессистемно, бесполезно или со всеми этими недостатками одновременно. Чаще всего резервное копирование делают нерегулярно, копируют не все, что надо, забывают, что и куда копировали, теряют диски с резервными копиями, забывают копировать самые важные изменения и обнаруживают это уже после безвозвратной потери данных. Случается также, что владельцы документов думают, что важно копировать одно, а те, кто выполняет резервное копирование, думают, что другое, и поэтому восстановить действительно важную владельцам информацию не получается.</p>
<p>Пожалуйста, обратите внимание на заголовок этого раздела &#8211; РЕГУЛЯРНОЕ резервное копирование &#8211; это штука для ВСЕХ полезная. Здесь умышленно выделены слова &laquo;регулярное&raquo; и &laquo;всех&raquo;. И если со &laquo;всеми&raquo; всем все понятно, то о регулярности надо себя заставлять помнить. Если вы один раз в неделю не почистили зубы, зубы останутся целы. Если вы один раз в неделю не сделали резервную копию, может статься, что именно в этот раз вы создали (а затем потеряли!) документ, на который потратили много времени и сил. Вы должны решить, как часто (например, раз в день, или каждый раз, когда вы завершили новый важный документ) вы делаете резервные копии, и придерживаться этого правила неукоснительно. Считайте, что это также важно, как предохранение при случайном сексе с незнакомыми или страховка ОСАГО для автомобиля. Если эти аллегории вас не убедили, придумайте себе свое сравнение, такое, чтобы отказ от резервного копирования казался вам самым страшным, что может настигнуть вас на этом свете.</p>
<p><strong>II. Как сделать полезную штуку беcполезной</strong></p>
<p>В одной компании, с которой мне довелось иметь дело, было пять или шесть офисов. В каждом из них был файловый сервер, и информация с этих серверов ежедневно копировались на сервер центрального офиса, где все данные ежедневно записывались на магнитную ленту. Казалось бы, все надежно, резервирование двойное. Какие при этом возникали проблемы?</p>
<p>1. несогласованность документов между собой. Так как резервное копирование проводилось пофайлово, могло так случиться, что вначале записывалась старая версия одного файла, а затем новая версия другого. В этом нет ничего плохого, кроме того, что если файлы были связаны между собой и изменялись одновременно (например, график и документ, в который он вставлен), в резервной копии оказывались файлы, несогласованные между собой. Ценность такой копии заметно ниже, чем ценность точной копии всех файлов, сделанной одновременно. Средства сделать точную мгновенную копию есть, но они либо стоят немалых денег, либо требуют хорошей организации копирования, либо вынуждают использовать более современные операционные системы. О них пойдет разговор чуть ниже.</p>
<p>2. отсутствие контроля за выполнением копирования. Автоматическое копирование не всегда спасает (например, оно может не пройти из-за нехватки места на сервере, или прекратиться из-за сбоя сети, или не завершиться вовремя из-за слишком большого количества данных или низкой скорости записи, или сервер окажется перегружен работой и процесс записи &laquo;подвиснет&raquo;). Словом, такое копирование, даже если оно делается специализированным программным обеспечением требует либо внимательного ручного контроля, либо очень тщательно настроенного и проверенного автоматического контроля, а лучше &#8211; и того, и другого.</p>
<p>3. отсутствие контроля за тем, что записывается на магнитную ленту. Центральный офис был расположен далеко от основных рабочих мест, ответственный сотрудник не появлялся там по нескольку дней, а при появлении новых важных данных они не были внесены в список того, что надо записывать на ленту. В результате никто не был осведомлен о том, что копирование могло по нескольку дней не выполняться вовсе, да и данные на ленту записывались не все. Часть лент вообще не использовалась, так как была испорчена, и никто не позаботился о закупке новых, что приводило к преждевременному стиранию сравнительно свежих копий. Решение об изменении схемы резервного копирования (перезаписывать кассеты чаще, а неисправные просто выбросить) было принято исполнителем самостоятельно, он никому об этом не рассказал.</p>
<p>Из этого опыта пришлось вынести три правила:<br />
1) нет контроля &#8211; считайте, нет копирования<br />
2) нет контроля надежности резервной копии &#8211; считайте, нет копии<br />
3) исполнитель резервного копирования должен быть надежным, обученным и ответственным</p>
<p><strong>III. Оценивайте риски трезво</strong></p>
<p>Резервное копирование слишком многих файлов или таблиц баз данных вредно, ибо тратит время и пространство. Оцените, какая именно информация вам важна и как часто она изменяется: кому пять дневных копий в неделю &#8211; достаточно, а кому требуются снимки файловой системы каждый час.</p>
<p>Используйте быстрые и надежные носители. Если вы копируете данные на флэшки, помните, что флэшки могут сильно отличаться по скорости записи, покупайте более дорогие, но быстрые экземпляры. Для важных данных используйте две разные флэшки. Перестаньте доверять рекламе: количество циклов перезаписи, которые выдерживает флэшка, на практике &#8211; не десятки тысяч циклов, а всего лишь сотни циклов. Храните флэшки там, где они не могут перегреться. Флэшки нельзя гнуть, швырять или наступать на них, несмотря на то, что ронять их обычно можно без вреда. После года использования переставайте доверять флэшке: она может работать еще долго, но уверенности, что с нее точно можно будет прочесть данные, уже быть не должно. Не жалейте денег на новые резервные носители &#8211; диски и флэшки, терять данные и время &#8211; всегда дороже!</p>
<p>Храните данные в резервных копиях в таком формате, который вы везде сможете прочесть. Так, файловую систему ZFS не поймет никто, кроме Solaris, Mac OS X и FreeBSD, а более свежие ее версии &#8211; только Solaris 11 Express. Диск в формате NTFS прочтется в Linux, но только если в данном экземпляре Linux установлена поддержка этой возможности. Наиболее универсальный формат &#8211; FAT32, но не все версии Windows умеют форматировать большие диски в FAT32 так, чтобы они везде прочлись, может понадобиться специальная программа. Я одну такую однажды купил в Интернете, но забыл название. Впрочем, сойдет любая &#8211; Google расскажет о многих.</p>
<p>Установите четкие пределы ответственности компании и сотрудников: сотрудники должны сами сохранять в надежные места важные для них документы. Компания должны позаботиться об общих файлах проектов, базах данных и проч. Если того требует политика безопасности в компании, сохраняйте централизованно все данные. Помните, что файлы, присланные по почте, сотрудники часто редактируют без записи на свой диск, и поэтому они могут оказаться во временных каталогах, копии которых никто не делает. Требуйте сохранять в отведенные для этого места всю присланную по почте информацию, если ее надо изменять. Почтовый сервер часто не сохраняет изменения в приложенных к письмам файлах.</p>
<p>Если вы занимаетесь веб-разработкой, делайте копии каждого веб-сайта, за который вы отвечаете, ежедневно. Помните, что важно сохранять не только содержание сайта, но и информацию о том, какое программное обеспечение используется для его отображения &#8211; версию веб-сервера, интерпретатора php, perl, базы данных mysql или postgresql и прочего. Если ваш проект связан с установленной на сервере виртуальной машиной Java, знайте точно ее версию. Сотрудники хостинговой компании или ваши коллеги-сисадмины могут без предупреждения изменить версию, и вы должны быть готовы потребовать &laquo;вернуть, как было&raquo;. Для этого надо знать, &laquo;как было&raquo;.</p>
<p>Если у вас есть веб-сайт, который кто-то разработал для вас, не надейтесь, что этот &laquo;кто-то&raquo; сделал резервную копию и запомнил версии установленного на сервере ПО. Сделайте это сами. Запишите версии в блокнот и сообщите всем вокруг, куда вы этот блокнот кладете.</p>
<p>Вся простая, важная и короткая информация типа номеров договоров с провайдером, имена и пароли доступа к важным документам и веб-сайтам должна быть записана на бумаге и сохранена в надежном месте (в сейфе, в банковской ячейке, у нотариуса, у адвоката).</p>
<p>Помните, что резервное копирование делается не только на случай сбоя оборудования, оно еще должно спасать от потопа, пожара и кражи. Поэтому резервные копии надо хранить вдалеке от оригиналов, и лучше всего &#8211; в другом здании, другом городе или даже другой стране. При этом надо заранее оценить время, которое вам требуется для получения доступа к резервной копии и решить, нравится ли оно вам. Нет? Надо поискать такое место для копии, чтобы оно удовлетворяло вас полностью.</p>
<p>Делайте достаточное количество резервных копий, и помните, где вы их храните.</p>
<p><strong>IV. Простые решения</strong></p>
<p>В одной небольшой компании, которая занимается производством вывесок, я наблюдал такой порядок резервного копирования:<br />
каждый вечер после конца рабочего дня владелец и директор компании в одном лице копировал все данные, накопленные за день, на внешний диск. По пятницам он делал полное копирование всех данных вообще. Диск он уносил с собой домой, где у него было еще четыре диска. С только что записанного диска все данные дома он копировал на домашний компьютер. На следующий день он приносил другой диск на работу и все повторялось.</p>
<p>Это &#8211; очень дешевое решение. Оно требует только немного времени и внимания &#8211; всего от одного человека.</p>
<p>Если вы можете использовать в качестве хранилища данных компьютер с системой Solaris 10, OpenSolaris или Solaris 11 Express, надо использовать встроенные средства резервного копирования. Файловая система ZFS, на которую по умолчанию устанавливается OpenSolaris и Solaris 11 Express, и которую можно использовать и в Solaris 10, позволяет сделать снимок всей файловой системы за мгновение: </p>
<p><code>zfs snapshot <имя-файловой-системы>@<имя-снимка></code></p>
<p>Например,</p>
<p><code>zfs snapshot rpool/home/filip@2010-11-16</code></p>
<p>После этого сделанный снимок можно передать по сети или записать на внешний диск командой</p>
<p><code>zfs send</code></p>
<p>Создание таких снимков снимает опасность резервного копирования несогласованных между собой файлов, так как копируется вся файловая система целиком, в том виде, в котором она была в момент создания снимка.</p>
<p>Для хранения самых важных документов можно использовать сервис компании Amazon, <a href="http://s3.amazon.com">s3.amazon.com</a>. Сейчас хранение первых пяти гигабайт файлов там бесплатное, каждый следующий гигабайт обойдется в несколько центов в месяц. Для хранения важных документов и фотографий &#8211; годится. Фильмы, конечно, туда не закачаешь: и долго, и недешево обойдется. Однако &laquo;важные&raquo; здесь не значит &laquo;конфиденциальные&raquo;. Передача по сети и размещение на чужих компьютерах, находящихся не под вашим управлением, любых конфиденциальных данных &#8211; дело крайне рискованное и неразумное. Для таких данных можно использовать флэшку с встроенным модулем шифрации, требующую аутентификации по отпечатку пальца. Такие давно есть.</p>
<p><strong>V. Когда резервное копирование не помогает</strong></p>
<p>Если вы случайно нажали кнопку &laquo;выйти без записи&raquo; после того, как целый день редактировали важный документ, и до того вы этот документ не записывали на диск &#8211; вам не повезло. От невезения резервное копирование не помогает.</p>
<p>Если у вас нет источника бесперебойного питания, это &#8211; тоже невезение. Но зато это легко исправить: такие источники стоят недорого и страхуют от неожиданных всплесков и пропадания электричества достаточно хорошо.</p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/backup-lessons-learned/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Программирование для прагматиков</title>
		<link>http://experience.openquality.ru/elena-sagalaeva-interview/</link>
		<comments>http://experience.openquality.ru/elena-sagalaeva-interview/#comments</comments>
		<pubDate>Fri, 12 Nov 2010 07:18:01 +0000</pubDate>
		<dc:creator>Елена Сагалаева</dc:creator>
				<category><![CDATA[Интервью]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[QA]]></category>
		<category><![CDATA[авралы]]></category>
		<category><![CDATA[уроки]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=237</guid>
		<description><![CDATA[В кругах профессиональных разработчиков Елена Сагалаева (Алена C++) широко известна благодаря своему блогу и докладам на конференциях. Нюансы С++, алгоритмы, геймдев, будущее индустрии, стартапы, обзоры книг &#8211; вот далеко не полный перечень тем, которые Елена поднимает в своих публикациях. Поднимает и раскрывает с присущей ей глубиной и основательностью. Программирование для прагматиков &#8211; название блога Елены и предмет нашего сегодняшнего разговора. Елена, представим, что машина времени существует и есть возможность вернуться &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><em>В кругах профессиональных разработчиков Елена Сагалаева (Алена C++) широко известна благодаря своему блогу и докладам на конференциях. Нюансы С++, алгоритмы, геймдев, будущее индустрии, стартапы, обзоры книг &#8211; вот далеко не полный перечень тем, которые Елена поднимает в своих публикациях. Поднимает и раскрывает с присущей ей глубиной и основательностью. <a href="http://alenacpp.blogspot.com/">Программирование для прагматиков</a> &#8211; название блога Елены и предмет нашего сегодняшнего разговора.</em></p>
<p><em>Елена, представим, что машина времени существует и есть возможность вернуться назад. Что вы посоветуете себе, начинающему разработчику, с высоты ваших текущих знаний? На что стоит обратить первоочередное внимание?</em></p>
<p>Излишний консерватизм, постановка маленьких и жалких целей. Мне следовало больше рисковать, чаще пробовать что-то новое. Я жалею, что не прочитала раньше такие <a href="http://alenacpp.blogspot.com/2006/05/blog-post.html">книги</a> как &laquo;Рефакторинг&raquo; и &laquo;Программист-прагматик&raquo;.</p>
<p><em>Алена C++ в своем первом проекте и Алена C++ сегодня: что изменилось в ваших подходах к созданию приложений, к стилю программирования?</em></p>
<p>В своих первых, студенческих работах, я писала код с целью, чтобы заработало. Теперь целей у меня значительно больше. Это и читаемость (код должны понимать другие, иначе он бесполезен), и необходимость думать о том, как будет развиваться система в целом; думать о производительности; думать о сроках. Сейчас я не боюсь ошибиться.</p>
<p>Я быстро теряла контроль над кодом. Если люди, которые могут запихнуть в голову огромное количество неструктурированного кода. Мне же очень рано пришлось задуматься о таких вещах как &laquo;читаемость&raquo;, &laquo;понижение сложности&raquo;, &laquo;архитектура&raquo;.</p>
<p>Ну и сейчас я зачастую руковожу другими программистами. Это большая ответственность.</p>
<p><em>Елена, сможете ли вы рассказать о наиболее значимой ошибке, которую вам доводилось видеть в проектах?</em></p>
<p>Самые суровые проблемы возникают из-за неверно принятых решений, не из-за того, что программист где-то плюс с минусом перепутал. И из-за попыток решить технические проблемы политическими методами. Или вот, например, ставим работать программистов, которые друг друга не знают. Не назначаем среди них главного и ждем, что они друг с другом договорятся. Они не договорятся. Это классическая запланированная катастрофа, я несколько раз такое наблюдала. Более конкретный пример, который мне запомнился &#8211; применение решений из <a href="http://alenacpp.blogspot.com/2006/09/c_115878321546413680.html">книжки</a> Александреску неопытной командой. Как следствие &#8211; деградация кода и потеря контроля над ним.</p>
<p><em>Какие выводы из подобных ошибок удалось извлечь?</em></p>
<p>Нужно понижать сложность приложений, всегда понижать сложность. Технические решения должны приниматься из сугубо прагматичных соображений, а не потому что &laquo;это клево, давайте попробуем&raquo; или &laquo;иначе Иван Иваныч обидится&raquo;.</p>
<p><em>Елена, расскажите, пожалуйста, о наиболее интересных багах в ваших проектах. Какой баг был самым забавным? Какой баг имел самые катастрофичные последствия, и что послужило его причиной? Какой баг проявлял себя самым непредсказуемым образом, и докопаться до его сути было особенно трудно? Что помогло с ним разобраться?</em></p>
<p>Забавную багу как-то поймал мой коллега. У нас по уровню ходили глаза. Дело было в том, что юниты не убивались до конца и застывали на последнем кадре анимации, на глазах собственно. Это был простой баг, он быстро его поправил.</p>
<p>Катастроф как-то не случалось&#8230; Я работала в двух противоположных условиях. Либо с очень хорошо организованным тестированием и там продукт с серьезными багами просто бы не прошел QA. Либо с очень плохим тестированием, и там любая бага была катастрофой и на них вообще переставали обращать внимания. Сейчас, когда я могу влиять на организацию проекта, я стараюсь не доводить до того, чтобы баги, рутинная в-общем проблема, могли быть причиной катастрофы.</p>
<p>Вечная проблема С++ программистов &#8211; это проблемы с памятью. Порча памяти &#8211; непредсказуемость живет здесь. Пытаемся повысить воспроизводимость. Смотрим что у нас происходит в памяти, что было затерто, чем было затерто. Очень помогает наличие дебажной кучи в VC++. Утечки памяти &#8211; тоже проблема, но с ними тоже известно как бороться.</p>
<p>Докопаться до сути багов сложно, если баги живут в сторонней библиотеке, от которой у тебя нет исходников. Очень не люблю такие библиотеки, обычно выступаю против их использования в проекте, если есть альтернативы. Ну и сложно работать, когда проблема проявляется на конфигурации, которую ты по каким-либо причинам не можешь восставновить и работать приходится вслепую.</p>
<p><em>Применяете ли вы TDD? Оправдан ли этот подход в ваших проектах?</em></p>
<p>Нет. Я работала в основном со спокойными дисциплинированными специалистами. Практики вроде TDD хороши для команд, склонных к ковбойству.</p>
<p><em>Каковы признаки плохого (ковбойского) кода? Можете ли вы привести примеры?</em></p>
<p>Сложнее всего работать не с кодом ковбоев, а с кодом, который написан демотивированными программистами. Код, который переходил от человека к человеку и им всем было все равно куда процесс движется, общего видения ни у кого не было. Они просто чинили потихоньку баги, за которые их били.</p>
<p>Внешние признаки, которые мне встречались: несоблюдение стандартов кодирования или их полное отсутствие, вплоть до того, что отступы могут &laquo;гулять&raquo; в одной и той же функции. Сильная связность кода, нарушение инкапсуляции. Неправильно выстроенное наследование, нарушенное <a href="http://alenacpp.blogspot.com/2005/12/is.html">IS-A отношение</a>. Волшебные константы (magic numbers). Странное именование переменных, полное отсутствие комментариев и документации. Как-то, распутывая кусок кода, наткнулась на комментарий //Fuck!!!!!. Это тоже плохой признак. :-)</p>
<p>В качестве примера могу привести код, который мне встречался в разных проектах и от которого я пыталась избавиться. Встречается у программистов, которые недавно перешли на С++ с С. Для того, чтобы обеспечить полиморфное поведение, они часто используют типично сишный прием: объявляют enum, в нем задают что-то типа TYPE_CIRCLE, TYPE_TRIANGLE и т.п. Потом в коде пишут длинный-длинный if.</p>
<p>
<pre>
if( type == TYPE_CIRCLE ){
... }
else if( type == TYPE_TRIANGLE ){
...}
else if ...
</pre>
</p>
<p>Такой код не является &laquo;плохим&raquo; сам но себе. Но при увеличении количества типов код становится все труднее и труднее контролировать. Это можно переделать на полиморфную иерархию, я несколько раз проводила такой рефакторинг.</p>
<p><em>Как сделать код более надежным, более читабельным?</em></p>
<p>Если вы пишете код, думая в о задаче, которую решаете, о требованиях менеджмента, о других программистах, которые будут читать ваш код, то он будет понятным, читаемым и, как следствие, более надежным. Если ваша цель &#8211; поднятие самооценки, то код быстро превратится в бесполезную нечитаемую кучу. В &laquo;Совершенном коде&raquo; есть хороший простой совет: &laquo;Пишите код так, будто человек, который будет его поддерживать &#8211; маньяк-психопат, который знает, где вы живете&raquo;.</p>
<p>Если есть проблемы с читаемость и надежностью уже существующего кода, это лечится рефакторингом. Мартин Фаулер в &laquo;Рефакторинге&raquo; все отлично рассказал.</p>
<p><em>Как обеспечить возможность легкого изменения кода в будущем?</em></p>
<p>Я стараюсь не делать железобетонные конструкции, которые никогда, ни в коем случае нельзя менять. Постоянно задаю себе вопрос &laquo;что будет если вот это решат поменять, как я буду действовать?&raquo;. Спрашиваю менеджеров про дальнейшие планы. Они, конечно, не смогут заранее предсказать все изменения, которые потребуются, но хотя бы часть их них смогут. Тогда в коде на это можно будет заложиться и написать комментарий или дописать в документацию &laquo;если менеджеры таки решат добавить летающие юниты, то это потребует таких-то действий в таких-то файлах&raquo;. Или &laquo;потребуется такой-то рефакторинг&raquo;.</p>
<p><em>Выбор языка программирования для создания приложения: насколько он важен?</em></p>
<p>Язык важен. Важно и то, чтобы решение об использовании какого-либо языка принималось всей командой. Если вы придете к С++ программистам и скажете, что с завтрашнего дня мы все пишем на PHP, они несколько расстроятся.</p>
<p><em>Может ли тот или иной язык сделать код более качественным, а разработку более эффективной? Сможете привести примеры?</em></p>
<p>Очень наглядный пример: сегодня неразумно заниматься веб-разработкой на С++. На вебе сейчас популярны другие языки. Это Ruby, Python, PHP. Также ставятся эксперименты с языками типа Erlang. Эти языки и безопаснее, и разработка на них пойдет быстрее, и специалистов обучить будет проще. На С++ если и писать, то какие-то высоконагруженные куски.</p>
<p><em>Табу для разработчика: каких ошибок не стоит допускать при создании программных продуктов?</em></p>
<p>Никогда не говори &laquo;никогда&raquo;. Я стараюсь действовать по обстановке, избегать карго-культа. Решения, над которыми я буду напряженно думать, прежде чем принять: использование сторонних библиотек без исходников, переписывание вместо рефакторинга.</p>
<p>Я предпочитаю простые решения сложным. Не забываю про <a href="http://en.wikipedia.org/wiki/Leaky_abstraction">закон текущих абстракций</a> и про <a href="http://c2.com/cgi/wiki?TruckNumber">число грузовика</a> (TruckNumber; иногда его называют числом трамвая или <a href="http://en.wikipedia.org/wiki/Bus_factor">автобуса</a>). В геймдеве также очень важно, чтобы конвейер разработки не останавливался ни в коем случае.</p>
<p><em>Елена, что представляет собой конвейер разработки и почему важно, чтобы он не останавливался ни в коем случае?</em></p>
<p>В разработке игры участвуют люди разных специализаций. Художники, аниматоры, дизайнеры уровней и т.п. Работа выстраивается таким образом, что результат работы одних передаются другим, получается конвейер. Программисты могут влиять на работу многих частей этого конвейера. Например, если у нас не запускается редактор уровней, то дизайнеры уровней не могут работать, геймдизайнеры не могут проверять какие-то свои идеи. Возможно, художники еще не могут увидеть как выглядят результаты их работы в игре. Остановка конвейера означает простой дорогостоящих специалистов, потерю времени, ну и нервничать и ругаться они будут. Поэтому надо как можно быстрее поднять наш редактор, бросив все. Пускай при этом часть функциональности будет отключена или будет глючить &#8211; лучше так, чем совсем никак. Просто для примера <a href="http://www.gamasutra.com/db_area/images/feature/4016/fig1.png">картинка</a> из вот этой <a href="http://www.gamasutra.com/view/feature/4016/game_tools_tuneup_optimize_your_.php">статьи</a>.</p>
<p><em>Каковы, на ваш взгляд, наиболее важные навыки для эффективного программиста? По каким критериям вы оцениваете потенциального сотрудника?</em></p>
<p>Нам нужен программист, который знает алгоритмы и структуры данных. Умеет оценивать сроки, чинить баги, рефакторить код, работать с унаследованным кодом без нытья и жалоб. Умеет оптимизировать код как по скорости, так и по памяти. Хорошо знает синтаксис языков и архитектуру платформ, с которыми мы работаем. Который постоянно в курсе новых тенденций в программировании. И так далее&#8230;</p>
<p>И все эти качества невозможно найти в одном человеке. Цель руководителя проекта &#8211; собрать такого супербизона из нескольких программистов. Так что ищем тех, кого нам не хватает, критерии сильно зависят от проекта и текущих задач. То есть, если у нас на проекте проблемы с оценкой сроков, надо искать программиста, который умеет хорошо оценивать сроки, и спрашивать не только алгоритмы и структуры данных&#8230; И не нужно брать на работу блестящего алгоритмиста, если предстоит долгая рутинная работа по рефакторингу огромной базы кода. Он довольно быстро уволится.</p>
<p>Вне зависимости от квалификации в целом бесполезны люди, которым наплевать на результат, которым все равно. Тут речь не о часто упоминающемся &laquo;блеске в глазах&raquo;. Можно без него, можно построить эффективную работу с циничными профессионалами, у которых глаза не блестят, но которые делают работу &laquo;от и до&raquo;.</p>
<p><em>Команда QA в большом проекте. Каковы сильные качества наиболее полезных тестировщиков? С какими тестировщиками наиболее комфортно работать? В каких условиях отдача от тестировщиков наиболее ощутима?</em></p>
<p>Мне повезло, я работала с блестящими QA-специалистами. Которые умеют сломать любую софтину, воспроизвести это и помочь программисту все исправить. На страдания программиста &laquo;ну может показалось, можете все-таки работает&raquo; реагируют с юмором и тактично, но настойчиво, объясняют, что проблема все-таки есть. При этом они понимают, что приносят, как правило, невеселые вести, поэтому ведут себя очень дипломатично. Их задача &#8211; не вогнать программистов в депрессию, а не дать выйти плохой софтине. Правда, мне приходилось столкнуться с тестировщиком, который возмущался при нахождении багов и отчитывал программистов. Один такой был. Но это клиника, конечно.</p>
<p><em>Гибкие методики (Agile, XP, Scrum): насколько они эффективны на практике?</em></p>
<p>Не работала ни с одной из них, но имею общее представление, знаю о командах, которые их используют. Они хорошо работают, если не относиться к ним как к карго-культу и выбирать практику, которую поддерживает вся команда.</p>
<p><em>Deadlines, авралы. Можно ли их избежать?</em></p>
<p>Deadline &#8211; это срок к которому должен быть сдан проект или часть проекта. Это нормально, они нужны. Авралы же &#8211; ненормальное явление. И они очень-очень сильно сказываются на морали и качестве кода. Как правило, признаки надвигающегося аврала видны задолго до. Часто их игнорируют. Недопущение авралов &#8211; это обязанность руководителей проекта. Я работала в авральных ситуациях, когда аврал был следствием ошибки менеджмента. Это нормально, все люди ошибаются. Задача программистов тогда: исправить это по мере сил. Но гораздо чаще бывает, что аврал &#8211; не следствие ошибки, а спланированная политика. Программисты работают дольше, а то и в выходные, и создается ощущение, что мы за те же деньги получаем больше.</p>
<p><em>Как вы планируете сроки (время, которое планируется на создание той или иной функциональности)?</em></p>
<p>Мне нравится метод Джоэла Спольски, он у себя в блоге <a href="http://www.joelonsoftware.com/items/2007/10/26.html">учил</a> оценивать время работы в часах. После некоторой тренировки это начинает хорошо получаться. Для того, чтобы можно было с такими сроками работать, требуется сильный менеджер, который четко знает чего хочет. С лучшим менеджером, с которым мне приходилось работать, мы работали над проектом с очень короткими и жесткими сроками, оценивали работу в часах и нам удалось очень хорошо попасть в наши оценки. <br /><em>[От редакции: <a href="http://alenacpp.blogspot.com/2010/11/blog-post_15.html">новая статья</a> Елены на тему временных оценок в программировании]</em>.</p>
<p><em>Существуют ли какие-нибудь эмпирические коэффициенты?</em></p>
<p>А, да, я слышала про &laquo;умножай на три&raquo;. Нет, это не работает. Если программист не умеет оценивать сроки, то подбором коэффицента эту проблему не решить.</p>
<p><em>Елена, каких возможностей вам не хватает в современных языках программирования?</em></p>
<p>Не хватает автоматического распараллеливания кода. Когда эту работу пытаются делать программисты, получается плохо, багов слишком много, баги неприятные, трудно воспроизводятся. Я сейчас наблюдаю за решениями, за языками, в которых пытаются это сделать. В этом смысле очень интересно выглядит язык Clojure, а также GpH (Glasgow Parallel Haskell). Возможно, транзакционная память поможет решить некоторые проблемы конкурентного программирования.</p>
<p>В мире С++ не хватает хорошего статического анализа кода. Все же слишком много ошибок у нас пролезает на этап выполнения. Но существующие статические анализаторы много шумят, не все находят и поэтому мало где применяются.</p>
<p><em>Что ожидает программирование и программистов в будущем?</em></p>
<p>Такие предсказания &#8211; вещь неблагодарная. Так что не буду долго гадать и растекаться мыслью. Я надеюсь, что разработка квантовых компьютеров наконец даст серьезные результаты. Боинформатика набирает обороты, думаю, что программисты с хорошим знанием биологии (генетики в основном) будут все нужнее и нужнее. В местах, где занимаются поиском, все еще будут нужны программисты со знанием лингвистики.</p>
<p><em>Что нового, на ваш взгляд, появится в языках и средствах разработки?</em></p>
<p>На мой взгляд, нужны средства массовой групповой разработки. Речь идет о работе сотен, тысяч программистов, работающих удаленно. В основном это нужно свободным open source проектам. Текущие решения все равно требуют пристального внимания небольшого количества человек, к которым в итоге стекается весь код, и это становится узким местом. GitHub &#8211; хороший большой шаг в этом направлении.</p>
<p><em>Елена, что вам хотелось бы изменить в развитии IT?</em></p>
<p>Все плачут от существующей системы американских патентов, которая каким-то невообразимым образом влияет на всех. На нее постоянно все жалуются, но решений этой проблемы нет. Можно запатентовать всем известные вещи или туманно описанные технологии и начать судиться, требовать за их использование  денег. Это привело к такому неприятному явлению как <a href="http://ru.wikipedia.org/wiki/%D0%9F%D0%B0%D1%82%D0%B5%D0%BD%D1%82%D0%BD%D1%8B%D0%B9_%D1%82%D1%80%D0%BE%D0%BB%D0%BB%D1%8C">патентные тролли</a>. Вот хорошая <a href="http://bits.blogs.nytimes.com/2010/03/04/an-explosion-of-mobile-patent-lawsuits/">иллюстрация</a>.</p>
<p><em>Елена, большое спасибо за интервью. Успехов в ваших проектах!</em></p>
<p></body></html></p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/elena-sagalaeva-interview/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

