суббота, 31 января 2009 г.

Можно ли заставить компилятор проверять изменение принципов работы компонентов?

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

Итак, были самостоятельные компоненты S и M, между которыми располагалась заплатка B. Мне потребовалось, чтобы при своем рестарте заплатка B отсылала компоненту S сообщение query_state, на которое компонент S должен был ответить сообщением current_state. Но, любое сообщение должно иметь как адрес получателя (в данном случае адрес компонента S), так и адрес отправителя. Когда сообщениями между собой обмениваются компоненты S и M проблем нет -- у каждого из них есть собственный адрес. Поэтому, когда M отправляет S сообщение query_state, то S знает, кому нужно отослать ответ. А у заплатки B нет собственного адреса. Заплатка знает только адрес компонента S. И перехватывает сообщения, которые либо отсылаются на адрес S, либо отправлены с адреса S. Поэтому B легко может отослать query_state на адрес S. Но что подставлять в качестве адреса отправителя?

Первым решением напрашивалось добавление в конфигурацию B адреса M. Тогда бы B мог отослать query_state от имени M. Но это решение было не самым лучшим. Во-первых, нарушилась бы совместимость между версиями B -- добавился бы обязательный параметр при конфигурировании. Во-вторых, снизилась бы гибкость использования B: ранее можно было менять взаимосвязи между S и M, не затрагивая B. Теперь, если бы пришлось заменить M на N, то потребовалось перенастроить не только S, но и B.

К счастью выяснилось, что S при получении query_state всегда отсылает current_state не отправителю query_state, а компоненту M (точнее, тому компоненту, адрес которого прописан в конфигурации S, обычно это компонент M). Поэтому вопрос о том, с какого адреса B должен отсылать query_state отпал сам собой -- это оказалось совершенно неважно.

Я уже не помню, по каким причинам S всегда отсылает current_state на определенный в конфигурации адрес. Очень похоже, что это явилось деталью реализации S: сообщение current_state отсылается в нескольких случаях, и везде используется один и тот же фрагмент кода, формирующий и отсылающий current_state беря адрес получателя сообщения из конфигурации. Забавно, что подобным образом поступают и еще два компонента, E и G, играющие подобную S роль, и разработанных после S. Вероятно, при создании E и G логика работы S использовалась в качестве образца.

Таким образом, в модифицированной версии B оказалось заложена зависимость от текущей логики работы компонента S (а так же E и G, с которыми B так же может использоваться). Если в дальнейшем логика S будет изменена так, что S будет отсылать current_state именно тому, кто отослал query_state, то B окажется неработоспособным.

Что может помочь выявить такое нарушающее совместимость изменение логики работы S? Пока только тесты. Но с тестами не все просто, поскольку именно такое взаимодействие S и B вряд ли возможно проверить с помощью unit-тестов. Для такой проверки необходима организация тестового стенда (с различными способами размещения S и B) и проведение тестирования на этом стенде. Понятно, что в некоторых случаях такое тестирование просто не будет организовано. Например, когда в S будет обнаружен какой-то критический баг, который придется править в очень сжатые сроки и сразу же запускать исправленную версию S в эксплуатацию. Хорошо бы, чтобы такого никогда не происходило, но раз в пару лет такие случаи все-таки возникают.

Гораздо лучше было бы, если бы изменение логики S привело к тому, что измененная версия S не могла бы даже быть скомпилирована. И вот вопрос, на который я пока не имею ответа: можно ли сделать так, чтобы компилятор контролировал изменение логики работы компонента, и не позволял бы скомпилировать измененную версию компонента, если она нарушает ожидания остальных компонентов?

Тут сразу же напрашивается аналогия с контрактами. Отсылка current_state в ответ на query_state на фиксированный адрес -- это текущий контракт S. Однако, как этот контракт выразить в программном коде (например, на C++)? Скажем, я знаю, как выглядят контракты в Eiffel. Но, в Eiffel контракты привязываются к прототипам синхронных методов (а мне нужен прием и отсылка сообщений), да и выполнение контрактов проверяется во время исполнения программы (а мне хотелось бы во время компиляции). Поэтому вопрос о том, как можно сделать систему контрактов для агентных систем, в которых агенты обмениваются асинхронными сообщениями, для меня пока остается открытым...

четверг, 29 января 2009 г.

I'm not standard. Programmer's productivity

Продолжение "I'm not standard". Тов.Чистяков, утверждает, что я читаю Ruby-скрипты средством повышения производительности программиста.

Это полная ерунда. Ruby не может быть средством повышения производительности программиста в общем случае. Бывают задачи, которые проще и быстрее решать на Ruby, чем на C++. В таких случаях я пытаюсь использовать Ruby. Иногда это получается.

Что же влияет на производительность программиста? Очень много факторов. Технические и организационные, например, поскольку о них я более-менее обосновано могу говорить. В отличие от, скажем, психологических факторов.

