<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	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/"
	>

<channel>
	<title>OpenQuality.ru &#124; Качество программного обеспечения &#124; Опыт экспертов</title>
	<atom:link href="http://experience.openquality.ru/feed/" rel="self" type="application/rss+xml" />
	<link>http://experience.openquality.ru</link>
	<description>Что такое качество программного обеспечения и как его улучшить</description>
	<lastBuildDate>Thu, 14 Aug 2014 05:31:08 +0000</lastBuildDate>
	<language>ru</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>SObjectizer: прошлое, настоящее, будущее</title>
		<link>http://experience.openquality.ru/sobjectizer-overview/</link>
		<comments>http://experience.openquality.ru/sobjectizer-overview/#comments</comments>
		<pubDate>Thu, 14 Aug 2014 05:29:54 +0000</pubDate>
		<dc:creator>Евгений Охотников</dc:creator>
				<category><![CDATA[Истории]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[архитектура]]></category>
		<category><![CDATA[техпроцесс]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=631</guid>
		<description><![CDATA[История проекта SObjectizer, архитектура продукта, принцип работы, примеры использования, особенности процесса разработки и тестирования, мягкая диктатура, релизная политика, просчеты и извлеченные уроки, прицел на будущее.]]></description>
			<content:encoded><![CDATA[<p><em>История проекта SObjectizer, архитектура продукта, принцип работы, примеры использования, особенности процесса разработки и тестирования, мягкая диктатура, релизная политика, просчеты и извлеченные уроки, прицел на будущее.</p>
<p>Команда проекта: Гродзицкий Николай, Охотников Евгений, Сивко Борис, Суднеко Артур, Томашенко Денис, Шмаков Николай</em></p>
<h2>Прошлое</h2>
<h3>Очень давнее прошлое</h3>
<p>Историю <em><a href="http://sourceforge.net/projects/sobjectizer/">SObjectizer</a></em> можно начинать с 1995 года, когда в отделе АСУТП Гомельского КБ Системного Программирования собралась команда для разработки объектно-ориентированной SCADA-системы. Этой командой был придуман способ построения приложения из агентов, обменивающихся асинхронными сообщениями. Способ этот был реализован в виде <a href="http://en.wikipedia.org/wiki/SCADA">SCADA</a>-системы под названием <em>SCADA Objectizer</em>, где слово <em>Objectizer</em> отражало активное использование ООП.</p>
<p>На разработку первой версии <em>SCADA Objectizer</em> в очень непростых экономических условиях ушло около трех лет. В 1999 году в эксплуатацию была успешно запущена разработанная на <em>SCADA Objectizer</em> система управления производством комбикорма на одном из местных комбинатов хлебопродуктов. К началу 2000 года была сделана вторая, более продвинутая версия <em>SCADA Objectizer</em>.</p>
<p>Две первые версии пытались идти по традиционному пути SCADA-пакетов: было разделение на GUI-пакет конструктора АСУТП и отдельную систему исполнения. К сожалению, сил на развитие системы в таком виде у команды не хватило.</p>
<p>В 2000 году был проведен эксперимент и создана третья версия <em>Objectizer</em>, в которой описание АСУТП создавалось не в GUI-конструкторе, а на специализированном декларативном языке. Это описание транслировалось в C++ код, который компилировался и линковался с системой исполнения. На выходе получался программный модуль, решающий конкретную прикладную задачу. Эксперимент был очень интересным, подтолкнувшим к переосмыслению механизма агентов, пониманию их удобства для разработки распределенных приложений, но &#8230;</p>
<p>Но все работы над <em>SCADA Objectizer</em> в КБСП фактически прекратились к концу 2000 года, когда распалась команда, занимавшаяся разработкой <em>SCADA Objectizer</em>. Более подробно история этого периода описана в старой <a href="http://www.nestor.minsk.by/kg/2000/37/kg03710.html">статье</a> в “Компьютерной Газете”.</p>
<h3>Давнее прошлое</h3>
<p>Новый виток в развитии истории <em>SObjectizer</em> начался в 2001 году, когда двое бывших разработчиков <em>SCADA Objectizer</em> из КБСП оказались в компании <a href="http://www.intervale.ru/ru/">“Интервэйл”</a>.</p>
<p>В одной из первых разработок, которую автор статьи выполнял в компании “Интервэйл”, потребовалось реализовать обмен асинхронными сообщениями между несколькими рабочими нитями приложения. Тогда стало очевидно, что идеи <em>агентного подхода</em> могут быть востребованы не только в АСУТП, и что механизм использования агентов и асинхронной передачи сообщений между ними может очень успешно применяться в многопоточных приложениях, в том числе и в распределенных.</p>
<p>Поэтому весной 2002 года появилась новая реализация старых идей под названием <em>SObjectizer-4</em>. Эта версия более-менее активно развивалась с 2002 по 2004 годы, и на ее основе было разработано и запущено в эксплуатацию несколько проектов в компании “Интервэйл”.</p>
<p>В 2005 году стало очевидно, что развитие <em>SObjectizer-4</em> внутри компании “Интервэйл” замедлилось. Поэтому, после длительных размышлений и сомнений, было принято решение выпустить SObjectizer-4 как проект Open Source. Так, в 2006 году <em>SObjectizer</em> появился на <a href="http://sourceforge.net/projects/sobjectizer/">SourceForge</a> под BSD-лицензией, а  в журнале RSDN Magazine &#8211; большая <a href="http://www.rsdn.ru/article/devtools/sobjectizer.xml">статья</a> об этом проекте.</p>
<h3>Недавнее прошлое</h3>
<p>С 2002 года по настоящее время <em>SObjectizer-4</em> активно применяется в нескольких больших проектах внутри компании “Интервэйл”. Время показало, что в <em>SObjectizer-4</em> допущено несколько <em>просчетов</em>, которые усложняют его использование и являются серьезными препятствиями к увеличению производительности SObjectizer-4 и построенных на его основе приложений: </p>
<div style="margin-left: 20px;">
<ul>
<li>Использовался один общий системный словарь, в котором находились описания всех коопераций, агентов, сообщений, событий и т.д. Доступ к этому словарю требовал синхронизации, что снижало общую производительность: слишком много времени рабочие нити ждали освобождения словаря.</li>
<p></p>
<li>Все сущности в <em>SObjectizer-4</em> идентифицировались строковыми именами. Это так же увеличивало накладные расходы за счет необходимости частого сравнения строковых имен.</li>
<p></p>
<li>Мешало отсутствие контроля со стороны компилятора. Пользователь мог ошибиться в содержимом строковой константы, но эта ошибка проявлялась только во время исполнения. Хотелось, чтобы большая часть подобных опечаток обнаруживалась во время компиляции, что позволило бы снизить количество проверок во время работы <em>SObjectizer</em>.</li>
<p> </p>
<li>Также, в некоторых случаях, серьезным препятствием было наличие одного общего системного словаря, который представлял из себя глобальную переменную. Это не позволяло запустить в приложении несколько независимых друг от друга экземпляров <em>SObjectizer-4</em>.</li>
</ul>
</div>
<p>В 2010 году стартовали работы по созданию новой версии &#8211; <em>SObjectizer-5</em>. Основные работы по созданию <em>SObjectizer-5</em> были проведены в течении 2010-2011 годов, после чего <em>SObjectizer-5</em> стал активно использоваться в разработке новых программных модулей, постепенно вытесняя <em>SObjectizer-4</em>. Однако первый публичный релиз <em>SObjectizer-5</em> состоялся на SourceForge лишь в мае 2013 года после того как <em>SObjectizer-5</em> в достаточной мере стабилизировался.</p>
<h3>Области применения и результаты</h3>
<p>Внутри компании “Интервэйл” <em>SObjectizer-4</em> и <em>SObjectizer-5</em> применяются в ряде проектов, которые можно охарактеризовать как <em>mission critical</em>. </p>
<p>Самым крупным из них является транспортная платформа для обслуживания SMS- и USSD-трафика. На начало 2013 года данная платформа могла показывать пропускную способность до 1500 SMS в секунду в пиковые периоды и работала под нагрузкой в несколько миллионов SMS/USSD-сообщений в день.</p>
<p>Меньшим по размеру и по нагрузкам, но не менее требовательным к качеству и надежности, был комплекс для обслуживания платежных транзакций в адрес операторов мобильной связи для одного из крупнейших российских банков.</p>
<p>Оба этих проекта представляют из себя многокомпонентные программные комплексы, развернутые на нескольких серверах. По сути, это два сложных, распределенных приложения, полностью построенных на основе <em>SObjectizer</em>. Примечательно то, что над данными проектами работала совсем небольшая команда, численность которой в лучшие времена составляла всего лишь шесть человек.</p>
<p>В последние годы бытует мнение, что C++ не является подходящим инструментом для разработки приложений подобного рода. Однако опыт использования <em>SObjectizer</em> говорит, что это далеко не так. У языка C++ есть свои недостатки, и для каких-то прикладных задач в C++ может просто не оказаться нужных инструментов. Однако, при наличии таковых, разработка нагруженных распределенных приложений на C++ оказывается вовсе не сложной, и с большими проектами могут справиться небольшие команды. Что, собственно, и подтверждалось проектами, которые были выполнены как на <em>SCADA Objectizer</em>, так и на <em>SObjectizer-4</em> и <em>SObjectizer-5</em>.</p>
<h2>Настоящее</h2>
<h3>Что такое SObjectizer-5</h3>
<h4>Состав</h4>
<p>Под названием <em>SObjectizer-5</em> скрывается несколько взаимосвязанных разработок. Есть ядро <em>SObjectizer-5</em> &#8211; это проект <em>so_5</em>. Именно <em>so_5</em> содержит средства для создания агентов и сообщений, а также среду исполнения, которая поддерживает обмен и обработку сообщений агентами.</p>
<p>Над <em>so_5</em> построены дополнительные библиотеки, их принято называть подпроектами. Эти библиотеки не расширяют понятие агентной модели, но зато существенно упрощают разработку больших распределенных приложений на основе <em>SObjectizer</em>.</p>
<p>Библиотека <em>so_log_2</em> является тонкой оберткой над <a href="http://www.cs.wustl.edu/~schmidt/ACE-overview.html">ACE</a> Logging и упрощает логирование информации из <em>SObjectizer-агентов</em>.</p>
<p>Библиотека <em>so_5_transport</em> предоставляет набор транспортных агентов, скрывающих специфику работы с TCP/IP-соединениями от прикладных агентов.</p>
<p>Библиотека<em> mbapi_4</em>, используя возможности <em>so_5_transport</em>, дает разработчику еще более высокоуровневый механизм обмена прикладными сообщениями. На основе <em>mbapi_4</em> разрабатываются распределенные приложения с прозрачным перемещением сообщений через границы процессов и узлов сети. То есть, агенту, отправляющему через <em>mbapi_4</em> сообщения другому агенту, даже не нужно знать, находится ли получатель в том же самом процессе или же в другом процессе на другом узле сети.</p>
<p>Библитека <em>so_sysconf_4</em> позволяет размещать фабрики для прикладных агентов в отдельных DLL, а затем строить приложение из этих DLL, как из кубиков в конструкторе LEGO.</p>
<p>В целом, <em>SObjectizer-5</em> представляет из себя слоеную конструкцию, основа которой &#8211; проект <em>so_5</em>,  отвечающий лишь за поддержку агентной модели. Этой основы может быть вполне достаточно, например, для разработки мелких утилит или приложений, базисом к которых служат другие фреймворки, такие как <em>Qt</em> или <em>wxWidgets</em>.</p>
<p>Если основы в виде проекта <em>so_5</em> недостаточно, то можно добавлять к ней дополнительные слои &#8211; <em>so_log_2</em> и <em>so_5_transport</em>. Если и этого недостаточно, то еще и <em>mbapi_4</em> и/или <em>so_sysconf_4</em>. </p>
<h4>Агентная модель</h4>
<p>Агентная модель, вокруг которой построен <em>SObjectizer</em>, состоит из нескольких базовых понятий.</p>
<p><em>Агент</em> &#8211; это объект класса, унаследованного от специального базового класса <em>agent_t</em>.</p>
<p><em>Сообщение</em> &#8211; это объект класса, унаследованного от специального базового класса <em>message_t</em>. Сообщения в <em>SObjectizer</em> передаются как динамически созданные объекты. Ответственность за их удаление берет на себя <em>SObjectizer</em>.</p>
<p>Сообщения делятся на две категории: обычные сообщения с данными внутри и сигналы, то есть пустые сообщения, для которых важен лишь сам факт их возникновения. Например, информация о вычитанном из сокета пакете данных передается посредством обычного сообщения (прочитанный пакет находится внутри сообщения), а уведомление о том, что пользователь хочет прервать работу приложения &#8211; сигналом (никаких данных внутри сообщения нет).</p>
<p><em>Почтовый ящик (mbox)</em> &#8211; это место, куда сообщения отсылаются и откуда они забираются агентами. Почтовые ящики в <em>SObjectizer</em> позволяют организовать взаимодействие как на основе модели <em>publish-subscribe</em>, так и по принципу <em>peer-to-peer</em>.</p>
<p>Отсылка сообщения может быть сиюминутной (сообщение сразу попадает в <em>mbox</em>) и отложенной (сообщение попадает в <em>mbox</em> только по прошествии указанного времени).</p>
<p>Понятие <em>состояния</em>. На идеологическом уровне состояние определяет, какие сообщения и каким именно образом агент будет обрабатывать, находясь в этом состоянии. В коде  каждое состояние описывается экземпляром специального типа <em>state_t</em> из состава <em>SObjectizer</em>.</p>
<p><em>Событие</em> &#8211; это реакция агента на конкретный тип сообщения в конкретном состоянии. Эта реакция реализуется посредством обработчика события (обычно это метод агента).</p>
<p><em>Диспетчер</em> &#8211; это объект, который отвечает за предоставление агентам рабочего контекста (т.е. рабочей нити), на котором будет запущен обработчик события. В состав <em>SObjectizer</em> входят пять штатных диспетчеров, реализующих разные политики предоставления агентам рабочих контекстов: от самого простого, в котором все агенты работают на контексте одной общей нити, до самого сложного, который использует пул потоков и позволяет выполнять несколько обработчиков событий одного агента параллельно на нескольких нитях. При запуске <em>SObjectizer</em> пользователь может создать произвольное количество диспетчеров, дав каждому из них уникальное имя. А затем привязывать своих агентов к разным диспетчерам в зависимости от того, какой диспетчер наиболее удобен для выполнения работы агента.</p>
<p>Взаимодействие между агентами может быть <em>асинхронным</em> (до недавнего времени это был единственный способ общения агентов друг с другом и все еще остается наиболее предпочтительным) и <em>синхронным</em> (с бесконечными или ограниченным по времени ожиданием ответа). При синхронном взаимодействии, однако, есть опасность возникновения тупиков, поэтому этот способ следует применять с особой осторожностью.</p>
<p>Еще одно важное понятие &#8212; это <em>кооперация агентов</em>. Иногда над одной задачей работают сообща несколько агентов. Например, первый агент читает сокет, второй агент обрабатывает и преобразовывает полученные данные, а третий агент пишет новые данные в базу данных. Эти три агента объединяются в одну кооперацию, и в таком виде они единовременно добавляются в <em>SObjectizer</em> (регистрируются) и изымаются из <em>SObjectizer</em> (дерегистрируются).</p>
<h4>Принцип работы</h4>
<p>В общих чертах принцип работы <em>SObjectizer</em> (точнее, его ядра, <em>so_5</em>) довольно прост.</p>
<p>Пользователь запускает среду исполнения <em>SObjectizer (SObjectizer Environment)</em> и регистрирует необходимое количество коопераций с прикладными агентами. При регистрации агенты привязываются к диспетчерам, состав и количество которых так же определяется пользователем.</p>
<p>Агенты отсылают сообщения в почтовые ящики (<em>mboxes</em>). В почтовом ящике для каждого типа сообщения хранится список его получателей, т.е. агентов, которые подписались на сообщения этого типа из этого почтового ящика. Для каждого получателя создается заявка на обработку этого экземпляра сообщения.</p>
<p>Заявки отдаются диспетчерам, к которым привязаны агенты-получатели. Там заявки складываются в соответствующие очереди (у каждого диспетчера своя система очередей, наиболее подходящая для диспетчера). Рабочие нити диспетчеров разбирают содержимое очереди заявок и, когда доходят до соответствующей заявки, вызывают обработчик события у агента-получателя, передавая в него экземпляр сообщения.</p>
<p>На практике все несколько сложнее. Например, при вызове обработчика события, проверяется, а может ли агент вообще обработать это сообщение в своем текущем состоянии. Также <em>SObjectizer</em> следит за тем, в какой момент экземпляр сообщения можно безопасно уничтожить (ведь это объект в динамической памяти, которую нужно освободить, когда этот объект перестает быть нужным). Но общий принцип именно таков.</p>
<p>Несколько слов о <em>синхронном</em> взаимодействии агентов. Синхронность существует только для агента, который инициирует запрос. Этот запрос трансформируется в специальное внутреннее сообщение <em>SObjectizer</em>, и обработчик запроса обрабатывает его как обычное асинхронное сообщение (т.е. через очередь заявок диспетчера). Все это время отправитель запроса “спит” в ожидании ответа, а <em>SObjectizer</em> “будит” его после того, как заявка с запросом будет обработана.</p>
<h4>Пример агента</h4>
<p>Допустим, что есть агент, занимающийся опросом датчика температуры воздуха. Этому агенту можно отослать синхронный запрос и получить текущее значение датчика. Но время выполнения такого запроса исчисляется несколькими секундами. Приостанавливать на это время работу прикладного агента, которому потребовалось текущее значение, нежелательно. Ниже показано, как может выглядеть вспомогательный агент-прокси, который будет кэшировать значение температуры воздуха, опрашивая счетчик только если полученная ранее информация устарела. Кроме того, агент-прокси будет работать с внешним миром асинхронно, то есть. когда у него запрашивают информацию, он в ответ отсылает сообщение с текущим значением.</p>
<p>Дабы не опрашивать датчик при каждом запросе, агент-прокси делает не более одного опроса в течении минуты. Если с момента последнего опроса прошло меньше минуты, то агент просто сразу отдает сохраненное значение.</p>
<p>Агент-прокси имеет два состояния: <em>st_expired</em>, которое означает, что у агента нет актуального значения температуры, и при поступлении запроса нужно заново опрашивать датчик, и <em>st_actual</em>, которое означает, что текущее значение актуально, и его можно сразу отдавать в ответ на запрос.</p>
<p>Агент-прокси реагирует на два сообщения. Первое сообщение, <em>msg_get_current_data</em>, &#8211; это запрос текущего значения. В этом сообщении агенту-прокси передается <em>mbox</em>, на который нужно будет отослать ответное сообщение с текущим значением.</p>
<p>Второе сообщение, <em>msg_data_expired</em>, &#8211; это сигнал о том, что с момента прошлого опроса прошла минута, и значение перестало быть актуальным.</p>
<p>Агент-прокси отсылает два сообщения. Первое сообщение, <em>msg_current_data</em>, используется для выдачи текущего значения температуры. Второе сообщение-сигнал, <em>msg_acquire_value</em>, выдается в виде синхронного запроса к агенту-датчику.</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">using</span> <span style="color: #0000ff;">namespace</span> so_5<span style="color: #008080;">;</span>
<span style="color: #0000ff;">using</span> <span style="color: #0000ff;">namespace</span> so_5<span style="color: #008080;">::</span><span style="color: #007788;">rt</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #666666;">// Запрос текущего значения.</span>
<span style="color: #0000ff;">struct</span> msg_get_current_data <span style="color: #008080;">:</span> <span style="color: #0000ff;">public</span> message_t
<span style="color: #008000;">&#123;</span>
  <span style="color: #666666;">// Обратный адрес, на который нужно отсылать ответ.</span>
  <span style="color: #0000ff;">const</span> mbox_ref_t m_return_mbox<span style="color: #008080;">;</span>
&nbsp;
  msg_get_current_data<span style="color: #008000;">&#40;</span> mbox_ref_t return_mbox <span style="color: #008000;">&#41;</span>
    <span style="color: #008080;">:</span> m_return_mbox<span style="color: #008000;">&#40;</span> std<span style="color: #008080;">::</span><span style="color: #007788;">move</span><span style="color: #008000;">&#40;</span>return_mbox<span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span>
  <span style="color: #008000;">&#123;</span><span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #666666;">// Ответное сообщение с текущим значением температуры.</span>
<span style="color: #0000ff;">struct</span> msg_current_data <span style="color: #008080;">:</span> <span style="color: #0000ff;">public</span> message_t
<span style="color: #008000;">&#123;</span>
  <span style="color: #0000ff;">float</span> m_value<span style="color: #008080;">;</span>
&nbsp;
  msg_current_data<span style="color: #008000;">&#40;</span> <span style="color: #0000ff;">float</span> value <span style="color: #008000;">&#41;</span> <span style="color: #008080;">:</span> m_value<span style="color: #008000;">&#40;</span>value<span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#123;</span><span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #666666;">// Сигнал о том, что текущее значение перестало быть актуальным.</span>
<span style="color: #0000ff;">struct</span> msg_data_expired <span style="color: #008080;">:</span> <span style="color: #0000ff;">public</span> signal_t <span style="color: #008000;">&#123;</span><span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #666666;">// Сигнал агенту-датчику о необходимости опросить датчик температуры.</span>
<span style="color: #0000ff;">struct</span> msg_acquire_value <span style="color: #008080;">:</span> <span style="color: #0000ff;">public</span> signal_t <span style="color: #008000;">&#123;</span><span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #0000ff;">class</span> a_temperature_meter_proxy_t <span style="color: #008080;">:</span> <span style="color: #0000ff;">public</span> agent_t
<span style="color: #008000;">&#123;</span>
<span style="color: #0000ff;">private</span> <span style="color: #008080;">:</span>
  <span style="color: #666666;">// Состояния агента.</span>
  <span style="color: #0000ff;">const</span> state_t st_expired <span style="color: #000080;">=</span> so_make_state<span style="color: #008000;">&#40;</span> <span style="color: #FF0000;">&quot;expired&quot;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
  <span style="color: #0000ff;">const</span> state_t st_actual <span style="color: #000080;">=</span> so_make_state<span style="color: #008000;">&#40;</span> <span style="color: #FF0000;">&quot;actual&quot;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
  <span style="color: #666666;">// Адрес агента, отвечающего за взаимодействие с датчиком.</span>
  <span style="color: #0000ff;">const</span> mbox_ref_t m_meter_mbox<span style="color: #008080;">;</span>
&nbsp;
  <span style="color: #666666;">// Последнее полученное от датчика значение.</span>
  <span style="color: #0000ff;">float</span> m_last_value<span style="color: #008080;">;</span>
&nbsp;
<span style="color: #0000ff;">public</span> <span style="color: #008080;">:</span>
  <span style="color: #666666;">// Конструктор.</span>
  a_temperature_meter_proxt_t<span style="color: #008000;">&#40;</span>
    <span style="color: #666666;">// Обязательный параметр SObjectizer Environment, в рамках</span>
    <span style="color: #666666;">// которого агент будет работать.</span>
    so_environment_t <span style="color: #000040;">&amp;</span> env,
    <span style="color: #666666;">// Адрес агента, отвечающего за работу с датчиком.</span>
    mbox_ref_t meter_mbox <span style="color: #008000;">&#41;</span>
    <span style="color: #008080;">:</span> agent_t<span style="color: #008000;">&#40;</span> env <span style="color: #008000;">&#41;</span>
    , m_meter_mbox<span style="color: #008000;">&#40;</span> std<span style="color: #008080;">::</span><span style="color: #007788;">move</span><span style="color: #008000;">&#40;</span>meter_mbox<span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span>
  <span style="color: #008000;">&#123;</span><span style="color: #008000;">&#125;</span>
&nbsp;
  <span style="color: #666666;">// Настройка агента для работы внутри SObjectizer.</span>
  <span style="color: #666666;">// Вызывается в процессе регистрации агента для того,</span>
  <span style="color: #666666;">// чтобы агент мог перейти в начальное состояние и</span>
  <span style="color: #666666;">// настроить свои подписки.</span>
  <span style="color: #0000ff;">virtual</span> <span style="color: #0000ff;">void</span> so_define_agent<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> override
  <span style="color: #008000;">&#123;</span>
    <span style="color: #666666;">// Изначально агент должен находиться в состоянии expired.</span>
    so_change_state<span style="color: #008000;">&#40;</span> st_expired <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
    <span style="color: #666666;">// Подписка на сообщения.</span>
    <span style="color: #666666;">// Этот агент реагирует только на сообщения, которые</span>
    <span style="color: #666666;">// отсылаются в его персональный mbox.</span>
&nbsp;
    <span style="color: #666666;">// Реакция на запрос значения, когда у агента нет</span>
    <span style="color: #666666;">// актуальных данных от датчика.</span>
    so_subscribe<span style="color: #008000;">&#40;</span> so_direct_mbox<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span>.<span style="color: #007788;">in</span><span style="color: #008000;">&#40;</span> st_expired <span style="color: #008000;">&#41;</span>
      .<span style="color: #007788;">event</span><span style="color: #008000;">&#40;</span>
         <span style="color: #666666;">// Метод, который будет обработчиком события.</span>
         <span style="color: #000040;">&amp;</span>a_temperature_meter_proxy_t<span style="color: #008080;">::</span><span style="color: #007788;">evt_get_data_from_meter</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
    <span style="color: #666666;">// Далее реакция на сообщения, которые агент обрабатывает</span>
    <span style="color: #666666;">// имея актуальную информацию от датчика.</span>
    so_subscribe<span style="color: #008000;">&#40;</span> so_direct_mbox<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span>.<span style="color: #007788;">in</span><span style="color: #008000;">&#40;</span> st_actual <span style="color: #008000;">&#41;</span>
      <span style="color: #666666;">// На запрос данных можно просто вернуть текущее значение.</span>
      .<span style="color: #007788;">event</span><span style="color: #008000;">&#40;</span>
         <span style="color: #666666;">// Обработчик события задается лямбда-функцией,</span>
         <span style="color: #666666;">// т.к. он настолько прост, что нет смысла</span>
         <span style="color: #666666;">// заводить отдельный метод.</span>
         <span style="color: #008000;">&#91;</span><span style="color: #0000dd;">this</span><span style="color: #008000;">&#93;</span><span style="color: #008000;">&#40;</span> <span style="color: #0000ff;">const</span> msg_get_current_data <span style="color: #000040;">&amp;</span> evt <span style="color: #008000;">&#41;</span>
         <span style="color: #008000;">&#123;</span>
           <span style="color: #666666;">// Сразу отсылаем ответное сообщение.</span>
           evt.<span style="color: #007788;">m_return_mbox</span><span style="color: #000040;">-</span><span style="color: #000080;">&gt;</span>deliver_message<span style="color: #008000;">&#40;</span>
             <span style="color: #0000dd;">new</span> msg_current_data<span style="color: #008000;">&#40;</span> m_last_value <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: #008000;">&#41;</span>
       <span style="color: #666666;">// На уведомление об истечении времени жизни последнего</span>
       <span style="color: #666666;">// замера нужно поменять состояние агента.</span>
       .<span style="color: #007788;">event</span><span style="color: #008000;">&#40;</span>
          <span style="color: #0000ff;">signal</span><span style="color: #000080;">&lt;</span> msg_data_expired <span style="color: #000080;">&gt;</span>,
          <span style="color: #666666;">// Опять очень простой обработчик, который</span>
          <span style="color: #666666;">// может быть представлен лямбда-функцией.</span>
          <span style="color: #008000;">&#91;</span><span style="color: #0000dd;">this</span><span style="color: #008000;">&#93;</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#123;</span> so_change_state<span style="color: #008000;">&#40;</span> st_expired <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span> <span style="color: #008000;">&#125;</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
   <span style="color: #008000;">&#125;</span>
&nbsp;
   <span style="color: #666666;">// Обработчик события запроса температуры, когда</span>
   <span style="color: #666666;">// последнее прочитанное значение уже не актуально.</span>
   <span style="color: #0000ff;">void</span> evt_get_data_from_meter<span style="color: #008000;">&#40;</span> <span style="color: #0000ff;">const</span> msg_get_current_data <span style="color: #000040;">&amp;</span> evt <span style="color: #008000;">&#41;</span>
   <span style="color: #008000;">&#123;</span>
     <span style="color: #666666;">// Получаем текущее значение у реального агента.</span>
     <span style="color: #666666;">// Ждем ответа не более 10 секунд. Если ответ не поступит,</span>
     <span style="color: #666666;">// то возникнет исключение, которое обработает SObjectizer.</span>
     m_last_value <span style="color: #000080;">=</span> m_meter_mbox<span style="color: #000040;">-</span><span style="color: #000080;">&gt;</span>get_one<span style="color: #000080;">&lt;</span> <span style="color: #0000ff;">float</span> <span style="color: #000080;">&gt;</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
       .<span style="color: #007788;">wait_for</span><span style="color: #008000;">&#40;</span> std<span style="color: #008080;">::</span><span style="color: #007788;">chrono</span><span style="color: #008080;">::</span><span style="color: #007788;">seconds</span><span style="color: #008000;">&#40;</span> <span style="color: #0000dd;">10</span> <span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span>
       .<span style="color: #007788;">sync_get</span><span style="color: #000080;">&lt;</span> msg_acquire_value <span style="color: #000080;">&gt;</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
     <span style="color: #666666;">// Данные у нас есть, переводим агент в состояние</span>
     <span style="color: #666666;">// наличия актуальной информации.</span>
     so_change_state<span style="color: #008000;">&#40;</span> st_actual <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
     <span style="color: #666666;">// И заказываем отложенное сообщение для того, чтобы</span>
     <span style="color: #666666;">// вернуться затем в состояние expired.</span>
     so_environment<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #000040;">-</span><span style="color: #000080;">&gt;</span>single_timer<span style="color: #000080;">&lt;</span> msg_data_expired <span style="color: #000080;">&gt;</span><span style="color: #008000;">&#40;</span>
       <span style="color: #666666;">// Сигнал будет доставлен в персональный mbox агента.</span>
       so_direct_mbox<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>,
       <span style="color: #666666;">// Через 60 секунд (значение задается в миллисекундах).</span>
       <span style="color: #0000dd;">60</span> <span style="color: #000040;">*</span> <span style="color: #0000dd;">1000</span> <span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
     <span style="color: #666666;">// Текущее значение нужно вернуть получателю.</span>
     evt.<span style="color: #007788;">m_return_mbox</span><span style="color: #000040;">-</span><span style="color: #000080;">&gt;</span>deliver_message<span style="color: #008000;">&#40;</span>
       <span style="color: #0000dd;">new</span> msg_current_data<span style="color: #008000;">&#40;</span> m_last_value <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: #008000;">&#125;</span><span style="color: #008080;">;</span></pre></div></div>

<h3>Сравнение с аналогами</h3>
<p>Фреймворков, позволяющих разрабатывать ПО с использованием <a href="http://en.wikipedia.org/wiki/Actor_model">Actor Model</a>, довольно много (см., например, соответствующий <a href="http://en.wikipedia.org/wiki/Actor_model#Actor_libraries_and_frameworks">список</a>). Однако, если ограничиться только языком C++, то количество аналогов существенно сокращается. Если добавить сюда еще и то, что за последний год признаки жизни подавало всего три OpenSource фреймворка для C++ (<em>SObjectizer</em>, <a href="http://actor-framework.org/">C++ Actor Framework (CAF)</a> и <a href="http://www.theron-library.com/">Theron</a>), то аналогов оказывается не так уж много.</p>
<p>При сравнении нельзя не учитывать один важный фактор: влияние языка <a href="http://www.erlang.org/">Erlang</a> на все, что появилось в этой области в последние 10 лет. Фактически, современные фреймворки, как для C++, так и для других языков программирования, пытаются перенести принципы функционирования Erlang-овских процессов в другие языки программирования. Где-то это получается лучше, где-то хуже. Но в целом клоны Erlang остаются клонами, лишь более-менее приближающимися к оригиналу.</p>
<p><em>SObjectizer</em> же появился тогда, когда широкой общественности не было известно про Erlang. Это оригинальная разработка, в которой так же, как и в Erlang, происходит обмен сообщениями между сущностями. Но на этом сходство заканчивается. Поэтому главное отличие <em>SObjectizer</em> от аналогов &#8211; это то, что <em>SObjectizer</em> не пытается повторить Erlang в C++.</p>
<p>Отсюда происходят все остальные, более мелкие отличия. Например, подход к организации группы взаимодействующих агентов. В <em>SObjectizer</em> для этого используются кооперации агентов. В клонах Erlang разработчик создает акторов поодиночке, а для того, чтобы они работали единой группой, используются аналоги Erlang-овских супервизоров.</p>
<p>Еще один пример важного отличия &#8211; это понятие <em>диспетчера</em> и возможность привязывать агентов к тем диспетчерам, которые наиболее подходят для конкретной задачи.</p>
<p>Далее идут состояния у агентов и упор на асинхронное взаимодействие между агентами. Это приводит к разработке агентов в виде конечных автоматов с наборами состояний и переходами из одного состояния в другое в процессе обработки событий. Для каких-то задач это более удобно, чем работа в стиле Erlang, где можно отослать сообщение другому процессу, а затем синхронно висеть на ожидании ответа. Для каких-то задач, напротив, стиль Erlang более удобен.</p>
<p>Кроме того, очень важен акцент, который делают разработчики аналогичных фреймворков. Так, разработчики CAF и Theron акцентируют внимание на высокой скорости передачи сообщений между агентами и на том, что их основная задача &#8212; это выжимание максимальной производительности из современного многоядерного железа. То есть, данные фреймворки в первую очередь позиционируются для <a href="http://en.wikipedia.org/wiki/Parallel_computing">parallel computing</a>.</p>
<p>В то же время во главу угла в <em>SObjectizer</em> всегда ставилось удобство разработки приложений, в которых агенты выполняют собственные прикладные действия, независимо друг от друга. При этом прикладные действия могут быть очень разные: как ресурсоемкие вычисления, так и блокирующие вызовы внешних функций или обращения к внешним ресурсам. Отсюда, например, и наличие различных типов диспетчеров: они нужны для того, чтобы различные типы агентов мирно сосуществовали друг с другом в рамках одного процесса, не оказывая существенного влияния друг на друга. Таким образом, <em>SObjectizer</em> в первую очередь позиционируется для <a href="http://en.wikipedia.org/wiki/Concurrent_computing">concurrent computing</a>.</p>
<p>Кроме того, <em>SObjectizer</em> находился в реальной повседневной эксплуатации несколько дольше своих C++ аналогов, поэтому, например, разделение на слои (<em>so_5, so_5_transport, mbapi_4, so_sysconf_4</em>) произошло уже довольно давно, тогда как CAF начал движение в этом направлении лишь несколько месяцев назад.</p>
<p>И еще об одном важном отличии: так как <em>SObjectizer</em> никогда не был исследовательским проектом и всегда ориентировался на решение практических задач, при разработке <em>SObjectizer</em> уделялось внимание простоте его использования. Поэтому <em>SObjectizer</em> в меньшей степени использует новые возможности C++11 и менее требователен к уровню поддержки компилятором стандарта C++11 в отличии от, скажем, CAF. Порог вхождения в <em>SObjectizer</em> относительно невысок, в том числе и за счет того, что <em>SObjectizer</em> не требует от программиста отличного знания тонкостей C++. Это неоднократно оправдывало себя на практике, когда новички быстро осваивали <em>SObjectizer</em> и оперативно подключались к разработке больших <em>SObjectizer</em>-проектов.</p>
<h3>Текущее состояние</h3>
<p>До 2013 года <em>SObjectizer</em>, уже будучи Open Source проектом, все же оставался внутренней разработкой компании “Интервэйл”. Это накладывало свой отпечаток как на приоритеты в развитии <em>SObjectizer</em>, так и на объем ресурсов, которые удавалось выделять на разработку <em>SObjectizer</em>. Но с середины 2013 года <em>SObjectizer</em> развивается как самостоятельный OpenSource-проект.</p>
<p>Сейчас над <em>SObjectizer</em> работает команда из пяти человек. Автор статьи занимается только <em>SObjectizer</em> в режиме <em>full time</em>, остальные члены команды активно помогают. В связи с этим разработка идет в режиме <em>мягкой диктатуры</em>: ответственные решения принимаются лидером разработки, но мнение каждого из членов команды принимается во внимание.</p>
<p>Вся разработка базируется на базе инфраструктуры, предоставляемой ресурсом <em>SourceForge</em>. Исходные тексты &#8211; в SVN-репозитории <em>SourceForge</em>. Баг-трекинг, списки задач, дискуссии и документация в Wiki &#8211; на сайте <em>SourceForge</em>.</p>
<p>В качестве <em>релизной политики</em> выбран периодический выпуск сборок всех подпроектов <em>SObjectizer</em>. В сборку входят все последние стабильные версии подпроектов на момент релиза сборки. При наличии возможности сборки выпускаются раз в 1-2 месяца. Если после выпуска сборки в каком-то из подпроектов обнаруживаются критически важные ошибки/проблемы, то после их исправления вне очереди выпускается обновленная сборка.</p>
<p>Пока все основные усилия команды сконцентрированы на ядре <em>SObjectizer</em> &#8211; проекте <em>so_5</em>. Эта часть <em>SObjectizer</em> сейчас развивается наиболее активно. За период с декабря 2013 по август 2014 было выпущено три серьезных обновления <em>so_5</em>, которые существенно расширили функциональность и подняли производительность ядра <em>SObjectizer</em>. Остальные подпроекты лишь слегка адаптировались под новые версии <em>so_5</em>, что было не очень сложно, так как команда разработки очень внимательно и трепетно относится к проблемам сохранения совместимости и минимизации проблем при обновлении <em>SObjectizer</em>.</p>
<p><em>Работа над очередной версией so_5 обычно строится следующим образом:</em></p>
<div style="margin-left: 20px;">
<ul>
<li>Перед началом работ формируется список “хотелок” новой версии (т.н. <em>wish-list</em>). Обычно этот <em>wish-list</em> оформляется в виде отдельной темы в списках обсуждений проекта на SourceForge.</li>
<p></p>
<li>По ходу работ над новой версией основная часть <em>wish-list</em> воплощается в жизнь. Какие-то не вошедшие в новую версию “хотелки” переносятся в будущие релизы (не все можно сделать сразу, в том числе и по причине ограниченности ресурсов), от каких-то отказываются из-за различных причин (например, при тщательной проработке может выясниться, что реальной пользы от “хотелки” не будет, или же для ее воплощения в жизнь придется жертвовать совместимостью с предшествующими версиями).</li>
<p></p>
<li>Для разработки новой версии в SVN-репозитории создаются новые ветки, изменения из которых, по готовности, сливаются в основную ветвь разработки.</li>
<p></p>
<li>При разработке новой версии баг-трекинг практически не используется, так как любая выявленная на этом этапе проблема рассматривается как <em>show-stopper</em> и разработка приостанавливается до тех пор, пока проблема не будет устранена. Таким образом, просто нет необходимости в списке проблем, так как в конкретный момент есть всего одна актуальная проблема, на решении которой сосредоточенны все имеющиеся силы. Штатный баг-трекер SourceForge задействуется для фиксации проблем, выявленных в уже состоявшихся релизах.</li>
</ul>
</div>
<p><em>Основное внимание при разработке новых версий <em>SObjectizer</em> уделяется двум вещам: <em>тестированию и документированию</em>. По субъективным оценкам, на каждый новый кусок функциональности около 40% времени уходит на проектирование и реализацию, а оставшиеся 60% &#8212; это покрытие кода тестами, устранение выявленных при тестировании проблем, разработка демонстрационных примеров и документирование.</em></p>
<p>В основном, тестирование ядра <em>SObjectizer</em> выполняется посредством <em>юнит-тестов</em> и небольших тестовых программ, работающих по принципам юнит-тестов (т.е. либо успешно отрабатывающих, либо же аварийно завершающихся при обнаружении ошибки). Используемая в разработке <em>SObjectizer</em> система сборки <a href="http://rsdn.ru/article/devtools/subversions.xml">Mxx_ru</a> имеет встроенную поддержку такого рода тестов, что позволяет запускать наборы тестов (как полные, так и частичные) как часть процесса сборки <em>SObjectizer</em>.</p>
<p>В некоторых подпроектах, например, <em>so_5_transport</em> и <em>so_sysconf_4</em> использование юнит-тестирования затруднено, и там активно используются тесты, которые нужно запускать вручную. Это не очень удобно, поэтому есть желание в большей степени автоматизировать процесс тестирования этих проектов. Но для этого пока в должной степени “не дошли руки”.</p>
<h2>Будущее</h2>
<p>Будущее, к сожалению или к счастью, никому не ведомо, поэтому о том, что случится с <em>SObjectizer</em> в ближайшей или отдаленной перспективе, можно только гадать. Говорить сейчас можно разве что о ближайших планах, которые есть у команды разработки <em>SObjectizer</em>, но что именно из этого получится покажет время.</p>
<p>Итак, после релиза версии 5.4.0 есть планы заняться демо-проектом, который способен продемонстрировать возможности <em>SObjectizer</em> в решении какой-либо практической задачи. Что это будет &#8211; вопрос пока открытый. Возможно, клиент для <a href="http://en.wikipedia.org/wiki/AMQP">AMQP</a>, возможно,  инструмент для работы с <a href="http://en.wikipedia.org/wiki/MQTT">MQTT</a> или <a href="http://en.wikipedia.org/wiki/Constrained_Application_Protocol">CoAP</a>, а может быть что-то еще. Выбор еще предстоит сделать, и основным критерием будет наглядность демонстрации возможностей <em>SObjectizer</em> при небольшой трудоемкости.</p>
<p>Параллельно с этим планируется вести работы над следующей версией ядра <em>SObjectizer</em>, <em>so-5.4.1</em>, релиз которой запланирован на конец октября 2014 года, после чего появится возможность вплотную заняться развитием подпроектов <em>SObjectizer</em>, а именно, <em>so_5_transport</em> и <em>mbapi_4</em>.</p>
<p>Отдельно будут выполняться работы по распространению информации о <em>SObjectizer</em>. До сих пор информация о нем не выходила дальше 3-4 русскоязычных ресурсов для разработчиков ПО. Эту ситуацию предстоит изменить. В первую очередь, за счет улучшения и увеличения объема документации и обучающих материалов, а также публикаций анонсов и новостей о выходе новых версий <em>SObjectizer</em> на популярных англоязычных ресурсах.</p>
<p>В целом, команде <em>SObjectizer</em> есть чем заняться до конца 2014 года, а в начале 2015 года  можно будет подводить первые итоги “свободного плавания” <em>SObjectizer</em> в качестве самостоятельного проекта, и, если к проекту будет интерес со стороны пользователей и сторонних разработчиков, выбирать путь его дальнейшего развития.</p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/sobjectizer-overview/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Список паттернов искаженного мышления</title>
		<link>http://experience.openquality.ru/distorted-thinking/</link>
		<comments>http://experience.openquality.ru/distorted-thinking/#comments</comments>
		<pubDate>Sun, 17 Feb 2013 18:33:30 +0000</pubDate>
		<dc:creator>Marlena Compton</dc:creator>
				<category><![CDATA[Методики]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=610</guid>
		<description><![CDATA[Если вы плохо позавтракали или недоступность облачной инфраструктуры не позволяет вам  тестировать, физическая реакция расстройства делает вас подверженным искаженному мышлению. Мы должны вовремя останавливать нагнетание катастрофы и начинать взаимодействовать с товарищами по команде. Если мы можем распознать искаженное мышление при выборе эвристик тестирования, мы наверняка сможем вовремя оставить эти искажения позади. Поляризация подходит для формальных методов, но не годится для друзей.]]></description>
			<content:encoded><![CDATA[<p><em><strong>От редакции</strong>. В мире, где требования к коробочному продукту меняются три раза в неделю (и каждый раз обоснованно!) и где подобная чехарда служит показателем “живучести” продукта, Agile-технологии <a href="http://experience.openquality.ru/agile-ruined-my-life/">с гиком и свистом</a> пробивают себе дорогу. “Надо быть гибким, отвечать требованиям рынка, релизиться раз в две недели, а не то нас  догонят или мы отстанем”. Планирование спринтов, ретроспектива, идеальный час, листочки на Scrum-доске и подобные артефакты становятся предметом заботы каждого разработчика. “Мы принимаем решения командой, все отвечают за всё”.  “Ты быстро пишешь надежный  код? Этого мало, нам нужно твое мнение по каждому вопросу”. Коммуникации занимают все бОльшую часть времени и порой выходят на первый план. Слово Марлене.<br />
</em></p>
<p>У меня есть подборка записей, почерпнутых в учебных классах по осознанности, во время встреч и медитативных посиделок, проходящих в Республике Калифорния.</p>
<p>Сегодня я публикую их в исходном виде. Для меня эти записи достаточно ценны, поэтому я хочу сохранить их навечно в Интернете. Если для этого мне нужно набрать их на клавиатуре и опубликовать, пусть так и будет. Я просто хочу держать их поблизости и предположу, что заложенные в них истины окажутся полезны не только мне.</p>
<p>Есть еще одна причина, по которой я выбрала для публикации это время. Markus Gärtner, автор  недавно опубликованной книги “<a href="http://www.amazon.com/ATDD-Example-Test-Driven-Addison-Wesley-ebook/dp/B008G1H3EG/ref=sr_1_1?s=digital-text&#038;ie=UTF8&#038;qid=1341377435&#038;sr=1-1&#038;keywords=atdd+by+example">ATDD by Example</a>” и тестировщик интернационализации (i18n) в Amazon, недавно опубликовал <a href="http://www.shino.de/2012/07/04/two-problems-with-context-driven-testing/">статью</a> о заблуждениях в контекстуальном тестировании. Его мысли оказались чрезвычайно близки к <a href="http://en.wikipedia.org/wiki/Cognitive_distortion">искаженному мышлению</a>, что неудивительно, поскольку все мы люди. </p>
<p>Если вы плохо позавтракали или недоступность облачной инфраструктуры не позволяет вам  тестировать, физическая реакция расстройства делает вас подверженным искаженному мышлению. В случае тестирования это может оказаться полезным для появления неожиданных идей, но в то же время мы должны вовремя останавливать нагнетание катастрофы и начинать взаимодействовать с товарищами по команде. Если мы можем распознать искаженное мышление при выборе эвристик тестирования, мы наверняка сможем вовремя оставить эти искажения позади. Поляризация подходит для формальных методов, но не годится для друзей.</p>
<p>Итак, приступим:</p>
<p><strong>Фильтрация:</strong> Вы видите негативные детали и преувеличиваете их, не обращая внимания на позитивные аспекты ситуации.</p>
<p><strong>Поляризованное мышление:</strong> Обстоятельства могут быть черными или белыми, хорошими или плохими. Либо вы должны быть безупречны, либо вы лажанулись. Нет компромисса.</p>
<p><strong>Чрезмерное обобщение:</strong> Вы приходите к общему заключению на основе одного события или факта. Если однажды случилось что-то плохое, вы ожидаете его повторения снова и снова. </p>
<p><strong>Чтение мыслей:</strong> Вы знаете, что люди чувствуют и почему они действуют так или иначе, не интересуясь  их мнением на этот счет. В частности, вы способны предсказать, что люди чувствуют по отношению к вам.</p>
<p><strong>Катастрофизация:</strong> Вы ожидаете беду. Вы замечаете или слышите проблему и начинаете думать в духе &laquo;А что если?&raquo;: &laquo;А что если случится трагедия? А что если это случится со мной?&raquo; </p>
<p><strong>Персонализация:</strong> Вы полагаете, что все слова и действия людей связаны с вами. Также, вы сравниваете себя с другими, пытаясь определить, кто умнее, кто лучше выглядит и т.п.</p>
<p><strong>Иллюзии контроля:</strong> Если вы ощущаете внешний контроль, вы видите себя беспомощной жертвой обстоятельств. Заблуждение относительно внутреннего контроля делает вас ответственным за боль и счастье людей вокруг вас.</p>
<p><strong>Иллюзия справедливости:</strong> вы чувствуете обиду, потому что вы думаете, что знаете правду, но другие люди с вами не согласятся.</p>
<p><strong>Обвинения:</strong> Вы налагаете на других людей ответственность за вашу боль или придерживаетесь другой линии, обвиняя себя во всех проблемах и провалах.</p>
<p><strong>Обязаловка:</strong> У вас есть список железобетонных правил, по которым должны действовать вы и другие люди. Те, кто нарушает эти правила, вызывают у вас гнев. Вы чувствуете себя виноватым, преступая эти правила.</p>
<p><strong>Эмоциональные рассуждения:</strong> Вы верите в то, что ваши ощущения есть истина в последней инстанции. Если вы чувствуете себя глупым и скучным, то вы обязательно глупый и скучный.</p>
<p><strong>Иллюзия изменений:</strong> Вы ожидаете, что другие люди изменятся и подстроятся под вас, если вы на них нажмете или умаслите. Вам нужно изменять людей, потому что ваши надежды на счастье, похоже, полностью зависят от них.</p>
<p><strong>Глобальные ярлыки:</strong> Вы обобщаете одно или два свойства в негативное глобальное суждение.</p>
<p><strong>Всегда прав:</strong> Вы постоянно дискутируете, доказывая правоту ваших позиций или действий. Быть неправым невообразимо, и вы зайдете как угодно далеко чтобы продемонстрировать свою правоту.</p>
<p><strong>Иллюзия вознаграждения на небесах:</strong> Вы ожидаете, что все ваши жертвы и самоограничения воздадутся сторицей, как будто кто-то ведет подсчет. Вы чувствуете горечь, когда вознаграждение не приходит.</p>
<p><a href ="http://marlenacompton.com/?p=3325">Источник</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/distorted-thinking/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Типичные сценарии распространения и обработки исключений. Часть 2.</title>
		<link>http://experience.openquality.ru/exception-handling-2/</link>
		<comments>http://experience.openquality.ru/exception-handling-2/#comments</comments>
		<pubDate>Sun, 19 Aug 2012 12:10:12 +0000</pubDate>
		<dc:creator>Артур Бакиев</dc:creator>
				<category><![CDATA[Методики]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[исключения]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=564</guid>
		<description><![CDATA[Продолжение. Первая часть серии статей опубликована здесь. Класс UserPreferences Класс CFile из первый части статьи продемонстрировал нам, каким образом API, на котором базируется класс, определяет политику распространения исключений данного класса. Там же говорилось о “функциональной совместимости”, которую приходится соблюдать при создании новых классов, использующих некоторый “базовый код”. В первой части в качестве примера рассматривалось создание новой библиотеки. Во второй части полученный опыт будет экстраполирован на создание пользовательского класса. Для демонстрации, &#160;[...]]]></description>
			<content:encoded><![CDATA[<p>Продолжение. Первая часть серии статей опубликована <a href="http://experience.openquality.ru/exception-handling/">здесь</a>.</p>
<p><em>Класс UserPreferences</em></p>
<p>Класс CFile из первый части статьи продемонстрировал нам, каким образом API, на котором базируется класс, определяет политику распространения исключений данного класса. Там же говорилось о “функциональной совместимости”, которую приходится соблюдать при создании новых классов, использующих некоторый “базовый код”. В первой части в качестве примера рассматривалось создание новой библиотеки. Во второй части полученный опыт будет экстраполирован на создание пользовательского класса.</p>
<p>Для демонстрации, я привлеку на помощь стандартную библиотеку C++ [C++ standard library] (<a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a> раздел 17). В чём очарование стандартной библиотеки? Она присутствует во всех реализациях, а значит универсальна, кросс-платформенна и знакома многим разработчикам.</p>
<p>Из всего множества классов этой библиотеки сейчас нас будут интересовать лишь потоки ввода/вывода [input/output library] (<a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a> раздел 27). Мы рассмотрим, как можно задействовать эти потоки для сохранения/восстановления состояния объектов и обсудим тактику распространения ошибок.</p>
<p>Для наглядности представим, что мы работаем над текстовым редактором. Наш редактор, помимо обязательных для него функций, даёт возможность пользователю сохранять различные настройки &#8211; размер и стиль шрифтов, путь для сохранения файлов, параметры авто-замены и т.п. Теперь предположим, что за работу с этими данными отвечает класс UserPreferences, который мог бы выглядеть следующим образом:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">class</span> UserPreferences
<span style="color: #008000;">&#123;</span>
<span style="color: #0000ff;">public</span><span style="color: #008080;">:</span>
    UserPreferences<span style="color: #008000;">&#40;</span><span style="color: #0000ff;">int</span> intValue, <span style="color: #0000ff;">const</span> std<span style="color: #008080;">::</span><span style="color: #007788;">string</span><span style="color: #000040;">&amp;</span> stringValue, <span style="color: #0000ff;">float</span> floatValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #0000ff;">private</span><span style="color: #008080;">:</span>
   <span style="color: #0000ff;">int</span> mIntValue<span style="color: #008080;">;</span>
   std<span style="color: #008080;">::</span><span style="color: #007788;">string</span> mStringValue<span style="color: #008080;">;</span>
   <span style="color: #0000ff;">float</span> mFloatValue<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span></pre></div></div>

<p>Мы с вами хотим, чтобы настройки, выбранные пользователем, не теряли свою силу после того, как приложение было закрыто, а затем открыто повторно. И для этого нам понадобится сохранять состояние класса UserPreferences. Как было сказано выше, мы будем делать это с помощью потоков ввода/вывода. Добавим следующие объявления двух глобальных функций:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&lt;&lt;</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out, <span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&gt;&gt;</span> <span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in, UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></pre></div></div>

<p>Это позволит нам в дальнейшем писать код сохранения и восстановления состояния объекта на манер стандартной библиотеки C++.</p>
<p>Так может выглядеть сохранение:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> MainApplication<span style="color: #008080;">::</span><span style="color: #007788;">SaveUserPreferences</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">ostringstream</span> out<span style="color: #008080;">;</span>
    out <span style="color: #000080;">&lt;&lt;</span> userPreferences<span style="color: #008080;">;</span>
    saveToDisk<span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>А так &#8211; восстановление:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
UserPreferences MainApplication<span style="color: #008080;">::</span><span style="color: #007788;">RestoreUserPreferences</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">string</span> str <span style="color: #000080;">=</span> readFromDisk<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">istringstream</span> in<span style="color: #008000;">&#40;</span>str<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    UserPreferences userPreferences<span style="color: #008080;">;</span>
    in <span style="color: #000080;">&gt;&gt;</span> userPreferences<span style="color: #008080;">;</span>
    <span style="color: #0000ff;">return</span> userPreferences<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>В свою очередь, реализации методов operator<< и operator>> будут обращаться к публичным методам класса UserPreferences:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&lt;&lt;</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out, <span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    userPreferences.<span style="color: #007788;">Serialize</span><span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">return</span> out<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&gt;&gt;</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in, UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    userPreferences <span style="color: #000080;">=</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">UnSerialize</span><span style="color: #008000;">&#40;</span>in<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">return</span> in<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Таким образом, вся реальная работа по сохранению объектов будет производиться в методах Serialize и UnSerialize.</p>
<p>Помимо методов Serialize и UnSerialize нам понадобится добавить конструктор по умолчанию [default constructor] &#8211; он используется в методе MainApplication::RestoreUserPreferences. Описание класса UserPreferences будет выглядеть следующим образом:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">class</span> UserPreferences
<span style="color: #008000;">&#123;</span>
<span style="color: #0000ff;">public</span><span style="color: #008080;">:</span>
    UserPreferences<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
    UserPreferences<span style="color: #008000;">&#40;</span><span style="color: #0000ff;">int</span> intValue, <span style="color: #0000ff;">const</span> std<span style="color: #008080;">::</span><span style="color: #007788;">string</span><span style="color: #000040;">&amp;</span> stringValue, <span style="color: #0000ff;">float</span> floatValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
   <span style="color: #0000ff;">void</span> Serialize<span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span><span style="color: #008080;">;</span>
&nbsp;
   <span style="color: #0000ff;">static</span> UserPreferences UnSerialize<span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #0000ff;">private</span><span style="color: #008080;">:</span>
   <span style="color: #0000ff;">int</span> mIntValue<span style="color: #008080;">;</span>
   std<span style="color: #008080;">::</span><span style="color: #007788;">string</span> mStringValue<span style="color: #008080;">;</span>
   <span style="color: #0000ff;">float</span> mFloatValue<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
&nbsp;
std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&lt;&lt;</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out, <span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&gt;&gt;</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in, UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></pre></div></div>

<p>К описанию класса также необходимо отнести и методы operator<< и operator>>, которые, хотя и описаны вне тела класса, тем не менее являются частью его интерфейса.</p>
<p>Сохранение состояния объекта &#8211; это большая тема, заслуживающая отдельного рассмотрения, но мы с вами не будем на ней останавливаться. Я скажу лишь пару слов о ключевых моментах, прежде чем перейду к вопросу обработки исключительных ситуаций.</p>
<div style="margin-left: 20px">
<em></p>
<p>Тем, кто заинтересовался вопросом сохранения состояния объектов, я бы порекомендовал краткий Serialization and Unserialization FAQ от Marshall Cline &#8211; <a href="http://www.parashift.com/c++-faq/serialize-decide-text-vs-binary.html">http://www.parashift.com/c++-faq/serialization.html#faq-36.3</a>. Данный FAQ является частью сборника ответов на часто задаваемые вопросы группы новостей <a href="http://groups.google.com/group/comp.lang.c++">http://groups.google.com/group/comp.lang.c++</a>.</p>
<p></em>
</div>
<p>Для сохранения/чтения состояния объектов с помощью потоков у нас есть возможность выбрать формат сохранения (текстовый или бинарный). У каждого из них есть свои плюсы и минусы. Мы остановимся на текстовом, как более наглядном. При использовании текстового формата нам необходимо выбрать подходящие разделители. Мы возьмём пробел в качестве разделителя для чисел. Это позволит нам при считывании числа полагаться на вызов basic_istream::operator>>, для которого пробел послужит “границей”.</p>
<p>С разделителем для строк чуть сложнее: мы не хотим ограничивать себя сохранением лишь тех строк, в которых не содержатся пробелы. Потому выберем в качестве разделителя нулевой символ (‘\0’) и создадим свой метод &#8211; метод Read &#8211; для считывания строк.</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
UserPreferences UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">UnSerialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">int</span> intValue<span style="color: #008080;">;</span>
    in <span style="color: #000080;">&gt;&gt;</span> intValue<span style="color: #008080;">;</span>
    in.<span style="color: #007788;">ignore</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span> <span style="color: #666666;">// #1 - символ разделителя всё ещё в потоке - пропускаем его</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">string</span> stringValue<span style="color: #008080;">;</span>
    Read<span style="color: #008000;">&#40;</span>in, stringValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">float</span> floatValue<span style="color: #008080;">;</span>
    in <span style="color: #000080;">&gt;&gt;</span> floatValue<span style="color: #008080;">;</span>
    in.<span style="color: #007788;">ignore</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span> <span style="color: #666666;">// #3 - позволяем сохранять в поток другие объекты</span>
    <span style="color: #0000ff;">return</span> UserPreferences<span style="color: #008000;">&#40;</span>intValue, stringValue, floatValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
<span style="color: #0000ff;">void</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">Serialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span>
<span style="color: #008000;">&#123;</span>
    out <span style="color: #000080;">&lt;&lt;</span> mIntValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span>
        <span style="color: #000080;">&lt;&lt;</span> mStringValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">'<span style="color: #006699; font-weight: bold;">\0</span>'</span>
        <span style="color: #000080;">&lt;&lt;</span> mFloatValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span><span style="color: #008080;">;</span> <span style="color: #666666;">// #2 - позволяем сохранять в поток другие объекты</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
<span style="color: #0000ff;">void</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">Read</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in, std<span style="color: #008080;">::</span><span style="color: #007788;">string</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">char</span> c<span style="color: #008080;">;</span>
    <span style="color: #0000ff;">while</span><span style="color: #008000;">&#40;</span>in.<span style="color: #007788;">get</span><span style="color: #008000;">&#40;</span>c<span style="color: #008000;">&#41;</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>c<span style="color: #000080;">==</span><span style="color: #0000dd;">0</span><span style="color: #008000;">&#41;</span>
        <span style="color: #008000;">&#123;</span>
            <span style="color: #0000ff;">break</span><span style="color: #008080;">;</span>
        <span style="color: #008000;">&#125;</span>
&nbsp;
        out.<span style="color: #007788;">push_back</span><span style="color: #008000;">&#40;</span>c<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>В этой нехитрой реализации следует помнить о двух моментах.</p>
<p>Первый касается метода basic_istream::operator>>. Этот метод, считав из потока число, оставит в потоке символ-разделитель, прервавший считывание (в нашем случае это пробел). Поэтому нам необходимо пропустить символ-разделитель перед считыванием следующего члена класса (строка #1 в функции UserPreferences::UnSerialize).</p>
<p>Второй момент относится к записи разделителя (опять же, пробела) после записи последнего члена класса &#8211; mFloatValue (строка #2 в функции UserPreferences::Serialize). А также к пропуску этого же разделителя при восстановлении состояния объекта (строка #3 в функции UserPreferences::UnSerialize). Мы добавляем данный код для того, чтобы разрешить сохранение в тот же поток других объектов.</p>
<p>Другими словами, класс UserPreferences оставляет потоки ввода и вывода в состоянии, в котором они готовы к повторному использованию. Это позволяет писать следующий код:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> MainApplication<span style="color: #008080;">::</span><span style="color: #007788;">Save</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences, <span style="color: #0000ff;">const</span> ApplicationData<span style="color: #000040;">&amp;</span> applicationData<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">ostringstream</span> out<span style="color: #008080;">;</span>
    out <span style="color: #000080;">&lt;&lt;</span> userPreferences <span style="color: #000080;">&lt;&lt;</span> applicationData<span style="color: #008080;">;</span>
    saveToDisk<span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
<span style="color: #0000ff;">void</span> MainApplication<span style="color: #008080;">::</span><span style="color: #007788;">Restore</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">string</span> str <span style="color: #000080;">=</span> readFromDisk<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">istringstream</span> in<span style="color: #008000;">&#40;</span>str<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    UserPreferences userPreferences<span style="color: #008080;">;</span>
    ApplicationData applicationData<span style="color: #008080;">;</span>
    in <span style="color: #000080;">&gt;&gt;</span> userPreferences <span style="color: #000080;">&gt;&gt;</span> applicationData<span style="color: #008080;">;</span>
    <span style="color: #666666;">// Восстанавливаем состояние приложения с помощью</span>
    <span style="color: #666666;">// userPreferences и applicationData</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>В скобках замечу, что возможность сохранения нескольких объектов в единственный поток имеет как плюсы, так и минусы. Наряду с удобством записи мы получаем риск потери состояния всех объектов при возникновении ошибки в момент сохранения/восстановления, тогда как при использовании связки “один поток &#8211; один объект” мы рискуем только одним объектом.</p>
<p>Возможно, удачным компромиссом будет сохранения в один поток лишь вложенных объектов. Например, будучи составным объектом, класс UserPreferences мог бы сохранять/восстанавливать своё состояние, делегируя операции частям, из которых состоит:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">Serialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span>
<span style="color: #008000;">&#123;</span>
    out <span style="color: #000080;">&lt;&lt;</span> mSubstitution <span style="color: #000080;">&lt;&lt;</span>
        <span style="color: #000080;">&lt;&lt;</span> mFonts <span style="color: #000080;">&lt;&lt;</span>
        <span style="color: #000080;">&lt;&lt;</span> mEnvironment<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
UserPreferences UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">UnSerialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    UserPeferences userPreferences<span style="color: #008080;">;</span>
    in <span style="color: #000080;">&gt;&gt;</span> userPreferences.<span style="color: #007788;">mSubstitution</span>
        <span style="color: #000080;">&gt;&gt;</span> userPreferences.<span style="color: #007788;">mFonts</span>
        <span style="color: #000080;">&gt;&gt;</span> userPreferences.<span style="color: #007788;">mEnvironment</span><span style="color: #008080;">;</span>
   <span style="color: #0000ff;">return</span> userPreferences<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Здесь мы полагаем, что mSubstitution, mFonts и mEnvironment уже не примитивные типы, а дисциплинированные объекты, умеющие сохранять себя сами. Любопытно отметить, что работая с объектами, мы поднимаемся на чуть больший уровень абстракции, и избавляемся от необходимости думать о разделителях.</p>
<div style="margin-left: 20px">
<em></p>
<p>Возможно, для многих это покажется очевидным, но необходимо отметить, что данную реализацию следует рассматривать лишь как обучающий пример, который не претендует на повторное использование. В реальных проектах от неё следует отказаться в пользу хорошо известных/зарекомендовавших себя сторонних библиотек. Библиотеки сохранения/восстановления состояния объекта избавляют разработчика от забот о версионности, поддержке различных локализаций а также порядке байт при работе с сетевыми протоколами.</p>
<p>Единственным преимуществом данной реализации является её простота, которая даёт некоторую уверенность, что данный код будет правильно работать в большинстве случаев. Но эта простота не перевешивает потенциальных проблем, с которыми может столкнуться конечный пользователь.</p>
<p></em>
</div>
<p>Итак, мы закончили с вступлением и теперь можем перейти к вопросу распространения ошибок. Рассмотрим каждую из функций нашего класса. И начнём мы с конструкторов.</p>
<p><em>UserPreferences::UserPreferences</em></p>
<p>Мы ещё не приводили тело конструктора по умолчанию для класса UserPreferences. Давайте это сделаем сейчас:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">UserPreferences</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Оставив тело конструктора пустым, мы позволили компилятору вызвать конструкторы по умолчанию для членов класса mIntValue, mFloatValue и mStringValue. Как следствие, переменные тривиальных типов (mIntValue и mFloatValue) будут инициализированы случайными значениями, а для mStringValue будет вызван конструктор:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">explicit</span> basic_string<span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> Allocator<span style="color: #000040;">&amp;</span> a <span style="color: #000080;">=</span> Allocator<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></pre></div></div>

<p>Последний и объясняет, почему нам приходится объявлять UserPreferences::UserPreferences без использования пустой спецификации noexcept [noexcept-specification].</p>
<div style="margin-left: 20px">
<em></p>
<p>Напомню, что последняя версия стандарта объявляет динамическую спецификацию исключений [dynamic-exception-specification] устаревшей. Взамен неё предлагается спецификация noexcept [noexcept-specification] (ISO/IEC 14882:2011, раздел 15.4). Таким образом, чтобы в новом стандарте объявить функцию, не испускающую исключений, необходимо добавить к ней суффикс noexcept взамен (привычного) throw():</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> method_does_not_throw_exception<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span> noexcept<span style="color: #008080;">;</span></pre></div></div>

<p></em>
</div>
<p>Согласно стандарту, конструктор basic_string(const Allocator&#038;) может инициировать любое исключение.</p>
<div style="margin-left: 20px">
<em></p>
<p>Интересно провести подробный анализ метода basic_string(const Allocator&#038;) с точки зрения возможной генерации исключения.</p>
<p>Шаблонный класс Allocator в данном случае &#8211; это std::allocator, который является распределителем памяти по умолчанию для стандартной библиотеки (<a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a> раздел 20.6.9). И для которого конструктор копирования объявлен как метод, который не генерирует исключений:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
allocator<span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> allocator<span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span> noexcept<span style="color: #008080;">;</span></pre></div></div>

<p>Т.е. если исключение и может быть инициировано, то только из тела метода basic_string(const Allocator&#038;).</p>
<p>Продолжим анализ и рассмотрим постусловия, накладываемые стандартом на данную функцию &#8211; basic_string(const Allocator&#038;) (<a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a> раздел 21.4.2):</p>
<table border="1">
<tr style="border-bottom: thin solid black;">
<td><strong>Элемент</strong></td>
<td><strong>Значение</strong></td>
</tr>
<tr style="border-bottom: thin solid black;">
<td>data()</td>
<td>Ненулевой указатель, который может быть скопирован и к которому может быть прибавлен 0 [a non-null pointer that is copyable and can have 0 added to it]</td>
</tr>
<tr style="border-bottom: thin solid black;">
<td>size()</td>
<td>0</td>
</tr>
<tr style="border-bottom: thin solid black;">
<td>capacity()</td>
<td>Неопределённое значение [an unspecified value]</td>
</tr>
</table>
<p>&nbsp;<br />
Здесь нас будут интересовать методы size() и capacity(). Требование к методу size() говорит о том, что basic_string(const Allocator&#038;) может не заниматься выделением памяти (и, как следствие, может не генерировать bad_alloc). Требование же к capacity() говорят об обратном &#8211; неопределённое значение, возвращаемое этим методом может быть любым, в том числе и не нулевым. Т.е. рассматриваемый конструктор вправе выделить память, что в свою очередь может привести к генерации bad_alloc.</p>
<p></em>
</div>
<p>Соберём всё вместе. Ни конструкторы тривиальных типов, ни конструктор копирования std::allocator не приводят к возникновения исключения. Исключение может произойти лишь в конструкторе по умолчанию basic_string (здесь “может” нужно понимать как &#8211; “стандарт разрешает”). Вероятно, это исключение будет иметь тип bad_alloc. Больше никаких утверждений сделать нельзя. Зато с большой долей уверенности можно сказать, что в системе должно произойти нечто серьёзное, чтобы конструктор строки по умолчанию инициировал исключение.</p>
<p>С одной стороны результат анализа может показаться неожиданным: безобидный, на первый взгляд, пустой конструктор может генерировать std::bad_alloc, а возможно и исключение неизвестного типа. Но давайте посмотрим на это с другой стороны.</p>
<p>Во-первых, стандарт утверждает, что все исключения, инициированные стандартной библиотекой, должны быть производными от класса std::exception (<a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a> раздел 18.8.1). То есть, класс исключения известен, и при желании мы всегда можем его перехватить.</p>
<p>Во-вторых, что более важно, данный конструктор &#8211; это не единственное место в нашем приложении, которое может генерировать “неудобное” исключение. Всё, что от нас потребуется &#8211; это корректно перехватить и корректно обработать подобную ошибку.</p>
<p>Вопросу перехвата будет посвящён отдельный раздел, сейчас же мы вернёмся к текущей задаче, а именно &#8211; к определению типов исключений, которые могут и/или должны генерировать методы класса UserPreferences.</p>
<p>Итак, мы обнаружили, что конструктор класса по умолчанию, UserPreferences(), может инициировать исключение, принадлежащее классу std::exception либо классу, который является наследником std::exception. Отметим это для себя и перейдём к следующему методу.</p>
<p><em>UserPreferences::UserPreferences(int intValue, const std::string&#038; stringValue, float floatValue);</em></p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">UserPreferences</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">int</span> intValue, <span style="color: #0000ff;">const</span> std<span style="color: #008080;">::</span><span style="color: #007788;">string</span><span style="color: #000040;">&amp;</span> stringValue, <span style="color: #0000ff;">float</span> floatValue<span style="color: #008000;">&#41;</span> <span style="color: #008080;">:</span>
    mIntValue<span style="color: #008000;">&#40;</span>intValue<span style="color: #008000;">&#41;</span>,
    mStringValue<span style="color: #008000;">&#40;</span>stringValue<span style="color: #008000;">&#41;</span>,
    mFloatValue<span style="color: #008000;">&#40;</span>floatValue<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Ничего принципиально нового в этом методе, по сравнению с конструктором по умолчанию, мы не видим. В данном случаем при создании члена класса mStringValue будет вызван метод basic_string(const basic_string&#038; str), который как и basic_string(const Allocator&#038;) допускает генерацию исключений. Поэтому, делаем вывод, что данный метод также инициирует исключение std::exception.</p>
<div style="margin-left: 20px">
<em></p>
<p>Любопытно отметить, что у нас есть возможность объявить ещё один конструктор, который будет очень похож на данный конструктор, но который не будет генерировать исключений. Для этого нужно воспользоваться нововведением последней версии стандарта C++, а именно конструктором переноса [move constructor], слегка изменив прототип нашей функции:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">UserPreferences</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">int</span> intValue, std<span style="color: #008080;">::</span><span style="color: #007788;">string</span><span style="color: #000040;">&amp;&amp;</span> stringValue, <span style="color: #0000ff;">float</span> floatValue<span style="color: #008000;">&#41;</span> noexcept<span style="color: #008080;">;</span></pre></div></div>

<p>Дело в том, что конструктор переноса для класса basic_string объявлен, как негенерирующий исключения (что логично, поскольку происходит лишь копирование данных без выделения памяти):</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
basic_string<span style="color: #008000;">&#40;</span>basic_string<span style="color: #000040;">&amp;&amp;</span> str<span style="color: #008000;">&#41;</span> noexcept<span style="color: #008080;">;</span></pre></div></div>

<p>Это позволяет, задействовав его, объявить конструктор UserPreferences также негенерирующим исключения.</p>
<p></em></div>
<p><em>void UserPreferences::Serialize(std::ostream&#038; out) const</em></p>
<p>Наконец мы можем перейти от конструкторов с пустым телом к рассмотрению методов, которые действительно что-то делают. Начнём с метода Serialize &#8211; приведём тело метода ещё раз:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">Serialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span>
<span style="color: #008000;">&#123;</span>
    out <span style="color: #000080;">&lt;&lt;</span> mIntValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span>
        <span style="color: #000080;">&lt;&lt;</span> mStringValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">'<span style="color: #006699; font-weight: bold;">\0</span>'</span>
        <span style="color: #000080;">&lt;&lt;</span> mFloatValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Реализация данного метода использует три различных метода operator<< для сохранения состояния класса:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
basic_ostream<span style="color: #000040;">&amp;</span> basic_ostream<span style="color: #008080;">::</span><span style="color: #007788;">operator</span><span style="color: #000080;">&lt;&lt;</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">int</span> val<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
basic_ostream<span style="color: #000040;">&amp;</span> basic_ostream<span style="color: #008080;">::</span><span style="color: #007788;">operator</span><span style="color: #000080;">&lt;&lt;</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">float</span> val<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
basic_ostream<span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&lt;&lt;</span><span style="color: #008000;">&#40;</span>basic_ostream<span style="color: #000040;">&amp;</span> os, <span style="color: #0000ff;">const</span> basic_string<span style="color: #000040;">&amp;</span> str<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></pre></div></div>

<p>Два первых метода являются членами класса basic_ostream, а последний объявлен в пространстве имён std. Нас будет интересовать вопрос: могут ли данные функции генерировать исключения. И если могут, то какому типу будут принадлежать эти исключения.</p>
<p>Первым мы рассмотрим basic_ostream::operator<<(int val). Данный оператор относится к функциям форматированного вывода [formatted output functions], требования к которым описаны в разделе 27.7.3.6 стандарта <a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a>. Стандарт достаточно строг в описании, а потому нам достаточно просто предсказать ситуацию, когда данный метод будет инициировать исключение. Для того, чтобы basic_ostream::operator<<(int val) мог генерировать исключение, необходимо чтобы пользователь класса basic_ostream предварительно (т.е. до вызова basic_ostream::operator<<(int val)) сделал вызов basic_ios::exceptions(iostate) с параметром ios_base::badbit и/или ios_base::failbit.</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> test<span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    out.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">failbit</span> <span style="color: #000040;">|</span> std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">badbit</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">const</span> <span style="color: #0000ff;">int</span> temp <span style="color: #000080;">=</span> <span style="color: #0000dd;">1</span><span style="color: #008080;">;</span>
    <span style="color: #666666;">// Следующая строка может генерировать исключение</span>
    out <span style="color: #000080;">&lt;&lt;</span> temp<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Для того, чтобы составить представление о возможных типах исключений, нам понадобится упомянуть о внутреннем классе basic_ostream::sentry (раздел 27.7.3.4 стандарта <a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a>). Стандарт обязывает метод basic_ostream::operator<<(int val) начинать свою работу с создания объекта класса basic_ostream::sentry. Это необходимо для выполнения безопасных с точки зрения исключений операций при входе и выходе из функции [exception safe prefix and suffix operations].</p>
<p>Так вот, при создании объекта класса basic_ostream::sentry может быть вызван метод basic_ios::setstate() с параметром ios_base::failbit. Вызов этого метода может генерировать исключение ios_base::failure, если пользователь сделал предварительный вызов basic_ios::exceptions(iostate) с параметром ios_base::failbit.</p>
<p>Кроме этого сценария, стандарт также описывает ситуацию, когда исключение может возникнуть в момент вывода данных методом basic_ostream::operator<<(int val). Если подобное происходит, стандарт обязывает метод basic_ostream::operator<<(int val) перехватить исключение. После того как исключение перехвачено, метод должен проанализировать выражение (basic_ios::exceptions()&#038;ios_base::badbit). Если данное выражение не равно нулю, метод должен позволить дальнейшее распространение исключения. Упомянутое выше выражение будет не равно нулю лишь в том случае, если пользователь сделал предварительный вызов basic_ios::setstate() с параметром ios_base::badbit.</p>
<p>Очевидно, что те же результаты даст нам исследование basic_ostream::operator<<(float val). Что касается basic_ostream::operator<<(basic_ostream&#038; os, const basic_string&#038; str), то хотя он и описан в разделе, посвящённом библиотеке строк (раздел 21.4.8.9), тем не менее, к нему применяются те же требования, как к функциям форматированного вывода (раздел 27.7.3.6.1 стандарта <a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a>).</p>
<p>Итак, подведём итог. Все упомянутые выше методы basic_ostream::operator<< могут генерировать исключение. Необходимым условием для этого является предварительный вызов basic_ios::exceptions(iostate) с параметром basic_ios::badbit и/или параметром basic_ios::failbit. Типом исключения будет либо ios_base:: failure либо тип, унаследованный от std::exception (напомню, что это базовый тип для всех исключений, генерируемых стандартной библиотекой).</p>
<p>Таким образом, у нас выбор между двумя реализациями метода Serialize.</p>
<p>В первом случае мы оставляем тело Serialize в виде, приведённом выше. Это позволит клиенту класса UserPreferences принимать решение: желает ли он обрабатывать значение, возвращаемое методом Serialize, либо ему удобней перехватывать исключение, если в процессе вывода случилась ошибка.</p>
<p>Во втором случае тело метода Serialize необходимо изменить для того, чтобы добиться безусловной генерации исключений в случае ошибки.</p>
<p>Сперва обсудим первый вариант реализации Serialize, при котором тело метода остаётся неизменным, и посмотрим на клиентский код. Так может выглядеть код клиента при использовании возвращаемого значения - “возвращаемым” значением в данном случае будет входной (он же выходной) параметр std::ostream:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> MainApplication<span style="color: #008080;">::</span><span style="color: #007788;">SaveUserPreferences</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">ostringstream</span> out<span style="color: #008080;">;</span>
    out <span style="color: #000080;">&lt;&lt;</span> userPreferences<span style="color: #008080;">;</span>
    <span style="color: #666666;">// здесь мы используем basic_ios::operator bool()</span>
    <span style="color: #666666;">// чтобы узнать чем закончилась последняя операция вывода</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        saveToDisk<span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
    <span style="color: #0000ff;">else</span>
    <span style="color: #008000;">&#123;</span>
        DisplayError<span style="color: #008000;">&#40;</span><span style="color: #FF0000;">&quot;Failed to save user preferences&quot;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>А так будет выглядеть код клиента, если ему более по душе перехват исключений &#8211; для генерации исключения клиент должен задействовать вызов basic_ios::exceptions():</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> MainApplication<span style="color: #008080;">::</span><span style="color: #007788;">SaveUserPreferences</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">ostringstream</span> out<span style="color: #008080;">;</span>
    out.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">failbit</span> <span style="color: #000040;">|</span> std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">badbit</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #666666;">// здесь мы полагаемся на генерацию исключения</span>
    <span style="color: #666666;">// в случае возникновения ошибки вывода</span>
    <span style="color: #0000ff;">try</span>
    <span style="color: #008000;">&#123;</span>
        out <span style="color: #000080;">&lt;&lt;</span> userPreferences<span style="color: #008080;">;</span>
        saveToDisk<span style="color: #008000;">&#40;</span>out<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>std<span style="color: #008080;">::</span><span style="color: #007788;">exception</span><span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        DisplayError<span style="color: #008000;">&#40;</span><span style="color: #FF0000;">&quot;Failed to save user preferences&quot;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Теперь рассмотрим альтернативный вариант Serialize, о котором говорилось выше &#8211; мы добавим безусловную генерацию исключений при возникновении ошибки:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">Serialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">const</span> std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">iostate</span> previousState <span style="color: #000080;">=</span> out.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    out.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">failbit</span> <span style="color: #000040;">|</span> std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">badbit</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">try</span>
    <span style="color: #008000;">&#123;</span>
        out <span style="color: #000080;">&lt;&lt;</span> mIntValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span>
            <span style="color: #000080;">&lt;&lt;</span> mStringValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">'<span style="color: #006699; font-weight: bold;">\0</span>'</span>
            <span style="color: #000080;">&lt;&lt;</span> mFloatValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
    <span style="color: #0000ff;">catch</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">exception</span><span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        <span style="color: #666666;">// Мы перехватываем исключение лишь с одной целью -</span>
        <span style="color: #666666;">// восстановить состояние “реакции на ошибки”</span>
        out.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span>previousState<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
        <span style="color: #0000ff;">throw</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
    out.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span>previousState<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>И код клиента для данного варианта Serialize сводится к виду:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> MainApplication<span style="color: #008080;">::</span><span style="color: #007788;">SaveUserPreferences</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">ostringstream</span> out<span style="color: #008080;">;</span>
    <span style="color: #666666;">// мы исходим из того, что Serialize всегда генерирует исключение в случае ошибки</span>
    <span style="color: #0000ff;">try</span>
    <span style="color: #008000;">&#123;</span>
        out <span style="color: #000080;">&lt;&lt;</span> userPreferences<span style="color: #008080;">;</span>
        saveToDisk<span style="color: #008000;">&#40;</span>out<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>std<span style="color: #008080;">::</span><span style="color: #007788;">exception</span><span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        DisplayError<span style="color: #008000;">&#40;</span><span style="color: #FF0000;">&quot;Failed to save user preferences&quot;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Что ж, давайте перечислим все “за и против” обоих подходов и сделаем выводы.</p>
<p><strong>Первый подход &#8211; решение о генерации исключения принимает код, вызывающий Serialize</strong></p>
<p><em>Плюсы:</em></p>
<div style="margin-left: 10px">
<ul>
<li>Код метода Serialize прост и компактен</li>
<li>Клиент управляет политикой распространения ошибок: исключение или возвращаемое значение</li>
<li>Политика распространения исключений совместима с методами operator<<, объявленными в пространстве имён std</li>
</ul>
</div>
<p><em>Минусы:</em></p>
<div style="margin-left: 10px">
<ul>
<li>Ошибка, случившаяся в процессе вывода, может быть проигнорирована вызывающим кодом</li>
</ul>
</div>
<p><strong>Второй подход &#8211; метод Serialize генерирует исключение, если в процессе вывода случилась ошибка<br />
</strong></p>
<p><em>Плюсы:</em></p>
<div style="margin-left: 10px">
<ul>
<li>Невозможно проигнорировать ошибку</li>
</ul>
</div>
<p><em>Минусы:</em></p>
<div style="margin-left: 10px">
<ul>
<li>Код метода Serialize неуклюж и многословен, а потому провоцирует ошибки кодирования</li>
<li>Пользователь класса UserPreferences должен будет добавить в свой код обработку исключительных ситуаций, если он этого ещё не сделал</li>
<li>Поведение метода operator<< (std::ostream&#038; out, const UserPreferences&#038; userPreferences) несовместимо с поведением методов operator<< из пространства имён std</li>
</ul>
</div>
<p>Невозможность проигнорировать ошибку может оказаться неоспоримым преимуществом:  вероятно, существует сценарий, в котором необработанная ошибка грозит катастрофическими последствиями (к сожалению, в голову не приходит подходящего примера). Но с практической точки зрения первый подход выглядит более привлекательно: он даёт возможность выбора пользователю класса, упрощает кодирование и обеспечивает согласованное поведение operator<<. В реальном проекте, я бы стал руководствоваться именно этими соображениями и остановился на первом (простом) варианте метода Serialize.</p>
<p>Добавлю ещё пару слов к обсуждению данной версии метода Serialize:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">Serialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span>
<span style="color: #008000;">&#123;</span>
    out <span style="color: #000080;">&lt;&lt;</span> mIntValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span>
        <span style="color: #000080;">&lt;&lt;</span> mStringValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">'<span style="color: #006699; font-weight: bold;">\0</span>'</span>
        <span style="color: #000080;">&lt;&lt;</span> mFloatValue <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Здесь необходимо пояснить, что происходит в этом методе если ошибка случается при выводе очередного значения, скажем, значения &#8211; mIntValue. Это тривиально, но позволит нам расставить все точки над i. Будь “включена” генерация исключений, ответ был бы очевиден: неудача во время операции вывода приводит к генерации исключения, и выполнение последующих инструкций в данном методе прекращается. Но поскольку исключения “выключены”, постольку следующим вызовом, после закончившегося неудачей вызова basic_ostream::operator<<(int), будет вызов метода basic_ostream::operator<<(basic_ostream&#038;,char), который в качестве входного параметра получит поток с установленными failbit или badbit. В этом случае, проверив состояние потока и обнаружив, что оно не равно goodbit, метод basic_ostream::operator<< просто вернёт управление в вызываемую функцию. (Это верно для библиотечных методов; разработчик, определяющий собственный operator<< должен позаботиться об этом сам). Что есть благо, поскольку избавляет нас от написания следующего кода:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">Serialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span>
<span style="color: #008000;">&#123;</span>
    out <span style="color: #000080;">&lt;&lt;</span> mIntValue<span style="color: #008080;">;</span>
    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        out <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span><span style="color: #008080;">;</span>
        <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span>
        <span style="color: #008000;">&#123;</span>
            out <span style="color: #000080;">&lt;&lt;</span> mStringValue<span style="color: #008080;">;</span>
            <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span>
            <span style="color: #008000;">&#123;</span>
                out <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">'<span style="color: #006699; font-weight: bold;">\0</span>'</span><span style="color: #008080;">;</span>
                <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span>
                <span style="color: #008000;">&#123;</span>
                    out <span style="color: #000080;">&lt;&lt;</span> mFloatValue<span style="color: #008080;">;</span>
                    <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span>out<span style="color: #008000;">&#41;</span>
                    <span style="color: #008000;">&#123;</span>
                        out <span style="color: #000080;">&lt;&lt;</span> <span style="color: #FF0000;">' '</span><span style="color: #008080;">;</span>
                    <span style="color: #008000;">&#125;</span>
                <span style="color: #008000;">&#125;</span>
            <span style="color: #008000;">&#125;</span>
        <span style="color: #008000;">&#125;</span>
    <span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p><em>UserPreferences UserPreferences::UnSerialize(std::istream&#038; in)</em></p>
<p>Ошибка, которая при работе с потоками вывода выглядит как экзотика, в случае потоков ввода становится обычным явлением. При выводе данных ошибки зачастую определяется средой &#8211; нехватка памяти, невозможность открытия описателя, нарушения прав доступа и т.п. &#8211; и случаются сравнительно редко. При считывании же данных основная причина ошибок &#8211; некорректный/неожиданный формат данных и случаются они заметно чаще. Хорошая новость состоит в том, что на реализацию класса UserPereferences данный факт не влияет.</p>
<p>Принцип работы basic_istream::operator>> аналогичен принципу работу basic_ostream::operator<<. То есть, мы можем заставить библиотеку генерировать исключение, сделав предварительный вызов basic_ios::exceptions(iostate). Иначе нам необходимо проверять состояние потока, чтобы выяснить успешно ли закончилась последняя операция.</p>
<p>Так же как и для basic_ostream::operator<< мы можем безопасно вызывать basic_istream::operator>> даже если объект класса basic_istream находится в “плохом” состоянии &#8211; вызов basic_istream::operator>> не изменит состояние переменной, в которую мы пытаемся прочитать значение.</p>
<p>Таким образом, мы встаём перед тем же выбором, что и при обсуждении метода Serialize &#8211; использовать ли для проверки результа метод basic_ios::operator bool() либо, в случае ошибки, генерировать исключение.</p>
<p>Вариант метода UnSerialize, использующего basic_ios::operator bool() (или “простой” вариант &#8211; по аналогии с “простым” вариантом Serialize) будет выглядеть следующим образом:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
UserPreferences UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">UnSerialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">int</span> intValue<span style="color: #008080;">;</span>
    in <span style="color: #000080;">&gt;&gt;</span> intValue<span style="color: #008080;">;</span>
    in.<span style="color: #007788;">ignore</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">string</span> stringValue<span style="color: #008080;">;</span>
    Read<span style="color: #008000;">&#40;</span>in, stringValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">float</span> floatValue<span style="color: #008080;">;</span>
    in <span style="color: #000080;">&gt;&gt;</span> floatValue<span style="color: #008080;">;</span>
    in.<span style="color: #007788;">ignore</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">return</span> UserPreferences<span style="color: #008000;">&#40;</span>intValue, stringValue, floatValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Второй вариант &#8211; вариант, генерирующий исключение &#8211; будет отличаться лишь установкой и восстановлением реакции на ошибки:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
UserPreferences UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">UnSerialize</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">const</span> std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">iostate</span> previousState <span style="color: #000080;">=</span> in.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    in.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">failbit</span> <span style="color: #000040;">|</span> std<span style="color: #008080;">::</span><span style="color: #007788;">ios_base</span><span style="color: #008080;">::</span><span style="color: #007788;">badbit</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #0000ff;">try</span>
    <span style="color: #008000;">&#123;</span>
        <span style="color: #0000ff;">int</span> intValue<span style="color: #008080;">;</span>
        in <span style="color: #000080;">&gt;&gt;</span> intValue<span style="color: #008080;">;</span>
        in.<span style="color: #007788;">ignore</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
        std<span style="color: #008080;">::</span><span style="color: #007788;">string</span> stringValue<span style="color: #008080;">;</span>
        Read<span style="color: #008000;">&#40;</span>in, stringValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
        <span style="color: #0000ff;">float</span> floatValue<span style="color: #008080;">;</span>
        in <span style="color: #000080;">&gt;&gt;</span> floatValue<span style="color: #008080;">;</span>
        in.<span style="color: #007788;">ignore</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
        <span style="color: #0000ff;">return</span> UserPreferences<span style="color: #008000;">&#40;</span>intValue, stringValue, floatValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
    <span style="color: #666666;">// Мы знаем, что как конструктор класса UserPreferences,</span>
    <span style="color: #666666;">// так и basic_istream::operator&gt;&gt; могут генерировать лишь</span>
    <span style="color: #666666;">// исключения унаследованные от std::exception.</span>
    <span style="color: #0000ff;">catch</span> <span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">exception</span><span style="color: #000040;">&amp;</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        in.<span style="color: #007788;">exceptions</span><span style="color: #008000;">&#40;</span>previousState<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
        <span style="color: #0000ff;">throw</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>Все соображения, приведённые при обсуждении метода Serialize, остаются верными и для метода UnSerialize, и поэтому мы уже знаем ответ на незаданный ещё вопрос: какой из этих методов нам следует выбрать. Мы должны выбрать ту реализацию, которая соответствует реализации метода Serialize. Для метода Serialize, напомню, мы выбрали реализацию, совместимую с basic_ostream::operator<<. Таким образом, метод UnSerialize должен быть совместим с basic_istream::operator>>. Разработчику, который решился на вариант Serialize, генерирующий исключение, по всей видимости понадобится вариант UnSerialize, который также генерирует исключение.</p>
<p>Для того, чтобы обсуждение было полным, необходимо сказать несколько слов о методе basic_istream::ignore(). Метод относится к группе функций неформатированного ввода [unformatted input functions] (раздел 27.7.2.3 стандарта <a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a>). Требования к этой группе слегка отличаются от требований, выдвигаемых к группе функций форматированного ввода [formatted input functions] (раздел 27.7.2.2.1 стандарта <a href="http://www.iso.org/iso/iso_catalogue/catalogue_ics/catalogue_detail_ics.htm?csnumber=50372">ISO/IEC 14882:2011</a>), к которой относится метод basic_istream::operator>>. Но эти отличия не касаются процесса генерации исключений, а потому всё, что мы говорили о методе basic_istream::operator>>, остаётся справедливым и касательно метода basic_istream::ignore().</p>
<p><em>void UserPreferences::Read(std::istream&#038; in, std::string&#038; out)</em></p>
<p>Чтобы завершить обсуждение класса, нам осталось взглянуть на метод Read, который отвечает за считывание строк:</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">void</span> UserPreferences<span style="color: #008080;">::</span><span style="color: #007788;">Read</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in, std<span style="color: #008080;">::</span><span style="color: #007788;">string</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #0000ff;">char</span> c<span style="color: #008080;">;</span>
    <span style="color: #0000ff;">while</span><span style="color: #008000;">&#40;</span>in.<span style="color: #007788;">get</span><span style="color: #008000;">&#40;</span>c<span style="color: #008000;">&#41;</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>c<span style="color: #000080;">==</span><span style="color: #0000dd;">0</span><span style="color: #008000;">&#41;</span>
        <span style="color: #008000;">&#123;</span>
            <span style="color: #0000ff;">break</span><span style="color: #008080;">;</span>
        <span style="color: #008000;">&#125;</span>
        out.<span style="color: #007788;">push_back</span><span style="color: #008000;">&#40;</span>c<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
    <span style="color: #008000;">&#125;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>В этом методе нам встречаются две функции, не обсуждавшихся ранее &#8211; это basic_istream::get(char_type&#038; c) и basic_string::push_back(char_type c).</p>
<p>Первый из этих методов также относится к группе функций неформатированного ввода [unformatted input functions], то есть, с точки зрения генерации исключений не отличается от рассмотренного выше basic_istream::ignore(), а потому мы не останавливаемся на его обсуждении. Хотя нужно отметить, что реализация метода Read останется неизменной вне зависимости от выбранного способа распространения ошибок &#8211; генерация исключения или анализ возвращаемого значения. Это объясняется тем, что метод Read может быть вызван только из метода UnSerialize, и поэтому возможность или невозможность генерации исключения из метода basic_istream::get(char_type&#038; c) будет определять реализацией UnSerialize.</p>
<p>Что касается метода basic_string::push_back, то помимо ожидаемого std:bad_alloc он может генерировать std::length_error в случае, если длина результирующей строки превышает basic_string::max_size(). Данным аспектом мы управлять не можем, поэтому нам остаётся лишь зафиксировать тот факт, что, в дополнение ко всему выше сказанному, метод Read может генерировать исключения типов std:bad_alloc и std::length_error.</p>
<p><em>Окончательный вид класса UserPreferences</em></p>
<p>Давайте посмотрим, что же мы имеем в итоге. Ниже ещё раз приведено описание класса  &#8211; на этот раз к каждому методу добавлен комментарий.</p>

<div class="wp_syntax"><div class="code"><pre class="cpp" style="font-family:monospace;">&nbsp;
<span style="color: #0000ff;">class</span> UserPreferences
<span style="color: #008000;">&#123;</span>
<span style="color: #0000ff;">public</span><span style="color: #008080;">:</span>
    <span style="color: #666666;">// Может генерировать std::exception</span>
    UserPreferences<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
&nbsp;
    <span style="color: #666666;">// Может генерировать - std::exception</span>
    UserPreferences<span style="color: #008000;">&#40;</span><span style="color: #0000ff;">int</span> intValue, <span style="color: #0000ff;">const</span> std<span style="color: #008080;">::</span><span style="color: #007788;">string</span><span style="color: #000040;">&amp;</span> stringValue, <span style="color: #0000ff;">float</span> floatValue<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
    <span style="color: #666666;">// Метод не генерирует исключений</span>
    <span style="color: #0000ff;">void</span> Serialize<span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span> <span style="color: #0000ff;">const</span><span style="color: #008080;">;</span>
&nbsp;
    <span style="color: #666666;">// Может генерировать std::exception по причине создания объектов типов std::string и UserPreferences</span>
    <span style="color: #666666;">// Может генерировать std:bad_alloc и std::length_error по причине вызова Read</span>
    <span style="color: #0000ff;">static</span> UserPreferences UnSerialize<span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #0000ff;">private</span><span style="color: #008080;">:</span>
    <span style="color: #666666;">// Может генерировать std:bad_alloc и std::length_error</span>
    <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> Read<span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in, std<span style="color: #008080;">::</span><span style="color: #007788;">string</span><span style="color: #000040;">&amp;</span> out<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #0000ff;">private</span><span style="color: #008080;">:</span>
    <span style="color: #0000ff;">int</span> mIntValue<span style="color: #008080;">;</span>
    std<span style="color: #008080;">::</span><span style="color: #007788;">string</span> mStringValue<span style="color: #008080;">;</span>
    <span style="color: #0000ff;">float</span> mFloatValue<span style="color: #008080;">;</span>
<span style="color: #008000;">&#125;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #666666;">// Метод не генерирует исключений</span>
std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&lt;&lt;</span> <span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">ostream</span><span style="color: #000040;">&amp;</span> out, <span style="color: #0000ff;">const</span> UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span>
&nbsp;
<span style="color: #666666;">// Может генерировать std::exception, std:bad_alloc и std::length_error</span>
std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> operator<span style="color: #000080;">&gt;&gt;</span><span style="color: #008000;">&#40;</span>std<span style="color: #008080;">::</span><span style="color: #007788;">istream</span><span style="color: #000040;">&amp;</span> in, UserPreferences<span style="color: #000040;">&amp;</span> userPreferences<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></pre></div></div>

<p>Любопытно отметить, что мы пришли к виду класса, в котором информация об ошибках распространяется как посредством генерации исключений, так и через возвращаемые функциями значения (точнее, посредством входных/выходных параметров std::ostream и std::istream).</p>
<p>Но, что важно отметить, данные ошибки принадлежат двум различным категориям.</p>
<p>Ошибки первого рода могут быть обработаны непосредственно клиентом класса UserPreferences. Например, столкнувшись с проблемой восстановления предварительно сохранённых параметров, клиент класса UserPreferences может попытаться использовать значения по умолчанию либо обратиться за помощью к конечному пользователю приложения.</p>
<p>Ошибки второго рода можно отнести к ошибкам катастрофическим с точки зрения приложения, от которых клиент класса UserPreferences вряд ли сможет самостоятельно восстановиться. Даже предположив, что клиент класса UserPreferences, перехватив исключения типа std:bad_alloc, попытается для продолжения работы загрузить значение по умолчанию, нет гарантии, что при создании требуемого значения клиент не столкнётся с повторной генерацией того же исключения std:bad_alloc, которое приведёт к аварийному закрытию приложения.</p>
<p>Забегая немного вперёд, скажу, что подобное деление ошибок на те, которые могут быть обработаны неким классом (в данном случае &#8211; клиентом класса UserPreferences) и те, которые класс обработать не в состоянии, не является характерной особенностью нашего примера. Оно, скорее, присуще всем системам, составленным из разных уровней абстракций, когда для выполнения некоторого “бизнес-действия” привлекается “низкоуровневая” реализация, а при возникновении ошибки, с которой “работник” справиться не в состоянии, происходит обращение за помощью к вызывающему коду.</p>
<p>Продолжение следует&#8230;</p>
<p>Предыдущая часть опубликована <a href="http://experience.openquality.ru/exception-handling/">здесь</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/exception-handling-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Нужно дело делать, а не языком трепаться</title>
		<link>http://experience.openquality.ru/alexey-pakhunov-interview/</link>
		<comments>http://experience.openquality.ru/alexey-pakhunov-interview/#comments</comments>
		<pubDate>Mon, 18 Jun 2012 04:40:02 +0000</pubDate>
		<dc:creator>Алексей Пахунов</dc:creator>
				<category><![CDATA[Интервью]]></category>
		<category><![CDATA[авралы]]></category>
		<category><![CDATA[техпроцесс]]></category>
		<category><![CDATA[уроки]]></category>

		<guid isPermaLink="false">http://experience.openquality.ru/?p=539</guid>
		<description><![CDATA[В послужном списке Алексея Пахунова, известного как &#171;Not a kernel guy&#187;, более семи лет работы в трех подразделениях Microsoft (Office, OSD и MSR), участие в двух крупных проектах (Axapta и Windows) и нескольких мелких. Текущее место работы &#8211; Google. Низкоуровневая разработка, “серьезность” разработчика, тестирование Windows и Chrome, “шерифы” в Google, TDD “по-взрослому”, время-качество-деньги, импровизация как причина авралов &#8211; вот некоторые темы нашей беседы. Алексей, каковы истоки выражения &#171;Not a kernel &#160;[...]]]></description>
			<content:encoded><![CDATA[<p><em>В послужном списке <a href="http://blog.not-a-kernel-guy.com/">Алексея Пахунова</a>, известного как &laquo;Not a kernel guy&raquo;, более семи лет работы в трех подразделениях Microsoft (Office, OSD и MSR), участие в двух крупных проектах (Axapta и Windows) и нескольких мелких. Текущее место работы &#8211; Google. Низкоуровневая разработка, “серьезность” разработчика, тестирование Windows и Chrome, “шерифы” в Google, TDD “по-взрослому”, время-качество-деньги, импровизация как причина авралов &#8211; вот некоторые темы нашей беседы.</em></p>
<p><strong>Алексей, каковы истоки выражения &laquo;Not a kernel guy&raquo;? Почему именно оно дало название вашему блогу? Что подразумевалось под таким позиционированием?</strong></p>
<p>Полностью эта фраза звучит как &laquo;Not a kernel guy in the Windows kernel team&raquo;, что было, фактически, буквальным изложением той любопытной ситуации, в которой я оказался. В 2006 году я перешел из Копенгагенского центра разработки Microsoft в команду, работавшую над ядром Windows. Это было большой удачей, так как мне всегда нравилось работать с низкоуровневым кодом. В тот момент, однако, мой опыт низкоуровневой разработки был достаточно скромным. Пришлось переучиваться из прикладного разработчика в системного, открывая для себя много нового и интересного по ходу дела. Где-то в этот момент у меня &laquo;дозрело&raquo; желание завести свой собственный блог. Осталось только придумать название. &laquo;Not a kernel guy&#8230;&raquo; лучше всего отражало тематику блога &#8211; я писал о том, что сам недавно узнал, понял и прочувствовал в процессе превращения в <span style="text-decoration: line-through;">тыкву</span> системного разработчика.</p>
<p><strong>Ядро Windows &#8211; это святая святых, сердце системы. Как организован техпроцесс низкоуровневой разработки и в чем его особенности по сравнению с прикладной областью?</strong> </p>
<p>Гм. Я долго пытался сформулировать что же не так с этой фразой. На первый взгляд, вроде все верно. Ядро &#8211; это ключевой компонент любой операционной системы (или, по крайней мере, большинства их них). Кроме того, это довольно сложный компонент, включающий заметное количество нетривиального кода. Но, тем не менее, эта фраза определенно режет мне слух. В этой фразе слишком много эмоций и почти нет фактов. И огромный простор для фантазий о том, как может выглядеть “святая святых” или “сердце системы”. На самом деле все немного проще и совсем по другому. Важных и сложных компонент намного больше, чем пальцев на руках. JIT компилятор .NET, DirectX и драйвера графических адаптеров, подсистема совместимости с предыдущими версиями системы, драйвера файловых систем, гипервизоры и мониторы виртуальных машин, инструменты разработки, сборки и профилирования, тестовая инфраструктура. Ядро &#8211; просто еще один элемент в этом списке. Многие из этих компонентов даже не входят в состав системы, но, тем не менее, без них система была бы совсем другой.</p>
<p>Если не вдаваться в детали, то фундаментальной разницы между техпроцессом разработки прикладных программ и процессом низкоуровневой разработки, как мне кажется, нет. Вернее, разница диктуется не типом разрабатываемого кода, а разной степенью “серьезности” (за неимением лучшего слова) разработчиков.</p>
<p>Скажем, цена ошибки в коде ядра или драйвера выше, чем в прикладном коде, так как первая может “уронить” всю систему, в то время как сфера действия второй ограничена рамками приложения. Чтобы получить успешно работающий у пользователей ядерный код, нужно, чтобы в нем было меньше ошибок, чем в прикладном. Как этого добиться &#8211; дело десятое. Понятное дело, будут применяться методики, улучшаюшие качество кода: более разносторонее и формальное тестирование, статический анализ, рецензирование кода, методики, позволяющие улучшить исходный дизайн, формальное доказательство корректности кода и т.д. Выбор конкретных методик зависит от команды, доступного бюджета и прочих обстоятельств.</p>
<p><strong>Да, “святая святых” и “сердце системы” &#8211; слишком пафосно и эмоционально. Спасибо, что спустили с небес на землю. Пишут ли системные разработчики псевдокод, создают ли прототипы или сразу же приступают к созданию «боевого» кода?</strong> </p>
<p>Системные разработчики делают все то же самое, что и прикладные: пишут псевдокод и рисуют коробочки на доске, создают прототипы, пишут тесты и, бывает, правят “по-живому”. Просто более тщательно. Если же, конечно, они ставят перед собой задачу написать качественный код.</p>
<p><strong>Вопрос о &laquo;серьезности&raquo;. Алексей Пахунов в своем первом проекте и сейчас: что потребовалось изучить/осознать на пути к задачам, которые поручают &laquo;серьезным&raquo; разработчикам?</strong> </p>
<p>Наиболее важными мне кажутся несколько вещей. Во-первых, опыт &#8211; в смысле, багаж шишек, набитых как на собственном лбу, так и на лбах коллег. Знания вида “если делать так, то получится эдак”. Наличие опыта позволяет быстрее приходить к верным решениям. В категорию опыта можно отнести и широту кругозора. В какой-то момент становится полезно знать, что происходит в индустрии, какие исследования проводятся в данный момент, текущие тренды и прочее. Такие поверхностные знания помогают быстрее разобраться в новой предметной области, если возникнет такая необходимость.</p>
<p>Во-вторых, важна репутация. Я это хорошо почувствовал при переходе в Google. На новом месте зарабатывать репутацию приходится почти с нуля. Репутация, зачастую, &#8211; основной критерий оценки, “сдюжит или нет”. “Стоит ли поручать эту задачу подчиненному? До сих пор он справлялся, каждый раз &#8211; со все более сложными задачами. Однозначно стоит!” “Стоит ли тратить время на этот проект? Они уже два раза начинали что-то похожее и бросали на полпути. Пожалуй, не стоит.”</p>
<p>В категорию репутации, вернее, методов её создания, можно отнести умение говорить на одном языке с собеседником. Инженеры, средний менеджмент, менеджеры проектов, шишки из руководства &#8211; все разговаривают на собственном диалекте. Бывает забавно наблюдать, как на совещании кто-нибудь из среднего менеджмента “переводит” вице-президенту то, что сказал инженер.</p>
<p>В-третьих, крайне важно уметь работать с людьми. Уметь договариваться и донести свою мысль. Уметь привлечь на свою сторону. Уметь понять собеседника. Не просто понять, что он говорит, но заодно и понять, почему и чем вызвана такая точка зрения. Уметь не принимать близко к сердцу рабочие конфликты.</p>
<p>И, наконец, нужно дело делать, а не языком трепаться. :-)</p>
<p><strong>Да, делу время, потехе час. Не ошибается тот, кто ничего не делает :) Что изменилось в ваших подходах к созданию приложений, к стилю программирования?</strong></p>
<p>Про свой подход к созданию приложений ничего внятного, откровенного говоря, сказать не могу. Я просто опираюсь на свой опыт и стараюсь не наступить повторно на одни и те же грабли. Чуть менее успешно, стараюсь не наступать на грабли, ранее разминированные коллегами. Понятно, почему &#8211; свои шишки болят больнее. Но, как вы понимаете, суммарный опыт нескольких людей больше чем опыт одного человека. Поэтому, например, следует всегда помнить, что если коллега несет пургу, то это еще не значит, что он не прав. :-)</p>
<p>И, раз мы об этом заговорили, про стиль. Использование единого стиля кодирования в команде/проекте крайне полезно. Единый стиль облегчает чтение и сопровождение кода, уменьшает число ошибок в коде. Особенно если это хорошо продуманный, проверенный практикой стиль кодирования.</p>
<p>Вместе с тем обсуждение деталей стиля &#8211; это самый бесполезный способ потратить время. Стоит только собрать больше пяти разработчиков вместе и предложить поговорить про целесообразность спорного пункта из правил. Если через час они не перессорятся, это можно считать большой удачей. Самое обидное, что если бы они потратили это время на написание юнит-тестов результат с хорошей вероятностью был бы лучше.</p>
<p>В связи с этим мне начинают импонировать команды, где эталонный стиль задан “указом сверху”. Кто-то мне рассказывал, что в некоей команде мерилом правильности стиля был стиль, в котором пишет наиболее старший (по должностной иерархии) разработчик. Стиль, наверное, был ужасен, но зато никаких споров. :-)</p>
<p>Я сам обычно просто перенимаю стиль, который используется в проекте (или в конкретном файле) и избегаю участия в обсуждениях стиля. Ну, разве что, если за обедом не о чем поговорить.</p>
<p><strong>Сможете ли вы рассказать о наиболее значимых ошибках, с которыми вам довелось столкнуться в проектах на тех или иных этапах? Какие выводы из них удалось извлечь?</strong></p>
<p>Расскажу то, что пришло в голову.</p>
<p>Случай первый. Жил-был один проект. Состоял он из двух больших кусков работы: 1) спроектировать и произвести нестандартное железо; 2) модифицировать существующий софт и запустить на этом железе. Обе задачи были сложными и масштабными для имевшихся в наличии ресурсов. Естественным образом сформировались две команды: одна, работающая над аппаратной частью, другая &#8211; над программной. В ходе работы возникли проблемы взаимной коммуникации между командами. То программные требования не доходили до ведома проектировщиков железа, то, наоборот, программисты узнавали специфические требования, предъявляемые железом, только когда что-то переставало работать. Поначалу это было похоже на обычные человеческие ошибки. Потом это стало походить на замалчивание информации. Контакты с внешними командами и вендорами тоже страдали похожих же проблем.</p>
<p>Закончилось это все, как вы можете догадаться, плачевно. Был большой разбор полетов и одна из команд получила по шапке за провал проекта. Причем совсем не факт, что другая команда не получила бы по шапке аналогичным образом, если бы проект не завалился. Пикантная часть истории заключается в том, что проблема из-за которой проект был закрыт была, в общем-то, тривиальна. Будь коммуникации более открытыми и прозрачными, она была бы обнаружена еще в самом его начале, когда исправить её было относительно легко и просто.</p>
<p>История вторая. Писал я некое приложение, для которого понадобилось хранить настройки. Причем код должен был быть максимально платформо-независимым. “Ну чё тут думать?”, сказал мой шеф. Пусть будут INI файлы и делов-то. И я написал код для разбора INI файлов. Проблема только, что, как позже выяснилось, тогда я не понимал как, собственно, должны разбираться INI файлы. Взглянув на получившееся, шеф тактично промолчал. Не до того было. А вот мой коллега, которому досталось сопровождение этого кода после моего ухода, говорил пространные речи про рак мозга. Совершенно справедливо, кстати. Мораль: изобретая велосипед, изучи аналоги. Иначе потом будет стыдно.</p>
<p>Еще одна история про то, как я ломал официальную сборку Chrome три раза подряд. Проект, над которым я сейчас работаю, живет в том же самом репозитории, что и Chromium &#8211; open source версия Chrome. И собирается вместе с ним же. Понадобилось нам сделать так, чтобы наши файлы архивировались вместе с файлами Chome. Ну что может быть проще, скажете вы? Раз наши файлы уже собираются вместе с Chrome, то достаточно сделать то же самое, что делается для других файлов &#8211; добавить в какой-нибудь список “архивируемых” файлов или что-то в этом роде. Единственная закавыка &#8211; никакой документации, как это сделать, не существует. Вся документация &#8211; это код и комментарии в нем. Совершенно обычная история. Тем более, что есть полнотекстовый поиск по исходному коду.</p>
<p>Я нашел нужный список файлов, добавил наши файлы, тщательно имитируя все то, что делается с остальными файлами. Проверил, что все добавленные файлы собираются как надо. Получил “добро” от владельцев соответствующего скрипта. Результат &#8211; официальная ночная сборка не собирается. Оказалось, что официальная сборка собирается немного не так, и наши бинарники просто не были скомпилированы.</p>
<p>Второй заход &#8211; исправил официальную сборку, проверил что файлы собираются так, как надо, снова получил “добро”. Естественно, на утро получаю раздраженное письмо: “Can you please test it locally before committing?”. Ну я, собственно, так и сделал, но сборка-то сломана! На этот раз причиной была ошибка в одном из скриптов, которая выползла наружу из-за моих правок.</p>
<p>В третий раз <span style="text-decoration: line-through;">пришел невод</span> я умудрился сломать сборку, правя скрипт, который подписывает файлы цифровой подписью. Доступа к машине, где запускается этот скрипт, у меня нет, чтобы избежать утечки приватного ключа подписи. В результате, несмотря на успешное локальное тестирование, не все удалось учесть и сборка, по своему обычаю, сломалась.</p>
<p>Во всех трех случаях причина проблем была в одном и том же &#8211; часть логики осталась неоттестированной из-за отсутствия возможности её протестировать без запуска официальной сборки. Те, кто имел дело с большими проектами, знают, что зачастую инфраструктура для сборки проекта выглядит как лоскутное одеяло. Дополнительные ограничения, связанные с безопасностью, только усложняют проблему. А сделать так, чтобы все можно было протестировать заранее, стоит немалых усилий и времени разработчиков и инженеров сопровождения. </p>
<p><strong>Алексей, есть ли какие-то особенности в организации тестирования в Microsoft и Google, на которые вы обратили внимание?</strong></p>
<p>И Microsoft, и Google состоят из отдельных команд, методы и подходы к работе которых могут очень сильно отличаться друг от друга. Скажем, подразделения Bing и Windows в Microsoft выпускают продукты совершенно по-разному. Я могу сравнить то, что видел собственными глазами: как поставлено тестирование Windows в Microsoft и Chrome в Google.</p>
<p>В Windows структура команды базируется на тройках: разработчики (Software Development Engineers, SDEs), тестеры (SDEs in Test) и менеджеры проектов (Program Managers, PMs). Численность разработчиков и тестеров примерно равная с перекосом в сторону разработчиков. Задача тестеров состоит в создании такой инфраструктуры автоматического тестирования, чтобы максимально проверить корректность кода. Код должен не только выдавать ожидаемый результат, но и быть быстрым, надежным, совместимым, локализуемым и т.д. Кроме этого, тестовая инфраструктура должна обеспечивать разработчика информативной диагностикой для обнаруженных проблем и давать возможность прогона тестов разработчиком перед тем, как код попадет в репозиторий.</p>
<p>Соответственно, задача разработчиков &#8211; написать код, который не только делает то, что нужно, но и позволяет себя проверить с помощью инфраструктуры, созданной тестерами. Сделать это без взаимодействия разработчиков и тестеров нельзя. Или, по крайней мере, сложно. Поэтому обычно разработчики и тестеры совместно работают над созданием новой функциональности. Начиная с написания спецификаций: разработчик пишет design specification, а тестер &#8211; test specification. Обе спецификации (на самом деле три &#8211; PM-ы пишут functional specification) перекрестно рецензируются и согласовываются. Далее разработчики с тестерами более-менее параллельно пишут код и полируют его до рабочего состояния.</p>
<p>Далее, в разработке Windows интенсивно используются ветки. Новый код сначала попадает в ветку нижнего уровня, а затем постепенно мигрирует выше, попадая со временем в основную ветку. Перед каждой интеграцией изменений в более высокую ветку код должен быть доведен до “зеленого” состояния. За тестерами при этом остается последнее слово &#8211; быть интеграции или не быть.</p>
<p>В случае Chrome все сказанное выше про код также верно. Отличие состоит в том, что тестеров гораздо меньше чем разработчиков. Пропорция, на мой взгляд, где-то один тестер к 5-10 разработчикам. Соответственно разработчики обязаны писать тесты сами и всячески тестировать свой код.</p>
<p>Сильный акцент ставится на автоматическое тестирование. Каждый коммит в репозиторий прогоняется через почти все имеющиеся тесты (т.н. commit queue, или CQ &#8211; автоматизированная очередь тестов перед коммитом). Часть тестов выполняется перед коммитом, часть (такие как прогон с анализатором памяти и остальные занимающие много времени проверки) &#8211; после.</p>
<p>Ветки используются только для release management. Все разработчики, фактически, коммитят код в основную ветку. Стабильность кода при этом обеспечивается тремя вещами. Во-первых, все изменения просматриваются коллегами в обязательном порядке. Во-вторых, значительная часть тестов прогоняется до коммита в репозиторий. В-третьих, коммиты, успешно прошедшие CQ, но сломавшие, тем не менее, сборку или какой-то тест, как правило, сразу откатываются назад. Занимаются этим так называемые “шерифы” &#8211; разработчики, назначенные следить за состоянием кода в репозитории. Каждый постоянный разработчик периодически отрабатывает пару дней “шерифом”, а затем возвращается к своим повседневным обязанностям.</p>
<p>Не возьмусь судить, какая из моделей лучше. В каждой есть свои положительные и отрицательные стороны. Наличие отдельной команды тестеров освобождает разработчиков от возни с тестовой инфраструктурой. Скажем, нестабильные тесты меня совсем не волновали, пока я работал над Windows. С другой стороны, обязательное рецензирование всех изменений очень положительно влияет на качество кода. Было бы интересно ввести правило, согласно которому тестер обязательно должен просмотреть коммит разработчика и наоборот. Уверен, что в конечном итоге это улучшило бы взаимодействие тестеров и разработчиков.</p>
<p><strong>Насколько эффективно, на ваш взгляд, вовлечение в техпроцесс выделенных тестировщиков?</strong> </p>
<p>Примерное равенство численности разработчиков и тестеров мне кажется очень хорошей идеей. Естественно, если при этом разработчики и тестеры совместно работают над повышением качества кода. Кто-то, наверное, может возразить, что сами разработчики вполне могут справиться с созданием автоматической тестовой инфраструктуры. Мне кажется, однако, что на практике это работает не очень хорошо. Разработчики слишком легко сбиваются на написание кода, откладывая тесты “на потом”. Выделенная QA-команда (даже если набрать её из тех же разработчиков) лучше уже тем, что перед ней стоит другая цель &#8211; не написать код, а убедиться в его качестве.</p>
<p><strong>Исходя из вашего опыта, как организовать тестирование продукта наиболее эффективным образом в координатах “время-качество-деньги”?</strong></p>
<p>У меня, конечно же, нет готового рецепта. Я бы выделил несколько вещей:</p>
<div style="margin-left: 20px;">
<p>1. &nbsp;Качественный код требует больших затрат времени и денег, чем менее качественный.<br />
2. &nbsp;Качество кода должно быть измеримо.<br />
3. &nbsp;Компьютер должен работать, а человек &#8211; думать.<br />
4. &nbsp;Наличие обратной связи на всех этапах от разработчика до потребителя кода очень важно.</p>
</div>
<p>Мне довольно часто приходилось наблюдать (и быть участником) ситуаций, когда умные, казалось бы, люди не понимают того, что создание более качественного кода требует затрат времени и денег. Если хотите использовать TDD “по-взрослому”, приготовьтесь, что половину времени разработчики будут тратить на написание тестов. Да, это означает, что будет написано вдвое меньше фич [features]. (Здесь сторонники TDD дружно потянулись за гнилыми помидорами :-) ). Хотите, чтобы написанные тесты запускались автоматически? Недостаточно купить оборудование. Запаситесь временем на диагностику ошибок, поддержку и сопровождение скриптов, серверов и т.п.</p>
<p>Верно и обратное: “денег в кассе” может и не быть, либо другие факторы могут быть важнее для выживания бизнеса. Может быть и так, что если некачественный продукт не выйдет через месяц, то компания вылетит в трубу, и шанса выпустить качественный продукт когда-либо вообще &#8211; просто не будет.</p>
<p>Существует мнение, что TDD и другие методики создания качественного кода требуют лишь начальных затрат на написание тестов и создание инфраструктуры. Утверждается, что как только начальный этап будет преодолен, то созданная инфраструктура начнет приносить дивиденды в виде ошибок, отловленных почти задаром на ранних подступах к коду проекта. Мне кажется, что это утверждение только сбивает с толку.</p>
<p>На практике происходит следующее. По мере развития инфраструктуры на неё возлагается все больше и больше задач, освобождая разработчиков от рутины. Все освободившееся время (и еще чуть-чуть) тут же тратится на дальнейшее улучшение качества кода, совершенствование инфраструктуры, процессов разработки и т.д. Т.е., получается, что времени и ресурсов тратится чем дальше, тем больше, но взамен появляется возможность создавать все более сложные и совершенные продукты.</p>
<p>Второй пункт говорит сам за себя. Мониторинг качества разрабатываемого продукта &#8211; необходимая составляющая процесса разработки качественного кода. Иначе откуда вы знаете, что вносимые изменения делают ваш продукт лучше (или хуже)? Качество кода &#8211; трудно измеримая величина. Не существует единственного параметра, определяющего качество. Лучшие из известных мне решений отслеживают массу параметров: состояние дел в баг-трекере, результаты прогонов тестов, результаты статического анализа кода, статистику баг-репортов, полученных от пользователей и многое другое. </p>
<p>Большая часть создаваемой инфраструктуры должна работать автоматически и на постоянной основе. Понятно, почему &#8211; так дешевле, надежней, а полученные результаты &#8211; повторяемы (что абсолютно необходимо для мониторинга качества). На данный момент создана масса удобных и полезных инструментов: статические анализаторы кода, динамические валидаторы кода, инструменты для сбора и анализа логов и т.п. Многие из них свободно доступны и бесплатны. Грех не пользоваться ими. Тем не менее, подобные инструменты используются не так широко, как можно было бы ожидать.</p>
<p>Наличие обратной связи. От момента зарождения идеи и до момента, когда продукт попадает к конечному пользователю, код проходит через множество этапов, видоизменяясь по пути. Это сложный процесс. Как показывает практика, даже самые умные люди не могут точно предсказать как покажет себя только что написанный код. Разработчик не может точно предсказать, где тестер найдет ошибку в его коде. Вся продуктовая команда не может точно предсказать, в каких условиях будет работать выпущенный продукт и где именно пользователи столкнутся с проблемами. Налаженная обратная связь позволит оперативно исправить код, “подогнав” его под реальность.</p>
<p>Также очень важно, чтобы обратная связь была проактивной, а не реактивной. Иными словами, тестер должен иметь возможность вносить коррективы в дизайн до написания кода; пользователи должны иметь возможность влиять на дизайн продукта (например, участвуя в тестировании прототипов). </p>
<p><strong>Авралы: как часто они случаются в вашей практике и что обычно является их причиной?</strong></p>
<p>От проекта зависит. Мне, вроде бы, удается пока держать баланс между авралами и нормальной работой. Помимо всего прочего, объем внеурочной работы напрямую зависит от готовности работника взваливать её на свои плечи. Практика показывает, однако, что многие срочные проблемы успешно решаются откладыванием их на завтра. Осталось только научиться уверенно отличать подобные “срочные” проблемы от действительно срочных&#8230; :-)</p>
<p>Фундаментальная причина авралов &#8211; импровизация. Возьмите любую процедуру, для которой существует контрольный список шагов. Любое отклонение от списка &#8211; возможная ошибка. Чем больше допущено отклонений, тем выше вероятность проблем. Тем выше вероятность, что их придется исправлять в срочном порядке. Тоже самое с существующими техпроцессами. Любое отклонение &#8211; возможная ошибка. Вы закоммитили код без прогона тестов &#8211; ждите поломанной сборки. Новый код не покрыт юнит тестами &#8211; значит, он сломается в будущем в самый неподходящий момент. Еще хуже ситуация, когда техпроцесса попросту не существует. Скажем первый выпуск продукта на рынок свежеиспеченной компанией. Сплошная импровизация &#8211; большое количество ошибок &#8211; непрерывный аврал.</p>
<p><strong>Алексей, в &laquo;<a href="http://blog.not-a-kernel-guy.com/2012/01/05/1250">Вестях с полей</a>&raquo; вы пишите: &laquo;То ли повезло, то ли менеджеры у меня были хорошие (кстати, хорошие менеджеры были, кроме шуток), то ли игры на самом деле не такие ужасные…&raquo;. Что характеризует хорошего менеджера и чем он отличается от плохого?</strong></p>
<p>“Нормальный” менеджер по крайней мере не мешает работать. Хороший &#8211; помогает. Очень хороший &#8211; помогает и не держит зла за допущенные ошибки (но, при этом, указывает на них и помогает не допустить их в будущем).</p>
<p><strong>Каких возможностей вам не хватает в современных языках программирования?</strong></p>
<p>Если в двух словах, то не хватает удобных средств борьбы со сложностью кода и возможности аналитически доказать корректность кода. К первым я бы отнес инструменты, помогающие находить сходные куски кода; диагностику, показывающую, как именно выполнялся код, как он может выполниться в похожих/граничных условиях; инструменты для статического и динамического анализа кода; возможность контролировать разрешённые конструкции языка; возможность расширять язык напрямую, вместо костылей типа перегрузки операторов и шаблонов в C++ и т.д.</p>
<p>К второй группе я бы отнес возможность задать (и гарантировать) условия, в которых должен выполнятся кусок кода (входные данные, определенное количество доступной памяти, немодифицируемость кода и данных, другие потоки не касаются той же памяти и т.п.) и возможность формально доказать корректность куска кода в данных условиях. Ну и на сладкое &#8211; возможность “сшить” такие формально доказанные куски кода в одну программу.</p>
<p><strong>Что нового, на ваш взгляд, появится в языках и средах разработки в ближайшие 5-10 лет?</strong></p>
<p>Прогнозы &#8211; неблагодарное дело. Средства разработки следуют за модными технологиями с задержкой в несколько лет. Видимо, появится больше инструментов для облачных приложений и виртуальных сетей. Что там еще модно на данный момент?</p>
<p><strong>Говоря о нынешней моде, я бы добавил мобильные приложения и социальные сети. Продажа Instagram за 1 млрд долларов вскружила голову многим. :) Что вам хотелось бы изменить в индустрии IT?</strong></p>
<p>В IT, как мне кажется, процветает полнейший дарвинизм. Выживает тот, кто смог заработать. Можно помечтать, что если бы критерием выживания был не коммерческий успех, а то самое декларируемое удобство пользователя и общая эффективность технологических решений, то и индустрия IT занималась бы совсем другими, более интересными вещами. Но сказка она на то и сказка, что там лучше живется. :-)</p>
<p><strong>Алексей, большое спасибо за интервью. Пусть сказка на вашем пути хоть иногда становится явью!</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://experience.openquality.ru/alexey-pakhunov-interview/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<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>5</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>9</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>2</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><a href="http://experience.openquality.ru/exception-handling-2/">Продолжение&#8230;</a></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>9</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>
	</channel>
</rss>
