This series is about design process and organizing our thoughts for effective development. The main goal is to establish a mental model and intuition for effective design process. This post explores questions to clarify thoughts in specific software lifecycle stages.
Questions For Requirements
I like to approach requirements with the 5ish interrogatives
- Who (am I solving a problem for)
- Why (is it important to them)
- What (do they see as necessary qualities of a solution)
- When (do they need it)
- How much (are they willing to invest in a solution)
The trio of what, when, and how much inform if a solution is even worth pursuing.
Most requirement techniques are different angles at these questions that try to achieve enough granularity for development. It’s also critical to align understanding between customers and developers. Again, it’s better to be wrong than vague. Producing artifacts (sketches, descriptions, diagrams, etc) that both implementers and the customer understand helps surface differences in understanding early and saves pain for everyone later.
Common requirement communication tools include
- Event Storming (original overview) (official resources)
- Behavior-Driven Development and Acceptance tests
- Story Maps and User Journey Maps
Questions for Architecture and High-level Design
I make a point to ask people about their views on architecture. Often, I get responses about X framework or Y technology stack. These are not architecture.
I like how Robert Martin broadly describes architecture as making the important decisions that allow you to put off the unimportant decisions. Unimportant decisions being things that shouldn’t control the overall structure of your program. They may be important for other reasons like cost or developer familiarity.
- Data Schema
- Frameworks & Storage
- Implementation details
- Domain / Non-Domain properties
- Details to defer (or hide at architecture level)
- Likely design changes
- Project scale
Seen another way, architecture is understanding the problem domain. Domain Driven Design is one of the most popular design methodologies. It prescribes that software should reflect the problem it solves and provides various techniques for understanding and reflecting the domain. For example, sub-domains (or separable responsibilities of the problem domain) can often be discovered by considering business departments.
My main lines of thought for evaluating a design include
- Essence vs accident: What is a fundamental part of the problem versus what could change by circumstance
- Could we be a (company type) if we didn’t (activity)?
- Volatility Analysis: Using likelihood of change to understand how fundamental concepts are to a problem
- Note: frameworks, data stores, data schemas, communication protocols, UI organization, and similar are almost never essential to a problem and highly likely to change. They are poor foundations for organizing a system
- Information Hiding: How can I minimize the scope a programmer needs to consider to understand a given piece of the system
- SOLID: A well-known set of design principles that guide ways to avoid key design problems.
Common design communication tools include
- Service Diagrams: Enumerate the major system components and how they interact
- Component signatures:
- the methods on a service
- the inputs and outputs of each method
- data contracts (any complex data types used as input or output to a service)
- expected side-effects of an action (i.e. exceptions, events)
- Activity diagrams: these lay out the steps of a process, if a process is sufficiently complex to design before construction. I rarely use these
- Domain Driven Design (book)
- I highly recommend Domain Modeling Made Functional. It covers a modeling process from end to end in digestable increments.
- Ports and Adapters, Clean Architecture, Hexagonal Architecture
I describe much of my design process in these posts
Questions for Construction
Many architecture and high-level design questions trickle down to construction, just at a smaller scale with more detail.
The main questions I ask myself while coding are
- Testing: How can I write automated tests for this code?
- Understandability: How can I communicate my intent clearly in code so future contributors can correctly modify the system?
- Coupling and Cohesion: Another view of volatility analysis and information hiding. How can I separate activities so each chunk is independently understandable and future change has minimal impact? How can I minimize and clarify expections between components?
- Design Patterns: What established patterns can I use to maximize understandability?
The primary communication tool for construction is code and tests. Diagram sketches, signatures, or duck docs can still be helpful for getting thoughts out. However, anything others should understand about the system should be made evident in the system itself.
- Test-Driven Development
- Pseudo-code Programming Process (Code Complete 2nd ed chapter 9)
- Use pseudo-code to progressively understand a process. Aids consistent abstraction / code that reads like a sentence and conceptually well-factored code
- Pair Programming
- Preparatory Refactoring (Refactoring Ch 2):
- How do I change my system so it’s easy to add the feature I want?
That’s a wrap for new content. Next we’ll review what we’ve covered.