ДонНТУ Портал магистров ДонНТУ ru
Магистр ДонНТУ Похилец Николай Васильевич
Похилец Николай Васильевич
Факультет компьютерных наук и технологий
Разработка средств контроля доступа к реестру ОС Windows.
ДонНТУ
Разделы:
Биографический:
О себе (Главное)
Автобиография
Тематический:
Автореферат
Библиотека
Ссылки
Отчет о поиске
Индивидуальный:
Статья: "Мой опыт профессионального программирования"

Мой опыт профессионального программирования

Зачем вообще люди поступают в университет? Кто-то потому, что потому что «так все делают», кому-то мама сказала «надо», но большинство, все же, для того, чтобы выучиться на первоклассного специалиста и найти себе перспективную высокооплачиваемую работу. Лично я руководствовался последним. Однако уже к третьему курсу, у меня сложилось стойкое впечатление что университет – это не совсем то средство, которое нужно для достижения этой цели. Используя все то, чему я научился в университете, самое лучшее, что я мог сделать это невзрачную ДОСовскую программку или простенькое Win32-приложение – вещи весьма далекие от коммерчески успешного программного продукта. А строчки «требуется … с опытом работы не меньше …» во встречающихся объявлениях о вакансиях окончательно портили настроение. Так что, когда у меня появилась возможность понюхать пороху и поучаствовать в «настоящем» программировании, я с радостью согласился.

Местом, куда я попал, оказалась фирма «XITEX Software». О фирме можно встретить самые разные отзывы – от истеричного восторга до лютой ненависти. По своему опыту скажу, что место действительно весьма специфическое и далеко от идеального места работы – бешеная текучка кадров (обусловленная, главным образом, низким уровнем з/п), полное отсутствие менеджмента, нет процесса разработки как такового, профессионализм среди членов команды является скорее исключением, нежели правилом. Но с другой стороны, для себя я здесь нашел предельно гибкий график, минимум ответственности, большую свободу для смелых экспериментов, веселый коллектив, состоящий преимущественно из действующих студентов и тех, кто был ими еще вчера. И самое главное, – я получил то, за чем я сюда пришел – нужный мне опыт работы. Так что я не жалею что провел здесь два года.

Полученный мною на XITEX-е опыт можно без сомнений назвать опытом того, как не надо программировать. За это время я сам сделал море ошибок и видел множество «неправильностей» за соседними столами. За это время я успел набить шишек на уровне кодирования, проектирования архитектуры, организации рабочего процесса и даже ловил себя на неправильном понимании конечных целей разработки. В полной мере прочувствовал на своей шкуре чего действительно стоит повторное использование кода. Попробовал на вкус и запах как наличие расширяемости и гибкости дизайна, так и отсутствие этих качеств. Самостоятельно пришел к пониманию необходимости дисциплины в команде и формализованного документооборота. Получил отличную возможность увидеть, что бывает с проектом, который развивается без реального тимлида.

Для того чтобы понять, нужно сравнить. Думаю, если бы мои первым местом работы была не «XITEX Software», а совершенно другая фирма – с многолетними традициями профессионализма, высокими требованиями к качеству на каждом этапе разработки, мудрыми наставниками, эффективным документооборотом и развитой системой тестирования, – я бы конечно быстро влился в общий процесс, но, скорее всего, воспринимал бы все эти прелести как должное, не оценивая их по достоинству, и вряд ли когда-нибудь задумался о вопросах профессионализма, качества программирования, методологии разработки и организации рабочего процесса. Так что, я считаю, что приобрел на «XITEX Software» поистине золотой опыт, который  фундаментальным образом повлияет на мою карьеру.

Первой технологией, с которой мне пришлось познакомиться на фирме, был фреймворк (framework) Qt. Фреймворк – это больше чем библиотека. При взаимодействии с библиотекой, пользовательский код пишется вокруг библиотеки, при работе с фреймворком – внутри фреймворка, в специально отведенных для этого местах. В результате фреймворк не только предоставляет набор возможностей, но и определяет некоторую идеологию, диктует клиентскому коду «можно», «нельзя» и «следует».

Фреймворк Qt предназначен для разработки кросс-платформенных приложений для настольных систем и мобильных устройств. Языком разработки является С++, расширенный с помощью макросов и специальных утилит до некоторого диалекта.

