О том, что затрудняет формализацию проекта и плодит скрытые ошибки

в 9:46, , рубрики: Алгоритмы, большие проекты, логика, принципы организации кода, Промышленное программирование, Совершенный код, метки:

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

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

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

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

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

Допустим, программист делает процедуру, которая подставляет номер счета в бухгалтерской проводке. Увидев в ТЗ список всех типов договоров которые присутствуют в системе или увидев фразу “все типы договоров”, программист делает первую ошибку — он обобщает, что может быть даже приятно для мозга, так как можно выразить мысль кратко. В результате его процедура, которая подставляет счет не анализирует тип договора. На первый взгляд никакого криминала нет.

Вот чем подвох — видите ли, понятие “все типы договоров” на момент написания ТЗ включало 10 типов. Потом это понятие изменяется, и через полгода добавляется еще пара типов. Такой случай не является проблемой в маленьких проектах, скажем до миллиона строк, где есть преемственность опыта и программисты быстро учатся друг у друга.

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

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

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

Сколько от этого пользы судите сами.

  1. Уменьшаются разногласия между составителем ТЗ и разработчиком. Общие слова подкреплены конкретикой.
  2. Уменьшается вероятность того, что в отлаженный алгоритм попадут параметры, на которых он не тестировался.
  3. Становятся видны места в коде, где надо добавлять обработку новых типов.
  4. Повышается уровень дисциплины в коде. Увеличивается вероятность, что логика будет концентрироваться в одном и том же месте, а не будет разбросана.
  5. Облегчается формализация и автоматический анализ кода.

Приведем примеры на псевдокоде, похожем на pl/sql.

//Пример 1.
//Неверный код, нет проверки на попадание нового типа.
//Список изменяется со временем, появляются разночтения с начальным ТЗ.
//Со временем этот кусок кода влияет на все большее количество данных,
//поэтому изменения становится рискованным.
forall type_id in (all_type_list) loop
  do_something;
end loop;

//пример 2
//тоже
if type_id not in (555,666) then 
  do_something;
end if;

//пример 3
//Неверный код, нет проверки на попадание нового типа.
//В случае нового типа выполнение продолжается без предупреждений
//Действие не выполняется, и на это могут не обратить внимания
if type_id in (333,444) then 
  do_something;
end if;

//////////////////////////////////////////////////////////////////////
//Верный код и его развитие. ////////////////////
//Пример 4.
if type_id in (1,2,3) then
  do something;
else
  //Попался неизвестный тип, сигнал добавить новую обработку, см ниже.
 raise error;
end if;


//Развитие кода для новых типов 4,5,6.
//Вариант 1, новые типы обрабатываются стандартно по старому.
if type_id in (1,2,3,4,5,6) then
  do_something;
else
 raise error; 
end if;


//Вариант 2, новые типы обрабатываются особо.
if type_id in (1,2,3) then
  do_something;
elsif type_id in (4,5,6) then
  do_something_2;
else
 raise error; 
end if;

Планирую написать продолжение на темы:

  • как обрабатывать связки условий типов/под-типов
  • как автоматически обнаруживать противоречивые требования в ТЗ
  • как автоматизировать составление списков и позволить аналитику ими управлять
  • как использовать математику для анализа системы
  • развитие проекта через мета-системы

Д.А.Рыбаков, 2016
к.т.н.

Автор: dim2r

Источник

Поделиться новостью

* - обязательные к заполнению поля