I can’t wait to share this simple secret with you right now.
The Secret: Effective Software Systems are the systems that easy to understand and operate with human brains.
Programmers are more productive with effective software systems. Programmers can better learn and grow these system. Programmers have less problems, work faster and make better decision with them.
Now, you can avoid spending time reading this post if you already know this secret and you know how to avoid building the software system that:
- almost impossible to understand in reasonable time
- has confusing and convoluted swamp of logic and structure
- scary to change as nobody has any clue what will be broken, but sure that it will be broken
If you are still interested, lets find out what makes software systems effective.
Software Development is a pure mental endeavor (except typing on keyboard) that includes 3 main activities:
- Understand - learn and know system concepts and implementation
- Evolve - build, modify and support growth of the system ideas in the code
- Share - communicate and exchange ideas about the system
Programmers should care about 7 areas to make the system better suited for our brains:
- Knowledge Creation and Retention – parsing, memorization and comprehension of the system ideas
- System Organization – elements, relations and structure in the system
- Sustaining Emerging Order – support evolution of the system and gain control over chaos
- Minimize Noise and Purify – avoid adding unnecessary stuff to the system
- System Discovery and Learning – making sense of the system
- Mental Models – our internal explanations for how things are working in the real system
- Shared Knowledge – ideas exchange, reconciliation of opinions and creation of mutually enhanced knowledge.
1. Knowledge Creation and Retention
Information is not knowledge. – Albert Einstein
Programmers should keep as less as possible things in the mind when work with the system. Less information, simpler and more logical ideas behind the code cause less brain damage and more understanding.
- Chunking – combining many units of information into a limited number of units and chunks, so that information is easier to process and remember. Logical groups (subsystems -> modules -> classes) should contain only few elements. These groups should emerge with growth of the system functionality and complexity. They should replace large clusters of difficult to remember similar and unstructured elements.
- Abstraction - generalize, find common meaning and remove redundancy in the similar concepts across the system. You can substantially reduce number of ideas required for understanding the system.
- Simplicity - simpler ideas make understanding (and programmers life) much easier.
- Isolation - reduce relations and dependencies between elements. These relations add significant complexity to the system and therefore effort to understand and remember them. The volume of knowledge about relations could easily exceed the knowledge about individual elements.
Examples of useful tools for supporting knowledge formation:
- Unit tests – capture knowledge about the system correct behavior. They evolve with the system and become a parallel verification system. Good unit tests reduce our effort and limit necessary for understanding scope as they focus on the system behavior in isolated components and functions.
- Visuals - take advantage of diagrams, mind maps and pictures to simplify, distill and integrate important knowledge and ideas about the system
- Storytelling - create imagery, emotions and understanding of the system behavior through stories (our episodic memory is very powerful). For example, create narratives how users accomplish tasks with your system. Or share dramatic stories how programmers doomed the system with ignoring important practices.
2. System Organization
Be regular and orderly in your system organization, so that you may be violent and original in your software solutions. (paraphrasing Flaubert)
Human programmers shouldn’t have hard time to memorize and comprehend a software system. Computers don’t care much about the organization as long as the logic is correct, but we, human programmers, do. Clear, logical and consistent organization make us much more productive.
- Modularity - managing system complexity by dividing large subsystems into multiple, smaller self-contained systems
- Layering - process of organizing information into related groupings in order to manage complexity and reinforce relationships in the information
- Hierarchy - hierarchical organization is the simplest structure for visualizing and understanding complexity
3. Sustaining Emerging Order
Evolution is not a force but a process. Not a cause but a law. – John Morley
The main challenge for effective system organization is the fact that a system is constantly changing during active development. Good organization today doesn’t mean good organization tomorrow with many new added features. New customer needs, contribution of fellow developers and growing complexity push a software system to chaos.
Therefore, we have to keep organization optimal for today needs and still ready for tomorrow changes with
- Refactoring – constant effort to improve the system internal structure and making it effective for recent changes to prevent system degradation and rule of chaos
- Simple principles and rules (that everybody follows) help to keep consistency and integrity of the system in time of intensive growth and modifications.
- Small independent, single-purpose components – enable rich behavior with minimum number of elements, which could participate in various and often unforeseen scenarios. Bigger components are less reusable, more specialized and lead to more code, complexity and duplication.
- Building adaptive system using agile approach
4. Minimize Noise and Purify
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
Complexity is one of the biggest problem for the programmers brains. While good organization and representation of the system ideas help to fight it, the ultimate challenge is to avoid adding unnecessary stuff to the system.
Principals for Complexity Reduction:
- Increase Signal-to-Noise Ratio – ratio of relevant to irrelevant information in the system. Minimize noise by removing unnecessary elements and minimize the expression of necessary elements.
- Follow 80/20 Rule (Pareto’s Principle ) – a high percentage of effects in any large system are caused by a low percentage of variables. In other words, 80% of requested functionality will be used rarely or not used at all, while substantially increasing system complexity.
- Consider Good Enough Alternatives – don’t dash against the rock implementing exactly what customer asks. Slight modifications and alternatives could often cover need with less effort and complexity.
5. System Discovery and Learning
The real voyage of discovery consists not in seeking new lands, but in seeing with new eyes. – Marcel Proust
Learning and navigating the complex software system is a difficult task. Usually developers read code, documentation and debug the system. These methods take time and often are not efficient.
A better way to discover the system and make it discoverable with:
- Entry Points – points of attentional entry into the system. There are points in the system that doesn’t require much knowledge about the rest of the system. They could be starting points to understand other elements. For a example, UI screens or the system input / output. Other parts could start making sense and uncover their purpose in relation to these entry points. Discover these points and help newcomers to find them.
- Progressive Disclosure – a strategy for managing information complexity in which only necessary information is visible at any given time. Good design and coding style allow understanding of intent and logic of the studied system area without deeply diving into boundless waters of implementation details.
Criteria criteria = Criteria.AddPriceRange(10, 100).AddDateRange(Period.LastMonth);
IList orders = Database.Orders.LoadBy(criteria);
decimal totalTaxes = orders.Sum(order => order.Tax)
- Recognition over Recall – memory for recognizing things is better than memory for recalling things. (e.g. multiple choice questions with possible answers vs. fill-in-the-blank questions). Minimize the need to recall information from memory whenever possible. Use tools to make available options clearly visible. For example, Visual Studio Intellisense and Resharper remove need in memorizing members, purpose and syntax of .Net library classes and methods. Build tools or extend a development environment to construct code without memorizing all details.
- Inverted pyramid – method of presentation in which information is presented in descending order of importance. Discover first what is most important and make it easily accessible following by less and less important information.
- Performance load – the greater the effort to accomplish a task, the less likely the task will be accomplished successfully. Most important tasks should be easy to accomplish and most valuable information should be easiest to access (including direct access to the people who has the best knowledge).
- Picture superiority effect – pictures are remembered better than words. Use pictures and words together to reinforce the learning.
- Associations with familiar – link new information to already familiar concepts. Build on this knowledge by comparing and contrasting to reach deep understanding.
- Examples – learning examples is always one of the best method to understand how to use the system (including best practices, how-to, and tutorials).
6. Mental Models
A mental model is an explanation in someone’s thought process for how something works in the real world. – Wikipedia
People understand systems and environments and interact with them by comparing the outcomes of their mental models with the system and real-world domain concepts. Good mental models bring better understanding and optimal decision making.
Schemata – networks of connected ideas or relationships that help to organize experience and information into a meaningful system. Conversation between individuals is most effective if both share common schemata.
- Design and architecture patterns – general reusable solutions to a commonly occurring problems in software design.
- System metaphors – a simple story of how the system works. A system metaphor provides common vision and shared vocabulary.
- Conventions – standard and well known approaches in architecture, design, code and user interface. They make intuitive and common sense and save mental energy for present and future developers. Custom and novel approaches should be used only if they have serious advantage.
7. Shared Knowledge
I know that you believe you understand what you think I said, but I’m not sure you realize that what you heard is not what I meant. – Robert McCloskey
The system is rarely envisioned, built and used by the same person. A successful system is a result of people interactions – intensive ideas exchange, reconciliation of opinions and creation of mutually enhanced knowledge. It is not easy for people to properly translate, communicate and understand each other ideas. Complex knowledge, different background and experience increase the gap that people should jump to completely understand each other.
- Ubiquitous language – the shared language that people with different perspectives use to exchange ideas about the system. The same meaning of the words is very important for understanding, but very difficult to achieve.
- Clear goals and intention – help to align people thinking and actions, and avoid wasteful effort caused by misunderstanding or confusion.
- Express business domain concepts in the code – represent business concepts in the code. Developers will better understand domain, synchronize implementation with customer ideas and align the system with relevant business concepts. As a result, the system can effectively grow with the refinement and expansion of business needs.
- Code readability – good naming and readable code enables better understanding by fellow programmers, reduce chances of errors and lead to correct future modifications.
- Shared mental models – frequent face-to-face discussions, pair programming and direct conversations with customers bring shared understanding, effective exchange of knowledge and most optimal solutions.
Evolution didn’t consider adaptation of human brains for software development. But our brains is the most important tool for programming. You cannot change brains (and human nature), so make software development compatible with them. Build effective software systems.
The Programmer’s Brains At Work: Understanding The Software System
Universal Principles of Design, William Lidwell , Kritina Holden, Jill Butler