Не могу сказать, что полностью разделяю идеологию Qt (в частности мне сильно не хватает механизма исключений), но считаю Qt примером продукта высочайшего качества. Не только богатая и динамично развивающаяся библиотека классов и оперативное исправление разработчиками найденных багов стали причиной широкой популярности фреймворка. Подробнейшая, удобная в навигации, документация, интуитивно понятный API, идеальная совместимость версий, удобный интерфейс графических инструментов – все эти свойства были неотъемлемыми атрибутами Qt, начиная с самых ранних версий.

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

Среди прочих, следует отметить механизм сигналов/слотов имеющийся в Qt. В C# и других языках .NET для описания этого механизма используются термины «событие» (event) и «делегат» (delegate). К сожалению, в C++ отсутствует встроенная поддержка для этой мощной техники ООП. Кроме Qt, есть и другие библиотеки, которые реализуют этот механизм – Boost.Signals, libsigc++, sigslot, но они являются скорее экспериментальными изысками, нежели практически применимым продуктом. А реализация этого механизма в Qt, хоть и является мощным средством промышленного уровня, но требует глубокой интеграции с фреймворком, т.е. не может быть использована отдельно от фреймворка в приложениях написанных не на базе Qt. Это побудило меня разработать собственную реализацию этого механизма для C++, которая была бы практически применимой при разработке реальных приложений, но не требовала бы дополнительных зависимостей.

Проектом, который разрабатывался с помощью фреймворка, был кросс-платформенный неразрушающий звуковой редактор. Забегая вперед, скажу что проект в конечном итоге закончился полным провалом – разработка была прервана, проект так и не дошел до состояния бета-релиза. Причин тому несколько, и среди прочего несоответствие моей компетенции на тот момент занимаемой должности – фактически, уже спустя два месяца я оказался в роли тимлида проекта, будучи совершенно к этому не готовым. Среди других причин могу назвать отсутствие спецификаций; отсутствие специалистов по звуковой части как со стороны фирмы, так и со стороны заказчика; форсирование сроков выполнения со стороны заказчика; большая текучка кадров при отсутствии документирования кода; отсутствие проектирования архитектуры как такового.

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

После завершения этого проекта непродолжительное время (около месяца) участвовал в разработке казуального игрового проекта. Этот период не отметился ничем выдающимся, но мне было интересно изучить архитектуру другого проекта и перенять опыт других разработчиков.

После этого в течении двух месяцев был привлечен к разработке программного обеспечения для комплекса программно-аппаратной системы сетевой безопасности. Работая над этим проектом, я смог в полной мере почувствовать разницу между языками C и C++ и уяснить для себя, что я предпочитаю программировать именно на последнем.

Последним на сегодняшний день проектом, в котором я успел поучаствовать работая на XITEX-е был проект по портированию аркадной игры на платформу Apple iPhone. Занимая в этом проекте роль тимлида небольшой команды (3 человека), я смог организовать процесс разработки с учетом своего предыдущего опыта и не допустить сделанных ранее ошибок. Результатом этого стал успешно завершенный основной проект, а также был создан фреймворк для написания приложений для Apple iPhone.

Фреймворк предоставляет набор удобных объектно-ориентированных интерфейсов на C++ (API iPhone OS написано на Objective-C) для решения следующих задач: рисования 2D-графики, воспроизведения звука и полноэкранного видео, управления встроенным акселерометром. Фреймворк реализует оконную систему с поддержкой множественных касаний и предоставляет набор собственных элементов управления. Фреймворк имеет две реализации интерфейсов – одну для компиляции приложения под iPhone OS и запуске на устройстве, и одну для компиляции в эмуляционном режиме для отладки логики приложения на компьютере. Последняя реализация основывается на Qt и потому является кросс-платформенной, т.е. позволяет выполнять отладку логики приложения в любой ОС и любой удобной IDE (Windows/MSVS).

Сейчас фреймворк, уже без моего участия, без изменений используется в следующем игровом проекте.

* * *

На этом описание истории моей профессиональной деятельности заканчивается, поскольку таковая приостановлена на время написания дипломной работы.

* * *

Дальше я хочу поделиться некоторыми своими наблюдениями, извлеченными уроками и личными соображениями по поводу процесса разработки программного обеспечения вообще и объектно-ориентированного программирования на C++ в частности.


