M A N N I N G Maurício Aniche Create clean, maintainable applications
Keeping units of code small Keeping objects consistent Managing dependencies Designing good abstractions Handling infrastructure and external dependencies Achieving modularization Make units of code small Make code readable and documented Move new complexity away from existing classes Ensure consistency at all times Design effective data validation mechanisms Encapsulate state checks Provide only getters and setters that matter Model aggregates to ensure invariants in clusters of objects Separate high-level and low-level code Avoid coupling to details or things you don’t need Break down classes that depend on too many other classes Inject dependencies Design abstractions and extension points Generalize important business rules Prefer simple abstractions Separate infrastructure from the domain code Make the best use of your infrastructure Only depend on things you own Encapsulate low-level infrastructure errors into high-level domain errors Build deep modules Design clear interfaces No intimacy between modules Simple object-oriented design The principles behind simple object-oriented design
Simple Object-Oriented Design
(This page has no text content)
Simple Object-Oriented Design CREATE CLEAN, MAINTAINABLE APPLICATIONS MAURÍCIO ANICHE M A N N I N G SHELTER ISLAND
For online information and ordering of this and other Manning books, please visit www.manning.com. The publisher offers discounts on this book when ordered in quantity. For more information, please contact Special Sales Department Manning Publications Co. 20 Baldwin Road PO Box 761 Shelter Island, NY 11964 Email: orders@manning.com ©2024 by Manning Publications Co. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps. Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books we publish printed on acid-free paper, and we exert our best efforts to that end. Recognizing also our responsibility to conserve the resources of our planet, Manning books are printed on paper that is at least 15 percent recycled and processed without the use of elemental chlorine. The author and publisher have made every effort to ensure that the information in this book was correct at press time. The author and publisher do not assume and hereby disclaim any liability to any party for any loss, damage, or disruption caused by errors or omissions, whether such errors or omissions result from negligence, accident, or any other cause, or from any usage of the information herein. Manning Publications Co. Development editor: Toni Arritola 20 Baldwin Road Technical editor: Matthias Noback PO Box 761 Review editor: Isidora Isakov Shelter Island, NY 11964 Production editor: Keri Hales Copy editor: Tiffany Taylor Proofreader: Katie Tennant Technical proofreader: Srihari Sridharan Typesetter: Gordan Salinovic Cover designer: Marija Tudor ISBN 9781633437999 Printed in the United States of America
To Laura, Thomas, Bono, and Duke, my lovely family/team
vi brief contents 1 ■ It’s all about managing complexity 1 2 ■ Making code small 19 3 ■ Keeping objects consistent 41 4 ■ Managing dependencies 70 5 ■ Designing good abstractions 91 6 ■ Handling external dependencies and infrastructure 112 7 ■ Achieving modularization 135 8 ■ Being pragmatic 156
vii contents preface xii acknowledgments xiv about this book xvi about the author xx about the cover illustration xxi 1 It’s all about managing complexity 1 1.1 Object-oriented design and the test of time 2 1.2 Designing simple object-oriented systems 3 Simple code 4 ■ Consistent objects 5 ■ Proper dependency management 7 ■ Good abstractions 8 ■ Properly handled external dependencies and infrastructure 8 ■ Well modularized 9 1.3 Simple design as a day-to-day activity 10 Reducing complexity is similar to personal hygiene 11 Complexity may be necessary but should not be permanent 11 Consistently addressing complexity is cost effective 11 High-quality code promotes good practices 11 ■ Controlling complexity isn’t as difficult as it seems 12 ■ Keeping the design simple is a developer’s responsibility 12 Good-enough designs 12
CONTENTSviii 1.4 A short dive into the architecture of an information system 12 1.5 The example project: PeopleGrow! 15 1.6 Exercises 17 2 Making code small 19 2.1 Make units of code small 20 Break complex methods into private methods 22 ■ Move a complex unit of code to another class 23 ■ When not to divide code into small units 23 ■ Get a helicopter view of the refactoring before you do it 24 ■ Example: Importing employees 24 2.2 Make code readable and documented 28 Keep looking for good names 28 ■ Document decisions 30 Add code comments 30 ■ Example: Deciding when to send an update email 32 2.3 Move new complexity away from existing classes 34 Give the complex business logic a class of its own 35 ■ Break down large business flows 36 ■ Example: Waiting list for offerings 37 2.4 Exercises 40 3 Keeping objects consistent 41 3.1 Ensure consistency at all times 42 Make the class responsible for its consistency 43 Encapsulate entire actions and complex consistency checks 43 ■ Example: The Employee entity 45 3.2 Design effective data validation mechanisms 48 Make preconditions explicit 48 ■ Create validation components 50 ■ Use nulls carefully or avoid them if you can 52 ■ Example: Adding an employee to a training offering 54 3.3 Encapsulate state checks 57 Tell, don’t ask 58 ■ Example: Available spots in an offering 59
CONTENTS ix 3.4 Provide only getters and setters that matter 60 Getters that don’t change state and don’t reveal too much to clients 60 ■ Setters only to attributes that describe the object 61 ■ Example: Getters and setters in the Offering class 61 3.5 Model aggregates to ensure invariants in clusters of objects 63 Don’t break the rules of an aggregate root 64 ■ Example: The Offering aggregate 65 3.6 Exercises 69 4 Managing dependencies 70 4.1 Separate high-level and low-level code 71 Design stable code 72 ■ Interface discovery 72 ■ When not to separate the higher level from the lower level 73 Example: The messaging job 73 4.2 Avoid coupling to details or things you don’t need 76 Only require or return classes that you own 77 ■ Example: Replacing the HTTP bot with the chat SDK 79 ■ Don’t give clients more than they need 80 ■ Example: The offering list 81 4.3 Break down classes that depend on too many other classes 82 Example: Breaking down the MessageSender service 83 4.4 Inject dependencies, aka dependency injection 86 Avoid static methods for operations that change the state 87 ■ Always inject collaborators: Everything else is optional 88 ■ Strategies to instantiate the class together with its dependencies 88 ■ Example: Dependency injection in MessageSender and collaborators 89 4.5 Exercises 90
CONTENTSx 5 Designing good abstractions 91 5.1 Design abstractions and extension points 92 Identifying the need for an abstraction 93 ■ Designing an extension point 94 ■ Attributes of good abstractions 95 Learn from your abstractions 96 ■ Learn about abstractions 96 ■ Abstractions and coupling 97 Example: Giving badges to employees 97 5.2 Generalize important business rules 102 Separate the concrete data from the generalized business rule 103 ■ Example: Generalizing the badge rules 105 5.3 Prefer simple abstractions 108 Rules of thumb 108 ■ Simple is always better 109 Enough is enough 109 ■ Don’t be afraid of modeling abstractions from day one 110 ■ Example: Revisiting the badge example 110 5.4 Exercises 111 6 Handling external dependencies and infrastructure 112 6.1 Separate infrastructure from the domain code 114 Do you need an interface? 115 ■ Hide details from the code, not from the developers 118 ■ Changing the infrastructure someday: Myth or reality? 119 ■ Example: Database access and the message bot 120 6.2 Use the infrastructure fully 122 Do your best not to break your design 123 ■ Example: Cancelling an enrollment 124 6.3 Only depend on things you own 127 Don’t fight your frameworks 129 ■ Be aware of indirect leakage 130 ■ Example: Message bot 131 6.4 Encapsulate low-level infrastructure errors into high-level domain errors 132 Example: Handling exceptions in SDKBot 133 6.5 Exercises 134
CONTENTS xi 7 Achieving modularization 135 7.1 Build deep modules 137 Find ways to reduce the effects of changes 138 ■ Keep refining your domain boundaries 139 ■ Keep related things close 139 ■ Fight accidental coupling, or document it when you can’t 140 7.2 Design clear interfaces 140 Keep the module’s interface simple 141 ■ Backward- compatible modules 142 ■ Provide clean extension points 144 ■ Code as if your module will be used by someone with different needs 144 ■ Modules should have clear ownership and engagement rules 145 7.3 No intimacy between modules 147 Make modules and clients responsible for the lack of intimacy 147 ■ Don’t depend on internal classes 148 Monitor the web of dependencies 150 ■ Monoliths or microservices? 151 ■ Consider events as a way to decouple modules 151 ■ Example: The notification system 152 7.4 Exercises 154 8 Being pragmatic 156 8.1 Be pragmatic and go only as far as you must 157 8.2 Refactor aggressively but in small steps 157 8.3 Accept that your code won’t ever be perfect 157 8.4 Consider redesigns 158 8.5 You owe this to junior developers 158 8.6 References 159 8.7 Exercises 159 index 161
xii preface Why write another book on object-oriented design when so many are out there? This was the question I had to answer for myself before embarking on this project. We already possess a wealth of knowledge about object-oriented design from the early works of Dave Parnas, Grady Booch’s books on UML and object-oriented analysis, and Eric Evans’ domain-driven design approach. However, object-oriented design is not merely a pure engineering task; it transcends into art. No prescribed sequence of steps will unfailingly lead us to an optimal design. Instead, object-oriented design demands a creative approach. This book delves into object-oriented design from two specific angles: how to prevent the complexity of a system from skyrocketing and how to achieve “good-enough” designs. First, most of a developer’s work revolves around maintaining and evolving existing systems. Unfortunately, without due care, every time you make changes to a software system, it becomes more com- plex, even if it is well designed from the outset. Therefore, this book greatly emphasizes how to combat the natural growth in complexity.
PREFACE xiii Second, more often than not, you initially have limited knowl- edge about what you’re building. Despite your best efforts, your first design may fall short. However, that’s acceptable if you arrive at a good-enough design. The purpose of this book is not to lead you to always achieve the absolute best possible design, but to enable you to create good designs that empower you to build software effectively. If you are familiar with the existing literature on object-oriented design, you will recognize many of the principles discussed here. Much of my perspective on good modeling has been inspired by exist- ing work. However, I’ve infused my own flavor into these ideas. I hope even seasoned developers can glean a thing or two from this book. Happy reading!
xiv acknowledgments First, I want to thank Dr. Ismar Frango Silveira. Ismar was the teacher of my first-ever formal course on object-oriented design during my undergrad studies back in 2004. The class was an eye- opener for me. Since then, I’ve been working diligently to sharpen my skills, but his instruction was the foundation. Although it’s been a long time since we spoke last, I’ve never forgotten his contribution to my career. I would also like to thank Alberto Souza. Besides being one of my best friends in life, Alberto loves good code as much as I do. Despite living one ocean apart, we still find ways to keep in touch not only about life but also about software engineering. Our conversations have always kept me sharp, and many of my thoughts on class design are influenced by his point of view. I would like to express my gratitude to Toni Arritola, my develop- ment editor at Manning. She has been a great partner on this jour- ney, offering numerous valuable suggestions and being an attentive listener. Whenever I was running low in energy, she consistently pro- vided me with a fresh boost. I also must thank Matthias Noback, trainer and consultant at Noback’s Office, who was the technical
ACKNOWLEDGMENTS xv editor for this book. He made many insightful comments that were very helpful. In addition, many thanks go to the behind-the-scenes production staff who helped create this book in its final form. To all the reviewers—Adail Retamal, Amit Lamba, Brent Honadel, Colin Hastie, Daivid Morgan, Emanuele Origgi, George Onofrei, Gilbert Beveridge, Goetz Heller, Harsh Raval, Helder da Rocha, Iago Sanjurjo Alvarez, Ismail Tapaal, Juan Durillo, Karl van Heijster, Laud Bentil, Marcus Geselle, Mikael Byström, Mustafa Özçetin, Najeeb Arif, Narayanan Jayaratchagan, Nedim Bambur, Nghia To, Nguyen Tran Chau Giang, Oliver Korten, Patrice Malda- gue, Peter Szabo, Ranjit Sahai, Robert Trausmuth, Sebastian Palma, Sergio Gutierrez, and Victor Durán—thank you. Your suggestions helped make this a better book. A special thank you goes to Paulo Afonso Parreira, Jr., who sent me very helpful, detailed feedback on the manuscript. Finally, I would like to thank Laura, my wife. She always supports whatever project I decide to start. Without her support, none of this would be possible.
xvi about this book Simple Object-Oriented Design presents a set of principles that help developers keep the complexity of their designs under control—in other words, keep it simple. The principles can be grouped into six higher-level ideas: Small units of code Consistent objects Proper dependency management Good abstractions Properly handled infrastructure Well modularized Who should read this book Simple Object-Oriented Design is a book for software developers who want to sharpen their object-oriented design skills. We discuss code complexity, consistency and encapsulation, dependency manage- ment, designing abstractions, handling infrastructure, and modular- ization in depth. If you are an advanced developer who is familiar with similar approaches, such as clean architecture, you’ll still bene- fit from this book’s unique perspective.
ABOUT THIS BOOK xvii Readers must have basic knowledge of object-oriented concepts such as classes, polymorphism, and interfaces. The code examples are written in pseudo-Java code but can be understood by develop- ers familiar with any object-oriented programming language such as C#, Python, or Ruby. How this book is organized: A road map This book contains object-oriented design principles derived from my experience. The principles are grouped into six dimensions (complexity, consistency, dependency management, abstractions, infrastructure, and modularization), one per chapter. Principles are first introduced theoretically and later illustrated with code examples. No new ideas are introduced in the code exam- ples, so more experienced readers can skip them if they wish. You’ll also notice that the examples are small in terms of lines of code and complexity. It’s impractical to illustrate all the principles in this book with real-world examples from large-scale software systems. Instead, I demonstrate the ideas with small snippets, and it’s up to you, the reader, to generalize the idea. I do my best to provide context, pros and cons, tradeoffs, and when and when not to apply the principles. Nevertheless, as with any best practice, you should consider your context and not blindly adopt what you find here. Chapters end with a few exercises in which I ask you to discuss the ideas covered in that chapter with a colleague. They are broad and open on purpose. I don’t provide answers to these questions because there are no one-size-fits-all responses. Chapter 1 introduces why systems become complex over time, why we must continuously combat the growth of complexity, and why this endeavor is less painful than it may seem. Then, chapters 2–7 delve into the six higher-level ideas. Chapter 2 discusses the importance of keeping code simple. In a nutshell, it covers how to break large units of code into smaller pieces, isolate new complexity from existing code units, and docu- ment code effectively to improve understanding.
ABOUT THIS BOOKxviii Chapter 3 focuses on maintaining objects’ consistency at all times. It explores the problems that arise when objects fall into an inconsistent state and how to implement validation mechanisms that ensure objects remain consistent throughout. Chapter 4 delves into dependencies and how managing them properly is fundamental for a simple design. It explains how to reduce the effect of coupling in the design, how to model stable classes that are less likely to change, and why dependency injection is crucial for effective dependency management. Chapter 5 discusses abstractions and how to design them to facili- tate software system evolution without altering numerous classes each time. Chapter 6 concentrates on handling infrastructure code without compromising the design. The chapter explains how to decouple infrastructure code from the domain, allowing changes in one with- out affecting the other. Chapter 7 explores modularity: specifically, how to design mod- ules that provide complex features through simple interfaces, how to minimize dependencies among modules, and the importance of defining ownership and engagement rules. Chapter 8 offers some final advice about the importance of being pragmatic, the necessity for continuous refactoring, and the value of perpetual learning about object-oriented design. About the code This book contains many examples of source code both in num- bered listings and in line with normal text. In both cases, source code is formatted in a fixed-width font like this to separate it from ordi- nary text. In many cases, the original source code has been reformatted; we’ve added line breaks and reworked indentation to accommodate the available page space in the book. Additionally, comments in the source code have often been removed from the listings when the code is described in the text. Code annotations accompany many of the listings, highlighting important concepts.
Comments 0
Loading comments...
Reply to Comment
Edit Comment