УДК 004.053 DOI: 10.25513/2222-8772.2017.3.96-107
О ФОРМАЛИЗАЦИИ ПРОЦЕССА РАЗРАБОТКИ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
Е.А. Тюменцев
генеральный директор, e-mail: [email protected]
LLC Hello World! Technologies
Аннотация. В книге «Мифический человеко-месяц, или Как создаются программные системы» Брукс ссылается на несколько исследований, на основании которых можно сделать вывод, что производительность труда программистов, измеряемая в количестве строк кода в единицу времени, падает по мере роста размера программного проекта. В настоящей работе описывается формализация процесса разработки программного обеспечения, как процесса редактирования исходного текста программы, с помощью которой определяется функция трудоёмкости, а также выводится достаточное условие постоянной производительности труда программистов, не зависящей от размеров проекта.
Ключевые слова: формализация, процесс разработки, программное обеспечение, производительность труда, Брукс, мифический человеко-месяц.
Введение
В статье 2000 года [1], посвященной двадцатипятилетию с момента выхода первого издания книги Фредерика Брукса «Мифический человеко-месяц, или Как создаются программные системы», были отмечены две важные особенности индустрии разработки программного обеспечения:
«невиданные в истории человечества темпы смены технологий», «лавинообразный рост численности персонала, занятого в отрасли». Эти две особенности характерны для отрасли и на сегодняшний день.
Брукс в [2] ссылается на исследование [3], выполненное Наусом и Фарром в компании System Development Corporation, результаты которого вполне могут объяснить причины возникновения и даже усиления этих особенностей. Наус и Фарр измерили объём работ как функцию от количества машинных команд на 11 крупных программных проектах. Оказалось, что зависимость имеет вид степенной функции с показателем 1,5. Для наглядности, продемонстрируем результаты этого исследования в виде графика на рисунке 1.
На рисунке 1 ось ординат — объём работ, измеряемый в человеко-месяцах, ось абсцисс — количество машинных команд в тысячах, составляющих программный проект. Сплошная линия — график зависимости объёма работ от количества машинных команд, составляющих программный проект, полученный
Рис. 1. График зависимости объёма работ от количества машинных команд
Наусом и Фарром, — является экстраполяцией эмпирических данных. Пунктирная линия показывает, как бы выглядел график зависимости объёма работ, если бы производительность труда не зависела об общего размера проекта.
Как видно из графика, производительность труда программистов может снижаться по мере роста размера программы. Опираясь на данное предположение, можно, например, объяснить следующие факты из области разработки программного обеспечения:
частая смена технологий, выпуск новых версий, переписанных заново — начинать с нуля дешевле и быстрее, чем добавлять новый функционал в существующий продукт;
дефицит кадров — чтобы сохранять один и тот же темп выпуска нового функционала, по мере роста размера проекта, требуется всё больше сотрудников;
разбиение проекта на итерации — чем меньше сроки планирования, тем меньше разница между планируемой (постоянной) и фактической производительностью, тем точнее оценка сроков разработки; • практика измерения velocity, применяемая в Agile-проектах, — это кусочно-линейная аппроксимация степенной функции (сплошная линия на рисунке 1). Velocity — это коэффициент, показывающий разницу между планируемым и фактическим объёмом работ. Используется для улучшения точности оценки предстоящего объёма работ: оценка, сделанная проектной командой, умножается на текущий velocity. Как правило,
velocity пересчитывается в конце каждой итерации;
за программистами закрепилась устойчивая репутация, что они срывают сроки, в том числе и потому, что прогнозы сроков даются исходя из предположения по постоянной производительности труда (пунктирная линия на рисунке 1).
Измерения, сделанные Наусом и Фарром [3] более чем 50 лет назад, были проведены только для 11 программных проектов, которые, по сегодняшним меркам, нельзя назвать крупными, поэтому нельзя утверждать, что выявленная эмпирическая закономерность справедлива хотя бы для некоторой части современных программных проектов.
Соответственно, возникают вопросы:
Существует ли эффект падения производительности труда программиста с ростом размера проекта при использовании современных технологий?
• Если да, то можно ли его формально обосновать?
Можно ли определить условия, при которых производительность труда не будет падать?
В настоящей статье предлагается формализация процесса разработки программного кода как процесса редактирования текста программы и выводится достаточное условие поддержания постоянной производительности труда программиста на всём протяжении проекта.
1. Идея
Прежде чем переходить к строгим математическим рассуждениям, опишем неформально идею.
Программа — это набор слов (который также является словом), записанных на формальном языке. Каждое слово — это последовательность лексем — символов алфавита формального языка. Когда программист вносит изменения в текст программы, то он либо добавляет новое слово, либо удаляет или редактирует существующее, добавляя или удаляя лексему.
Следовательно, можно выделить набор атомарных операций, которые модифицируют текст программы неделимым образом. Определим трудоёмкость разработки каждого слова программы как количество атомарных операций, которые были сделаны для записи этого слова. Трудоёмкость написания текста программы — это суммарная трудоёмкость написания всех его слов, в том числе и тех, которые были удалены в процессе редактирования.
Оценим количество слов программы в зависимости от количества выполненных атомарных операций. Это и будет производительность труда.
2. Атомарные операции
Пусть L — формальный язык над некоторым алфавитом £. Множество всех слов над алфавитом £ будем обозначать как £*, а сами слова символами греческого алфавита а,в,7, длину слова a - как |a|. Напомним, что £* является
полугруппой, то есть
Va,e е Е* : ав е Е*. Пустое слово в Е* будем обозначать как е.
Определение 1 (Удаление символа). Будем говорить, что слово а' получено удалением символа а е Е из слова а = в«7, где в, Y е Е*, причём одно из слов в или y может быть пустым тогда и только тогда, когда а' можно записать в виде а' = eY.
Определение 2 (Добавление символа). Будем говорить, что слово а' получено добавлением символа а е Е в слово а = eY, где в, Y е Е*, причём одно из слов в или y может быть пустым тогда и только тогда, когда а' можно записать в виде а' = ваY.
Пример 1. Рассмотрим алфавит языка С++. Слово
void f() {
int
}
получено добавлением символа int из слова
void f() {
}
И наоборот, второе слово может быть получено из первого удалением символа int.
Мы хотим ввести формальное определение процесса разработки над языком L. В рамках этого процесса могут появляться одинаковые слова из Е*. Чтобы их различать между собой, будем рассматривать не сами слова, а пары (а,п), где а е Е*, n е N, и обозначать такие пары а, а само слово а называть словом пары а, если будет необходимо.
Здесь и далее по тексту полагаем, что множество натуральных чисел N содержит 0. Пусть
S = {äi,ä2,... ,ä„},
— некоторое множество пар (а,, n,), где а, е Е*, n е N, i е N, i е (1,2,..., n), при этом все n попарно различны между собой.
Определим на множестве пар S четыре типа операций:
1. Говорят, что множество пар S' получено добавлением в S пары (а,к), где а е Е*, |а| = 1, k е N, если и только если
S' = S U {(а, k)}, (а, k) е S,
при этом ^1(в,£) е S : k = t.
2. Говорят, что множество пар Б' получено из Б удалением пары (а, к), где а е £*, к е N если и только если
Б' = Б \{(а,к)}, (а, к) е Б.
3. Говорят, что множество пар Б' получено из Б заменой пары (а, к), где а е £*, к е N на пару (а',к), где слово а' было получено из слова а добавлением символа а е £ в смысле определения 3, если и только если
Б' = Б \ {(а, к)} и {(а', к)}, (а, к) е Б.
4. Говорят, что множество пар Б' получено из Б заменой пары (а, к), где а е £*, к е N на пару (а',к), где слово а' было получено из слова а удалением символа а е £ в смысле определения 2, если и только если
Б' = Б \ {(а, к)} и {(а', к)}, (а, к) е Б.
Замечание 1. У а е £* существует такая последовательность операций, результатом применения которых является пара (а,п) для некоторого п е N.
Доказательство. Пусть а е £*. Предположим, что а = а\а2 ...ап, где п > 0. Тогда возьмём следующий набор операций. С помощью операции типа 1 добавим пару (а\,п). Затем для каждого символа аг,1 > 1 с помощью операции типа 3 заменим пару (а\а2... аг-\, п) на пару (а\а2... аг-хаг, п). ■
Замечание 2 (Корректность набора операций). Пусть Б — произвольное множество пар. Тогда существует последовательность операций, результатом применения которых является данное множество пар.
Доказательство. Выполняется индукцией по количеству пар в множестве Б и применением предыдущего замечания к каждой паре. ■
3. Процесс разработки
Пусть Ь — подмножество слов языка программирования Ь над некоторым алфавитом £. Подмножество Ь символизирует те ограничения, которые накладывают программисты на используемый язык в связи с выбранной методологией программирования, например, процедурной, объектно-ориентированной и т. д. или стандартами кодирования, принятыми в рамках процесса разработки, например, запрет на использование глобальных переменных.
Определение 3. Пусть
Б = {аг|г е (1, 2,... ,п)},
где п е N аг е £* — некоторое множество пар. Б называется программой на языке Ь тогда и только тогда, когда Уаг : аг е Ь.
Определение 4. Последовательность множеств пар:
Ро ^ р1 р2
где Р0 = 0, Р^, где г > 0 — множество пар, Р^ получено из Р^ь с помощью операции с одного из перечисленных выше типов, при этом для всех операций типа 1 все добавленные пары имеют вид (а, к), где а € Е*, к — номер шага, на котором операция была выполнена, называется процессом разработки Рг.
Определение 5. Говорят, что программа Р получена с помощью процесса разработки Рг, если Зг > 0 : Р^ = Р.
Все дальнейшие рассуждения будем проводить в предположении, что имеется некоторый процесс разработки Рг:
Р0 Р1 Р2 ....
Замечание 3. В силу определения процесса разработки для любого к € N либо не существует пары (а, к), которая была создана в процессе разработки, либо существует и при том только одна.
Определим для удобства последовательность множеств удалённых пар как:
1. D0 = 0,
2. Di = и {а}, если а было удалено на шаге г из множества Р^-1 с помощью операции типа 2, и Di = Di-1 в противном случае.
Предложение 1. Уп € N : Рп П Dn = 0.
Доказательство проводится по индукции в силу построения множества пар Рп.
4. Скорость процесса разработки
Далее определим на множестве Е*xN — декартовом произведении множеств Е* и N — последовательность функций
Fi : (Е* х N ^ N,2 € 1, 2,..., п :
1. Ро(£* х N) = 0.
2. Предположим, что все функции , ] ^ г определены. Пусть а € Е* х N — некоторая пара. Тогда
• ^¿(а) = 1, если пара а = (а, г) была добавлена в Pi с помощью операции типа 1 на шаге г.
• ^(а) = (а') + 1, если пара а = (а, к) была получена из а' = (а', к) с помощью операции типа 3 или типа 4 на шаге г.
• Fi(a) = Fi-1(a) + 1, если пара а = (a,k) была удалена на шаге i из набора Pi-i с помощью операции типа 2.
• Fi(a) = Fi-i(а), в противном случае.
Предложение 2.
Уа е Е* х N Vk,m е N : k>m ^ Fk (а) ^ Fm (а). Следует из построения набора функций Fi : Е* х N ^ N, i е N.
Предложение 3. Пусть а е Е* х N. Тогда последовательность {Fi(a),i е N} либо сходится к некоторому С е N либо УС е N 3k > 0 : Fk (а) > С.
Следует из определения функций Fi, i е N и предложения 2.
Определение 6. Пусть а е Е* х N. Тогда асимптотической трудоёмкостью написания слова а в процессе разработки Pr называется lim Fi(a).
i—у^о
Определение 7. Определим FPr : Е* х N ^ N — асимптотическую трудоёмкость процесса разработки Pr как FPr(а) = lim Fi(a).
i—те
Определение 8. Пусть F : N ^ R — некоторая функция из множества N в R. Говорят, что процесс разработки Pr обладает скоростью F тогда и только тогда, когда
|Pn|
lim = C, га—те F (n)
где C > 0.
5. Достаточное условие линейной скорости процесса разработки
Пусть задан некоторый процесс разработки Pr
P0 P1 P2 ... В терминах предыдущего параграфа Предложение 4.
Уп е N |Pra| + IDnl ^ n. Доказательство утверждения выполняется индукцией по номеру п. Предложение 5.
Уп е N Y^ Fn(a) = п.
«ePnUDn
Доказательство утверждения выполняется индукцией по номеру п.
Предложение 6. Предположим, что ЗС > 0, С € N Уп € N Уа € Рп и Dn : Рп(а) < С. Тогда
п
С ^ |Рп и Dn| ^ п.
Доказательство. Ограничение сверху следует из предложений 1 и 4. Ограничение снизу прямо следует из условия и предложения 5. ■
Предложение 7. Предположим, что ЗС > 0, С € N Уп € N Уа € Рп и Dn : Рп(а) < С. Тогда
|Рп и Dn| ~ О(п).
Доказательство. Следует из предложения 6 и свойств предела числовой последовательности. ■
Теорема 1 (Достаточное условие линейной скорости процесса разработки). В условиях предложения 7 предположим, что lim (Dt = C, где C ^ 0. Тогда
|Рп| ~ 0(п).
Доказательство. Следует из предложений 1, 7 и свойств предела числовой последовательности. ■
Рассмотрим теперь случай, когда асимптотическая трудоёмкость разработки каждого слова ограничена некоторой константой, но при этом не существует такой константы, которая ограничивает сразу асимптотическую трудоёмкость разработки всех слов. Представим данное условие в виде двух высказываний:
Уп € N Уа € Рп и Dn ЗС> 0: Еп(а) < С. (1)
УС > 0 Зк € N Зв € Рк и Dk : ^(в) > С. (2)
Пусть С > 0 — некоторая константа. Построим множество 5 пар а следующим образом:
1. В силу предположения (2) Зк € N Зв € Рк и Dk : (в) > С. Тогда определим 50 = {в}.
2. Предположим, что для всех ] < г множества уже построены. Среди всех в : Зк € N Зв € Рк и Dk : (в) > С + г выберем, что У? < г : в € . Если такое в существует (доказательство существования см. в предложении 8 ниже по тексту), то определим 5 = и {в}.
Замечание 4.
Уг € N : |й| = г.
Замечание 5. Уг > 0 : С
Предложение 8. 5 условиях пункта 2 построения множества 5 в существует.
Доказательство. От противного. Предположим, что в не существует, тогда в силу (2), замечания 5 и сделанного предположения
V7 3k е N : (Y е Pk и Dk) Л (Fk(7) > C + г) ^ Y е Si-!,
где ^ — это импликация. Поскольку множество Si-1 — конечное, и для каждой пары a из Si-1 в силу (1) существует константа Са, что Fn(a) < Ca. Тогда мы можем определить общую константу Cg = max Ca, что Vn е N Va е Pn U Dn :
a£Si—1
Fn(a) < Cg. Получаем противоречие с (2), следовательно, в существует. ■ Определим множество S как
S = U Si.
ieN
Предложение 9. Множество S бесконечное.
Доказательство. Следует из построения множества S в силу замечания 5. ■
Теорема 2. Пусть (1) и (2) справедливы для процесса разработки. Тогда
VC> 0 3k е N : |Pk U Dk | > C.
Доказательство. Пусть C > 0 — некоторая константа, S — множество, построенное, как описано выше. В силу предложения 9 3k е N : |Sk| > C. В силу построения множества S : 3m е N : Sk С Pm U Dm. Следовательно, |Pm U Dm| >C. ■
Условий (1) и (2) недостаточно, чтобы процесс разработки обладал линейной скоростью. Рассмотрим следующий пример.
Пусть C — некоторая константа. Рассмотрим процесс разработки Prex, при котором последовательно был получен следующий набор слов:
a1 : FPr(ai) = C,a2 : FPr(a2) = C +1, a3 : FPr(a3) = C+2,... Следовательно,
£ Fk (ai) = Ck + . (3)
i=1
Тогда набор пар
(ai, 1), (a2, C +1), (a3, C + 2),..., (ctk, C + k(k ~ 1))
составляет множество пар Pn для некоторого шага n процесса Pr. Заметим, что |Pn| = k. В силу предложения 5 и формулы (3)
™ k(k - 1) Ck + —- = n.
Нетрудно показать, что в таком случае имеет место
|Pn| - O(Vn).
Другими словами, рассмотренный нами в примере процесс разработки Prex имеет скорость у/и.
Предложение 10. Процесс разработки Prex существует. Доказательство. Рассмотрим функцию
void f1() {
}
Данная функция состоит из 7 лексем и согласно нашему определению трудоёмкости при условии, что мы только последовательно добавляли лексемы, её трудоёмкость составляет 6+1.
Пусть k Е N, k > 1. Рассмотрим функцию
void f1...1() {
//•♦♦А
}
Имя этой функции состоит из f и k единиц, а тело функции содержит k пустых операторов. То есть данная функция состоит из 6 + k лексем и, следовательно, при условии, что мы последовательно добавили k лексем, трудоёмкость её разработки составит 6 + k. Мы показали, как построить функцию для любого заданного k. ■
6. Полученные результаты и практика
Достаточное условие линейной скорости процесса разработки (Теорема 1) подтверждается практиками разработки программного обеспечения:
• Мартин Фаулер в [4] с. 54-56 даёт рекомендацию: «классы, процедуры, функции должны быть небольшими».
• The Open-Closed Principle [5]: «Программные сущности (классы, модули, функции и т.п.) должны быть закрыты от изменений...»
Важность ограничений на модификацию программного кода легко проиллюстрировать на следующем примере. Рассмотрим рисунок 2. Прямоугольники обозначают процедуры, а стрелки — какие-либо зависимости одной процедуры от другой. Например, если одна процедура содержит в своём теле вызов второй, то первая процедура зависит от второй. Основание стрелки — это процедура, которая зависит от второй, на которую показывает стрелка. Предположим, что нам необходимо внести изменения в код процедуры, обозначенной цифрой 1. Кроме тестирования самой процедуры 1, нам придётся, как минимум, протестировать, а, возможно и вносить правки в код процедур 2 и 3. В случае правок в процедуре 2, придётся проверять, и, возможно изменять процедуру 4, а в случае правок в процедуре 3 — выполнить те же действия для процедур 5 и 6. И так далее.
Этот пример показывает, что при внесении изменений в какой-либо код могут понадобиться правки в коде, который использует модифицируемый. При этом объём кода, работоспособность которого может быть нарушена, не зависит и никак не может быть ограничен разработчиком кода, для которого потребовалась модификация.
Существует немало примеров, ко-
Рис. 2. Расходящиеся модификации гда небольшие правки затрагивали
большое число программных проектов
по всему миру:
• Проблема 2000 года. Проблема была связана с форматом хранения даты, который был рассчитан на хранение диапазона дат с 1970 по 1999 годы. Никто не мог сказать, как наступление 1 января 2000 года отразится на работоспособности программного обеспечения. Серьёзных последствий удалось избежать лишь потому, что эта проблема была поднята за несколько лет до наступления 2000 года: и производители оборудования, а затем и программисты с учётом темпов смены технологий заблаговременно внесли изменения в программный код.
• Февраль 2016 года [7].
Разработчик функции, которая отрезает пробелы слева, удалил соответствующий npm пакет из репозитория javascipt пакетов. В результате существенное число популярных javascript проектов перестало собираться. Многие разработчики даже не догадывались, что их проекты зависят от этой функции, так как зависимость была у сторонних проектов, включённых в проект.
Страуструп, создатель языка С++, в [6] признается, что некоторые полезные и изящные конструкции не вошли в состав стандарта языка C++, так как они могли нарушить обратную совместимость.
Заключение
В настоящей статье было определено достаточное условие линейной скорости процесса разработки программного обеспечения, которое не зависит от языка и используемой парадигмы программирования, и которое, в основном, сводится к требованию, чтобы на разработку каждой программной сущности: класса, процедуры, функции и т. д. - не должно уходить более, чем заранее фиксированное число операций.
Кроме этого, показано, что требование, чтобы каждая программная сущность лишь не модифицировалась, то есть имела конечную трудоёмкость разработки, недостаточно, чтобы процесс разработки обладал линейной скоростью.
Литература
1. Колесов А. Мифический человеко-месяц: двадцать пять лет спустя // PCWeek. 2000. № 7(229). URL: https://www.pcweek.ru/themes/detail.php?ID= 53642.
2. Чапел Х., Брукс Ф. Мифический человеко-месяц, или Как создаются программные системы. СПб. : Символ-Плюс, 2010.
3. Nanus B., Farr L. Some cost contributors to large-scale programs // AFIPS Proc. SJCC. Spring 1964. Vol. 25. P. 239-248.
4. Фаулер М. Рефакторинг: улучшение существующего кода. СПб. : Символ-Плюс, 2003.
5. Martin R. The Open-Closed Principle // Object Mentor. 1996. URL: http://www. objectmentor.com/resources/articles/ocp.pdf.
6. Страуструп Б. Дизайн и эволюция языка C++. СПб. : ДМК Пресс, 2006.
7. Williams C. How one developer just broke Node, Babel and thousands of projects in 11 lines of JavaScript. URL: https://www.theregister.co.uk/2016/03/23/ npm_left_pad_chaos/.
ABOUT THE FORMALIZATION OF THE SOFTWARE DEVELOPMENT
PROCESS
E.A. Tyumentcev
CEO, e-mail: [email protected]
LLC Hello World! Technologies
Abstract. In the book "Mythical man-month, or How software systems are created," Brooks refers to several studies on the basis of which it can be concluded that the productivity of the work of programmers, measured in the number of lines of code per time, decreases as the size of the program project grows. In this paper, we describe the formalization of the software development process, as the process of editing the source code of the program, with the help of which the laboriousness function is defined, and a sufficient condition for the constant productivity of the programmers, independent of the size of the project, is derived.
Keywords: formalization, development process, software, productivity, Brooks, mythical man-month.
Дата поступления в редакцию: 25.04.2017