Нужно находить баланс между общностью и частностью.

Любой проект растет и развивается подобно живому существу. И в процессе этого развития происходит непрерывное переосмысление как целей и функций продукта, так и способов реализации внутренних деталей. Обычно существенные изменения такого рода происходят уже даже в пределах одной версии, но даже если вам повезло, и имеется предельно четкое,  подробное и недвусмысленное ТЗ, то такие изменения все равно будут происходить от версии к версии. По мере накопления новой информации, постепенно возрастает неудовлетворенность существующими решениями, возникает некоторое напряжение, которое будет расти, пока не будет, в конце концов, разрешено. А для его разрешения придется переделать ту или иную часть проекта. А потому возникает естественное желание сразу сделать «как надо», чтобы потом не переделывать. Желание похвальное, и по большому счету правильное. Но, как правило, для того чтобы сделать «как надо» сразу недостаточно информации. И есть большая опасность, что созданное в результате такой попытки более общее, более мощное, более универсальное решение, на создание которого ушло на порядок больше времени, все равно окажется не «как надо», – время, когда будут задействованы новые возможности, все никак не наступает, зато появляется потребность в совершенно других функциях, которые все-таки не реализованы. Во избежание такого развития событий, лучше подождать пока возрастающая естественная необходимость сама не подтолкнет к принятию правильного (простого и эффективного) решения; использовать вышеупомянутую напряженность как флюгер, определяющий, в каком направлении следует развивать проект.

Также нужно отметить, что реализация задачи это не только проектирование и кодирование, но и тестирование и отладка. Вероятность внесения ошибок при программировании зависит экспоненциально от объема внесенных изменений. А это значит, что при постепенном развитии проекта, пусть и с переделыванием ранее написанных кусков, вероятность возникновения ошибок значительно меньше, а их обнаружение и исправление намного проще.


Рефакторинг.

Рефакторинг – это перепроектирование программы с полным сохранением ее поведения. Целью рефакторинга является повышение четкости и логичности кода, упрощение его для понимания. Рефакторинг выполняется как последовательность элементарных, атомарных изменений. Каждое такое изменение по отдельности является очевидным и может показаться несущественным, но в совокупности они способны радикально улучшить проект. Поскольку каждое из изменений очень мало, то вероятность внесения ошибки минимальна. А сама программа на протяжении всего процесса рефакторинга находится в неизменно работоспособном состоянии.

В процессе рефакторинга приходится, во-первых, взглянуть на существующий код под другим углом, принять во внимание ранее упущенные аспекты, а, во-вторых, внимательно просмотреть каждую деталь существующего кода. В результате этого, нередко в процессе рефакторинга аналитическим путем обнаруживаются ошибки, которые не были выявлены в процессе тестирования. Но! Категорически нельзя исправлять найденные ошибки «попутно». Должно быть четкое разделение изменений, которые являются рефакторингом кода, от изменений, которые являются исправлением ошибок. Т.е. найденная ошибка должна быть задокументирована, учтена в рабочем расписании и исправлена позже – после завершения текущего изменения или после завершения всего этапа рефакторинга. Или же, если ошибка является настолько важной,  откатить изменения, сделанные на текущем шаге рефакторинга, исправить ошибку и после этого продолжить рефакторинг.

Подходящим моментом для проведения рефакторинга является начало некоторой новой вехи в жизни проекта. Появляются новые требования, становятся доступными новые инструменты и технологии, придумываются решения лучшие уже существующих – все это создает предпосылки для рефакторинга. В конечном итоге рефакторинг выполняется в силу конкретных объективных причин. Пытаться делать рефакторинг «каждую пятницу» потому что «в книжках пишут, что рефакторинг это хорошо» – бесполезно и даже вредно.


Атомарные коммиты.

Есть такие штуки – называются системы контроля версий. Обычно они используются при коллективной работе над проектом, но я нашел их крайне полезными и для индивидуальной работы. Я использую Subversion с TortoiseSVN в качестве графического клиента под Windows и Subcommander для других ОС.

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

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

