Chapter objectives
A friend in the book business told me the introduction is often read by someone standing in a bookstore, deciding whether to buy the book. If that’s you, then welcome! I hope this introduction gives you what you need to make a buying decision. If you already own the book, it will orient and ready you for the rest of the book.
All programmers design software, so we have a sense for what it means. However, I will define it so we are on the same page. I also want you to understand the slightly cryptic title of the book. And finally, you should understand how I intended you to use the book to maximize your learning.
1
Introduction
Important terms
you’ll learn these terms in this chapter
1
Introduction
Software design means making programming decisions
This is a book about software design. There are many definitions out there for the term. I don’t want to comment on the others, nor do I want to replace them. I will just define it for the scope of this book, because I find this definition most useful for the material ahead:
Software design is making decisions about how we write the software.
While coding, we have a lot of choices to make. Do I use a for loop or a while loop? Do I break this function into two smaller functions? How do I name them? What types should the function’s arguments be? What data structure should I use to store this data in memory? Where should I break up my modules?In short, software design is the total of all of your decisions when making software. That’s everything from what the software should do to how to architect it to the line-by-line decisions a programmer must make while coding. Anything less arbitrarily chops up the creation of software.
My definition tracks with definitions found in other fields where they use the term design. Steve Jobs has a famous quote: Design is not just what it looks like and feels like. Design is how it works.
To me, that means design is the whole thing. It’s not just prettying up the code. It’s also the choice of data structure—which affects the memory usage—which affects its performance—which affects how the user perceives it. There’s no easy line to draw to divide design from non-design activity. Software design is holistic.
Wait, if software design is everything, isn’t it synonymous with programming? Well, yes, it is. However, calling it software design highlights that we are making those decisions. Programming has more of a “tell the computer what to do” vibe. One purpose of this book is to bring a new awareness to the many decisions we make so that we can make different decisions from the habits we’ve developed over the years.
2
Making design decisions is hard
3
Introduction
Software design decisions are hard. There are three reasons. Firstly, there are thousands if not millions of design decisions to make in the building of a significant piece of software.
Secondly, design decisions have multi-dimensional impact. Some of design choices we make are small and not meaningful, while others can seriously impact the performance, readability, and modifiability (among other aspects) of our code—let alone whether the code does what it’s supposed to.
And thirdly, design decisions are interdependent. For instance, if I move a function into another module, its name may no longer make sense in its new context. If I choose to use a hash map because it makes my function lookup fast, it may lose the order of insertion that another function needs.
These characteristics add up to a very hard problem. It’s too big to fit the entire design in our heads. It’s nearly impossible to understand the first-order consequences of our decisions, let alone the second-order ones. Software design is complicated.
As an industry, we’ve relied on a kind of oral tradition to teach design. A senior programmer works closely with a junior programmer to impart what knowledge they can. The junior eventually graduates to senior. This kind of one-on-one tutoring works, but it doesn’t scale.
Some skilled programmers have tried to broadcast their software design skill through books, conference talks, and blog posts. They develop principles and rules-of-thumb. However, without the one-on-one interaction to show when the principles apply and when they don’t, the principles often get misapplied by beginners, often resulting in dogmatic rule adherence instead of nuanced design skill.
I’ve taken it as a challenge to organize the material in this book not as principles and rules but as perspectives. Each perspective gives us a different way to look at the problem. And through different perspectives we can begin to assemble a holistic view of the software we build.
Introduction
We make better decisions with more information
4
I’ve defined software design as making decisions. The quality of software design is therefore proportional to the quality of decisions we make. So now our problem is easier: Just make better decisions. Better design will follow.
But how do we make better decisions? One way we make better decisions is to have better information. We want more information about the consequences of each design decision. What impact does a decision have on the software’s ability to do its job— and further on expressivity, on readability, on performance, on maintainability? And how does that decision affect other design choices we must make?
For example, we must choose what data structure to use to represent a coffee order. If we use an array of coffees, does that enable important use cases or hinder them? Does it make the code easier to write and read?
Further, arrays maintain insertion order. Does that make it easier or harder to decide how to implement adding a cofffee to an order? What about removing a coffee from an order?
We can’t immediately answer the questions. Often, the best we can do is “it depends.” What we mean is that we need more information to decide. Many of the skills in this book are about extracting as much information as possible from the context so that we make better decisions.
What I aim to do in this book is to equip you, the reader, to be an explorer. I will give you questions to ask. I will tell you what to look for. I will equip you with a vocabulary and important concepts. I will help them exercise your judgment. Then I will set you loose to make decisions.
The domain is the best source of information
Domain modeling as an approach to software design uses the domain as the most important source of information for making decisions. That means that any time you have a question to answer, you must seek an answer in the domain. Examples:
Q: How should I name this method? A: What’s it called in the domain?
Q: Should I split this function in two? A: Is it split in the domain?
Q: Should I add a layer of indirection? A: Does it correspond to anything in the domain?
Q: Have I covered all of the cases? A: Are there more cases in the domain?
That doesn’t mean that you will always do exactly what the domain experts say. Software does impose its own constraints and considerations. But the domain is an important source of information.
The domain is the context in which your program must operate correctly. If you’re writing accounting software, the domain is accounting. Most of your program is organized to get that right—it’s the core of your software. If you get the domain model wrong or messy, your code will be riddled with complex workarounds. A poor domain model is the source of the code complexity other design methodologies focus on.
Typical software design approaches rely on code metrics or code smells. Some measure things in the code like the length of methods or the number of nested if statements. Others use a more qualitative analysis to identify problem areas (“code smells” or coupling.). These can be useful to indentify messy code. But they miss the cause: An incorrect domain model requires more complex code to use.
These approaches ignore the domain because every domain is different and teaching someone to analyze a domain properly is difficult. In this book, I will teach you to extract information from the domain and encode it in your software.
5
Introduction
Introduction
We make better decisions by considering more options
Another way we can make better decisions is by considering more options. When you’re playing chess, you make better moves by considering more moves. Likewise, you make better decisions by consider more options for each decision.
When we’re learning to program, there is so much to learn and so many decisions to make, we are taught simple methodologies that help us make progress. For instance, I was taught in school that we should make a class for each concept in the problem description. That would mean if I had to build a university course registry, I would make a Student class and a Course class.
Over years of experience, as our skills grow, we should drop these simplistic methodologies, but often we do not. They become ingrained habits that we never question. o we are presented with a design decision, we apply our habit, and never consider other options.
In my case, it took years of exploration and introspection for me to rid myself of that habit that went back to 1583 Object-Oriented Design. It started with considering other options. What if I used a plain data structure to hold the data for a Student? What if I use functions instead of classes? What if I model something besides what’s in the problem description?
By asking these questions and experimenting with different design decisions, I got rid of the habit and became a better designer. That’s the process I want to guide you through as well. It requires you to become aware of your default mode of programming and interrupt it with new possibilities.
6
We make better decisions through iteration and feedback
7
Introduction
Most of our decisions will be suboptimal because we can’t really hold the complexity of our software in our heads. There are too many choices and the consequences of each choice are hard to foresee.
The best way to understand the consequences of our coding decisions is to code it and see. In other words, we have to proceed empirically. We make a choice, code it, and see what it looks like. That’s iteration and feedback.
At that point, we may back up and try a different decision, or we may make small adjustments. The point is that we are working in the medium—code—and not at a remove. We can directly understand the affect design decisions have on the overal design of our software.
Iteration and feedback are important in other types of design. Furniture designers might not be able to iterate quickly enough in the original medium, let’s say walnut, but they can find aproximations, for instance working in softer plywood or even cardboard to quickly create prototypes to experiment on the shape of the chair they’re working on. Other media, like oil paint, is more immediate.
I want to teach you techniques for working in code that allow the kind of iteration I’m talking about. It involves working in code that the right level of abstraction (using types and function signatures), then stubbing them out to be able to run the functions.
To give you more feedback, each chapter gives you a high resolution look at how well the design models the domain. These perspectives surface problems early so you can iterate faster, consider more options, and evaluate them better.
Introduction
What are runnable specifications?
Okay, it’s time to addres the book’s title, Runnable Specifications. That needs some explanation. It means three things:
Let’s break each one down.
Separating specification from implementation
Separating specification from implementation has been called the most important skill that separates juniors from seniors. Some people say “interface” instead of “specification.” But it means the same thing. We separate the meaning from how it is implemented.
When we say something is messy or undesigned, often we mean there is no way to understand the interface except by understanding exactly what it does. In such cases, there is no separation between specification and implementation. In this book, you will learn how to reason about the interfaces separately from the implementation. In fact, even though there is plenty of code in this book, we will do very little implementation—just enough to make things runnable.
Modeling your domain in code
Domain modeling means to encode an abstraction of the problem domain. That can get technical and a lot of this book is about the skills of modeling. It’s too much to explain in detail here in the introduction—but here’s a short description.
An abstraction maps information from the concrete to an abstract representation. The mapping is good if it preserves operations. That is, the important operations in the concrete domain are also available in the abstract representation.
How do we choose what to represent? What operations are important? Those are hard questions to answer in general. And they’re also hard in the specific. It takes trial-and-error and lots of iteration. And for that, we need rich feedback.
8
9
Introduction
Seeking fast, rich feedback
Feedback allows us to learn faster. If we learn faster, we can try more thing in the same period of time. And we can also work for longer because we get less frustrated and achieve little wins along the way.
Because software design is so difficult, often the only way we can understand the consequences of a design is by making the design and seeing how it works. It is so complicated, we must externalize it. The thing we externalize will have good parts and bad parts. Learning about those is feedback.
I believe the best source of feedback is the medium itself—code. Code must be syntactically correct. It must pass the type checker. And it must run and work. Running the code is the richest form of feedback possible. UML and other visual modeling media do let you have some feedback—visual information. But nothing beats the feedback of running code with interesting tests. In this book, we’ll see learn techniques for increasing the speed and information content of the feedback.
The trouble is, if we write running code, we run the risk of crossing the line from specification into implementation. It’s a real problem. This book addresses several concepts and skills to prevent implementing prematurely while still writing code that might make it to production.
Introduction
This book is organized into lenses
The material in this book is organized into lenses. Each lens gives a different perspective on the domain and how to model it. Each lens has concepts and skills that help us extract more information from the domain and how to encode that information into our software. With enough different persepectives, we can find constraints that help us tame the multi-faceted and complex nature of our software design decisions.
I had a real challenge organizing the material. I did not want to prescribe principles or rules-of-thumb. Those do not work unless you are already good enough to apply them correctly. Too often, beginners take a good rule and over-apply it. They don’t know how to see when the rule is meant to be applied and when there is an exceptional case.
Also, I wanted to avoid dictating a process for designing software. Again, the complexity of the decisions we make does not lend any linearization of the process. I instead opted for increasing the resolution of your vision. With the concepts and skills in these lenses, you will learn to see things that have always been there. You will learn what to look for and how to evaluate what you see. And you will learn to encode what you see into software. Upgraded and equipped, the process is up to you.
10
How does runnable specifications compare with UML?
11
Introduction
Inevitably, when I mention the topic of this book during its writing, people ask me how it compares to other paradigms. They are fair questions. I might as well address them here and now to help you contrast them and understand whether this book is for you.
Universal Modeling Language (UML) is a visual modeling language meant to aid in the software design process. The creators of UML wanted to present their design to stakeholders, many of whom were non-programmers. Code contained too much information to understand at a glance, and the important design decisions were intertwined with incidental implementation decisions. So they developed a visual language to capture the main design decisions without the extraneous details of the syntax of a programming language. UML is a modeling language. The main content of UML diagrams is important characteristics of the model.
The main difference is that UML is visual, while RS uses code as the medium. Instead of using an intermediate modeling language then translating it into code, in RS we go directly to code. However, we avoid the problem of having the extraneous details because in RS we choose to use a very restricted subset of code. That subset is selected so that we stay firmly in the specification (where important design decisions are made) and out of the implementation (which is rife with extraneous details).
But because we are writing it in code, we get several advantages:
The main thing we lose compared to UML is that it is not visual. It is harder to read for non-programmers. We also risk slipping into implementation inadvertently.
Introduction
How does RS compare with Domain Driven Design?
Another popular movement is the Domain Driven Design (DDD) community which began with a book of the same name by Eric Evans. In DDD, they design models in code, similar to in RS. In fact, I considered whether to make this a Domain Driven Design book. But there are several important differences that made me want to avoid the DDD branding.
First, the DDD book (and the ensuing community) are using the typical software design principles, rules-of-thumb, and patterns approach, which I don’t think works very well to teach software design. I’ve already mentioned why. I don’t want RS to be assimilated into that, with the skills in this book seen as patterns.
Second, the DDD book has a lot of project management advice, and this book avoids that. Project management is important and DDD probably has some great advice in it. But this book focuses much more on the technical skills of software design.
Third, DDD is mostly framed in object-oriented terms. That’s fine, but I am framing RS in functional programming (FP) terms. I believe FP has unique advantages for modeling (in particular, immutable data allows for better modeling of changes over time) and I want to highlight those.
Finally, this one is perhaps a little unfair, but it’s worth mentioning. The DDD book is a very large tome and contains a lot of ideas and wisdom from Eric Evans. Unfortunately, it’s so big that people can find whatever they like. Two people discussing DDD often are discussing different subsets of ideas. So although they belong to the same community, they often don’t share much in common. I didn’t want RS to get lost in that sea.
All of that said, I do think there is a lot of kinship between RS and the ideas specific to modeling from DDD. One might consider RS to be a very practical guide to domain modeling that a DDD enthusiast might use to teach others how to do it.
12
How does RS compare with software design in general?
13
Introduction
Runnable Specifications is about software design, so I should place it in relation to the software design movement in general. Software design is an important field. We should be thinking about the way our software is constructed and try to optimize it.
However, I believe software design has taken a wrong turn. The main problem is that most software design advice focuses on the code artefact. That is, it uses code metrics to identify complexity, code smells to find trouble spots, and coding patterns to prescribe how to make bad code better.
Now, to be clear, code quality is very important. I don’t want to diminish the importance of it. But it’s not al there is. What about the semantics of your code? Does your code correspond to the thing it is supposed to do? It is as if we have a whole field dedicated to style, but no field dedicated to substance.
It is akin to giving a novelist writing advice but focusing only on word choice, rhythm, and other stylistic concerns.What is missing is the substance of the novel. What about plot, characters, setting? Those are the things that make a novel worth reading; style should support those. Software design advice analogously ignores the domain, the main content of software. What is the software trying to do? What are the important facts that needs to be captured? How does the software model those facts so that the problem can be solved? Domain modeling puts the content (the domain) first. The style must support the domain. RS follows that lead by centering design decisions on the information from the domain.
Further, software design advocates often talk about so-called “non-functional requirements” as the goals of software design, particularly maintainability or readability. These are worthy things to strive for, but in my opinion, the best way to get maintainable and readable code is to make sure your domain model is good. A bad domain model begs for messy, hard-to-modify code. A good domain model makes readable code possible.
Introduction
How to use this book
14
There are three guiding points I’d like to make about how you can best use this book.
Read the chapters straight through
I’ve written the chapters to be read in order. Some of them build upon the previous ones, so they are not meant to be read independently. They are also ordered to start with the most important and fundamental skills and build from there.
Do the exercises
This book teaches practical skills, and to do that you need to get some practice. To maximize what you get out of this book, do the exercises before moving on. Each exercise has been designed to practice what was just discussed in the text. And when you’re done, you can compare against the answer. Since it’s code, there is rarely a single correct answer, but it can help to see how someone else has done it.
Note that in the early release version (that you are reading now), there are very few exercises. I will add more later as I get closer to publication.
Use the supplements as reference
Some chapters are followed by supplements. You can read the supplements as you get to them, but feel free to skim or skip them. They are meant to be used as a reference for some of the material. For instance, the Data Lens Supplement contains common relationship structures and lists how they are typically encoded.
Conclusion
In this introduction, we understood software design as making decisions, and saw ways to improve our decision making. We also learned about domain modeling, runnable specifications, and how they relate to other software design methodologies. Finally, we learned how to use the rest of the book. I hope this introduction convinces you to read the rest of the book.
Summary
Up next . . .
We’ve set the stage, now it’s time to get into the nitty gritty. We’ll start with the data because that’s a skill we’ve all developed to an extent. But we’ll use it as an entry point into using the domain as the main source of information.
15
Introduction