tag:blogger.com,1999:blog-31799648355931377942024-03-13T17:58:08.601+03:00Распутывая нитиМного загадочного и непонятного скрывает в себе мир вообще и мир программирования в частности.<br>
Не запутаться бы.Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.comBlogger150125tag:blogger.com,1999:blog-3179964835593137794.post-11275924205347954242017-11-29T16:34:00.000+03:002017-11-29T16:34:31.246+03:00Ленивы ли ваши объекты?<div dir="ltr" style="text-align: left;" trbidi="on">
<a href="http://www.yegor256.com/2015/05/07/ctors-must-be-code-free.html" target="_blank">Конструкторы не должны содержать кода</a>. Так думает Егор Бугаенко, и я с ним согласен. И у меня возникли некоторые соображения по этому поводу.<br />
<br />
Для начала представляю вам объект:<br />
<br />
<pre>class HardCalculationValue:
def __init__(self):
# Точка 1
pass
def value(self):
# Точка 2
return None
</pre>
<br />
Этот объект - абстракция. Он может делать тяжелые операции разной природы. Работа с внешними ресурсами, как-то БД, файлы, сеть. Какие-то тяжелые преобразования. Конечно это могут быть и не очень тяжелые преобразования, но на тяжелых преобразованиях сильнее чувствуется боль. :)<br />
<br />
И нам нужно вставить тяжелую операцию. Какую точку вы выберите 1 или 2?<br />
Почему-то вспомнилось: ... я покажу тебе, как глубоко ведет кроличья нора. :)<br />
<br />
<a name='more'></a>Мы все подсознательно стремимся к оптимальным решениям с точки зрения производительности. Мы постоянно оптимизируем. И в данном случае многие наверное подумали - Если метод value() будут дергать много раз, то это будет жутко неэффективно... Значит правильный ответ - точка 1.<br />
<br />
<pre>class HardCalculationValue:
def __init__(self):
self.value = calculateHardValue()
def value(self):
return self.value
</pre>
<br />
Но в данной схеме мы имеем большую проблему, которая заключается в том, что мы не можем создать этот объект заранее. В момент создания он пойдет и немедленно сделает ту грязную работу, для которой создан.<br />
<br />
Это значит, что мы всегда должны создавать его в том месте, где необходимо выполнить эту грязную работу.<br />
<br />
Мы не можем его тестировать, поскольку должны зашить в пользовательский код информацию о типе этого объекта. Кто-то скажет, что можно использовать фабрику, которая будет в необходимый момент генерировать объект необходимого типа. Это немного снимает проблему с жестко закодированной информацией о типе, но не сильно облегчает тестирование. Ведь помимо тестовых объектов нам придется генерировать тестовые фабрики.<br />
<br />
Правильный объектно ориентированный ответ - 2<br />
<br />
<pre>class HardCalculationValue:
def __init__(self):
pass
def value(self):
return calculateHardValue()
</pre>
<div>
<br /></div>
Это легко тестировать. Нам не нужна фабрика, поскольку мы можем сам объект передать в качестве внешней зависимости в те классы, которые хотят им пользоваться. Мы можем передать один объект сразу в несколько мест, поскольку в процессе работы <a href="http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html" target="_blank">его внутреннее состояние не меняется</a>.<br />
<br />
Вопрос с производительностью можно решить с помощью кеширующих декораторов в необходимых местах.<br />
<br />
PS: Это получается, что фабрика - признак агрессивных классов...<br />
<br />
<br /></div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-85796078261768294422017-06-30T15:09:00.000+03:002017-06-30T15:09:32.164+03:00Объектно ориентированное юниттестирование<div dir="ltr" style="text-align: left;" trbidi="on">
Возникло неудержимое желание написать статью. :)<br />
<br />
Последнее время все мои мысли занимает true oop. Вокруг в этом плане царит уныние. И хуже того. Читая достаточно старые книги, например про Smalltalk, я с грустью наблюдаю объекты, количество методов у которых исчисляется десятками. Это какой-то неправильный ООП.<br />
<br />
И возникло у меня желание реализовать фреймворк для юниттестирования, построенный на принципах правильного ООП.<br />
<br />
<a name='more'></a>Но что не так в тестовых фреймворках сейчас? - спросите вы. Вообщем ничего страшного с ними нет, только они совсем не про ООП.<br />
<br />
<br />
Взять к примеру <a href="https://github.com/google/googletest/blob/master/googletest/docs/Primer.md" target="_blank">googletest:</a><br />
<br />
<i>Tests use assertions to verify the tested code's behavior.</i> <br />
<br />
Но давайте заглянем в книги... к примеру в эту: <a href="http://www.ozon.ru/context/detail/id/4127815/" target="_blank">Шаблоны тестирования XUnit</a>, которую я крайне рекоммендую.<br />
<br />
<i>Нормальный способ проверки результата называется проверкой состояния (State Verification).</i><br />
<br />
Проверка поведения сложнее в связи с динамической природой поведения и требует введения тестовых двойников разных типов. Обычно здесь используются различные Mock-фреймворки и тд.<br />
<br />
Почему же так получилось, что тестовый фреймворк позиционируется как инструмент для проверки поведения? (Возможно это было написано немного неосознанно, тем не менее). Проблема в том что в умах программистов доминирует процедурное мышление. Никого не хочу обидеть, но мы привыкли думать алгоритмами и последовательностями операций. И современные "ООП" языки культивируют процедурный подход. Для ООП - это ошибка.<br />
<br />
Недавно слышал предложение о том, что std::string слишком велик (150 методов), давайте растащим его на свободные функции. Ну йо...<br />
Честно признаюсь, по работе я все тесты делю на секции - Given/When/Then. Но последнее время я понимаю, что и это тоже ошибка. Тот самый процедурный подход в действии.<br />
<br />
<b>Проверка поведения - это не про ООП!</b> <br />
<br />
<br />
Каким же должен быть объектно ориентированный подход в плане юниттестового фреймворка?<br />
<br />
Ну для начала в ООП фреймворке <b>не должно быть тестовых методов</b>. Объект, который описывает в себе несколько разных тестов как минимум нарушает SRP. Каждый объект должен делать свое дело.<br />
<br />
В ООП фреймворке <b>не должно быть утверждений</b>. Я программирую на c++, и в c++ это обычно реализуется через макросы, которые совсем не про ООП. Кроме того выделяя утверждения - мы даем возможность писать в одном тесте по нескольку утверждений. Хотя все гуру говорят что это плохо, но тем не менее все так делают по моим наблюдениям. Кроме того отдельные утверждения как бы подразумевают, что существует и другой код, и что система нуждается в настройке - а это процедурный подход. Правильно приготовленный объект в настройке не нуждается!<br />
<br />
Все тесты реализуются через <b>проверку состояния</b>.<br />
<br />
В ООП фреймворке <b>должна быть объектная композиция</b> всех тестов. В Объектно-ориентированном программировании задачи решаются через композицию взаимосвязанных объектов. Логика тестирования должна быть заложена на этапе создания тестового приложения.<br />
<br />
Это достаточно не просто, потому что требует, чтобы все объекты могли быть созданы без дополнительной настройки и без дополнительной нагрузки на CPU. В процессе создания никакой активности не должно происходить. Это достигается чистыми иммутабельными объектами, которые ничего не делают в конструкторе.<br />
<br />
Современные фреймворки слишком толерантно относятся к вкусам пользователей. Позволяя писать почти как угодно. Никак не дисциплинируя их. :) Так нельзя!<br />
<br />
В процессе размышлений и экспериментов у меня сформировалась следующая концепция:<br />
<br />
Существуют классы тестов. - Тест сам по себе является утверждением. Каждый вид теста рассчитан на определенный тип проверок. Например есть TestEqual - класс, заточенный на проверку равенства. Таких тестов будет некоторое количество встроенных, и пользователь сможет добавлять свои, если очень понадобиться.<br />
<br />
Тесты не имеют имени, есть декоратор, который позволяет дать тесту имя. Тесты не рассчитаны на возникновение исключения, есть декоратор, который позволяет отловить ошибки такого типа.<br />
<br />
Тесты не умеют принимать на вход любые типы. Тесты заточены на строки и текстовые представления. Пользователь может сделать текстовые представления для своих классов и пользоваться стандартными тестами.<br />
<br />
В результате это выглядит примерно <a href="https://github.com/DronMDF/2out/blob/master/test/JUnitXmlReportTest.cpp" target="_blank">так</a>.<br />
<br />
Не знаю, можно ли этим уже пользоваться - сам попробую в ближайшее время. Но за обратную связь буду благодарен.<br />
<br />
<a href="https://github.com/DronMDF/2out" target="_blank">Follow me on github</a><br />
<br />
PS: Наверное после долгого перерыва статья получилась немного сумбурная. Ну уж извиняйте. :)</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-82972468162666040112016-05-11T23:10:00.000+03:002016-05-11T23:10:36.990+03:00Чем плох мой код?<div dir="ltr" style="text-align: left;" trbidi="on">
Сейчас мало пишу, но зато много просматриваю и принимаю. Иногда бывает трудно объяснить, чем плох тот или иной код. Мне кажется что люди не хотят быть понятными, и, как будто, специально усложняют свой код. Я же в свою очередь виду ущербность, но обосновать не могу.<br />
<br />
Размышляя об этом осознал два важных момента. Можно сказать - ключевых:<br />
<ul style="text-align: left;">
<li>Код должен располагаться на месте</li>
<li>Код должен быть необходим</li>
</ul>
<a name='more'></a> Хотел ввести еще третий ключевой момент - код должен быть уникален. Но неуникальность (дублирование), это следствие того, что код располагается не там, где должен быть. Либо этот код изначально не расположили в нужном месте. Либо в процессе развития системы поленились перенести его в нужное место.<br />
<br />
Расположение кода на месте - понятие весьма растяжимое. Это может быть библиотека, отсутствие которой вынуждает принимать неоптимальные решения. Это может быть класс, нарушающий SRP и делающий больше чем должен, в результате чего определенное поведение невозможно расшарить и необходимо в том или ином виде дублировать.<br />
<br />
Но дело не только в дублировании (а то так получится, что выброшенный третий ключевой момент каким-то образом стал важнее первого). Нарушение первого ключевого момента может проявляться в том, что код располагается не на том уровне абстракции. Что-то у меня вечером не придумывается подходящий пример, но это примерно как в один список поместить свечу зажигания и автомобиль.. Кто-то явно не на месте.<br />
<br />
Еще это может проявляться когда мы вынуждены писать код там, где этого кода не должно быть. В проекте была ситуация, когда два приложения использовали библиотеку, которая вынуждала в одном из приложений писать заглушки для функций из доменной области другого приложения. Явно кто-то не на месте.<br />
<br />
Перейду к необходимости кода. Как сказал Экзюпери: Il semble que la perfection soit atteinte non quand il n’y a plus rien à ajouter, mais quand il n’y a plus rien à retrancher (Совершенство достигается не тогда, когда уже нечего прибавить, но когда уже ничего нельзя отнять). Но не все так просто.<br />
<br />
Иногда мы начинаем пилить объектную архитектуру. Иногда это доходит до крайностей. Недавно поймал себя на мысли - что это синтаксический, объектно ориентированный сахар, как и обычный сахар в больших количествах вреден. Что он там, кариес вызывает и диабет. И я подумал, что хотел бы убрать все объектные наслоения и для начала увидеть код, который собственно делает что-то полезное. Глядя на этот код я наверное смог бы сказать что в нем не так... Но глядя на большую иерархию классов, где содержательный код размазан и просматривался с трудом, я не мог сказать что в ней не так... Все? Нельзя ли убрать эти классы?<br />
<br />
Нет, я не предлагаю все свалить в одну большую функцию. Но сам по себе алгоритм работы - хорошая отправная точка для того, чтобы понять где что должно быть.<br />
<br />
Деля код на классы и методы мы уменьшаем неясность кода, значит приближаемся к совершенству. Но до определенного предела, преодолев который мы рискуем скатиться в макаронный хаос...<br />
<br />
При итеративном подходе очень правильно писать лишь нужное. Не усложняя архитектуру для какой-то потенциальной пользы, которая может и не наступить.<br />
<br />
Не пишите лишнего и неуместного. Вот такая мораль.<br />
<br />
<br />
<br /></div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-18480404149275630082016-04-26T22:45:00.001+03:002016-04-26T22:45:16.290+03:00Булевые входные параметры<div dir="ltr" style="text-align: left;" trbidi="on">
Вижу странное рвение параметризовать функции булевыми параметрами. Примеры буду черпать из ядра Linux, ну не из рабочего же кода их брать. :)<br />
<br />
<pre>void imx6sl_set_wait_clk(bool enter);
</pre>
<br />
Считаю такой подход крайне неинтуитивным. Зачастую по коду, в котором этот метод вызывается, не возможно сказать - что означает false или true. Для того, чтобы это понять - ты должен открыть прототип.<br />
<br />
Интуитивная понятность очень важна.<br />
<br />
<a name='more'></a>И главное, мне не очень понятно, что люди пытаются на этом выгадать?<br />
<br />
<pre> static void pt_config_start(bool start)
{
u64 ctl;
rdmsrl(MSR_IA32_RTIT_CTL, ctl);
if (start)
ctl |= RTIT_CTL_TRACEEN;
else
ctl &= ~RTIT_CTL_TRACEEN;
wrmsrl(MSR_IA32_RTIT_CTL, ctl);
if (!start)
wmb();
}</pre>
<pre> </pre>
Причем по коду эту функцию никогда не вызывают с true... :) Но даже если бы true был бы нужен, гораздо понятнее написать две функции:<br />
<br />
<pre> static void pt_config_start()
{
u64 ctl;
rdmsrl(MSR_IA32_RTIT_CTL, ctl);
wrmsrl(MSR_IA32_RTIT_CTL, ctl | RTIT_CTL_TRACEEN);
wmb();
}
static void pt_config_stop()
{
u64 ctl;
rdmsrl(MSR_IA32_RTIT_CTL, ctl);
wrmsrl(MSR_IA32_RTIT_CTL, ctl & ~RTIT_CTL_TRACEEN);
}</pre>
<pre> </pre>
Но это даже не самая главная польза.<br />
Самая главная польза в том, что вместо <br />
<br />
<pre> pt_config_start(true);
....
pt_config_start(false);
</pre>
<br />
Мы будем писать:<br />
<br />
<pre> pt_config_start();
....
pt_config_stop();
</pre>
<br />
В первом примере невозможно точно понять, действительно ли true/false oзначает start/stop... Это может быть какая нибудь совершенно другая отвлеченная вещ - типа сохранять ли контекст или не сохранять. Или печатать сообщение или не печатать...<br />
<br />
Выходные же параметры использовать, на мой взгляд, вполне допустимо.<br />
<br />
<pre>int __must_check kstrtobool(const char *s, bool *res);
</pre>
<br />
В отличии от входного аргумента здесь мы должны явно задать переменную, и контекст вызова самодостаточен..<br />
<br />
<pre> bool bv;
rc = kstrtobool(buffer, &bv);
</pre>
<br />
Особенно хорошо получается, если не называть переменную bv. (sic)</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-24991785770633674192016-04-08T23:41:00.000+03:002016-04-08T23:41:07.707+03:00Правильный интерфейс...Есть много всяких принципов и практик. Часто в интерфейсе класса можно учуять множество всяких запахов. Абсолютно правильно наверное не бывает, это всегда компромисс.<br />
<br />
Наверное последнее время я достаточно мало пишу кода. Я чуствую, что мне не хватает набитой руки. И вокруг меня как-то мало крутого и правильного кода. И мне кажется, что посади меня сейчас плотно программировать - я бы делал это медленно. Слишком много бы думал. Хотя руководящая нагрузка снялась, кто ж знает что было бы?<br />
<br />
Но сейчас про код...<br />
<br />
<a name='more'></a>Есть неторопливая фоновая задача, и я ее думаю. Задача заключается примерно в следующем: Есть модуль, отвечающий за взаимодействие между узлами, он сообщения через TLS гоняет. Но есть определенные неудобства в том плане, что пока соединение не установится - мы не знаем куда слать сообщение. Их нужно хранить в ожидании подключений. Сейчас там все достаточно криво и очередь существует толко у уже подключенных соединений.<br />
<br />
Нужно сделать буфер, который будет независим от соединений. Соединения сами по себе, а буффер единый и живет своей жизнью. И этот буфер может хранить сообщения для отправки и дефрагментировать сообщения, которые валятся к нам.<br />
<br />
Это было краткое вступление.<br />
<br />
Сущесвующие классы жестко провязаны через коллбеки, и это не очень удобно. В случае с сетевым буффером вполне можно сделать чистый класс, который почти ни от кого не зависит и все действия осуществляются через его интерфейс. То есть добавляем сообщения на отправку, извлекаем получившиеся фрагменты и отправляем. Аналогично поступаем на вход. Загружаем туда фрагменты - а оттуда достаем собранные сообщения и пробрасываем в систему. Поскольку вся система построена на асинхронщине - такой интерфейс вполне удобен. Вопрос в деталях.<br />
<br />
Отправляющая часть.<br />
<br />
Сам по себе буфер достаточно умный - умеет следить за своим размером, чистит память если надо. Никто не обещал, что сообщения будут жить вечно. Поэтому в плане добавления сообщений проблем никаких нет.<br />
<pre>buffer.addOutgoingMessage(m);
</pre>Вопрос встает в плане извлечения. Поскольку этим занимается другой поток, он точно не знает - есть сообщения или нет. Но поскольку речь идет о потоках - следующий вариант серьезно не рассматривается. Считаю что каждый метод должен быть самодостаточным. Но мы к нему еще вернемся.<br />
<pre>if (buffer.hasOutgoingFragment()) {
const auto f = buffer.getOutgoingFragment();
}
</pre>Если посмотреть на tbb::concurrent_queue, нам предлагается такой вариант:<br />
<pre>bool try_pop( T& destination );
</pre>Такой подход помоему нарушает сразу два принципа.<br />
<pre>Fragment f;
if (buffer.getOutgoingFragment(f)) {
...
}
</pre> Во первых он нарушает RAII. Это приводит к тому, что сперва срабатывает конструктор по умолчанию, а потом произойдет копирование объекта. Я не сторонник преждевременной оптимизации, но это не значит, что я должен конструировать каждый объект дважды.<br />
<br />
Во вторых этот код нарушает принцип Command/Query Separation. Один метод пытается что-то нам вернуть, и одновременно пытается донести до нас - смог он это сделать или не смог. Меня немного коробит от осознания того факта, что f может остаться неинициализированным.<br />
<br />
Есть другой способ, не намного лучший, вернуть tuple.<br />
<pre>const auto fpair = buffer.getOutgoingFragment();
if (get<0>(fpair)) {
// Можно работать с get<1>(pair)
}
</pre>Или даже так:<br />
<pre>const auto fpair = buffer.getOutgoingFragment();
if (get<bool>(fpair)) {
// Можно работать с get<Fragment>(fpair)
}
</pre>Или можно даже имея переменные забиндить их через tie, но в этом случае мы возвращаемся к проблеме RAII.<br />
<pre>bool have;
Fragment f;
tie(have, f) = buffer.getOutgoingFragment();
if (have) {
// Можно работать с f
}
</pre>И как-то это все многословно. Хочется чище... Да какого черта, вот чистый и безопасный вариант:<br />
<pre>try {
const auto f = getOutgoingFragment();
...
} catch (const Buffer::Empty &) {
...
}
</pre>Это похоже на логику на исключениях. Да это она.<br />
<br />
Кто-то скажет про скорость, что исключения дескать медленные. Тут есть один ньюанс. Когда сообщений много и идет нагрузка - скорость крайне важна. Но когда сообщений нет, нам пофиг производительность.<br />
<br />
Я провел небольшое сравнение, и в случае возвращения значений не заметил разницы между вариантом с исключениями и всеми остальными. Кажется что вариант с референсом немного проигрывает. Но это все на уровне погрешности измерений.<br />
<br />
Другое дело, когда сообщений нет и последний вариант бросает исключения. В этом случае он работает в 1000 раз дольше, чем остальные. Но не все ли равно?<br />
<br />
Это не все равно, в плане приемной части. Потому что фрагментов может быть на порядки больше, чем собранных сообщений. В этом случае можно совместить два подхода. Метод проверки наличия и метод извлечения с исключением. Это будет безопасно в плане многопоточности.<br />
<br />
В принципе для единообразия можно и отправку тоже снабдить методом проверки. Он может быть полезен...<br />
<pre>class Buffer {
public:
void addOutgoingMessage(const Message &m);
bool hasOutgoingFragment() const;
Fragment getOutgoingFragment();
void addIncomingFragment(const Fragment &f);
bool hasIncomingMessage() const;
Message getIncomingMessage();
};
</pre>Здесь нужно понимать, что один сценарий достаточно оптимален, в то время как другой крайне неоптимален. По какому сценарию используется класс? Насколько оптимально его использование?<br />
<br />
Логика на исключениях... наверное я буду гореть за это в аду...<br />
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-90168917811656617462014-03-25T13:31:00.002+04:002014-03-25T13:31:36.341+04:00AgileDays'14, день второй<div dir="ltr" style="text-align: left;" trbidi="on">
<a href="https://plus.google.com/105555804149403561366/posts/KHcE2XSpGkZ" target="_blank">Выступление Антона Волкова на AgileDays'13</a> произвело на меня очень сильное впечатление. Я долго думал, как бы использовать эти идеи у себя в команде, К концу года у меня созрел план и вот уже почти месяц я тестирую на команде свою систему рейтингов. Не удивительно что я очень ждал доклада Антона.<br />
<br />
<a name='more'></a>Возможно поэтому доклад Дамира Тенишева почти не отложился в памяти :). Пришлось восстанавливать по записям. К вопросу про старичков - ротация необходима, хотя и болезненна. Группировка вокруг задач, ну это все и так ясно. Понравилось ограничение на размер кода по фиче.<br />
<br />
Нынешний доклад Антон Волкова про взаимопонимание. Люди - это самое важное, что есть. Правильные люди построят процессы, изобретут технологии и заработают деньги.<br />
<br />
Для того, чтобы понять какие люди нужны, нужно понять, насколько эти люди разделяют твою цель. А для этого нужно понять, какова твоя цель. <a href="http://alternativaplatform.com/ru/" target="_blank">Альтернатива Платформ</a> поставила себе амбициозную цель на пять лет вперед. И построили план достижения этой цели. И идут по нему.<br />
<br />
В обычной жизни не все люди понимают, куда идет их компания. Не всем понятно даже, зачем они пишут код? Для чего? Для кого они выпускают релиз? Зачем в релизе все эти фичи? Да что говорить о простых разработчиках, даже руководители не всегда понимают - нафига. Нужно попытаться больше задавать неудобных вопросов, чтобы побудить руководителей задуматься. Конечно, какие-то планы у нашей компании есть. Может быть они тоже амбициозные, но проблема в том, что рядовые сотрудники вообще не в теме.<br />
<blockquote class="twitter-tweet" lang="ru">
Подглядел на сайте с анекдотами, но все же: "Если у вас нет цели в жизни, значит, вы будете работать на того, у кого она есть"<br />
— Maxim Dorofeev (@cartmendum) <a href="https://twitter.com/cartmendum/statuses/448214015823855616">24 Март 2014</a></blockquote>
Что же касается коммуникаций - каждый понимает в меру своей испорченности. Не надо рассчитывать что сказанное будет понято правильно. Фиксируйте, детализируйте, визуализируйте в конце концов. И что важно - никому нельзя верить на слово. :) Кто там код взялся без требований писать, когда непонятно, как это должно быть?<br />
<br />
В систему соглашений они добавили коэффициенты по разным аспектам. Открытость, Сроки, Ошибки, Практики, Технологии, точно не помню. Команда может осознанно продолбать срок, но компенсировать это на Технологиях к примеру. Но думаю, что для этого надо достичь определенного уровня зрелости в команде. Я пока не хочу усложнять свою крайне простую систему, про котороую попозже обязательно напишу.<br />
<br />
Асхат, как всегда на гребне волны. Тренд NoEstimations появился сравнительно недавно. И всем в SCRUM влом тратить по полтора дня на планирование. Логично, что надо меньше планировать. :) Кроме того нельзя недооценивать важного факта: Нет оценок, значит их нельзя сорвать. :)<br />
<br />
У нас сейчас scrumban. Не вижу смысла сильно заморачиваться на спринтах, пусть люди лучше меньше прерываются и работают в едином потоке. Мини-планирование мы проводим по каждой фиче перед началом. Хотя конечно к демонстрации надо готовиться, но это более высокоуровневая задача, нежели реализация потока задач.<br />
<br />
Психологические доклады из малого зала не сильно зацепили. Хотя что-то в голове про фиксацию и про позитивную психологию крутится. Чиксентмихайю я читал, надо будет Селигмана почитать, и про фиксацию.<br />
<br />
Ретроспективы. У нас не очень то проводятся ретроспективы, надо это дело чинить. Тимофей Евграшин предложил много интересных методик проведения и инструментов ретроспектив. Шесть смайликов - понравилось, каждый участник на ретроспективе должен выбрать два, которые по его мнению характеризуют прошедший спринт.<br />
<br />
Максим Дорофеев зря прибедняется. Великий гуру теории ограничений. :) Надо будет поприставать по почте, а то блин Голдратта почитал, Детмера почитал, но полноценных диаграмм пока не получается. Понятно, что за каждой диаграмой у Макса тоже стоят часы раздумий. Не совсем понятно, что делать с нытьем. У Стейкера бумажки делятся на факты, мнения и предположения. Вероятно в диаграммы надо стараться включать факты, чтобы диаграмма была корректна для всех (никто ж против фактов не спорит :). Грозовая туча должна строится только при наличии двух сторон, одна сторона не может конфликтовать. Короче, есть еще куда расти. <br />
<br />
Несколько мыслей напоследок:<br />
<br />
Не все хорошие идеи достойны реализации.<br />
<br />
Перевод идей в Ready to Devel, это обязательство заказчика. Тем самым он подтверждает, что одобряет затраты на разработку.<br />
<br />
Когда команды работает над тем, что не приносит пользу, это называется Failure Demand (Ложная загрузка).<br />
PS: Подарков было много, но всеравно хочется больше. :) Спасибо организаторам и всем участникам.</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-56691496227149183642014-03-23T22:37:00.003+04:002014-03-23T22:37:55.668+04:00AgileDays'14<div dir="ltr" style="text-align: left;" trbidi="on">
Вчера закончилась конференция AgileDays. Попробую систематизировать те мысли, которые у меня возникли по ходу этой конференции.<br />
<br />
<a name='more'></a><br />
Конференция организована очень здорово. Никогда такого уровня не
видел. Лишь один раз за два дня передо мной возникла очередь, Это была
очередь в гардероб после закрытия конференции... из пяти человек... Особенно это странно видеть, вспоминая AgileDays'13, когда мы целый час стояли в очереди на регистрацию. <br /><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg55IjAG2ZFa7iNxSPME6NkBwHunv3u40W5yoUjURhZBF3IOSeUGU64PlvtsOrVAlmi_1ZTsekLsydm8o3Wj37Zy2Pz3oD-NpR3fuAE2v29kVPofnF2TERRZ0r0p5gNsYgu35fAw22EmRK4/s1600/1395380363339.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg55IjAG2ZFa7iNxSPME6NkBwHunv3u40W5yoUjURhZBF3IOSeUGU64PlvtsOrVAlmi_1ZTsekLsydm8o3Wj37Zy2Pz3oD-NpR3fuAE2v29kVPofnF2TERRZ0r0p5gNsYgu35fAw22EmRK4/s1600/1395380363339.jpg" height="240" width="320" /></a>В трех самых больших залах - столы. Никогда на конференциях такого не видел. Очень много места. <br />
<br />
Никакого ажиотажа в столовой. В первый день я правда чуть не проспал обед, а когда пришел в столовую - там вообще никого не было. Что? Обед кончился? - Нет, не кончился. Чудеса...<br />
<br />
Вероятно все это связанно с ограниченным количеством участников - 900 человек. За это большое спасибо организаторам, респект и уважуха.<br />
Но давайте перейдем поближе к докладам...<br />
<br />
Почему-то, доклад Дэвида Андерсона у меня в памяти отложился лучше, чем последовавший за ним доклад Ахмеда Сидки. Возможно он больше перекликается с мои предрелизным состоянием и желанием поотрывать ноги тем, кто любое пожелание сразу начинает пилить. У Дэвида многое было про то, что хотелки нужно фильтровать. И не один раз... Потому что невыгодно тратить усилия на бесполезные вещи.<br />
<br />
С Алексеем Пименовым я немного знаком, слежу за его активностями. Но этот доклад у него новый. Креативность и мотивация - близки. Люди, с интересом выполняющие свою работу, то есть замотивированные, с удовольствием генерируют полезные для организации идеи. Обилие теории в начале едва не отпугнуло. К сожалению сейчас без шпаргалки под рукой не могу вытащить из своей головы подробности. Перечитаем и пересмотрим.<br />
<br />
Александр Бындю. Я с ним тоже общался через интернет. Разделение Команд и Запросов - это более глобальный принцип, чем просто OOP. Доклад затронул некоторые слои моего сознания, которые обдумывают архитектуру будующей версии Континент. К сожалению у меня так и не выдалась возможность подойти и познакомиться с Александром, что-то закрутился. В другой раз.<br />
<br />
Алексей Мариза, Алексей Горбунов, Алексей Трошин. Три доклада почти слились в одно большое человеческо присихологическое поучение, если бы не Трошин, который рассказывал о том, как снимать с людей бытовые заботы, чтобы их мозг работал на тебя.<br />
<br />
Последним докладом первого дня, не считая виски-пати, для меня стал практикум Никиты Филипова. Всех Скрамтрековцев я знаю где-то с 2007 года, когда они у нас в компании, одним из первых, строили SCRUM. Практикум посвящался ранней выбраковке идей (опять актуальная для меня тема). У нас была замечательная тема - сделать карту мародеров, каким-то образом скрестив навигацию, планы помещений, добавив социальную составляющую - но опрошенные нами люди, все как один зарезали такую замечательную идею. Они конечно не правы, наверное мы опрашивали нецелевую аудиторию. :)<br />
<br />
После этого было виски-пати, где мы с сотрудником мучали Пименова, Трошина и других о том, что делать с пенсионерами в проекте - ответ почти однозначный. Людей надо переключать, так они лучше работают.<br />
<br />
Во второй день нас ждал Волков, сейчас кажется, что второй день несколько затмил для меня первый. Про второй день я напишу завтра.<br />
<br /></div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-22934108350301941102013-11-22T00:28:00.003+04:002013-11-22T00:28:36.381+04:00Расстановка приоритетов<div dir="ltr" style="text-align: left;" trbidi="on">
Тайм менеджмент очень сильно напоминает планирование работ по проекту. Странно, что методы, почему-то, не перенимаются.<br />
<br />
Недавно прочитал про систему расстановки приоритетов под названием <a href="http://www.routinesystem.com/" target="_blank">Рутина</a>. Это замечательная вещь в плане отсеивания бесполезной фигни из беклога.<br />
<br />
<a name='more'></a>Для этого существует самый главный вопрос:<br />
<br />
<b>Что случится, если этого вообще не делать?</b><br />
<br />
Случится страшное? Тут лучше расписать ужасы для себя во всей красе.<br />
- Если мы не сделаем эту суперважную фичу, то все пропало, гипс снимают, клиент уезжает!<br />
- Really?<br />
- Потеряем кучу денег!<br />
- Большую?<br />
- Ну так, кучу не кучу а пачку-другую...<br />
- Ты в этом точно уверен?<br />
- ... нет... <br />
<br />
<br />
Некоторые вещи несомненно надо делать. Они приносят деньги, завоевывают рынок и вообще нужны и полезны. Но часть вещей после такого простого вопроса отваливается, потому, что никаких негативных последствий от их отсутствия нет.<br />
<br />
Анализировал список ошибок по проекту. Надо сказать, что у нас стабилизация и 80% ошибок - это откровенная шняга. Правда в плане расчистки этого списка кажется - что лучше сперва исправить все, что исправляется. Но выясняется что их исправление не скажется на пользовательских качествах продукта. Это пустая трата времени.<br />
<br />
С другой стороны те ошибки, которые никак не хотят исправляться - реально важны. И тут реально помогает второй важный вопрос:<br />
<br />
<b>Зачем это делать?</b><br />
<br />
Но это в стандартная практика при написании пользовательских историй.<br />
<br />
Что касается пользовательских историй. Промелькнула мысль, что списки дел тоже неплохо бы оформлять в виде историй. Поскольку в историях есть роль, дело и цель - мы убиваем сразу трех зайцев...<br />
<br />
Ну что это за задача такая - купить батон? Любой программист возмутился бы! Нет, надо писать так: <i>- Как кормилец семьи я должен купить батон, для того, чтобы семья была сыта.</i><br />
- Really?<br />
<br />
Клевая идея, обязательно надо попробовать. А на сайте системы Рутина можно бесплатно скачать <a href="http://www.routinesystem.com/#!book/c662" target="_blank">электронную книгу</a>.</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-78991618445097041142013-11-15T23:23:00.002+04:002013-11-15T23:23:57.160+04:00Unit testing handmade (часть вторая)<div dir="ltr" style="text-align: left;" trbidi="on">
С прошлого поста я сумел сделать более правильные параметризованные тесты, теперь в описании теста не нужно перечислять структуру тупла в списке параметров, да и помимо туплом можно использовать любой другой тип.<br />
<br />
<pre>static const auto encrypt_params = {
make_tuple(key01, 0xCCCCCCCCU, 0x33333333U, 0xF5FE5211U, 0x17E8D02EU),
make_tuple(key01, 0x33333333U, 0xCCCCCCCCU, 0x6390ED97U, 0x3A962C89U),
make_tuple(key02, 0xCCCCCCCCU, 0x33333333U, 0x2A78B7E0U, 0x800A0268U),
...
};
UP_PARAMETRIZED_TEST(encryptShouldBeCorrect, encrypt_params) {
const auto key = get<0>(encrypt_params);
const auto A = get<1>(encrypt_params);
const auto B = get<2>(encrypt_params);
const auto eA = get<3>(encrypt_params);
const auto eB = get<4>(encrypt_params);</pre>
<br />
Но порассуждать хотел в основном на тему утверждений.<br />
<br />
<a name='more'></a>У меня ASSERT_EQUAL реализован на основе темплейтов. В бусте насколько знаю, тоже. <i>Хотел посмотреть для верности, но что-то зарылся там, ну его нафиг.</i> Учитывая, что пихнуть в утверждение могут все, что угодно (особенно в мое), проще всего параметризовать функцию сравнения одним типом:<br />
<br />
<pre>template <typename T>
bool isEqual(const T &a, const T &b) const {</pre>
<br />
Это с одной стороны дает нам гарантию, что типы точно одинаковые. Но с другой стороны вызывает ряд сложностей. Самая большая сложность, которая мне не нравится - это то, что нельзя сравнивать знаковые и беззнаковые типы. Казалось бы, почему бы не написать просто:<br />
<br />
<pre>UP_ASSERT_EQUAL(colection.size(), 5);</pre>
<br />
Но это не прокатит, потому, что размер коллекции меряется в size_t, который без знака... а 5 - это int. И все, шаблон не параметризуется, код не компилируется.<br />
<br />
Вообще не люблю полагаться на неявные преобразования. Люблю все делать явно. Как вычитал в одной умной статье - неявные и сложные вещи повышают когнитивную нагрузку на мозг. Нужно стараться писать понятно, а свободный от нагрузки мозг не станет бездельничать - он найдет на какие еще мысли потратить свои ресурсы. Хотя в си все так привыкли к неявным преобразованиям, что всегда думают о них и боюсь что мое решение может оказаться не очень хорошо воспринятым. Но я всеравно сделаю это, потому, что считаю это правильным.<br />
<br />
В сях -1 == UINT_MAX. Мне это совершенно не кажется правильным! Я даже не хочу задумываться что к чему приводится.<br />
<br />
В моем фреймворке отрицательное число не равно беззнаковому. А знаковое число может быть равно беззнаковому только в общем для них числовом диапазоне. И это справедливо для всех целочисленных типов от сhar до long long... можно сравнивать char и unsigned long long, но они будут сравнимы только в диапазоне от 0 до 127.<br />
<br />
Вопрос конечно спорный и дискуссионный, но мне кажется что это математически правильно.<br />
<br />
Другая интересная тема - непреднамеренное завершение теста. С исключениями - тут все просто. А вот ради перехвата сигналов пришлось поломать голову. Надо сказать, что я не очень дружу с сигналами. В жизни работа с ними требуется достаточно редко. И тупая попытка установить хендлер, который будет бросать исключения сработала, но только один раз. На втором тесте все благополучно свалилось в cегфолт. Пришлось лезть в буст и смотреть как это сделано там. Можно сказать, скопипастил.<br />
<br />
Правда не обошлось без колебаний. С одной стороны приложения не должны падать. Любая ошибка такого рода - это фатально и требует немедленного исправления. Но с другой стороны - тестовый фреймворк должен показать полное состояние приложения, и будет как-то не честно при первом же делении на ноль тупо упасть, даже если при этот будет выведена вспомогательная информация. Тест по любому считается сбойным, но тестовый набор надо прогнать полностью.<br />
<br />
Ввел я в свой фреймворк и чекпоинты. Надо сказать чекпоинты в бусте как-то мало помогали. Чекпоинт имеет смысл, когда тест непреднамеренно завершается. Но буст, в этом случае, всегда мне показывал координаты предыдущего теста. Учитывая что я запускаю тесты в случайном порядке - ценность этой информации нулевая. Мои чекпоинты стоят везде.<br />
<br />
<div style="text-align: right;">
</div>
<div style="text-align: right;">
<i>Наши кошки живут в гнезде,</i></div>
<div style="text-align: right;">
<i>Летают - везде.</i></div>
<div style="text-align: right;">
<i>Прилетели во двор, </i></div>
<div style="text-align: right;">
<i>Завели разговор:</i></div>
<div style="text-align: right;">
<i>Кар-кар.</i></div>
<div style="text-align: right;">
Вспомнилось, не знаю к чему...<i> </i></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
В каждом тесте по умолчанию как минимум три чекпоинта. Перед инициализацией фикстуры, перед выполнением теста, перед финализацией фикстуры. Исключение из деструктора вылететь по идее не должно, но нарушение наверное может возникнуть? Черт, у меня нарушения транслируются в исключения - надо бы это потестить, а то может настать конец света... </div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Кроме этого любое утверждение в коде является чекпоинтом. И конечно можно ставить свои, сколько надо. Не одно нарушение не останется незамеченным и нелокализованным! </div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
На этой радостной ноте закругляюсь. Проект по прежнему <a href="https://github.com/DronMDF/upp11" target="_blank">живет на github</a>.</div>
</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-23668027293412253002013-11-05T23:26:00.000+04:002013-11-05T23:26:22.457+04:00Юнит тестинг по моему<div dir="ltr" style="text-align: left;" trbidi="on">
В основном я пользуюсь boost::test. Это в принципе неплохой фреймворк, но не так давно, занимаясь хобби проектиком подумал... Последнее время для себя все больше пишу на новом C++, и часто получается так, что boost тащится только ради тестирования. И более того, если хочется чего-то необычного - докумментация boost::test не дает ответов на эти вопросы. Нужно лезть в исходники и разбираться, например как вызвать все тесты из своего main? Как в один тест запихнуть несколько разных параметров?<br />
Закончилось все тем, что решил написать свой фреймворк, с <strike>блекджеком и ш..</strike> параметризованными тестами и сравнением коллекций...<br />
<br />
<a name='more'></a>Первый вопрос - можно ли реализовать синглтон в инклюде. Это необходимо во фреймворке, для того, чтобы тесты из всех исходников регистрировались в одной коллекции. А создавать фреймворк в виде библиотеки ну очень не хотелось.<br />
<br />
Синглтон от GoF требует .сpp файл для описания статического указателя - не вариант. А вот синглтон Майерса - прекрасно живет в инклюде.<br />
<br />
<pre>class TestCollection {
static TestCollection &getInstance() {
static TestCollection collection;
return collection;
};
</pre>
<br />
Вторым встал вопрос о том, как реализовать параметризованные тесты. boost <a href="http://mdf-i.blogspot.ru/2010/05/boost-data-driven-test.html" target="_blank">умеет параметризовать тесты</a>, но выглядит это не очень изящно. Мне же хотелось увидеть минимум букв, что-то типа того:<br />
<br />
<pre>const auto params = {
{ 1, "one" },
{ 2, "two" }
};
UP_PARAMETRIZED_TEST(test, params)
{
...
}
</pre>
<br />
Но реальность жестока... компилятор не может типизировать braces list во что-то конкретное, кроме std::initializer_list, но std::initializer_list должен состоять из элементов одного типа. Я должен явно сказать, что список состоит из std::tuple. И исходный список параметров трансформировался в:<br />
<br />
<pre>const auto params = {
make_tuple(1, "one"),
make_tuple(2, "two")
};
</pre>
<br />
Но и дальше не все так просто... для разворачивания макро UP_PARAMETRIZED_TEST мне необходимо явно прописать из чего состоит этот тупл.<i></i><br />
<br />
<pre>UP_PARAMETRIZED_TEST(test, params, int, const char *)
{
...
}
</pre>
<br />
Хотя возможно я погорячился и смогу убрать это чуть позже, может быть шаблоны прокатят. Но были какие-то причины и сложности. Tuple сохраняет строку как указатель на char, она не хочет сама по себе превращаться в string... пичаль. Внутри теста параметры раcтупливаются обычным способом. И их может быть произвольное количество.<br />
<br />
<pre>static const auto encrypt_params = {
make_tuple(key01, 0xCCCCCCCCU, 0x33333333U, 0xF5FE5211U, 0x17E8D02EU),
make_tuple(key01, 0x33333333U, 0xCCCCCCCCU, 0x6390ED97U, 0x3A962C89U),
make_tuple(key02, 0xCCCCCCCCU, 0x33333333U, 0x2A78B7E0U, 0x800A0268U),
make_tuple(key02, 0x33333333U, 0xCCCCCCCCU, 0x462DA336U, 0xEAB90129U),
make_tuple(key03, 0xCCCCCCCCU, 0x33333333U, 0x8BB8CF97U, 0x533CDA6BU),
make_tuple(key03, 0x33333333U, 0xCCCCCCCCU, 0xBE407AB5U, 0x5C055B4FU),
make_tuple(key04, 0xCCCCCCCCU, 0x33333333U, 0x895A9742U, 0x02DB134CU),
make_tuple(key04, 0x33333333U, 0xCCCCCCCCU, 0xDAA70325U, 0xB95DDC39U),
make_tuple(key05, 0xCCCCCCCCU, 0x33333333U, 0x401EBED9U, 0x56F5D77DU),
make_tuple(key05, 0x33333333U, 0xCCCCCCCCU, 0x4E790503U, 0x73FE0118U),
};
UP_PARAMETRIZED_TEST(encryptShouldBeCorrect, encrypt_params,
vector<uint8_t>, uint32_t, uint32_t, uint32_t, uint32_t)
{
const auto key = get<0>(encrypt_params);
const auto A = get<1>(encrypt_params);
const auto B = get<2>(encrypt_params);
const auto eA = get<3>(encrypt_params);
const auto eB = get<4>(encrypt_params);
</pre>
<br />
По моему не плохо получилось...<br />
<br />
Фикстуры реализуются достаточно просто, поэтому я и параметризованные тесты дополнил фикстурами, если необходимо. <br />
<br />
Пришлось изрядно повозиться с универсальным сравнителем. Очень раздражает BOOST_REQUIRE_COLLECTIONS_EQUAL. У меня все сравнивается через UP_ASSERT_EQUAL (ну или не сравнивается через UP_ASSERT_NE). В него можно передать числа, строки, коллекции разных типов. Коллекции сравниваются по содержимому. Если что-то не сравнивается - то ошибки компиляции не будет - при запуске сфейлится утверждение и напечатает всю ту шнягу, которую в него запихнули.<br />
<br />
Интересная история с ASSERT_EXCEPTION. Этот метод я посчитал необходимым, поскольку часто им пользуюсь. Но передавать в макро код, который должен выполниться в рамках try/catch - cчитаю крайне неестественным. Если мы хотим передать куда-то код - мы должны описать блок кода, как будто наше макро - часть языка. Или на худой конец передать сallable object, чтобы было похоже что наше макро - функция.<br />
<br />
Первый вариант на первый взгляд показался совершенно нереализуемым:<br />
<br />
<pre>UP_ASSERT_EXCEPTION(std::exception) {
...
};
</pre>
<br />
Но мне кажется, что такое макро можно написать. Просто сразу что-то не заработало и я решил не заморачиваться. К тому же мучают сомнения насчет того, какой вариант более естественно выглядит для плюсов. Остановился на варианте с лямбдами.<br />
<br />
<pre>UP_ASSERT_EXCEPTION(std::exception, []{
...
});
UP_ASSERT_EXCEPTION(std::exception, message, []{
...
});
</pre>
<br />
Тесты можно группировать в наборы, но выборочное выполнение пока не сделано.<br />
<br />
И чтобы вообще не заморачиваться - в одном из тестовых модулей нужно написать:<br />
<br />
<pre>UP_MAIN();</pre>
<br />
этот стандартный main поддерживает тихий запуск теста, тайминг по тестам, определение порядка выполнения тестов (через рандом сид) и все. Ничего лишнего.<br />
<br />
Не стал делать сравнение с плавающей точкой. Во первых сам почти не работаю с плавающей точкой. Во вторых моя универсальная функция сравнения принимает только два параметра, описывать отдельную - помоему не очень хорошая идея. Любое, самое извращенное сравнение можно реализовать через UP_ASSERT. :)<br />
<br />
На этом пока все. <a href="https://github.com/DronMDF/upp11" target="_blank">Follow me on GitHub</a>.</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-7522566866760049812013-10-23T23:19:00.002+04:002013-10-23T23:19:50.901+04:00Время - инструмент планирования<div dir="ltr" style="text-align: left;" trbidi="on">
Для чего оценивают время выполнения задачи? Наверное для руководителя проекта? Чтобы тот мог планировать скорость разработки и прогнозировать сроки выполнения всего проекта. Иначе откуда мог появиться такой устойчивый стереотип - что время нужно оценивать с запасом, и еще подстраховаться на случай непредвиденных осложнений? На всякий случай умножить на два и всеравно потом не уложиться в срок... <br />
<br />
Такова человеческая психология. Даже если говоришь - дайте мне оптимистичную оценку - всеравно умножают, закладываются и накидывают - на всякий случай, вдруг потом кто-то спросит.<br />
<br />
<a name='more'></a><br />
Изложу свою точку зрения:<br />
<br />
Люди почему-то не склонны вдаваться в детали. Часто делая комфортную оценку времени они даже не задумываются о сложности работы: "<i>A зачем, ведь именно для этого я и умножаю ее на три</i>". Мозг расслаблен и не думает о плохом. Да и сам человек не напрягается, пока половина срока не пройдет - "<i>Ну я же на два умножил, зачем спешить успею до понедельника</i>". <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6C4nleb-LEPsOdpICUIg6fWLPMfjVXSzyu2wnY5IAvGyH3ShHaRH8eV6LaCfK_TjKI6Rx8437UCSxIhbKaM7RyratuTEXNHdczELew8O6-uiCvolbEVLgevqP6zkdCJLgAfmiptYRCLtx/s1600/IMG_20131023_222900.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6C4nleb-LEPsOdpICUIg6fWLPMfjVXSzyu2wnY5IAvGyH3ShHaRH8eV6LaCfK_TjKI6Rx8437UCSxIhbKaM7RyratuTEXNHdczELew8O6-uiCvolbEVLgevqP6zkdCJLgAfmiptYRCLtx/s1600/IMG_20131023_222900.jpg" height="166" width="400" /></a></div>
<br />
<br />
<br />
Поэтому обычно ставят t4. :)<br />
<br />
Время - это мощный инструмент для извлечения тайных страхов из подсознания. Даже больше - время само по себе не интересно. Тайные страхи гораздо интереснее.<br />
<br />
Когда человек ставит себе время, близкое к нереальному, рассудок начинает протестовать и подкидывать всякие проблемы, которые непременно произойдут. К ним очень важно прислушиваться. Их нужно фиксировать. Любая проблема - это риск, который угрожает срыву этих нереальных сроков. Вероятность возникновения риска может быть больше или меньше. Но любой руководитель проекта предпочел бы о возможных сложностях услышать заранее. Это хороший повод декомпозировать задачу, для того, чтобы исключить из нее неопределенность. Например проведя предварительные исследования, чтобы на раннем этапе избавиться от неопределенности или быстрее предпринять меры, если этот риск воплотиться.<br />
<br />
У жесткого времени есть и другой аспект - это мотивирует. Человек подсознательно ориентируется на обозначенную цифру. Шансов успеть за t1 практически нет, но человек с таким же успехом зафейлит и t4. Работа, как известно занимает все отведенное для нее время.<br />
<br />
Мне кажется - что правильно ставить t1.<br />
<br />
К тому же не стоит забывать что это оценка а не реальные сроки. Иногда люди начинают закладывать отвлечения. Я не спрашиваю, сколько ты будешь это делать, я спрашиваю во сколько ты это оцениваешь! Реальное время конечно окажется больше, но так и формируется фокус фактор.<br />
<br />
<br />
И кажется, что человек лучше будет извлекать свои страхи, если оставить его наедине с собой. Это своего рода медитативный процесс вникания в суть проблемы. Как-то у меня не вяжется это со скрамовской сессией планирования. А может просто не умеем?</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-25720287395811428522013-03-30T22:48:00.000+04:002013-03-30T22:48:34.262+04:00Agile'2013<div dir="ltr" style="text-align: left;" trbidi="on">
<br />
Вернулся с конференции AgileDays. Голова гудит от обилия информации, много полезного узнал. Попробуем немного систематизировать кашу в голове... Времена меняются, agile уже другой. Нет, не хуже... :) Agile становится гибче, его проще применять и использовать...<br />
<br />
<a name='more'></a><br />
Все понимают, что над командой давлеют многие риски, основной из которых — неопределенность. Неопределенность присутствует всегда. Даже в том случае, если команда планирует задачи на ближайший день. <a href="http://www.scrum.org/About/All-Articles/articleType/ArticleView/articleId/95/Commitment-vs-Forecast-A-subtle-but-important-change-to-Scrum" target="_blank">Commitment в описании практик заменили на forecast</a>. То есть команда больше не берет на себя обязательств, команда делает прогноз.<br />
<br />
Обязательства как бы говорят о том, что команда что-то должна, и если она это не выполняет?... Повод ее наказать..., неконструктивно... Когда мы называем это прогнозом - это ослабляют психологическое давление на команду, что повышает продуктивность. Но жесткость планирования - дело сугубо индивидуальное, можно варьировать в широких пределах, главное, чтобы было продвижение вперед.<br />
<br />
Интересно наблюдать за эволюцией. ScrumTrack проводил у нас тренинги в 2007 году. Тогда все искренне верили, что Story Points - это в основе своей - часы... Потом были статьи, что использовать часы плохо, <a href="http://labs.openviewpartners.com/scrum-why-story-points-are-better-than-hours/" target="_blank">используйте абстрактные величины</a>. Кто-то предлагал даже оценивать истории фруктами. Сейчас пришли к тому, что Story points вообще зло.<br />
<br />
Для прогнозов достаточно грубой оценки, успеем или нет. Более продвинутая оценка в майках позволяет PO строить графики и оценивать общие сроки проекта, для этого ему всё равно часы не нужны. Ещё более точные оценки нужны для улучшений. Когда команда уже работает хорошо, а хочет работать ещё лучше. Тут может стать полезен более детальный разбор полетов...<br />
<br />
И напоследок несколько моментов от вашего КО:<br />
<br />
Все участники должны понимать куда мы движемся, и видеть цель и, главное, усилия каждого должны быть направлены к цели.<br />
<br />
Заблаговременные требования наверное вообще не нужны, напишите хотелку, и совместно придумайте, как ее решать перед тем, как начинать ее решать. Что в принципе не отрицает возможности задокументировать это решение в процессе реализации.<br />
<br />
Ошибки - по одной или пачками - это обычные артефакты в беклоге. И как обычные артефакты их надо приоритезировать и отсеивать малоперспективные. Но делать это должен PO, которого у нас к сожалению нет, который лучше понимает интересы пользователей.<br />
<br />
Наш рабочий процесс тоже меняется, по окончанию конференции понимаю, что мы все делаем правильно.<br />
<br />
Мы в своей работе вообще отошли от планирования спринта в пользу планирования фич, поток фич - он непрерывный не связан с границами спринтов... Нет необходимости сильно расслабляться, если до окончания спринта остался день, надо двигаться дальше.<br />
<br />
Мы перестали использовать стори поинты. Всеравно у нас планирование никогда не сходилось. Чтобы планирование сходилось - нужно научиться доводить фичу до состояния готовности. А то получается что сторипоинты все съели, все таски выполнили, а фичу еще неделю допиливаем... Как-то это неправильно. Доведение до конца - это первичный навык. А точные оценки, как я уже писал выше, это на мой взгляд инструмент усовершенствований.<br />
<br />
И теперь мне лучше видно, куда стоит двигаться дальше.<br />
И все что написано выше - мое личное мнение не претендующее ни на что...<br />
</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com2tag:blogger.com,1999:blog-3179964835593137794.post-59768502142909625012012-09-25T23:36:00.003+04:002012-09-25T23:36:52.563+04:00100 дней до нового года<div dir="ltr" style="text-align: left;" trbidi="on">
Решил принять участие в марафоне, идею которых предложил <a href="http://petrosian.ru/" target="_blank">Армен Петросян</a>, которого я всегда с интересом читаю.<br />
<br />
Подумываю даже создать новый блог, чтобы этот - технический, не засорять. Но боюсь, что не о чем будет писать. Надо еще подумать.<br />
<br />
Цели мои, может быть покажутся смешными. Я отношусь к марафону, как к средству не дать себе расслабиться, и, может быть, довести до конца то, что долго не мог доделать. Но видимо я и так живу слишком счастливой жизнью, никак не могу придумать что-то стоящее. :)<br />
<a name='more'></a>Первой целью для меня стало доведение до конца ремонта. В августе мы капитально отремонтировали большую комнату и балкон, но на балконе остались недоделанные хвосты.<br />
Если взяться хорошенько, там работы то на неделю максимум. Но мне некогда серьезно чем-то заниматься.. Дети в школу ходят, сам работаю... когда мне крутить-сверлить? Но надо собраться с силами, найти время и доделать в этом году. Желательно до наступления зимы.<br />
<br />
Вторым пунктом я выбрал бег. Недавно начал бегать, надо поддерживать регулярность тренировок, чтобы вошло в привычку. Не знал как оформить эту цель стодневки. Ставить перед собой какие-то нормативы в плане скорости-дистанции забегов - не понятно как оценивать... Можно один раз напрячься, пробежать выбранный норматив, а дальше что? Поэтому цель я выбрал, при моем текущем уровне бега, достаточно длительную. Но на первый взгляд не очень страшную. До конца года собираюсь пробежать 50 километров. Учитывая, что бегаю 1-2 раза в неделю по 2-3 километра. Должен суметь, если не буду расслабляться. Если дело пойдет лучше - можно будет увеличить цель.<br />
<br />
<iframe frameborder="0" height="217" scrolling="no" src="http://www.endomondo.com/embed/user/summary?id=4082233&sport=0&from=20120923&to=20121231&measure=0&zone=Gp0300_MOS&width=680&height=217" width="680"></iframe><br />
<br />
А дальше как-то все несерьезно... даже не знаю.<br />
<br />
Давным давно не могу доделать модель корабля - Элизабет. Года два уже наверное. Какой отрицательный пример детям. :( Надо бы доделать.<br />
<br />
Есть желание сертифицироваться на скраммастера. Надеюсь фирма меня спонсирует? :) Но это вопрос буквально нескольких дней. Главное не откладывать в долгий ящик и напоминать об этом руководству, чтобы не расслаблялись...<br />
<br />
Хочется к новому году доделать версию, как от нас того и требуют.. Но боюсь что помимо моего желания здесь участвует слишком много других сил, чтобы эту цель можно было считать личной. Но, желание есть, возможности тоже есть (я всетаки практически тимлид) - будем стараться.<br />
<br />
Может какие новые цели появятся.<br />
<br />
Вем участникам марафона хочу пожелать интересно и с пользой провести все это время, и главное - действовать!<br />
<br />
Оффтопик на этом оканчивается. :)</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-50079460245318289762012-09-16T22:51:00.001+04:002012-09-24T09:46:43.510+04:00Командная работа<div dir="ltr" style="text-align: left;" trbidi="on">
Наша команда совершенно не кроссфункциональная. У нас есть тестировщики и программисты, программисты тоже не все одинаково полезны, Есть программисты под Windows, есть программисты под Unix. Все это приводит к тому, что у нас есть задачи, выполнить которые может только определенная часть команды.<br />
<br />
И поскольку работы во многом взаимосвязанны, то часть из них заблокирована. Тестировщики не могут начинать тестирование, пока не написан код. А пока тестировщики тестируют первую фичу - программисты уже пишут следующую. Вот против этого я и хочу немного восстать.<br />
<a name='more'></a>Вся работа команды состоит из последовательности фич. При таком рассмотрении это конвейер. Время выполнения работы на разных этапах не равно. Программисты могут справляться со своей работой быстрее тестировщиков. Но это совершенно не повод забегать вперед. Они могут пилить следующую фичу, чтобы для тестировщиков была работа. Но все последующие фичи должны быть заблокированы.<br />
<br />
Программисты начинают ныть - нам нечем заняться, дай нам третью фичу. Они начинают говорить, что я не забочусь о сроках выхода и специально затягиваю выполнение эпика. Но они забывают, что без тестирования эпика нету. А тот код, который программисты успевают накодить мешает тестированию просто самим фактом своего существования.<br />
<br />
Не понимают, что у них появляется возможность сделать то, что нельзя включать в планы. Программисты всегда могут найти чем заняться. Написание тестов, рефакторинг. В конце концов можно пойти к тестировщикам и помочь им с тестированием.<br />
<br />
Это приводит к снижению ежедневных сторипоинтов, но они и так исчисляются не часами.<br />
<br />
Программисты не хотят думать маленькими фичами... Они начинают сразу решать эпические проблемы. А что, говорят, мы будем по 10 раз возвращаться к одному и тому же коду. Лучше, говорят сразу написать все, что надо. В то же время написать эпик мы успеваем только за полтора месяца... при двухнедельных итерациях это совершенно неприемлемый срок достижения результата. Результаты должны быть маленькими и быстрыми. И такая разработка замедляет достижение краткосрочного результата.<br />
<br />
Нашел замечательную аналогию. наш путь - это набор наших фич. И весь его надо пройти, но кто-то проходит его быстрее, кто-то двигается в конце.<br />
<br />
В начале у нас бегут аналитики. Они уже почти зарелизились. Чем они занимаются сейчас - лично мне не понятно, но чувствую, что когда мы дойдем до очередной фичи, аналитики уже забудут кто и зачем писал эти требования.<br />
<br />
Дальше бежит архитектор, он тоже витает в облаках, в смысле забегает вперед. По долгу службы. За архитектором гонятся прораммисты.<br />
<br />
Программисты торопятся, больше накодить.. но результата то больше не станет, потому что результат определяется тем, как быстро бегут тестировщики.<br />
<br />
Тестирощики бегут медленно, спотыкаются через баги, но программистам некогда исправлять баги, они уже далеко. Но смысл в том, что команда сдает только то, что сделали тестировщики. Они Определяют результат.<br />
<br />
Комнада не должна разбегаться по дистанции. Надо поддерживать тех, кто бежит медленнее, и тогда все вместе финишируют быстрее.<br />
<br /></div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com2tag:blogger.com,1999:blog-3179964835593137794.post-21038771211423912542012-08-29T23:03:00.000+04:002012-08-29T23:06:45.766+04:00Agile с ошибками<div dir="ltr" style="text-align: left;" trbidi="on">
Не верю в отсутствие ошибок. Ошибки появляются всегда, в любой программе. И не все ошибки одинаковые. Может где-то это все удается решить через общение в рамках итерации. Но когда народу много, ошибки необходимо фиксировать, хотя бы для того, чтобы не забыть об имеющихся проблемах.<br />
<br />
Мы тут пытаемся выработать свою линию поведения в случае обнаружения ошибок в проекте.<br />
<a name='more'></a><br />
Фиксируются ошибки немного по разному, в зависимости от того, к какой части проекта они относятся. Ошибки по функционалу, не разрабатываемому в данный момент попадают в беклог. Ошибки по текущему функционалу не зависимо от критичности линкуются к текущей истории.<br />
<br />
В процессе итерации программистам вроде бы нечего делать, основной функционал уже написан, и они начинают чинить все баги подряд, надо же чем-то себя занять. Но это приводит к тому, что тестировщики продолжают колупать фичу, при этом генерируется еще много низкоприоритетных багов и все идет по кругу.<br />
<br />
Когда же остановиться? Нужен Definition of done, и он у нас негласно есть. Нельзя закрыть фичу, пока в ней присутствуют критические или высокоприоритетные баги. Надо это крупными буквами написать на доске.<br />
<br />
А оставшиеся баги? Помоему оставшиеся даже не следует начинать. Эта моя революционная мысль вызвала бурные дебаты на работе. Как можно не исправлять баги? Да очень просто.<br />
<br />
Что важнее, эти вот низкоприоритетные баги, или очередная фича по плану? Можно так заковыряться с мелочами что к концу итерации нечего будет демонстрировать.<br />
<br />
Тестировщик при создании бага должен честно оценить важность этого бага. Он важен для релиза? Важен - чиним, не важен - не чиним. Тут трудно выразить конкретные критерии, даже опечатка в одну букву может быть критична для релиза.<br />
<br />
Если возникает желание починить какую нибудь из мелких багов - возможно эта бага не так уж незначительна как казалось? может быть она всетаки важна - тогда поднимаем приоритет и чиним, нет - фтопку.<br />
<br />
С другой стороны не все важные с точки зрения тестировщиков баги стоит сразу же чинить. Возможно важность баги завышена - если это так - понижаем приоритет и фтопку.<br />
<br />
Я не предлагаю выкидывать баги. Просто в конце итерации все не взятые в работу баги перекочуют в беклог и будут там пылиться до скончания веков. Может быть когда нибудь, мы сделаем все фичи, исправим все более важные баги и дойдем до этих... <br />
<br />
когда нибудь...</div>
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com2tag:blogger.com,1999:blog-3179964835593137794.post-74855706010352079152012-05-31T00:12:00.000+04:002012-05-31T00:12:31.250+04:00Высокая нагрузка<div dir="ltr" style="text-align: left;" trbidi="on">
В компании я работаю над проектом <a href="http://www.securitycode.ru/products/continent/" target="_blank">Континент</a>. Серверное ПО, жесткие требования. Как добиться качества? В тестовых условиях достаточно проблематично организовать тысячи клиентских машин. Что уж говорить про десятки тысяч, которые мы хотим поддерживать.<br />
<div>
<br /></div>
<div>
У нас были тестовые утилиты, но раньше перед нами не стояло таких амбиций. В условиях десятитысячной нагрузки эти утилиты не выдерживают никакой критики. Ну кто, скажите мне, организовывает тысячи инстанций на тредах? А до того было вообще, на процессах. Я, как человек, не очень далекий от устройства операцонных систем понимаю, что система, оперирующая тысячью активных потоков вряд ли сможет выделить каждому достаточное время для работы.</div>
<div>
<br /></div>
<div>
Эти мысли долго терзали меня, пока я наконец не сел, и не написал свой фреймворк сетевого нагрузочного тестирования. Правильный. :)</div>
<div>
<br />
<a name='more'></a></div>
<div>
Правильное тестовое приложение, обеспечивающее высокую нагрузку, на мой взгляд должно быть однопоточным. Вообще современные тенденции в мире высоких нагрузок таковы, что чем меньше потоков, тем лучше. Загрузить современные многопроцессорные компьютеры можно и однопоточными приложениями.</div>
<div>
<br /></div>
<div>
Однопоточность дает несколько плюсов. Во первых нам не нужно ничего синхронизировать между потоками. Во вторых нам не нужны никакие блокировки. Есть и другие.</div>
<div>
<br />
Архитектура однопоточного приложения строиться на основе дескрипторов, состояний и событий. Не всегда узким местом является сетевой вводо-вывод, но основная идея крутится вокруг него. При таком подходе мы можем одним системным вызовом опрашивать готовность сразу всех дескрипторов.</div>
<div>
<br /></div>
<div>
Традиционные приложения пишутся так, что системный вводо-вывод оказывается в самой глубине иерархии классов. Поэтому код типичных приложений в качестве клиентов нагрузки использовать не получается. Надо почти все переписывать. Дескриптор необходимо иметь отдельно от клиента.</div>
<div>
<br />
Однопоточность так же позволяет расставить четкие приоритеты в клиентских операциях. Нет необходимости писать в сокет, если мы не успеваем обрабатывать то, что приходит. Нет необходимости тратить процессорное время (например на шифрование), если мы не успеваем отправлять данные.<br />
<br /></div>
<div>
Еще интересный момент с протоколированием. Нет смысла с тысячи клиентов все валить на стандартный вывод. Информации будет столько, что толку от нее не будет никакого. Вопрос очень сложный в том плане, что объем протоколируемой информации зависит от того, как мы собираемся эту информацию обрабатывать. В примитивном варианте меня устраивает стандартный вывод, но при этом я вывожу на экран активность только одного клиента из всех. Но если мне захочется отследить какие-то другие характеристики, возможно мне понадобиться запись большого количества информации в файл для последующей обработки. Это все можно настроить. Не знаю только, как удовлетворить сразу все интересы из коробки. С другой стороны избыток протоколирования приведет к снижению полезной нагрузки от приложения. Дилемма.</div>
<div>
<br /></div>
<div>
Первый тестовый модуль, который я выложил на <a href="https://bitbucket.org/MDF/tap" target="_blank">bitbucket</a>, тестирует http.<br />
К серверу nginx удалось одновременно подключить 25000 тестовых http клиентов. Если установить nginx на отдельной машине, наверное можно и больше. http клиенты упираются только в сеть.</div>
<div>
<br />
Старался все сделать так, чтобы можно было легко писать своих клиентов, и собирать с них интересующую информацию. Будет здорово, если кому-то окажется полезным, хотя я еще не определился с форматом проекта... то ли это либа, то ли тестсьют? Как его лучше оформить?</div>
</div>Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com8tag:blogger.com,1999:blog-3179964835593137794.post-22646371775247357502011-10-27T15:22:00.001+04:002011-10-27T15:22:48.591+04:00Что-то мне в последнее время не нравится make...Что-то мне начал надоедать make. А так хочется быть гибким, хочется применять всякие новостные методики, но make мешается. <br />
<br />
В принципе конечно все проблемы разрешаются, но что-то мне не хочется их решать. Да пусть этих Makefile вообще не будет. В каждом каталоге лежат, чтоб им пусто было. Лучшая система сборки та, которой нету.<br />
<br />
Да, я, как всегда, занимаюсь велосипедостроением, но давайте обо всем по порядку.<br />
<a name='more'></a><br />
Очень хочется применять TDD. Так, чтобы про настоящему, с максимальным покрытием кода. Состояния полного просветления я еще не достиг, и иногда начинаю просто писать код. Но вскоре понимаю, что действую неправильно. Если, ради того, чтобы сошелся тест надо написать еще один классик, то для нового класса тоже нужны тесты. Причем до того, как я начну его реализовывать. Хобби проект очень хорошее место для того, чтобы отточить навыки в TDD и освоить на практике новый стандарт c++.<br />
<br />
Ради тестирования структура проекта выстраивается так, чтобы функция main была бы отдельно и не мешалась, а остальной код можно было бы использовать как с упомянутой выше main, так и в рамках тестового модуля. Классики TDD об этом почему-то не пишут. Может быть потому, что они все работают на java. Но в c ++ иначе никак не получится.<br />
<br />
Итак, весь рабочий код, без main, удобно держать в отдельной папке. При компиляции эта папка сворачивается в один объектный модуль. Здесь я конечно не говорю про большие проекты из десятков библиотек, там иерархия сборки немного более сложная. Мы будем говорить о проектах, где некоторый набор исходных текстов собирается в один или несколько исполняемых модулей. Исходные тексты могут быть разложены по подкаталогам, это по моему правильно, и не меняет сильно дела. И в итоге получается, что логика сборки весьма проста и прямолинейна.<br />
<br />
Кроме того, это помогает ускорить процесс сборки. Собирать цель из сотен объектных модулей по моему долго. Гораздо оптимальнее поделить модули на группы, каждую группу сворачивать в бандл, а потом просто слинковать несколько получившихся бандлов. Такая система используется, например, в ядре linux. А для TDD есть прямой резон в том, чтобы при сборке минимизировать количество компилируемых/линкуемых файлов. Поскольку в TDD изменения сильно локализованы и незначительны по размеру, а сборки — достаточно часты.<br />
<br />
Короче, нужна максимально быстрая сборка. И make с этим более-менее справляется, хотя к его скорости и есть серьёзные претензии у сообщества.<br />
<br />
Кроме того нужна минимальная пересборка. И с этим make справляется, если прикрутить к нему файлы с зависимостями. <br />
<br />
Но если, в полном соответствии с лучшими практиками, как то рефакторинг, мы начнем переименовывать include файлы, то у нас съезжают зависимости, и мы получаем ошибку сборки. И это происходит не смотря на то, что другие файлы в списках зависимостей претерпели изменения и на самом деле сборка цела. Чтобы преодолеть это, надо обновлять файл зависимости. Честно говоря, не могу придумать, как сделать это выборочно, а полная очистка вызывает полную пересборку, чего мы хотели бы избежать.<br />
<br />
И тут я подумал, что вообще не хочу беспокоится о зависимостях. Даже более того, я готов терпеть чуть более долгий процесс сборки, ради того, чтобы сборка при любых изменениях собиралась корректно и, конечно, в минимальном объеме. <br />
<br />
И тогда я написал скрипт, который умеет превращать каталог в один объектный модуль. Конечно, с учетом всех модификаций и изменений. Этот, очень простой скрипт на питоне можно найти <a href="http://code.google.com/p/paranoid/source/browse/script/build.py">здесь</a>. <br />
<br />
Его использование дает несколько больших плюсов:<br />
<ul><li>Мне больше не нужны Makefile в подкаталогах. От корневого Makefile отказываться, конечно, не стоит. Но на его плечи ложиться сравнительно небольшая нагрузка, как то свести все воедино, и подчистить за собой.</li>
<li>Зависимости я определяю на лету, думаю что препроцессор отрабатывает сравнительно быстро, и мне нет смысла с этим заморачиваться пока у меня проект не перерос какие-то разумные размеры.</li>
<li>Мне больше нет необходимости наблюдать вывод сборки. Я могу сделать дружественные сообщения, вплоть до прогресса. Это можно было сделать и в Makefile, но там это не так гибко.</li>
</ul><br />
Сам про себе скрипт, конечно не универсален, и имеет минусы:<br />
<ul><li>Все исходные тексты, для полноты картины даже main.cpp, необходимо раскладывать по отдельным папочкам. Ведь не для того все это затевалось, чтобы потом некоторые файлы собирать чем-то еще?</li>
<li>Пока он никак не многопоточен. Это большой минус и его можно решить по разному. Можно, например, запускать несколько каталогов паралельно - самое простое. Или можно встроить в скрипт пул компиляторов, но тогда это сильно утяжелит его, думаю в этом нет особой необходимости.</li>
</ul><br />
Сейчас он решает мою конкретную задачу, и этого довольно. Если идея кому-то покажется интересной, я всегда открыт для общения.<br />Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com25tag:blogger.com,1999:blog-3179964835593137794.post-23764821670886582222011-09-07T22:58:00.000+04:002011-09-07T23:00:24.951+04:00Если не strcmp...Опять встретилась проблема в коде. Не то, чтобы очень уж страшная проблема, некоторые даже думают что тем самым они наоборот вносят ясность в код. Но я, как обычно, против.<br />
<br />
Давайте посмотрим на следующий код, который я для примера выдрал из ядра linux:<br />
<pre>if (!strncmp(name, p, k) && p[k] == '=') {
p += k + 1;
...</pre><a name='more'></a>Вроде бы ничего особенного... Что же мне может не нравится в таком замечательном коде?<br />
<br />
Глядя на этот код, лично я, никак не могу понять каким боком там стоит логическое отрицание? Проблема возникает, когда я пытаюсь этот код прочитать...<br />
<br />
Если не strcmp... тогда кто?<br />
Если strcmp ложно... а в чем собственно заключается истина?<br />
<br />
Проблема в том, что результат функции не логический. Функция возвращает конкретную разницу, число. В спецификации написано открытым текстом, функция возвращает меньше, больше и равно нулю. Почему бы не написать просто: Если результат вызова strcmp равен нулю то...<br />
<pre>if (strcmp(...) == 0) {
...</pre>Это не проблема работоспособности кода, это проблема понятности кода. Конечно все программисты знают что делает функция strcmp и что она возвращает. Но давайте представим на секунду, что мы видим совершенно незнакомую функцию: <code>if (!foobar(...))</code> Как вы думаете, что проверяет это условие? Кто нибудь может сходу ответить? <br />
<br />
Да, согласен, по окружающему контексту можно судить - насколько 'не фубар' это хорошо... Кроме того, функция могла бы иметь более понятное название. Чтобы понять смысл выражения нужно будет для начала изучить прототип foobar. При непонятном имени придется изучить еще и исходник...<br />
<br />
Но, и самое главное, если правильно ее использовать в условии, можно сразу, не задумываясь ответить на вопрос - что же она возвращает. <code>foobar(...) == '\0'</code> - Эта функция возвращает char и вероятно наткнулась на символ окончания строки. <code>foobar(...) == nullptr</code> А эта возвращает нулевой указатель (Мы все знаем, что NULL в C++ - это плохо, а 0 очень похож на int :) ), Если же мы видим сравнение с нулем - это значит что нас интересует именно ноль сам по себе. Как число, как константа.<br />
<br />
Конечно, никому в здравом уме не придет в голову писать <code>if (foobar(...) == false)</code>... Именно для этого случая существует логическое отрицание. И только в случае логического значения можно не писать явное сравнение.<br />
<br />
По моему в давно пора перестать рассматривать ноль, как false, а не ноль, как true... Это было в прошлом веке.<br />
<br />
Очень часто встречается, например, со стандартными библиотеками си:<br />
<pre>int fd = open(...);
if (fd >= 0)</pre>Ну какой ноль??? В мане серым по черному написано, что в случае ошибки функция возвращает -1, почему бы не написать условие явно? <br />
<pre>if (fd == -1) {
error;</pre>Хотя, может быть это и не самый удачный пример (много этих стандартов в мире юникс, может быть где-то и не -1?).<br />
<br />
Код должен читаться, буквально по английски. Если получается чушь, значит стоит подумать о том, насколько явно вы доносите свои намерения в коде.<br />
<br />
А применяя вместо хитрых трюков типизированные константы, типа '\0', nullptr мы даем читателю подсказки к пониманию кода. С понятными именами у него не должно возникнуть необходимости к тому, чтобы полезть искать прототип, или хуже того - изучать реализацию.<br />
Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com6tag:blogger.com,1999:blog-3179964835593137794.post-78232125463538711032011-08-30T14:04:00.004+04:002011-08-30T14:33:08.013+04:00Давным давно, в одном редко вызываемом модуле...Нет, все действия происходят в настоящее время. Наткнулся на интересный кусок кода:
<br /><pre> boost::format value("%s:%s");
<br /> value % head;
<br />
<br /> BOOST_FOREACH(value_type &item, container) {
<br /> if (item.valid()) {
<br /> value % item.value();
<br /> }
<br /> }
<br />
<br /> return 0;
<br />}</pre>Все совпадения с реальным кодом следует считать совпадениями, конечно я его немного поменял. :)
<br /><a name='more'></a>
<br />Наткнулся потому, что из за него повалились вдруг тесты - мало аргументов для boost::format.
<br />
<br />Долго думал, пока наконец не понял, что этот код совершенно никому не нужен. value, объявленная в начале фрагмента благополучно уничтожается в конце оного.
<br />
<br />Полез в историю, и выяснил, что в мае 2006 году объявление value находилось в другом месте, в начале модуля. И действительно использовалось в другом месте.
<br />
<br />Через два месяца, в июле 2006, код, который использовал это значение был переписан. значение осталось без использования.
<br />
<br />Через месяц определение value было перенесено в начало вышеуказанного фрагмента. В этот момент уже можно было оценить всю бесполезность кода, но почему-то этого не произошло, даже более того...
<br />
<br />В новейшей истории, в этом месяце, код был переписан с использованием BOOST_FOREACH.
<br />
<br />Пока наконец сегодня не сломались тесты, и этот код не был выброшен на свалку истории.
<br />
<br />Забавно, что почти 6 лет совершенно бесполезный код продолжал работать, работать и работать. :) В реальности он немного более наворочанный, возможно это его и спасало все это время.Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-49777455091757966502011-08-16T18:33:00.003+04:002011-08-16T18:57:50.324+04:00Семь раз отмерь...Интересно, откуда пошла поговорка? Но речь не об этом.
<br />
<br />Если жить в соответствии с дао, я имею ввиду TDD и рефакторинг, то методы надо делать меньше. Как сказал Роберт Мартин - 'нет, методы надо делать еще меньше'. И классы в соответствии с SRP надо делать меньше.
<br />
<br />Я никогда не встречал проектов, которые бы разрабатывались в полном соответствии с этой концепцией, но чувствую проблемы. Проблемы заключаются в том, что количество таких объектов может уйти далеко за рамки 7+-2, которые человек в состоянии эффективно анализировать.
<br /><a name='more'></a>
<br />Разрастание количества классов может привести к плохо обозримой кодовой базе, в следствии чего увеличится дублирование кода. Что же делать?
<br />
<br />Очень просто - необходимо распространять принцип 7+-2 дальше. не более 7+-2 классов в модуле, не более 7+-2 модулей в компоненте, не более 7+-2 компонентов в приложении. Это даст хорошую локализацию функционала, но опять таки может привести к дублированию, потому что усложняется расшаривание общего кода. Что же делать?
<br />
<br />Общий код (тоже 7+-2 :) ) нужно выносить в библиотеки...
<br />
<br />И осталось только все это аккуратно и гармонично расположить по каталогам проекта. В древовидном виде, конечно.
<br />
<br />Собственно к цифре 7+-2 надо относится прагматично. Не стоит на каждый чих вести отсчет. Достаточно подсчитывать определяющие поведение классы, методы или что похуже. Утилитарные однострочники не достойны считаться полноценными классами.
<br />
<br />Очень кстати удобно сделано в linux kernel - каждый подкаталог в нем сворачивается в объектный бандл, один объектный файл, который объединяет в себе все скомпилированное содержимое каталога.
<br />
<br />А сколько классов/файлов в одном каталоге живет у вас?
<br />Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com2tag:blogger.com,1999:blog-3179964835593137794.post-28062826595599296942011-08-05T11:08:00.004+04:002011-08-05T12:00:34.567+04:00Использование исключенийЯ очень люблю исключения. Но в последнее время я начал понимать тех, кто в своих стандартах кодирования запрещает исключения. Просто поразительно насколько часто, даже грамотные люди не могут оценить исключительность ситуации..<br /><br />Исключения надо применять исключительно в исключительных ситуациях. Тогда, когда ты не можешь даже предугадать как обработать ту или иную ситуацию. И даже тогда надо десять раз подумать - а нет ли способа проще. может быть просто вернуть пустое значение, которое удовлетворит вышестоящие уровни? Это будет реально проще, чем бросать исключения.<br /><a name='more'></a><br />Теперь давайте я приведу несколько примеров, которые меня бесят. Все совпадения с реальным кодом - реальны, имена только изменены, и код немного подчищен...<br /><pre>type Client::receive()<br />{<br /> struct special_case { };<br /> type data;<br /><br /> try {<br /> if (...) {<br /> throw runtime_error("...");<br /> }<br /> ... skipped many throws ...<br /> if (...) {<br /> throw special_case();<br /> }<br /> } catch (exception &) {<br /> data = type();<br /> } catch (special_case &) {<br /> throw runtime_error("...");<br /> }<br /> return data;<br />}</pre><br />В данном случае мы видим реализацию переходов на базе исключений. Я не вижу в этих ситуациях ничего исключительного, и более того. В этой функции мы прекрасно представляем, что собираемся с этим сделать.<br /><br />Очень костыльно выглядит особый случай, который вполне легко решается одним throw, когда мы убираем все "goto"...<br /><pre>string Informer::get()<br />{<br /> int sock = socket(AF_UNIX, SOCK_STREAM, 0);<br /> if (sock < 0) { <br /> throw runtime_error("...");<br /> }<br /> ... skip more code with throws ...</pre><br />Конечно, мы же на C++ пишем, зачем мы будем связываться с кодами ошибок и тд... <br />С кодами ошибок связываться нет необходимости, проблема не в этом. Проблема в том, что функция приватная. В публичной функции мы видим следующее:<br /><pre>try {<br /> string r = get(components[i].comp);<br /> ... do something ...<br />} catch (const runtime_error &e) {<br />}</pre><br /><br />То есть в рамках класса мы опять таки прекрасно знаем, что будем делать в том случае, если возникнет исключение. Так давайте уберем исключение и сразу сделаем то, что хотим? Например вернем пустую строку.<br /><br />Следующий пример больше не по генерации, а по перехвату исключений. Так уж сложилось, что наш интерфейс базы данных бросает исключения, если выборка пуста. С этим ничего не поделаешь, приходится мириться, хотя много кода, похожего на этот:<br /><pre>try {<br /> scoped_ptr<Set> cursor(db->all_records());<br /> ... do something ...<br />} catch (EmptySet &) {<br /> // ничего не надо делать<br />}</pre><br />Костыльно конечно, но что делать..<br />И тут я увидел это:<br /><pre>void convert_records(shared_ptr<RecordSet> &in, list<Record> &records) {<br /> do {<br /> try {<br /> shared_ptr<RecordSet> cur_rec((*in)->get_records());<br /> convert_records(cur_rec, records);<br /> } catch (EmptySet) {<br /> Record rec;<br /> convert_record(**in, rec);<br /> records.push_back(rec);<br /> }<br /> } while (!in->next()); <br />}</pre><br />Смысл в том, что там добавилась древовидность записей, одна запись получила возможность ссылаться на другие. И записи отныне делятся на листовые и групповые.<br /><br />Я сперва не мог сообразить, почему две одинаковые функции используют совершенно разные параметры, буква s в конце имени функции очень плохо видна.. а за такую перегрузку - вообще бы убил.<br /><br />Пришлось трясти автора, и тогда я узнал всю страшную правду. Эта функция вызывается для корневых элементов, и каждый элемент пытается проходить вглубь. Для негрупповых элементов происходит исключение, где, в ветке catch, и происходит натуральная работа - наполнение списка записей...<br /><br />Условие нельзя было поставить? чтобы одним легким движением вообще отказаться от исключений в этом месте? Исключительность ошибок - я еще могу оправдать. Но здесь через catch проходит основная логика программы, основная ветвь выполнения.<br /><br />Вот после этого я и подумал - наверное в Google все таки не дураки сидят, что запретили исключения?Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com9tag:blogger.com,1999:blog-3179964835593137794.post-54965123044286561412011-05-31T10:28:00.003+04:002011-05-31T11:28:13.603+04:00fastdb queryСуществует такая библиотека, <a href="http://www.garret.ru/fastdb.html">fastdb</a>. Это объектная in-memory база данных. Мы используем ее в своем проекте, чтобы хранить конфигурацию сервера. Администратор вносит изменения в конфигурацию, при этом программа управления - весьма тонкий клиент, не обладающей собственной логикой. Вся логика находится на сервере, который должен проверить, что то, что сделал администратор не противоречит функционированию системы в целом.<br /><br />Для проверки в основном использовались dbQuery. Мне всегда казалось это неудобным, поскольку в Query не так то просто подсунуть программные константы. Кроме того мучили сомнения относительно эффективности данного подхода, которые и сподвигли меня на измерение производительности.<br /><a name='more'></a><br />Запросов достаточно много, на каждый случай - свой. Причем каждый запрос характеризует невозможную ситуацию, если какой нибудь объект удовлетворяет критериям запроса - это фейл. Основной вопрос - насколько dbQuery быстрее циклического перебора полной выборки. Особенно учитывая, что в одном цикле можно проверить несколько кейсов. Но давайте по порядку.<br /><br /><span style="font-style:italic;">Надо сказать что я не сильно заморачивался и тестирование получилось достаточно поверхностное.</span><br /><br />Для измерения была создана тестовая база с миллионом достататочно простых структурок.<br /><pre>struct dbItem {<br /> db_nat4 id;<br /> db_nat4 type;<br /> const char *name;<br /> TYPE_DESCRIPTOR((KEY(id, AUTOINCREMENT|INDEXED), FIELD(type), FIELD(name)));<br />};</pre>Строковой параметр ввел специально, чтобы поднапрячь движок. Все поля, кроме id, который AUTOINCREMENT, заполнил случайным образом. Общий размер базы данных составил гигабайт. Нетипично большая база, но думаю, она более показательна для наших измерений.<br /><br />Вообще dbQuery достаточно тяжелый объект, поскольку он должен интерпретировать запрос, который в него передается. Внутри он хранит оптимизированное представление. Если запрос сам по себе достаточно неизменен, имеет смысл использовать ключевое слово static. Но на выборке в миллион элементов эффект от статичности dbQuery совершенно нивелируется.<br /><br />На моей выборке один select выполняется примерно 130мс. Но только в том случае, если в запросе используется неиндексированное поле. Если же поле используется индексированное, то время выборки сокращается до 500нс! Такая же скорость наблюдается при использовании selectByKey. Для сравнения я провел аналогичные замеры для map - он справился с поиском за 100нс.<br /><br />Индексированные поля показывают выдающиеся результаты, но к сожалению наши выборки строятся на разных полях. Все же поля не станешь делать индексированными! И не стоит забывать о цели нашего тестирования. Нам ведь хотелось сравнить запросы с простым перебором. <br /><br />А простой перебор без запросов справился с этим делом за 80мс. Что на ~30% быстрее, чем аналогичный query. При этом, если мы будем в простой перебор добавлять дополнительные условия, это не будет нам стоить практически ничего. В то время как каждый новый dbQuery стоит 130мс...<br /><br />Для сравнения - поиск по такой же выборке, загруженной в list, занимает 9мс. Загруженной в vector - 2мс...<br /><br />Резюмируя можно сказать, что dbQuery позволят вам получить значительный выигрыш, если вы грамотно используете в запросах индексированные поля. В противном случае простой перебор окажется быстрее. Но с другой стороны dbQuery кому-то могут показаться более наглядными, если скорость не критична.<br /><br />Кроме того интересовал вопрос - стоит ли кешировать значения из БД в памяти? Если доступ к элементам БД осуществляется через индексированное поле - кешировать однозначно не нужно, по моему это только усложняет код, и все из за жалких 400 наносекунд. :)<br /><br />Теперь можно убрать нафиг эти dbQuery, и мы получим возможность абстрагироваться от БД в плане проверок.Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com2tag:blogger.com,1999:blog-3179964835593137794.post-25691800085028981822011-04-27T16:32:00.003+04:002011-04-27T17:00:03.287+04:00Эволюционный AgileКогда внедряют SCRUM, часто применяют все классические практики сразу. Так было и у нас. Мы практиковали непонятно что, потом приехали ребята из <a href="http://scrumtrek.ru/">ScrumTrek</a>, научили нас жить по новому.<br /><br />Сейчас прошло больше года. Наш процесс уже нельзя назвать скрамом. Мы не рисуем Burndown, мы не оцениваем время. Мы нашли что-то свое. Новые люди говорят, что у нас не очень плохо получается.<br /><br />Мы от SCRUM пришли к удобному для себя формату и это является генеральной линией гибкости. Все так делают. И возникла мысль - а почему ту же гибкость мы не используем при внедрении Agile?<br /><a name='more'></a><br />Конечно для начала необходима личная заинтересованность - вовлеченность. Все должны хотеть жить лучше и понимать что это возможно.<br /><br />Начинать необходимо с какой нибудь одной практики. Думаю что хорошим вариантом будет ретроспектива. Начинаем собираться всей командой к примеру раз в неделю и дружно думать о том - что мы можем улучшить в нашем процессе.<br /><br />Под улучшением можно подразумевать внедрение какой нибудь новой практики, либо какие-то организационные меры. Если речь идет о практиках - мы не заморачиваемся на SCRUM. Мы просто выбираем тот вариант, который нас устраивает.<br /><br />Например - нужны ли нам итерации? Итерация предполагает приращение продукта, то есть очередной выпуск. Или нам удобнее демонстрировать фичи по готовности? Бывает разная специфика. Бывает что один эпик длится два месяца, а оставшиеся фичи мы быстро подбираем за пару недель. Бывает даже так, что пока делается какая-то длительная работа - нечего показывать.<br /><br />Так же под вопросом таскборд. Некоторые команды обходятся без него. В принципе можно и на уровне багтрекинга (даже самого отсталого) построить гибкую систему.<br /><br />Нужны ли стендап митинги? спорный вопрос. Зависит от интенсивности разработки. Их можно проводить например пару раз в неделю.<br /><br />И мы не пытаемся построить новый мир сразу (внедрение SCRUM), чтобы разрушать его потом, а сразу ищем для себя лучший путь.<br /><br />Смущает во всем этом только то, что если команда и так гибкая и понимает куда следует двигаться дальше - то вряд ли им придется начинать с нуля. :) Хотя под руководством это наверное возможно и для непосвящённой команды.<br /><br />У нас сейчас прижилась схема с месячными итерациями и недельными спринтами. Демонстрация у нас проводится в конце итерации, то есть раз в месяц. Фокусфакторами мы тоже не страдаем.Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com0tag:blogger.com,1999:blog-3179964835593137794.post-25841891949358211852011-04-12T17:11:00.002+04:002011-04-12T17:35:43.018+04:00mbuf overrunНаткнулся на интересную проблему... <br /><br />Не буду винить команду FreeBSD, как я люблю, ибо в стандартной комплектации FreeBSD-6 вообще не поддерживает данный адаптер. Нам, для поддержки необходимого оборудования пришлось взять самый последний драйвер от Intel. Драйвер изначально ориентирован на FreeBSD-7, но нам удалось прикрутить его к шестерке. Оригинальные драйвера от Intel используются во FreeBSD практически без изменений. Только фря старая, и драйвер седьмой версии в нее не втащить. До сего дня использовался 6.9.12<br /><a name='more'></a><br />Но это все была присказка, приоткрывающая завесу тайны. Началось все с того, что на новой платформе версия продукта начала падать...<br /><br />Наших менеджеров хлебом не корми, дай что нибудь продать. А продукт у нас аппаратно-программный. И для продаж последней версии закупили новую платформу. Очень новую... для FreeBSD 6.3 просто невозможно новую... Но драйвер em из семерки мы втащили в ядро раньше. Надо сказать семерка, в своих ранних проявлениях, не сильно отличается от шестерки.<br /><br />Все имеющиеся платформы оттестировали вдоль и поперек, но на новой продукт почему-то стал падать при прохождении трафика. И мало того, Из за него начали падать соседние шлюзы, которые раньше прекрасно работали.<br /><br />Почти неделя прошла в медитациях.<br /><br />Сперва, как обычно, грешил на невыясненные глюки в коде, даже работающее решение предоставил, где ничего не падает и почти полное счастье. Но истинная причина оставалась неизвестной.<br /><br />Потом выяснилось, что в пакете, пока он находится в очереди на шифрование, не просто портится длина, а весь пакет чудесным образом заменяется на другой. А FreeBSD ведет себя хитро, чтобы работать с пакетом в заголовке пакета длину меняют на host order. Представляете себе удивление ip_fragment, когда он пытался фрагментировать пакет у которого длинна оказалась в некорректном (сетевом) порядке байт.<br /><br />Причина падения ясна - а вот причина чудесной подмены пакетов - нет. Собственно лечить FreeBSD - не входило в мои планы. В таких случаях я обычно начинаю читать svn.freebsd.org... Но сейчас это не помогло. В шестерке драйвер значительно старее нашего, а в семерке - уже значительно новее. <br /><br />Нашелся новый драйвер от Intel (6.9.21), который решил проблему окончательно, подмены пакетов прекратились.<br /><br />Не знаю какая мораль из всего этого. Обновление драйверов спасает нас почти при любых проблемах с новым железом. Но с каждым новым железом сделать это все труднее, FreeBSD-6 уже не развивается. Как мы дальше будем ее поддерживать? - не знаю...Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com2tag:blogger.com,1999:blog-3179964835593137794.post-45536078388590490642011-04-05T00:28:00.005+04:002011-04-05T01:03:15.775+04:00Понять ветвленияЦентрализованные системы контроля приучили нас к единому репозиторию. Ветвления осуществляются между каталогами, или депотами, как их не назови. Ну CVS мы за систему контроля (поддерживающую ветвления) вообще не считаем. Во всех остальных, насколько я знаю - деревья.<br /><a name='more'></a><br />Вообще любой подход имеет как плюсы, так и минусы. svn хорошо, пока не захочешь посмотреть историю по ветке... В которой обычно можно наблюдать только последствия мерджей (MFC, MFC, MFC, MFC) - реальная природа изменений похоронена в транке.<br /><br />Децентрализованные системы сливают весь чейнджсет, и мы можем видеть историю, не зависимо от того, в какой ветке находимся, это плюс. <br /><br />Но я, почему-то, думал что одной центральной базы хватит всем. Нет! Природа децентрализованных систем такова, что каждая ветка, до слияния, живет в своей базе. Коммиты, попавшие в базу никуда не исчезают. И если вдруг есть вероятность, что данная ветка может не понадобиться, ее нельзя пушить в центральную базу, она там не нужна. Можно хранить ее на сервере, но только в отдельной базе.<br /><br />Так же, традиционный воркфлов, вероятно, подразумевает многочисленные локальные клонирования. Вытянул у Пети, посмотрел, вытянул у Васи, посмотрел - в любом случае нужна отдельная база, куда втягиваются неподтвержденные изменения.<br /><br />Есть возможности публиковать только часть изменений - git публикует текущую ветку, mercurial'у можно указать что публиковать (со всеми парентами). При этом mercurial выталкивает на сервер все родительские коммиты. Сделал локальное слияние - все уйдет на сервер.<br /><br />Создать временный бранч - тоже не получается. Mercurial будет грязно ругаться, если ты не предполагаешь публиковать его на сервере. Получается, что именованные бранчи локально не работают. Для этих целей базу надо клонировать.<br /><br />Думал, что можно выбрать в качестве альтернативы для текущей, централизованной системы? Mercurial вероятно наиболее подходящий кандидат. Во первых в базе Меркуриала есть номера ревизий. Пускай в каждой клонированной базе и свои, главное, что на сервере они неизменны. Я могу применять их в сборках и при этом сборки однозначно будут идентифицироваться с соответствующим коммитом. Этим свойством обладают почти все централизованные, и отсутствие этого служит препятствием к переходу. git или bazar не обладают такой упорядоченностью и компактностью (одна цифра) версий.<br /><br />В остальном системы, по моему, идентичны. В bazar вообще нет локальных ветвлений, но как написано выше, они и в mercurial не особо помогают.<br /><br />Надо сказать, что у меня не большой опыт работы с DVCS. Чтобы полноценно это осознать - нужно поработать в распределенной команде. Я только учусь. Возможно я и до сих пор в чем-то ошибаюсь.Andrey Valyaevhttp://www.blogger.com/profile/05020625213099509709noreply@blogger.com10