Работая на XITEX-е, не редко доводилось наблюдать коммиты (добавления новых ревизий в базу) с комментариями вроде «что-то сделал, должно заработать». К «должно заработать» я еще вернусь, а пока обсудим «что-то сделал». Одна проблема заключается в том, что по таким неинформативным сообщениям сложно ориентироваться в истории репозитория, нет возможности контролировать ход работ над проектом. Но куда более серьезная проблема заключается в том, что, как правило, содержимое коммита  полностью соответствует комментарию – это на самом деле «что-то». Изменения носят сумбурный, размытый характер. Человек, сделавший коммит, не обладает глубоким пониманием сути внесенных изменений, не может четко объяснить их причину, не осознает возможных последствий. В этом проявляется различие между компетентным специалистом и непрофессионалом.

Коммит должен быть логически атомарным, неделимым. Каждый коммит должен решать одну и только одну задачу. Нередко бывает так, что уже в ходе работы над одной задачи выявляется новая, ранее не учтенная, которая должна быть сделана перед первой, поскольку первая задача зависит от второй. Но при этом уже имеются существенные изменения по первой задаче, откатывать которые непозволительно. В этом случае для обеспечения атомарности следует создать дополнительную рабочую копию. Реализовать в ней вторую задачу и сделать коммит. После этого вторую рабочую копию можно удалить. Затем обновить первую рабочую копию и продолжить работу над первой задачей.

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

Перед отправкой коммита в репозиторий целесообразно просмотреть сделанные изменения. В системах контроля версий имеются средства, которые наглядно выделяют места в текстовых файлах, которые были изменены. Внимательный просмотр сделанных изменений позволяет исправить «по горячим следам» до 50% ошибок, причем с минимальными временными затратами.

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

Следование вышеперечисленным несложным правилам позволяет снизить риск появления ошибок в 10-20 раз и значительно сократить время на отладку и исправление дефектов.


Спецификации.

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

Проект не имеющий четких спецификаций завершен быть не может. Он будет асимптотически приближаться к туманному и неясному состоянию завершенности, но достигнуть его так и не сможет. Новые задачи будут возникать все реже и реже, но их поток никогда не прекратиться. В конце концов, одной из сторон это надоест, и разработка проекта будет прервана, невыполненные задачи отброшены, а текущее состояние будет объявлено окончательным. В итоге будет сдан проект, выполненный на 99%. И проблема здесь даже не в оставшемся одном проценте, 100%-й идеал является практически недостижимым. Проблема в том, что разрабатывая проект целенаправленно можно получить 99,99%,  затратив при этом в 4-5 раз меньше времени и средств.

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

Работа со спецификациями происходит по следующей схеме:

  1. Сравнивается имеющийся образ проекта с фактическим состоянием дел.
  2. Формируется список отличий.
  3. Список отличий трансформируется в список задач.
  4. Задачи распределяются между членами команды.
  5. Задачи исполняются.
  6. Проверяется качество исполнения задач.
  7. Обновляется информация о фактическом состоянии дел и все повторяется с начала.

Этот процесс повторяется в цикле до тех пор, пока фактическое состояние проекта не совпадет с его целевым образом.

При этом в пунктах 1 и 2 вырабатывается цельная картина состояния процесса разработки, которая необходима для эффективной постановки задач, а значит и для их выполнения. В условиях отсутствия спецификаций пункты 1 и 2 исчезают, постановка задач выполняется на основании отрывочных сведений, что нередко приводит к неверной постановке задач и выполнению бесполезной работы. Кроме того эти отрывки информации поступают относительно медленно, что в итоге приводит к тому что конечные исполнители задач загружены не полностью.

В идеале спецификации должны разрабатываться единожды и оставаться неизменными до конца разработки. Но в реальности так не бывает. В ходе работы над проектом и испытания полученных результатов всегда появляются дополнения, исправления и уточнения к образу проекта. После каждого изменения в спецификациях заново запускается цикл обработки спецификаций. В результате чего появляются новые задачи, которые возможно противоречат проделанной ранее работе – нужно переделать или совсем убрать то, что было сделано ранее, на что были потрачены время и ресурсы. Может возникнуть желание сопротивляться этому процессу, чтобы сэкономить время и силы. Делать этого не стоит. С одной стороны, за изменениями в спецификациях стоят потребности конечных пользователей – сэкономить на разработке 15-20% и получить в результате совершенно неконкурентоспособный продукт бессмысленно. А с другой стороны, если разработкой спецификаций занимаются компетентные люди, то развитие спецификаций имеет тенденцию к повышению четкости и упрощению для понимания, что крайне положительно сказывается на проектировании архитектуры программного продукта.

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

