Strategies for building large systems that can be easily adapted for new situations with only minor programming modifications.

Time pressures encourage programmers to write code that works well for a narrow purpose, with no room to grow. But the best systems are evolvable; they can be adapted for new situations by adding code, rather than changing the existing code. The authors describe techniques they have found effective—over their combined 100-plus years of programming experience—that will help programmers avoid programming themselves into corners.

Hanson C., Sussman G. J. Software design for flexibility: how to avoid programming yourself into a corner / C. Hanson, G. J. Sussman, Cambridge: The MIT Press, 2021. 424 c.

1. Гибкость в природе и архитектуре ПО

Наследие механики/классической_инженерии:

  1. Механизм общего назначения, успешно выполняющий широкий спектр работ, создать крайне сложно.
  2. Большая часть механизмов - относительно узко специализированы.
  3. Прикладное ПО тоже относительно узко специализировано, а вследствие того, что еще и спроектировано со значительными ограничениями, то не может эволюционировать.

Две ценности ПО - поведение и структура, структура - это как раз возможность эволюционировать.

Решение - аддитивное (расширяющее) программирование (additive programming), суть его в расширении, а не изменении функциональности ПО, см. Open-Closed Principle. Чтобы применять этот способ необходимо:

  1. Минимизировать предположения о том, как работает и как будет использоваться программа. Невежественный код. Вместо этого программы должны принимать just-in-time решения на основании среды/контекста, в котором выполняется код.
  2. Формировать новое поведение за счет комбинации отдельных частей, не обладающих желаемым поведением по отдельности. Здесь важно, чтобы отдельные части имели отдельные, независимые ответственности Single Responsibility Principle.
  3. Упрощать, абстрагировать и обобщать отдельные части системы. Более того, нужно стремиться к тому, чтобы диапазон выходных значений одной части был небольшим и четко определенным - значительно уже, чем диапазон принимаемых значений любой другой части, способной с ней сотрудничать. Be conservative in what you do, be liberal in what you accept from others.
  4. Generics. Иметь/создавать высокоуровневый исполняемый архитектурный план. Исполняемость важна - это возможность получать раннюю обратную связь; высокоуровневость - это абстрагирование от незначительных на старте деталей реализации. В ходе разработки именно этот план должен уточняться деталями и в конечном итоге привести к созданию работающей системы. Другими словами, функционировать система должна с первого дня разработки.
  5. Создавать маленькие взаимозаменяемые модули. Контраст уровня детализации спецификаций в биологии и инженерии. В итоге, чем проще модуль, чем более он обобщен, тем проще могут быть его спецификации. Чем выше способность модуля самостоятельно адаптировать поведение под окружающую среду, тем менее точными могут быть спецификации.
  6. Layers. Создавать самоподобные структуры (обертки и декораторы). Это позволит как использовать для выполнения функции отдельные модули, так и сложные комбинации модулей, обладающие тем же интерфейсом.
  7. Redundancy and degeneracy. Создавать избыточные и вырожденные модули (т.е. обладающие большим, чем нужно, запасом прочности и несколькими способами выполнения поставленной задачи).
  8. Exploratory behavior. Встраивать исследовательское поведение - то есть обобщенную генерацию стратегий и специфичное их тестирование/фильтрацию. Похоже на разделение на “что” и “как”, абсолютно независимые друг от друга механизмы.