Технические факторы, к которым относится использование того или иного языка программирования, достаточно очевидны и неинтересны. Язык программирования далеко не самый важный из них. Точнее, даже не сам язык, а его приспособленность для решения конкретной задачи. Не много найдется написанных на C++ web-приложений, поскольку это не ниша C++. Более важным техническим фактором я считаю инструментарий, в который входят редакторы, IDE, компиляторы, отладчики, профайлеры и пр. Качественный профайлер способен сэкономить программисту дни, если не недели. Ну и самым важным техническим фактором я бы назвал наличие готовых библиотек, которыми располагает программист. Поскольку их использование способно сократить трудозатраты на разработку буквально в разы.

Но технические факторы вряд ли заслуживают отдельного пристального внимания, поскольку они и так на виду. К тому же зачастую программист способен воздействовать на технические факторы. Если не на уровне выбора языка, то хотя бы на уровне выбора библиотек и IDE. Гораздо более важными являются организационные факторы. Тем более, что далеко не всегда программист может на них повлиять и изменить.

Вот, к примеру, такой очень важный фактор, как атмосфера в рабочем коллективе.

Атмосфера должна быть комфортной и располагающей к спокойной, продуктивной работе. Очень сложно сосредоточится на коде, когда вокруг звонят телефоны, ведутся совещания, обсуждаются посторонние темы, включается или выключается кондиционер, открываются или закрываются окна, хлопают входные двери, работает радио и пр. Именно из-за этих не очень существенных, но многочисленных, мелочей лично я предпочитаю работать дома, а в офисе появляться только по необходимости.

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

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

Очевидно, что "комфортная и спокойная" является противоположностью "свободной и творческой", если речь идет о большом числе людей в одном помещении. Решение этого противоречия уже давно найдено и озвучено, например, в книге ДеМарко и Листера "Человеческий фактор". В идеале, каждый разработчик должен иметь отдельный кабинет, обязательно с плотно закрывающейся дверью. Но это в идеале. На практике же не редкость, когда в одной комнате работают четыре-пять, а то и больше программистов :(
 
А еще рабочая атмосфера должна быть открытой (по крайней мере, внутри трудящейся над одним проектом команды). Очень плохо, когда программисты намеренно скрывают друг от друга знания или отказываются помогать друг другу. Еще хуже, когда участники проекта по каким-то соображениям искажают относящуюся к проекту информацию.

Со мной в EPAm-е произошел показательный случай. Когда мне было поручено определить, насколько хорошо закомментирован исходный код проекта, я выяснил, что Javadoc-комментариев очень мало. На вопрос о причинах такого положения вещей я ответил, дословно: "Потому что программисты ленятся или не успевают". За что был несколько раз отчитан начальством разного уровня о том, что нельзя "выносить сор из избы". Этот эпизод стал одним из первых звоночков о том, что EPAm не та контора, в которой мне хотелось бы работать. Поскольку мне быстро дали понять, что целью является не сам проект, а соблюдение неких корпоративных приличий. Если уж эти приличия настолько важны, то пусть их соблюдает какой-нибудь менеджер, который находится между разработчиками и заказчиками. Но внутри команды, которая занимается проектом, не должно быть недомолвок или намеренного искажения реальной ситуации.

Еще одним организационным фактором, который серьезно сказывается на производительности программиста, является заинтересованность разработчика в том, чем он занимается. Если поставленная передо мной задача интересна мне, захватывает меня, если я берусь за нее с энтузиазмом, то она будет решена мной гораздо, гораздо быстрее и качественнее, чем не зацепившая меня. Можно попробовать приплести сюда профессионализм. Мол, профессионалу должно быть все равно, что делать. Может быть и должно, но в моем случае это точно не так. Хотя большую часть времени как раз приходится заниматься тем, что "нужно" и "должно", а не тем, чем хотелось бы. Так что фокус в том, чтобы, суметь найти хоть какой-нибудь интерес в том, что нужно запрограммировать сейчас. Если фокус удается, то производительность заметно возрастает. Ну а если нет... :(

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

Не всегда цель определяется самим разработчиком. Ее, по идее, должен определять заказчик, а затем уточнять менеджер. Но это, как правило, стратегические цели. А вот пути их достижения -- это уже вотчина разработчика.

Например, заказчик озвучил мне проблему: "При таких-то стечениях обстоятельств в таком-то компоненте начинает накапливаться очень большая очередь ожидающих обработки транзакций". И поставил задачу: "Устранить эту проблему. Очередь транзакций не должна накапливаться". Это задача. Но для меня, как для программиста, еще нет цели. Целью должно стать некоторое решение этой задачи. Естественно, у любой проблемы есть несколько решений. Далеко не всегда первое найденное решение будет удовлетворять заказчика или требовать минимальных усилий разработчика. Поэтому очень желательно рассмотреть (как говорят, обсосать) решение со всех сторон, чтобы убедиться, что именно оно подлежит реализации. Иногда, еще до начала реализации, при тщательном изучении решения с разных точек зрения становится очевидно, что оно ошибочно и лучше выбрать какое-то другое. И фокус в том, чтобы перед началом программирования точно знать, какое же именно решение будет запрограммировано (это и есть цель).

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

Вот такое вот плавное замыкание моего порочного круга и возврат к первой теме -- программированию на бумаге :)