Для отслеживания изменений в спецификациях очень удобно хранить их в репозитории вместе с исходными файлами проекта (с соответствующими настройками прав доступа) либо в отдельном репозитории. Большинство систем контроля версий имеют встроенные средства для сравнения простых текстовых файлов и изображений. В пакете MS Office 2007 есть встроенные средства для сравнения файлов Word (которые могу быть вызваны из TortoiseSVN). Существуют аналогичные средства для PDF-файлов и других форматов.

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


Если ощущается потребность в инструменте – напишите его.

Программы создаются для автоматизации рутинной работы, выполняемой человеком. Это справедливо для любой работы, в том числе, и для самого процесса программирования. И тем не менее, по моим наблюдениям, среди программистов мало кто осознает, что они могут написать сами (или даже заказать написание другим) программу для оптимизации их собственной работы. Это может быть плагин для IDE, средство автогенерации исходных текстов, верификатор правил кодирования, редактор входных файлов программы с удобным интерфейсом, IM-нотификатор о новых заданиях и многое другое.

Подобного рода работы являются замечательным средством от «закисания» мозгов, поскольку нередко для их выполнения приходится изучать совершенно оригинальные API и знакомиться с новыми идеями. Такое задание может компенсировать недостаток творческой самореализации в «скучном» основном проекте. Если кто-то из членов команды временно оказался без задач, то хорошим решение будет направить его выполнение такого рода работы. Или использовать в качестве вводного задания для новичков. Регулярная практика написания средств для внутренних нужд приучает членов команды задумываться над эффективностью своего труда и возможностями его оптимизации. А личная заинтересованность в качестве отдельных инструментов сделанных «для себя», имеет тенденцию переноситься и на основной проект.

Но! Само собой, надо реалистично сопоставлять затраты на разработку такого средства с эффектом от его внедрения, и приниматься за его разработку только в случае действительной экономической целесообразности.


Упорядоченность классов.

Связанность (coupling) – мера взаимной завязанности двух различных модулей друг на друге. Высокая связность осложняет внесение изменений в любой из модулей, мешает повторному использованию, затрудняет проведение тестирования и уменьшает его результативность.

Для достижения низкой связанности ООП программы есть одно простое правило – потребовать строгой упорядоченности от всех классов, которые взаимодействуют друг с другом. При этом A > B, если класс A знает об интерфейсе класса B (но не о реализации), а класс B не знает ни об интерфейс, ни о реализации класса A. Классы, которые между собой не взаимодействую, в данном контексте, не сравнимы. При этом, если A > B и B > C, то нельзя C > A – либо  A > C, либо они не взаимодействуют.

В рамках парадигмы ООП вся программа есть набор объектов, которые обмениваются между собой сообщениями. В C++ посылка сообщения есть вызов метода, и для того чтобы послать сообщение (т.е. вызвать метод) источнику сообщения нужно знать интерфейс цели. А это значит, что если выполняется A > B, то класс A может вызывать методы класса B (слать сообщения), но класс B не может послать сообщение классу A. А это является удовлетворительным далеко не всегда. Часто возникают ситуации, когда, не смотря на упорядоченность классов, необходимо слать сообщения в обратном направлении. При этом прямое и обратное направления не равноправны. Посылки «прямого» сообщения «хочет» класс A и он же его отправляет, зная кому посылается сообщение. «Обратное» сообщение отправляется классом B, но «хочет» его опять-таки класс A, и класс B не знает, кому отправляется сообщение. И «прямые» и «обратные» сообщения описываются интерфейсом класса B.

Одним из способов решения этой проблемы является паттерн Observer. В рамках этого паттерна, создается дополнительный интерфейс A2, через который класс B и посылает сообщения. Класс A реализуется этот интерфейс и для посылки сообщения передает классу B указатель на этот интерфейс. При этом класс A также имеет «основной» интерфейс A1, а интерфейс A2 использует «для внутренних нужд». В итоге имеем, A1 > B > A2, A = A1 + A2.

