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. Гибкость в природе и архитектуре ПО
Наследие механики/классической_инженерии:
- Механизм общего назначения, успешно выполняющий широкий спектр работ, создать крайне сложно.
- Большая часть механизмов - относительно узко специализированы.
- Прикладное ПО тоже относительно узко специализировано, а вследствие того, что еще и спроектировано со значительными ограничениями, то не может эволюционировать.
Две ценности ПО - поведение и структура, структура - это как раз возможность эволюционировать.
Решение - аддитивное (расширяющее) программирование (additive programming), суть его в расширении, а не изменении функциональности ПО, см. Open-Closed Principle. Чтобы применять этот способ необходимо:
- Минимизировать предположения о том, как работает и как будет использоваться программа. Невежественный код. Вместо этого программы должны принимать just-in-time решения на основании среды/контекста, в котором выполняется код.
- Формировать новое поведение за счет комбинации отдельных частей, не обладающих желаемым поведением по отдельности. Здесь важно, чтобы отдельные части имели отдельные, независимые ответственности Single Responsibility Principle.
- Упрощать, абстрагировать и обобщать отдельные части системы. Более того, нужно стремиться к тому, чтобы диапазон выходных значений одной части был небольшим и четко определенным - значительно уже, чем диапазон принимаемых значений любой другой части, способной с ней сотрудничать. Be conservative in what you do, be liberal in what you accept from others.
- Generics. Иметь/создавать высокоуровневый исполняемый архитектурный план. Исполняемость важна - это возможность получать раннюю обратную связь; высокоуровневость - это абстрагирование от незначительных на старте деталей реализации. В ходе разработки именно этот план должен уточняться деталями и в конечном итоге привести к созданию работающей системы. Другими словами, функционировать система должна с первого дня разработки.
- Создавать маленькие взаимозаменяемые модули. Контраст уровня детализации спецификаций в биологии и инженерии. В итоге, чем проще модуль, чем более он обобщен, тем проще могут быть его спецификации. Чем выше способность модуля самостоятельно адаптировать поведение под окружающую среду, тем менее точными могут быть спецификации.
- Layers. Создавать самоподобные структуры (обертки и декораторы). Это позволит как использовать для выполнения функции отдельные модули, так и сложные комбинации модулей, обладающие тем же интерфейсом.
- Redundancy and degeneracy. Создавать избыточные и вырожденные модули (т.е. обладающие большим, чем нужно, запасом прочности и несколькими способами выполнения поставленной задачи).
- Exploratory behavior. Встраивать исследовательское поведение - то есть обобщенную генерацию стратегий и специфичное их тестирование/фильтрацию. Похоже на разделение на “что” и “как”, абсолютно независимые друг от друга механизмы.