Введение в технологию программирования

Планирование, управление и тестирование

Разбить на страницы
Показывать лекцию целиком

Планирование

Результатом фазы оценки осуществимости являются детальная спецификация, план работы и оценка стоимости. Наиболее традиционной формой плана можно считать сетевой график, который представляется в виде ориентированного графа с двумя выделенными вершинами – начало и конец работы. Вершинами графа являются события, соответствующие пунктам плана, а ребрами – работы. События должны выражаться глаголами совершенного вида: "тексты программ переданы в базу данных исходников", "все тесты пропущены", "группа оценки качества дала положительное заключение" и т.д., но уж никак не "выполняется прогон тестов". Ребра нагружаются оценками длительности работ, например, в днях или неделях.

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

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

Рассмотрим пример.

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

  • начало работы;
  • коллектив сформирован, рабочие места подготовлены;
  • проектирование завершено;
  • программирование завершено;
  • комплексная отладка завершена;
  • оборудование закуплено;
  • группа технических писателей получила описание проекта и необходимые пояснения от проектировщиков;
  • то же для ПО, разработка проектной документации завершена;
  • группа технических писателей получила всю необходимую информацию об интерфейсах с пользователем, разработка программной документации завершена;
  • группа оценки качества (Quality Assurance – QA) разработала тесты;
  • группа QA оценила проект положительно;
  • группа QA завершила автономное тестирование;
  • группа QA завершила комплексное тестирование, получила всю документацию и действующий вариант системы;
  • проверка качества (проблемам качества будет посвящена отдельная лекция) завершена;
  • конец работы (конечно, это не конец, будет еще сопровождение, но пример-то надо закончить).
  • Под каждым ребром графа записана планируемая длительность соответствующей работы (в неделях). Еще раз повторю, что это только пример, прошу не придираться к техническим деталям, в частности, разумеется, каждая передача (3-7, 3-10 и т.д.) не может длиться более одного-двух дней, но не хотелось возиться с дробями.

    Критическими путями являются пути 1-6-2-3-4-5-9-13-14-15 и 1-6-2-3-4-5-12-13-14-15, т.е. вся работа не может быть выполнена быстрее, чем за двадцать одну неделю. Понятно, что с точки зрения оптимальной загрузки коллектива было бы лучше, чтобы все пути в графе от начала к концу имели примерно одинаковую длительность с тем чтобы как-то уменьшить длину критического пути. Например, есть соблазн заставить группу QA проводить даже начальное тестирование, уменьшив нагрузку на программистов, работа которых находится на критическом пути. Но тогда очень трудно определить границы ответственности, программисты начинают выдавать откровенную халтуру и в результате сроки даже удлиняются. В реальных проектах, где работ очень много, все-таки удается путем перераспределения работ улучшать сетевой график, по крайней мере, к этому стремятся все руководители.

    Еще несколько замечаний по данному примеру. Явно неудачно спланированы работы между событиями 1, 2, 6. Коллектив сформирован за одну неделю, а рабочие места еще не готовы. Группа технических писателей начинает работать на шесть недель позже проектировщиков, а группа QA имеет трехнедельный перерыв перед завершением проектирования и т.д.

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

    Еще одной популярной формой графического представления плана работ является диаграмма Ганта (Gantt). Диаграмма Ганта представляет собой прямоугольник: слева направо равномерно отсчитываются периоды времени (недели, месяцы), сверху вниз перечисляются работы, причем каждая работа представляется в виде отрезка, начало и конец которого размещаются в соответствующем периоде.

    Для сравнения с сетевым графиком нарисуем диаграмму Ганта для того же примера:

    Если в сетевом графике особенно наглядно видны зависимости работ друг от друга, (например, работа 12-13 может начаться только после завершения работ 5-12 и 11-12), то в диаграмме Ганта основной упор делается на то, что происходит в каждую конкретную неделю, например, видно, что группа QA имеет перерыв в работе в 2 недели между работами 11-12 (автономное тестирование) и 12-13 (комплексное тестирование). Именно поэтому "большие начальники" предпочитают диаграммы Ганта: в конце каждой недели проводишь вертикальную линию и сразу видишь, какие работы велись, что должно кончиться, а что начаться. Должен признаться, что я и сам нашел пару ошибок в расчетах по сетевому графику, пока строил диаграмму Ганта для этого примера. Тем самым я еще раз убедился, что диаграмма Ганта нагляднее сетевого графика, или в том, что я уже большой начальник, а не технический специалист. Технические менеджеры предпочитают сетевые графики, так как им важнее информация о том, что от чего зависит, да и пересчитывать критические пути им приходится практически каждую неделю.

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

    Управление

    На самом деле управление[8] почти неотделимо от планирования. Вы спланировали работу, приступили к ее исполнению. Каждую неделю Вы собираете еженедельные отчеты от руководителей групп и оцениваете состояние каждой работы по сравнению с сетевым графиком. Если все в порядке – можно плевать в потолок и ждать конца следующей недели. К сожалению, так бывает очень редко. Допустим, какая-то работа затянулась. Если она не на критическом пути – ничего страшного. Нужно провести воспитательную работу, дать дополнительный срок (причем надо следить, чтобы от этого увеличения не возник новый критический путь!), продумать какие-то меры, чтобы не допустить повторения подобных ситуаций. Если же проваленная работа лежит на критическом пути, необходимо перепланирование, причем основной проблемой является невозможность (чаще всего) сдвига сроков завершения. Мы только что (при планировании) занимались оптимизацией сетевого графика, прошло всего несколько недель — и вот мы снова занимаемся тем же самым.

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

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

    Графически проблему управления можно представить в виде треугольника:

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

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

    Неплохо иметь "пожарную команду" из двух-трех высококлассных специалистов, которые в промежутках между "пожарами" занимаются исследованиями или развитием инструментальных средств.

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

    Жертвенность на работе – это, в первую очередь, признак непрофессионализма.

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

    Многие годы для нас таким авторитетом является Ф. Брукс, который руководил отделением программирования IBM (несколько тысяч человек) в шестидесятые годы, как раз тогда, когда создавалась знаменитая серия IBM/360. Покинув IBМ, Ф. Брукс стал профессором университета Чапел-Хилл в Северной Каролине и написал ставшую бестселлером книгу "Как проектируются и создаются программные комплексы. Мифический человеко-месяц. Очерки по системному программированию". Эта книга в 1979 году была переведена на русский язык и издана в издательстве "Мир". За несколько лет до этого по СССР ходил "левый" перевод этой же книги, выполненный (и, кстати, гораздо лучше, чем официальный) академиком А.П. Ершовым. Через 20 лет вышло юбилейное переиздание книги[5], дополненное поздними статьями Ф. Брукса и подробным анализом, какие идеи, высказанные 20 лет назад, сохранили свою силу, а какие оказались ошибочными. Кроме несомненного дара системного программиста и огромного опыта руководства большими коллективами Ф. Брукс обладает ярким литературным талантом – многие фразы из книги стали общеизвестными лозунгами программистов. В декабре 2000 года мне удалось побеседовать с ним прямо на его рабочем месте, и личная встреча только усилила мои впечатления об этом замечательном ученом.

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

    Основная идея состоит в том, что при правильной организации, с хорошо подготовленным коллективом можно уменьшить вероятность срывов, предугадать их на возможно более ранних сроках, но если это все же случилось – только личный опыт и интуиция руководителя помогут выбраться из этой ситуации с наименьшими потерями. Одна из более поздних статей Ф. Брукса "No silver bullet" как раз и посвящена обоснованию того факта, что нет волшебного простого средства, с помощью которого можно легко решить проблемы разработки ПО и, в частности, проблемы управления.

    Но ведь люди работают, сроки горят, значит, какие-то выходы из этой ситуации есть. Я спросил Лена Эрлиха, нашего американского заказчика, как поступают американские менеджеры в ситуации, когда заваливается критически важная работа. Ответ был прост, как выстрел. Подключаются 1-2 мощных специалиста, проводятся совещания по ходу работ – каждый день, а если мало, то дважды в день, обязательно в присутствии высшего руководства. Те функции, ошибки в которых исправить трудно, безжалостно выкидываются. Главное, чтобы в срок заработала основная масса функций – тогда есть шансы договориться с заказчиком о дополнительном времени для завершения работы, если же к нужному времени показать нечего, скандал неминуем.

    В общем-то, ситуация классическая: ученые говорят, что "добавлять людей в горящий коллектив — это все равно что заливать пожар керосином", а практики добавляют, ужесточают контроль и часто побеждают. "Ученый думает, что знает, но у него не совпадает, у практика все совпадает, но почему – никто не знает".

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

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

    Тестирование, обеспечение качества

    Для начала вспомним три аксиомы одного из самых первых советских программистов с несколько необычной фамилией Шура-Бура.

    Аксиома 1. В каждой программе есть ошибка.

    Аксиома 2. Если в программе нет ошибок, значит, в исходном алгоритме есть ошибка.

    Аксиома 3. Если ни в программе, ни в алгоритме ошибок нет, то такая программа никому не нужна.

    При всей шутливости этих аксиом в них отражена суровая правда жизни.

    Исходные идеи тестирования абсолютно понятны [9].

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

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

    Бесконечность тестов определяется наличием циклов, рекурсии, разнообразием значений данных. Ситуация обычна для нашей специальности. Теоретическая невозможность не снимает с нас ответственности за поиск практических способов проверки – пусть не полных, но дающих какую-то степень уверенности. Итак, что же можно сделать?

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

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

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

    Программирование – это конструктивный созидательный процесс, который требует высокой квалификации и определенного оптимизма (cм. аксиомы Шуры-Буры).

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

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

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

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

    Должна быть конкуренция между программистами и тестировщиками, нужен дух соревнования: "Я все равно тебя поймаю" против "Врешь, не поймаешь".

    Необходимо уметь оценивать вероятность и примерное количество оставшихся ошибок. Например, известен метод оценки количества рыб в пруду. Ловят, скажем, 100 рыбок, помечают их и выпускают обратно в пруд. Затем снова ловят 100 рыбок. Если почти все они мечены, значит, в пруду примерно 100 рыбок и есть, если же меченых попалось мало, то рыб, скорее всего, больше, причем в той же пропорции, какова доля непомеченных из 100 вновь пойманных. Во многих фирмах применяют тот же прием: специальные люди вставляют некоторое количество разнообразных ошибок в программы и возвращают их на доработку авторам с требованием обнаружить ровно столько же ошибок. По тому, какая часть найденных ошибок относится к специально вставленным, а какая – к вновь найденным ошибкам, можно судить о числе оставшихся ошибок. Еще проще – поручить тестирование одной и той же программы двум группам тестировщиков и оценить процент ошибок, найденных обеими группами.

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

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

    Различают тестирование по типам:

  • черный ящик (без просмотра исходного текста);
  • белый ящик (с изучением исходного текста);
  • и по объему:

  • маленький тест, типа печати "hello, world", чтобы понять, есть ли вообще о чем говорить;
  • получаcовое тестирование (американцы говорят "Тест на одну сигарету"), обычно проверяют по одному тесту на каждую функцию программы перед серьезным тестированием;
  • модульное тестирование;
  • комплексное тестирование.
  • Особого упоминания заслуживают тестирование граничных значений входных данных, тесты на максимальный объем счета, проверка предположений об ошибках ("Я бы сделал ошибку здесь").

    Иногда применяется перекрестное чтение особо ответственных участков программы, когда одна группа разработчиков читает программы другой группы (insрection peer review). В США очень популярны Bugs festivals, когда незадолго перед выпуском системы фирма платит определенную сумму за каждую найденную ошибку. Список таких приемов можно расширить, но вряд ли их можно считать общеупотребительными.

    В зрелых программистских компаниях для каждого проекта ведется своя база данных ошибок, в которую вносится:

  • кто нашел ошибку, дата;
  • описание ошибки;
  • модуль, в котором ошибка обнаружилась (возможно, это наведенная ошибка, может быть, она вызвана ошибкой в совсем другом модуле);
  • версия продукта;
  • статус ошибки:
  • open: найдена
  • fixed: исправлена
  • can't reproduce: невозможно воспроизвести
  • by design: ошибка проектировщиков
  • wont fix: это не ошибка (тестеру показалось)
  • postponed: сейчас исправить трудно, исправим в следующей версии
  • regression: исправленная ошибка появилась вновь
  • Важность (severity) ошибки:
  • сrash: все падает, полная потеря данных
  • major problem: падает частично, частичная потеря данных
  • minor problem: что-то не то, но данные не теряются
  • trivial: сейчас не стоит исправлять
  • Приоритет ошибки:
  • highest: невозможно поставить продукт с такой ошибкой, не можем перейти к следующей версии
  • high: поставить не можем, но можем перейти к следующей версии
  • medium: можем и исправим
  • low: косметические улучшения – оставим на следующую версию.
  • Особенно заметна ценность базы данных ошибок для больших и длительных проектов. Если идентификатор одной ошибки встречается десятки раз в различных письмах, недельных отчетах – это верный признак того, что требуется вмешательство руководства. Все руководители разных рангов знают на память записи из этой базы данных о текущих ошибках с высшей важностью и приоритетом. По завершении каждого проекта база данных ошибок внимательно анализируется. Интересно распределение ошибок по тому, кто их совершил, по времени исправления, кто чаще ошибки находит и т.д. Если в одну неделю разработчик 1 сделал 10 ошибок, а разработчик 2 – только 2, это еще ни о чем не говорит. Но если же за весь период разработки разработчик 1 сделал 100 ошибок, а разработчик 2 – только 10, то по этим цифрам можно уже судить об их квалификации. Можно также оценивать целые группы или, скажем, качество проектирования, а можно сделать какие-то выводы по мощности и надежности используемых инструментальных средств.

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

    Мы говорили о тестировании, только отдавая дань многолетней традиции. На самом деле, любая зрелая программистская компания имеет большую независимую группу оценки качества ПО (Quality Assurance или просто QA), функции которой намного шире, чем просто тестирование. Для каждого продукта проверяется:

  • полнота и корректность документации;
  • корректность процедур установки и запуска;
  • эргономичность использования;
  • полнота тестирования.
  • QA должна играть роль придирчивого пользователя, но внутри компании. В США разработчики и QA сидят в разных концах коридора, не поощряется даже неформальное общение. В западных компаниях довольно существенную часть зарплаты (примерно 20-30%) составляет бонус, выплачиваемый в конце проекта и только в случае его успешного завершения. Так вот, если QA найдет слишком много ошибок разработчиков, те остаются без бонуса, но если QA принял разработку, а затем пользователи начнут жаловаться, то QA остается без бонуса, хотя жалобы связаны с ошибками разработчиков. Таким искусственным разделением ответственности и непоощрением дружеских отношений между разработчиками и QA владельцы компаний пытаются застраховаться от неудачи на рынке. В США говорят, что каждому продукту дается только одна попытка выхода на рынок. Если пользователям по разным причинам продукт не понравился, репутация теряется навсегда. Поэтому ошибки так дорого стоят.

    Страницы:

    Планирование

    Результатом фазы оценки осуществимости являются детальная спецификация, план работы и оценка стоимости. Наиболее традиционной формой плана можно считать сетевой график, который представляется в виде ориентированного графа с двумя выделенными вершинами – начало и конец работы. Вершинами графа являются события, соответствующие пунктам плана, а ребрами – работы. События должны выражаться глаголами совершенного вида: "тексты программ переданы в базу данных исходников", "все тесты пропущены", "группа оценки качества дала положительное заключение" и т.д., но уж никак не "выполняется прогон тестов". Ребра нагружаются оценками длительности работ, например, в днях или неделях.

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

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

    Рассмотрим пример.

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

  • начало работы;
  • коллектив сформирован, рабочие места подготовлены;
  • проектирование завершено;
  • программирование завершено;
  • комплексная отладка завершена;
  • оборудование закуплено;
  • группа технических писателей получила описание проекта и необходимые пояснения от проектировщиков;
  • то же для ПО, разработка проектной документации завершена;
  • группа технических писателей получила всю необходимую информацию об интерфейсах с пользователем, разработка программной документации завершена;
  • группа оценки качества (Quality Assurance – QA) разработала тесты;
  • группа QA оценила проект положительно;
  • группа QA завершила автономное тестирование;
  • группа QA завершила комплексное тестирование, получила всю документацию и действующий вариант системы;
  • проверка качества (проблемам качества будет посвящена отдельная лекция) завершена;
  • конец работы (конечно, это не конец, будет еще сопровождение, но пример-то надо закончить).
  • Под каждым ребром графа записана планируемая длительность соответствующей работы (в неделях). Еще раз повторю, что это только пример, прошу не придираться к техническим деталям, в частности, разумеется, каждая передача (3-7, 3-10 и т.д.) не может длиться более одного-двух дней, но не хотелось возиться с дробями.

    Критическими путями являются пути 1-6-2-3-4-5-9-13-14-15 и 1-6-2-3-4-5-12-13-14-15, т.е. вся работа не может быть выполнена быстрее, чем за двадцать одну неделю. Понятно, что с точки зрения оптимальной загрузки коллектива было бы лучше, чтобы все пути в графе от начала к концу имели примерно одинаковую длительность с тем чтобы как-то уменьшить длину критического пути. Например, есть соблазн заставить группу QA проводить даже начальное тестирование, уменьшив нагрузку на программистов, работа которых находится на критическом пути. Но тогда очень трудно определить границы ответственности, программисты начинают выдавать откровенную халтуру и в результате сроки даже удлиняются. В реальных проектах, где работ очень много, все-таки удается путем перераспределения работ улучшать сетевой график, по крайней мере, к этому стремятся все руководители.

    Еще несколько замечаний по данному примеру. Явно неудачно спланированы работы между событиями 1, 2, 6. Коллектив сформирован за одну неделю, а рабочие места еще не готовы. Группа технических писателей начинает работать на шесть недель позже проектировщиков, а группа QA имеет трехнедельный перерыв перед завершением проектирования и т.д.

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

    Еще одной популярной формой графического представления плана работ является диаграмма Ганта (Gantt). Диаграмма Ганта представляет собой прямоугольник: слева направо равномерно отсчитываются периоды времени (недели, месяцы), сверху вниз перечисляются работы, причем каждая работа представляется в виде отрезка, начало и конец которого размещаются в соответствующем периоде.

    Для сравнения с сетевым графиком нарисуем диаграмму Ганта для того же примера:

    Если в сетевом графике особенно наглядно видны зависимости работ друг от друга, (например, работа 12-13 может начаться только после завершения работ 5-12 и 11-12), то в диаграмме Ганта основной упор делается на то, что происходит в каждую конкретную неделю, например, видно, что группа QA имеет перерыв в работе в 2 недели между работами 11-12 (автономное тестирование) и 12-13 (комплексное тестирование). Именно поэтому "большие начальники" предпочитают диаграммы Ганта: в конце каждой недели проводишь вертикальную линию и сразу видишь, какие работы велись, что должно кончиться, а что начаться. Должен признаться, что я и сам нашел пару ошибок в расчетах по сетевому графику, пока строил диаграмму Ганта для этого примера. Тем самым я еще раз убедился, что диаграмма Ганта нагляднее сетевого графика, или в том, что я уже большой начальник, а не технический специалист. Технические менеджеры предпочитают сетевые графики, так как им важнее информация о том, что от чего зависит, да и пересчитывать критические пути им приходится практически каждую неделю.

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

    Управление

    На самом деле управление[8] почти неотделимо от планирования. Вы спланировали работу, приступили к ее исполнению. Каждую неделю Вы собираете еженедельные отчеты от руководителей групп и оцениваете состояние каждой работы по сравнению с сетевым графиком. Если все в порядке – можно плевать в потолок и ждать конца следующей недели. К сожалению, так бывает очень редко. Допустим, какая-то работа затянулась. Если она не на критическом пути – ничего страшного. Нужно провести воспитательную работу, дать дополнительный срок (причем надо следить, чтобы от этого увеличения не возник новый критический путь!), продумать какие-то меры, чтобы не допустить повторения подобных ситуаций. Если же проваленная работа лежит на критическом пути, необходимо перепланирование, причем основной проблемой является невозможность (чаще всего) сдвига сроков завершения. Мы только что (при планировании) занимались оптимизацией сетевого графика, прошло всего несколько недель — и вот мы снова занимаемся тем же самым.

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

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

    Графически проблему управления можно представить в виде треугольника:

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

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

    Неплохо иметь "пожарную команду" из двух-трех высококлассных специалистов, которые в промежутках между "пожарами" занимаются исследованиями или развитием инструментальных средств.

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

    Жертвенность на работе – это, в первую очередь, признак непрофессионализма.

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

    Многие годы для нас таким авторитетом является Ф. Брукс, который руководил отделением программирования IBM (несколько тысяч человек) в шестидесятые годы, как раз тогда, когда создавалась знаменитая серия IBM/360. Покинув IBМ, Ф. Брукс стал профессором университета Чапел-Хилл в Северной Каролине и написал ставшую бестселлером книгу "Как проектируются и создаются программные комплексы. Мифический человеко-месяц. Очерки по системному программированию". Эта книга в 1979 году была переведена на русский язык и издана в издательстве "Мир". За несколько лет до этого по СССР ходил "левый" перевод этой же книги, выполненный (и, кстати, гораздо лучше, чем официальный) академиком А.П. Ершовым. Через 20 лет вышло юбилейное переиздание книги[5], дополненное поздними статьями Ф. Брукса и подробным анализом, какие идеи, высказанные 20 лет назад, сохранили свою силу, а какие оказались ошибочными. Кроме несомненного дара системного программиста и огромного опыта руководства большими коллективами Ф. Брукс обладает ярким литературным талантом – многие фразы из книги стали общеизвестными лозунгами программистов. В декабре 2000 года мне удалось побеседовать с ним прямо на его рабочем месте, и личная встреча только усилила мои впечатления об этом замечательном ученом.

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

    Основная идея состоит в том, что при правильной организации, с хорошо подготовленным коллективом можно уменьшить вероятность срывов, предугадать их на возможно более ранних сроках, но если это все же случилось – только личный опыт и интуиция руководителя помогут выбраться из этой ситуации с наименьшими потерями. Одна из более поздних статей Ф. Брукса "No silver bullet" как раз и посвящена обоснованию того факта, что нет волшебного простого средства, с помощью которого можно легко решить проблемы разработки ПО и, в частности, проблемы управления.

    Но ведь люди работают, сроки горят, значит, какие-то выходы из этой ситуации есть. Я спросил Лена Эрлиха, нашего американского заказчика, как поступают американские менеджеры в ситуации, когда заваливается критически важная работа. Ответ был прост, как выстрел. Подключаются 1-2 мощных специалиста, проводятся совещания по ходу работ – каждый день, а если мало, то дважды в день, обязательно в присутствии высшего руководства. Те функции, ошибки в которых исправить трудно, безжалостно выкидываются. Главное, чтобы в срок заработала основная масса функций – тогда есть шансы договориться с заказчиком о дополнительном времени для завершения работы, если же к нужному времени показать нечего, скандал неминуем.

    В общем-то, ситуация классическая: ученые говорят, что "добавлять людей в горящий коллектив — это все равно что заливать пожар керосином", а практики добавляют, ужесточают контроль и часто побеждают. "Ученый думает, что знает, но у него не совпадает, у практика все совпадает, но почему – никто не знает".

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

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

    Тестирование, обеспечение качества

    Для начала вспомним три аксиомы одного из самых первых советских программистов с несколько необычной фамилией Шура-Бура.

    Аксиома 1. В каждой программе есть ошибка.

    Аксиома 2. Если в программе нет ошибок, значит, в исходном алгоритме есть ошибка.

    Аксиома 3. Если ни в программе, ни в алгоритме ошибок нет, то такая программа никому не нужна.

    При всей шутливости этих аксиом в них отражена суровая правда жизни.

    Исходные идеи тестирования абсолютно понятны [9].

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

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

    Бесконечность тестов определяется наличием циклов, рекурсии, разнообразием значений данных. Ситуация обычна для нашей специальности. Теоретическая невозможность не снимает с нас ответственности за поиск практических способов проверки – пусть не полных, но дающих какую-то степень уверенности. Итак, что же можно сделать?

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

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

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

    Программирование – это конструктивный созидательный процесс, который требует высокой квалификации и определенного оптимизма (cм. аксиомы Шуры-Буры).

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

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

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

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

    Должна быть конкуренция между программистами и тестировщиками, нужен дух соревнования: "Я все равно тебя поймаю" против "Врешь, не поймаешь".

    Необходимо уметь оценивать вероятность и примерное количество оставшихся ошибок. Например, известен метод оценки количества рыб в пруду. Ловят, скажем, 100 рыбок, помечают их и выпускают обратно в пруд. Затем снова ловят 100 рыбок. Если почти все они мечены, значит, в пруду примерно 100 рыбок и есть, если же меченых попалось мало, то рыб, скорее всего, больше, причем в той же пропорции, какова доля непомеченных из 100 вновь пойманных. Во многих фирмах применяют тот же прием: специальные люди вставляют некоторое количество разнообразных ошибок в программы и возвращают их на доработку авторам с требованием обнаружить ровно столько же ошибок. По тому, какая часть найденных ошибок относится к специально вставленным, а какая – к вновь найденным ошибкам, можно судить о числе оставшихся ошибок. Еще проще – поручить тестирование одной и той же программы двум группам тестировщиков и оценить процент ошибок, найденных обеими группами.

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

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

    Различают тестирование по типам:

  • черный ящик (без просмотра исходного текста);
  • белый ящик (с изучением исходного текста);
  • и по объему:

  • маленький тест, типа печати "hello, world", чтобы понять, есть ли вообще о чем говорить;
  • получаcовое тестирование (американцы говорят "Тест на одну сигарету"), обычно проверяют по одному тесту на каждую функцию программы перед серьезным тестированием;
  • модульное тестирование;
  • комплексное тестирование.
  • Особого упоминания заслуживают тестирование граничных значений входных данных, тесты на максимальный объем счета, проверка предположений об ошибках ("Я бы сделал ошибку здесь").

    Иногда применяется перекрестное чтение особо ответственных участков программы, когда одна группа разработчиков читает программы другой группы (insрection peer review). В США очень популярны Bugs festivals, когда незадолго перед выпуском системы фирма платит определенную сумму за каждую найденную ошибку. Список таких приемов можно расширить, но вряд ли их можно считать общеупотребительными.

    В зрелых программистских компаниях для каждого проекта ведется своя база данных ошибок, в которую вносится:

  • кто нашел ошибку, дата;
  • описание ошибки;
  • модуль, в котором ошибка обнаружилась (возможно, это наведенная ошибка, может быть, она вызвана ошибкой в совсем другом модуле);
  • версия продукта;
  • статус ошибки:
  • open: найдена
  • fixed: исправлена
  • can't reproduce: невозможно воспроизвести
  • by design: ошибка проектировщиков
  • wont fix: это не ошибка (тестеру показалось)
  • postponed: сейчас исправить трудно, исправим в следующей версии
  • regression: исправленная ошибка появилась вновь
  • Важность (severity) ошибки:
  • сrash: все падает, полная потеря данных
  • major problem: падает частично, частичная потеря данных
  • minor problem: что-то не то, но данные не теряются
  • trivial: сейчас не стоит исправлять
  • Приоритет ошибки:
  • highest: невозможно поставить продукт с такой ошибкой, не можем перейти к следующей версии
  • high: поставить не можем, но можем перейти к следующей версии
  • medium: можем и исправим
  • low: косметические улучшения – оставим на следующую версию.
  • Особенно заметна ценность базы данных ошибок для больших и длительных проектов. Если идентификатор одной ошибки встречается десятки раз в различных письмах, недельных отчетах – это верный признак того, что требуется вмешательство руководства. Все руководители разных рангов знают на память записи из этой базы данных о текущих ошибках с высшей важностью и приоритетом. По завершении каждого проекта база данных ошибок внимательно анализируется. Интересно распределение ошибок по тому, кто их совершил, по времени исправления, кто чаще ошибки находит и т.д. Если в одну неделю разработчик 1 сделал 10 ошибок, а разработчик 2 – только 2, это еще ни о чем не говорит. Но если же за весь период разработки разработчик 1 сделал 100 ошибок, а разработчик 2 – только 10, то по этим цифрам можно уже судить об их квалификации. Можно также оценивать целые группы или, скажем, качество проектирования, а можно сделать какие-то выводы по мощности и надежности используемых инструментальных средств.

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

    Мы говорили о тестировании, только отдавая дань многолетней традиции. На самом деле, любая зрелая программистская компания имеет большую независимую группу оценки качества ПО (Quality Assurance или просто QA), функции которой намного шире, чем просто тестирование. Для каждого продукта проверяется:

  • полнота и корректность документации;
  • корректность процедур установки и запуска;
  • эргономичность использования;
  • полнота тестирования.
  • QA должна играть роль придирчивого пользователя, но внутри компании. В США разработчики и QA сидят в разных концах коридора, не поощряется даже неформальное общение. В западных компаниях довольно существенную часть зарплаты (примерно 20-30%) составляет бонус, выплачиваемый в конце проекта и только в случае его успешного завершения. Так вот, если QA найдет слишком много ошибок разработчиков, те остаются без бонуса, но если QA принял разработку, а затем пользователи начнут жаловаться, то QA остается без бонуса, хотя жалобы связаны с ошибками разработчиков. Таким искусственным разделением ответственности и непоощрением дружеских отношений между разработчиками и QA владельцы компаний пытаются застраховаться от неудачи на рынке. В США говорят, что каждому продукту дается только одна попытка выхода на рынок. Если пользователям по разным причинам продукт не понравился, репутация теряется навсегда. Поэтому ошибки так дорого стоят.

    Вернуться к учебному плану