На C++ это реализуется примерно так: есть класс AA, который является «нормальным» родительским классом для A. В классе A используется множественное наследование – с помощью public-наследования наследуется AA и с помощью protected/private – наследуется A2. Реже используется public-наследование и для A2 – в тех случаях, когда установка связи между A и B выполняется третьей стороной.

Альтернативой паттерну Observer является механизм событий/делегатов (сигналов/слотов). Этот механизм не имеет «родной» реализации в C++, но есть несколько библиотек которые реализует этот механизм – Qt, Boost.Signals, libsigc++, sigslot и, написанная мною, cpp-events. Тема событий/делегатов очень обширна и интересно, ее подробное рассмотрение выходит за пределы данной статьи, поэтому за подробной информацией отсылаю читателя к документации этих библиотек.


Синглтоны.

Синглтон (Одиночка, Singleton) – порождающий шаблон проектирования, который ограничивает количество экземпляров класса одним экземпляром и предоставляет к нему глобальную точку доступа.

Работая на XITEX в течении двух лет я наблюдал стойкую тенденцию к неуместному использованию этого шаблона. Я и сам одно время успел переболеть манией сингтонирования, но успел после нее оправиться и последний проект сделал с множеством глобальных объектов, но без единого синглтона.

Использование синглтонов связано со следующими проблемами:

  1. Наличие глобальной точки доступа к  синглтону позволяет не протягивать указатель на его экземпляр к клиентам синглтона. Связь между синглтоном и его клиентами при этом никуда не исчезает, но она становится скрытой. Это значительно затрудняет понимание кода.
  2. Экземпляр синглтона создается и удаляется слабо предсказуемым образом. Это затрудняет отладку и может приводить к труднообнаруживаемым ошибкам.
  3. Введение синглтонов приводит к высокой связанности между классом синглтона и его клиентами. Клиенты всегда обращаются к экземпляру конкретного класса, нет возможности использовать полиморфизм.
  4. Синглтон ограничивает количество своих экземпляров. Как правило, для этого нет никаких оснований. А даже если они и есть, то более целесообразно вынести функцию ограничения числа экземпляров в другой объект.
  5. Синглтоны сохраняют свое состояние на протяжении всего времени работы программы. Это делает юнит-тестирование просто невозможным.

Обычно синглтонами делают объекты, которые существует в единственном числе. Но, проблема в том, в единственном числе объект существует не просто так, а в рамках некоторого контекста – окна, потока, программы, адресного пространства, компьютера, планеты Земля, Вселенной и т.п. Исходя из этого я нашел очень эффективную альтернативу паттерну «Синглтон». Назовем ее паттерном «Вселенная». Суть паттерна в следующем – вводиться класс, который представляет собой контекст, в котором живут другие объекты (жильцы). Каждый из таких объектов при создании получает указатель на экземпляр контекста и через него может получить доступ к сервисам, которые являются глобальными в этом контексте. Соответственно, все объекты, которые были кандидатами в синглтоны, становятся дочерними объектами объекта-контекста. Объект-контекст управляет созданием и удалением «кандидатов в синглтоны». Также, нередко, на объект-контекст ложатся функции фабрики объектов-жильцов.

На сегодняшний день, все случаи использования синглтонов, которые мне встречались, можно было бы с успехом заменить паттерном «Вселенная».

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


* * *

Надеюсь, изложенные здесь мысли окажутся для кого-нибудь полезными.

Полезные ссылки.

  1. www.xitexsoftware.com – официальный сайт фирмы XITEX Software.
  2. www.developers.org.ua/company-db/xitex-software/ – горячие дебаты о фирме.
  3. http://www.rsdn.ru/forum/management/2972662.flat.aspx – обсуждение должности тимлида на форуме RSDN.
  4. http://qt.nokia.com/ – официальный сайт Qt.
  5. http://doc.trolltech.com/qq/qq13-apis.html – Matthias Ettrich. «Designing Qt-Style C++ APIs».
  6. http://code.google.com/p/cpp-events/ – моя библиотека, реализующая механизм событий/делегатов (сигналов/слотов) для чистого C++.
  7. http://refactoring.com/ – портал Мартина Фаулера о рефакторинге.
  8. http://blogs.msdn.com/scottdensmore/archive/2004/05/25/140827.aspx -- Scott Densmore. «Why Singletons are Evil».