Primary Technical Imperative¶
📝 "There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies, and the other is to make it so complicated that there are no obvious deficiencies."
—C. A. R. Hoare
📝 "Управление сложностью — самый важный технический аспект разработки ПО. По-моему, управление сложностью настолько важно, что оно должно быть Главным Техническим Императивом Разработки ПО.
Managing complexity is the most important technical topic in software development. In my view, it's so important that Software's Primary Technical Imperative has to be managing complexity."
—"Code Complete" 2nd edition by Steve McConnell, перевод: Издательско-торговый дом "Русская Редакция"
📝 "Дейкстра пишет, что ни один человек не обладает интеллектом, способным вместить все детали современной компьютерной программы (Dijkstra, 1972), поэтому нам - разработчикам ПО — не следует пытаться охватить всю программу сразу. Вместо этого мы должны попытаться организовать программы так, чтобы можно было безопасно работать с их отдельными фрагментами по очереди. Целью этого является минимизация объема программы, о котором нужно думать в конкретный момент времени. Можете считать это своеобразным умственным жонглированием: чем больше умственных шаров программа заставляет поддерживать в воздухе, тем выше вероятность того, что вы уроните один из них и допустите ошибку при проектировании или кодировании.
На уровне архитектуры ПО сложность проблемы можно снизить, разделив систему на подсистемы. Несколько несложных фрагментов информации понять проще, чем один сложный. В разбиении сложной проблемы на простые фрагменты и заключается цель всех методик проектирования ПО. Чем более независимы подсистемы, тем безопаснее сосредоточиться на одном аспекте сложности в конкретный момент времени. Грамотно определенные объекты разделяют аспекты проблемы так, чтобы вы могли решать их по очереди. Пакеты обеспечивают такое же преимущество на более высоком уровне агрегации.
Стремление к краткости методов программы помогает снизить нагрузку на интеллект. Этому же способствует написание программы в терминах проблемной области, а не низкоуровневых деталей реализации, а также работа на самом высоком уровне абстракции.
Суть сказанного в том, что программисты, компенсирующие изначальные ограничения человеческого ума, пишут более понятный и содержащий меньшее число ошибок код.
Dijkstra pointed out that no one's skull is really big enough to contain a modern computer program (Dijkstra 1972), which means that we as software developers shouldn't try to cram whole programs into our skulls at once; we should try to organize our programs in such a way that we can safely focus on one part of it at a time. The goal is to minimize the amount of a program you have to think about at any one time. You might think of this as mental juggling—the more mental balls the program requires you to keep in the air at once, the more likely you'll drop one of the balls, leading to a design or coding error.
At the software-architecture level, the complexity of a problem is reduced by dividing the system into subsystems. Humans have an easier time comprehending several simple pieces of information than one complicated piece. The goal of all software-design techniques is to break a complicated problem into simple pieces. The more independent the subsystems are, the more you make it safe to focus on one bit of complexity at a time. Carefully defined objects separate concerns so that you can focus on one thing at a time. Packages provide the same benefit at a higher level of aggregation.
Keeping routines short helps reduce your mental workload. Writing programs in terms of the problem domain, rather than in terms of low-level implementation details, and working at the highest level of abstraction reduce the load on your brain.
The bottom line is that programmers who compensate for inherent human limitations write code that's easier for themselves and others to understand and that has fewer errors."
—"Code Complete" 2nd edition by Steve McConnell, перевод: Издательско-торговый дом "Русская Редакция"
📝 "Главным Техническим Императивом Разработки ПО является управление сложностью. Управлять сложностью будет гораздо легче, если при проектировании вы будете стремиться к простоте.
Есть два общих способа достижения простоты: минимизация объема существенной сложности, с которой приходится иметь дело в любой конкретный момент времени, и подавление необязательного роста несущественной сложности.
Software's Primary Technical Imperative is managing complexity. This is greatly aided by a design focus on simplicity.
Simplicity is achieved in two general ways: minimizing the amount of essential complexity that anyone's brain has to deal with at any one time, and keeping accidental complexity from proliferating needlessly."
—"Code Complete" 2nd edition by Steve McConnell, перевод: Издательско-торговый дом "Русская Редакция"
📝 "При выполнении других заданий человек может удерживать в памяти 7±2 дискретных элементов [Miller, 1956]. Если класс содержит более семи элементов данных-членов, подумайте, не разделить ли его на несколько менее крупных классов [Riel, 1996].
The number "7±2" has been found to be a number of discrete items a person can remember while performing other tasks [Miller 1956]. If a class contains more than about seven data members, consider whether the class should be decomposed into multiple smaller classes [Riel 1996].
- [Miller, 1956]
Miller, G. A. 1956. "The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity for Processing Information." The Psychological Review 63, no. 2 (2): 81–97.
- [Riel 1996]
Riel, Arthur J. 1996. Object-Oriented Design Heuristics. Reading, MA: Addison-Wesley."
—"Code Complete" 2nd edition by Steve McConnell, перевод: Издательско-торговый дом "Русская Редакция"
По поводу последнего изречения - лучше один раз увидеть на примере метафоры в виде картинки со схожим эффектом:
Как и в "Законе Миллера", суть картинки сводится к тому, что у человека есть предел способности воспринимать информацию, и если количество единиц поступающей информации превышает этот предел (не зависимо от его природы, будь то особенность работы рецепторов сетчатки или предел возможностей краткосрочной памяти), то начинается "жонглирование", т.е. неспособность рассмотреть (в прямом и в переносном смыслах) всю информацию единовременно и изолированно.
Вероятное объяснение этого явления заключается в том, что:
💬 "Your eye's receptors are stimulated and influenced by the activity of neighboring receptors. In a complex, repetitive grid like this, one receptor can have trouble perceiving the dots accurately because of stimulation occurring in a nearby receptor."
Внимание - это избирательная направленность восприятия. Периферийное зрение - это способность видеть те предметы, которые выходят за фокус основного внимания. Слово "сфокусировать" - означает "сосредоточить", как в прямом (оптическом), так и в переносном (сконцентрироваться) смыслах. Основной принцип управления сложностью - это её декомпозиция до такого уровня, над которым обеспечивается перевес умственных возможностей человека. Т.е. когда объем рассматриваемой изолированно сложности "вмещается" в фокус внимания человека.
См. также "Принцип ледокола".
📝 "These were elucidated in the mid-70s by Yourdon & Constantine in Structured Design and haven't changed. Their argument goes like this:
We design software to reduce its cost.
The cost of software is ≈ the cost of changing the software.
The cost of changing the software is ≈ the cost of the expensive changes (power laws and all that).
The cost of the expensive changes is generated by cascading changes — if I change this then I have to change that and that, and if I change that then…
Coupling between elements of a design is this propensity for a change to propagate.
So, design ≈ cost ≈ change ≈ big change ≈ coupling. Transitively, software design ≈ managing coupling.
(This skips loads of interesting stuff, but I'm just trying to set up the argument for why rapid decomposition of a monolith into micro-services is counter-productive.)"
Managing Coupling
Note I don't say, "Eliminating coupling." Decoupling comes with its own costs, both the cost of the decoupling itself and the future costs of unanticipated changes. The more perfectly a design is adapted to one set of changes, the more likely it is to be blind-sided by novel changes. And so we have the classic tradeoff curve:
You manage coupling one of two ways:
Eliminate coupling. A client and server with hard-coded read() and write() functions are coupled with respect to protocol changes. Change a write() and you'll have to change the read(). Introduce an interface definition language, though, and you can add to the protocol in one place and have the change propagate automatically to read() and write().
Reduce coupling's scope. If changing one element implies changing ten others, then it's better if those elements are together than if they are scattered all over the system —less to navigate, less to examine, less to test. The number of elements to change is the same, but the cost per change is smaller. (This is also known as the "manure in one pile" principle, or less-aromatically "cohesion".)
—"Monolith -> Services: Theory & Practice" by Kent Beck
Eric Evans дает неплохое определение Constantine's Law нетехническим языком:
💬 "МОДУЛИ дают возможность посмотреть на модель с разных сторон: во-первых, можно изучить подробности устройства модуля, не вникая в сложное целое; во-вторых, удобно рассматривать взаимоотношения между модулями, не вдаваясь в детали их внутреннего устройства.
<...>
То, что при делении на модули должна соблюдаться низкая внешняя зависимость (low coupling) при высокой внутренней связности (high cohesion)- это общие слова. Определения зависимости и связности грешат уклоном в чисто технические, количественные критерии, по которым их якобы можно измерить, подсчитав количество ассоциаций и взаимодействий. Но это не просто механические характеристики подразделения кода на модули, а идейные концепции. Человек не может одновременно удерживать в уме слишком много предметов (отсюда низкая внешняя зависимость). А плохо связанные между собой фрагменты информации так же трудно понять, как неструктурированную "кашу" из идей (отсюда высокая внутренняя связность).
MODULES give people two views of the model: They can look at detail within a MODULE without being overwhelmed by the whole, or they can look at relationships between MODULES in views that exclude interior detail.
<...>
It is a truism that there should be low coupling between MODULES and high cohesion within them. Explanations of coupling and cohesion tend to make them sound like technical metrics, to be judged mechanically based on the distributions of associations and interactions. Yet it isn't just code being divided into MODULES, but concepts. There is a limit to how many things a person can think about at once (hence low coupling). Incoherent fragments of ideas are as hard to understand as an undifferentiated soup of ideas (hence high cohesion)."
—"Domain-Driven Design: Tackling Complexity in the Heart of Software" by Eric Evans, перевод В.Л. Бродового