Robillard Introduction to Softw are Design w ith Java Introduction to Software Design with Java Martin P. Robillard Third Edition 3rd Ed.
Introduction to Software Design with Java
Martin P. Robillard Introduction to Software Design with Java Third Edition
Martin P. Robillard School of Computer Science McGill University Montreal, QC, Canada ISBN 978-3-032-11820-2 ISBN 978-3-032-11821-9 (eBook) https://doi.org/10.1007/978-3-032-11821-9 © Springer Nature Switzerland AG 2019, 2022, 2026 This work is subject to copyright. All rights are solely and exclusively licensed by the Publisher, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way, and transmission or information storage and retrieval, electronic adaptation, computer software, or by similar or dissimilar methodology now known or hereafter developed. The use of general descriptive names, registered names, trademarks, service marks, etc. in this publication does not imply, even in the absence of a specific statement, that such names are exempt from the relevant protective laws and regulations and therefore free for general use. The publisher, the authors and the editors are safe to assume that the advice and information in this book are believed to be true and accurate at the date of publication. Neither the publisher nor the authors or the editors give a warranty, expressed or implied, with respect to the material contained herein or for any errors or omissions that may have been made. The publisher remains neutral with regard to jurisdictional claims in published maps and institutional affiliations. Cover Photograph: © 2017 Circlecreativestudio/iStock This Springer imprint is published by the registered company Springer Nature Switzerland AG The registered company address is: Gewerbestrasse 11, 6330 Cham, Switzerland If disposing of this product, please recycle the paper.
Preface The goal of this book is to help readers learn software design by discovering the experience of the design process. I share my knowledge and experience of software design through a narrative that introduces each element of design know-how in con- text, and explores alternative solutions in that context. The narrative is supported by hundreds of code fragments and design diagrams. This book is grounded in two decades of teaching software design at McGill University. Initially, my approach was to explain the existing software design know- how. However, I soon realized that the main challenge of teaching software design lay not in communicating how to apply a given design technique, but rather in which context and, most importantly, for what reason. My hope is that this book can serve as an effective resource for learning soft- ware design. However, I do not believe that it is possible to develop significant design skills solely by reading a book. In my own learning process, I have benefited hugely from reading other people’s code, regularly writing code, and relentlessly refactoring existing code to experiment with alternative design solutions. For this reason, this book emphasizes coding and experimentation as a necessary comple- ment to reading the text. To support this aspect of the learning process, I provide a companion website with practice exercises, and two sample applications that illus- trate numerous design decisions. An orientation through these sample applications is provided in Code Exploration insets throughout the chapters. As its title indicates, this book provides an introduction to software design us- ing the Java programming language. The code used throughout the book, as well as the sample applications, are in Java. The Java programming language, however, is a means to communicate design ideas, and not the topic of the book. I aimed to cover design concepts and techniques that are applicable in a host of technologies. Many concepts, such as encapsulation, will be relevant in any technology. Others, such as inheritance, will be paradigm-specific, but relevant in multiple program- ming languages. For both general and paradigm-specific information, it should be straightforward to adapt the examples to other programming languages. In a few cases, I address a Java-specific mechanism with implications on design. In such cases, the mechanism is presented as one realization of a more general idea. v
in a given chapter. discussions. fragments can be downloaded from the companion website (see below). Prefacevi This book is targeted at readers who have a minimum of programming experience and want to move from writing small programs and scripts to tackling the develop- ment of larger systems. This audience naturally includes students in university-level computer science and software engineering programs. However, I kept the prerequi- sites to specialized computing concepts to a minimum, so that the content is also ac- cessible to programmers without a primary training in computing. In a similar vein, understanding the code fragments requires only a minimum knowledge of Java, such as would be taught in an introductory programming course. Information about Java that is crucial to understand the text is provided in an appendix, more advanced features are introduced and explained as necessary, and I make a minimum of refer- ences to elements of the language’s class library. My hope is thus that the book can be useful to anyone who wants to write clean, well-designed software. Organization of the Book The first chapter is a general introduction to software design. The subsequent chap- ters provide a progressive coverage of design concepts and techniques presented as a continuous narrative anchored in specific design problems. In addition to the main content, the book includes different features to orient readers and help use the book as a launchpad for further exploration and learning. • Chapter Overview: At the beginning of each chapter, a callout lists the concepts, principles, patterns, and antipatterns covered in the chapter. • Design Context: Following the overview, a paragraph titled Design Context in- troduces the design contexts that are used as running examples in the chapter. It is thus not necessary to read all previous chapters to understand the code discussed • Diagrams: Each chapter includes numerous diagrams that illustrate design ideas. Although they are provided to illustrate the ideas in the text, the diagrams are also realistic illustrations of diagrams that can be used in practice as part of design • Code Fragments: Each chapter includes many code fragments. The code gen- erally follows the conventions presented in Appendix B, with occasional con- cessions made to make the code more compact. A complete version of the code • Insights: In each chapter, the main numbered sections are followed by an un- numbered section titled Insights. This section forms an actionable summary of the key information and advice provided in the chapter. It is meant as a catalog of applicable design knowledge, and assumes the material in the chapter has been mostly assimilated. The insights are in bullet points to be easily perused. • Code Exploration: At various points in the text, insets titled Code Exploration provide a discussion of software design in practice. To facilitate good flow and avoid getting lost in details, the design contexts discussed in the main chapter text are kept as simple as possible. As a result, some interesting aspects of the
viiPreface software design experience can get lost in the simplification. The code explo- ration activity is the opportunity to consider how some of the topics presented in the chapter manifest themselves in practice. The Code Exploration insets point to specific parts of the code of sample applications. In concert with reading the text of a Code Exploration inset, I recommend reviewing the code referenced and trying to understand it as much as possible. The sample applications are de- scribed in Appendix C. They include JetUML, the application used to create all the diagrams in the book. • Further Reading: The Further Reading section provides pointers to references that complement the material presented in the chapter. • Index and Lists of Concepts: The book includes a detailed index. In addition to a catalog of key terms in context, the index includes lists of related signifi- cant terms covered in the book. These include the list of: design patterns, design antipatterns, design principles, and UML diagrams. Companion Resources Additional resources for this book are available on GitHub at https://github. com/prmr/DesignBook. The material in that repository includes a complete and commented version of the code that appears in the text, as well as practice exercises and their solution. All the code samples in the book are also available in a special interactive format at https://codesample.info. Each webpage on that site is a complete Java code example annotated with additional explanations that can be revealed on demand. The website also contains supplementary code samples on the essentials of Java programming, and on how to use common Java library classes. Two complete sample Java applications, an interactive card game and a diagram- ming tool, are provided as a basis for additional study and exploration. The two applications were developed following many of the principles and techniques de- scribed in the book. A description of the sample applications and instructions for accessing their code is provided in Appendix C. Acknowledgments I am most grateful to Mathieu Nassif, who carried out a detailed technical review of the entire manuscript of the first edition, providing me with hundreds of corrections, suggestions, and interesting points for discussion. I warmly thank Jin Guo for reviewing most of the chapters, providing me feed- back on later editions, and testing some of the material in her own teaching. Alexa Hernandez, Kaylee Kutschera, Brigitte Pientka, Clark Verbrugge, and Maurício Aniche provided feedback on various parts of the manuscript. I heartily thank Ralf Gerstner, the editorial director in charge of computer science at Springer, for believ-
Prefaceviii ing in the project from the start and for seeing it through its multiple phases with his usual diligence and professionalism. As this is the third edition, I am very grateful to the readers and to the instructors who have adopted the book to support their teaching and learning. The enthusiasm many have expressed has been a major source of motivation for me to continue this project. The feedback I received, and the numerous interesting discussions I had about the content, were an invaluable contribution to this revised text. Martin P. Robillard July 2025
Contents 1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1 Defining Software Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2 Design in the Software Development Process . . . . . . . . . . . . . . . . . . . 6 1.3 Capturing Design Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.4 Sharing Design Know-How . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2 Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.1 Encapsulation and Information Hiding . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.2 Encoding Abstractions as Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.3 Scopes and Accessibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.4 Object Diagrams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.5 Escaping References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.6 Immutability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.7 Exposing Internal Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 2.8 Input Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 2.9 Design by Contract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3 Types and Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.1 Decoupling Behavior from Implementation . . . . . . . . . . . . . . . . . . . . . 43 3.2 Specifying Behavior with Interface Types . . . . . . . . . . . . . . . . . . . . . . 46 3.3 Class Diagrams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3.4 Function Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.5 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 3.6 The ITERATOR Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.7 The STRATEGY Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 3.8 Dependency Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.9 The Interface Segregation Principle . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 ix
Contentsx Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 4 Object State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.1 The Static and Dynamic Perspectives of a Software System . . . . . . . 67 4.2 Defining Object State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 4.3 State Diagrams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.4 Nullability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 4.5 Final Fields and Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 4.6 Object Identity, Equality, and Uniqueness . . . . . . . . . . . . . . . . . . . . . . 82 4.7 The FLYWEIGHT Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.8 The SINGLETON Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 4.9 Objects of Inner Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 5 Unit Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 5.1 Introduction to Unit Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 5.2 Unit Testing Framework Fundamentals with JUnit . . . . . . . . . . . . . . . 100 5.3 Organizing Test Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 5.4 Metaprogramming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 5.5 Structuring Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 5.6 Testing Exceptional Conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 5.7 Encapsulation and Unit Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 5.8 Testing with Stubs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 5.9 Test Coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 6 Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 6.1 Composition and Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 6.2 The COMPOSITE Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 6.3 Sequence Diagrams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 6.4 The DECORATOR Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 6.5 Combining COMPOSITE and DECORATOR . . . . . . . . . . . . . . . . . . . . . 142 6.6 Polymorphic Copying . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 6.7 The PROTOTYPE Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 6.8 The COMMAND Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 6.9 The Law of Demeter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
xiContents 7 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 7.1 The Case for Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 7.2 Inheritance and Typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 7.3 Inheriting Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 7.4 Inheriting Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 7.5 Overloading Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 7.6 Polymorphic Copying with Inheritance . . . . . . . . . . . . . . . . . . . . . . . . 171 7.7 Inheritance Versus Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 7.8 Abstract Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 7.9 The DECORATOR Pattern with Abstract Classes . . . . . . . . . . . . . . . . . 181 7.10 The TEMPLATE METHOD Design Pattern . . . . . . . . . . . . . . . . . . . . . . 183 7.11 Proper Use of Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 8 Inversion of Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 8.1 Motivating Inversion of Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194 8.2 The Model–View–Controller Decomposition . . . . . . . . . . . . . . . . . . . 196 8.3 The OBSERVER Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 8.4 Applying the OBSERVER Design Pattern . . . . . . . . . . . . . . . . . . . . . . . 207 8.5 Introduction to Graphical User Interface Development . . . . . . . . . . . . 214 8.6 Graphical User Interface Component Graphs . . . . . . . . . . . . . . . . . . . . 217 8.7 Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 8.8 The VISITOR Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 9 Functional Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 9.1 First-Class Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 9.2 Functional Interfaces, Lambda Expressions, and Method References 243 9.3 Using Functions to Compose Behavior . . . . . . . . . . . . . . . . . . . . . . . . . 250 9.4 Using Functions to Supply, Consume, and Map Objects . . . . . . . . . . 254 9.5 First-Class Functions and Design Patterns . . . . . . . . . . . . . . . . . . . . . . 258 9.6 Functional-Style Data Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262 Insights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 A Essential Java Programming Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 A.1 Variables and Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 A.2 Objects and Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274 A.3 Static Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275 A.4 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275 A.5 Packages and Importing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 A.6 Generic Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 A.7 Collection Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
xii Contents A.8 Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 B Coding Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 C Sample Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Chapter 1 Introduction In 1988, a fascinating little piece of code hits the limelight. That year, one of the winners of the annual International Obfuscated C Code Contest features a program that writes out to the terminal console the text of an eighteenth-century poem titled The Twelve Days of Christmas. Figure 1.1 shows the first three verses of the text, as they appear on the output console when executing the code. This poem is particular in that its text has a regular structure. Text with such a structure is amenable to being constructed by software in a way that goes beyond printing hard-coded data. With a poem like The Twelve Days of Christmas, there was thus opportunity for creating a clear and compact solution for displaying a poem on the console. However, as promised by the name of the contest where it was featured, the program is anything but clear. If fact, its inner workings are unfathomable. Figure 1.2 reproduces the complete code of the program. On the first day of Christmas my true love gave to me a partridge in a pear tree. On the second day of Christmas my true love gave to me two turtle doves and a partridge in a pear tree. On the third day of Christmas my true love gave to me three French hens, two turtle doves and a partridge in a pear tree. ... Fig. 1.1 Partial output of The Twelve Days of Christmas program of Figure 1.2 This quirky piece of computer science trivia illustrates the impact of a lack of self-evident structure in software. Here, we have a programming problem with triv- ial requirements: the functionality of interest requires no input and produces a sin- © Springer Nature Switzerland AG 2026 M. P. Robillard, Introduction to Software Design with Java, https://doi.org/10.1007/978-3-032-11821-9_1 1
2 1 Introduction main(t,_,a ) char* a;{return!0<t?t<3?main(-79,-13,a+main(-87, 1-_,main(-86, 0,a+1 )+a)):1,t<_?main( t+1, _, a ):3,main(-94, -27+t, a )&&t == 2 ?_<13 ? main ( 2, _+1,"%s %d %d\n" ):9:16: t<0?t<-72?main( _, t,"@n’+,#’/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+\ ,/w{%+,/w#q#n+,/#{l,+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/’r :’d*’\ 3,}{w+K w’K:’+}e#’;dq#’l q#’+d’K#!/+k#;q#’r}eKK#}w’r}eKK{nl]\ ’/#;#q#n’){)#}w’){){nl]’/+#n’;d}rw’ i;# ){nl]!/n{n#’; r{#w’r\ nc{nl]’/#{l,+’K {rw’ iK{;[{nl]’/w#q#n’wk nw’ iwk{KK{nl]!/w{\ %’l##w#’ i; :{nl]’/*{q#’ld;r’}{nlwb!/*de}’c ;;{nl’-{}rw]’/+,\ }##’*}#nc,’,#nw]’/+kd’+e}+;#’rdq#w! nr’/ ’) }+}{rl#’{n’ ’)# \ }’+}##(!!/"):t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main ((*a == ’/’)+t, _,a+1):0<t?main ( 2, 2 , "%s"):*a==’/’||main (0,main(-61,*a,"!ek;dc i@bK’(q)-[w]*%n+r3#l,{}:\nuwloca-O;m\ .vpbks,fxntdCeghiry"),a+1);} Fig. 1.2 Source code of the 1988 The Twelve Days of Christmas C program by Ian Phillips. This code compiles and executing it will produce the output illustrated in Figure 1.1. © 1988, Landon Curt Noll and Larry Bassel. Reproduced with permission. gle, unchangeable output. Yet, the code to support this functionality cannot be un- derstood by a normal human being. But what is the problem, if the code works? Software needs to change, and for software to change, at least one person must be involved at some point. Software needs to change for a variety of reasons, from fixing bugs to adapting the code to an evolving world. For example, many of the gifts referred to in the poem are European birds (e.g., partridge, turtle doves, French hens). Contemporary software development best practices include the localization of software applications, namely, the option to tailor a software application to ac- count for region-specific characteristics. It would thus be nice to adapt the code of the application to replace the name of European birds to some that readers could re- late to based on their own region (for example, to replace partridge with turkey for North American users). To modify a piece of code, however, one must understand its structure, and this structure must, to a certain extent, accommodate the change. In the case of The Twelve Days of Christmas, any ambition to ever change the code is hopeless. The example of The Twelve Days of Christmas is facetious for the sake of illus- tration. Because this code was obfuscated on purpose, it would be comforting if we could discount it as irrelevant. Unfortunately, because writing messy code is often the path of least resistance in the complex social, technological, and economic real- ity of software development, badly designed code is not hard to find. For example, in a famous high-profile case where automotive software was determined by the courts to be responsible for a fatal accident, the experts who reviewed the software likened its structure to that of a bowl of spaghetti. Whether code is cryptic purpose- fully or accidentally, the result is similar: it is hard to understand and change without introducing errors.
1 Introduction 3 To explore the contrast, let us design a version of the program where the structure is evident. Consistently with the rest of the code in this book, the program is in Java. First, we tackle the issue of producing the first line of a verse: static String[] DAYS = {"first", "second", ..., "twelfth"}; static String firstLine(int day) { return "On the " + DAYS[day] + " day of Christmas my true love gave to me:\n"; } This code is clear because the function is short, it abstracts an obvious concept (the creation of the first line), and the only parameterization involved maps directly to the problem domain (changing the day). The second sub-problem is to create the list of gifts for a given day. In this case we can leverage the inherent recursion in the poem’s structure to organize the code in a function that creates a list of gifts by adding the last gift to a smaller list of gifts: static String[] GIFTS = { "a partridge in a pear tree", "two turtle doves", ... }; static String allGifts(int day) { if (day == 0) { return "and " + GIFTS[0]; } else { return GIFTS[day] + "\n" + allGifts(day-1); } } The allGifts function provides a classic implementation of a recursive algo- rithm. In this case, the code’s structure is explicit because it directly realizes a foun- dational strategy in computing. At this point the only thing left it to put the poem together by assembling the twelve verses. Here, the only small issue is that, in the first verse, we do not add the word and in front of a partridge. No matter how small a program, it can be difficult to completely avoid annoying corner cases. static String poem() { String poem = firstLine(0) + GIFTS[0] + "\n\n"; for (int day = 1; day < 12; day++) { poem += firstLine(day) + allGifts(day) + "\n\n"; } return poem; } At a glance, we see the overall structure of the code: a special case for the first verse, then an iteration through the remaining eleven verses, where each verse is created by concatenating the output of two functions: one to create the first line, and the other to create the list of gifts.
4 1 Introduction 1.1 Defining Software Design Software design is a mysterious activity. For many software development projects, “the design” is not necessarily something one can retrieve and look at. Similarly, very few people walk around with the title of “software designer”. In that sense, designing software is not like designing furniture or clothing. The word design is both a verb and a noun, so it can refer to both a process (to de- sign) and the outcome of this process (a design). My working definition of software design (the process) is the construction of abstractions of data and computation and the organization of these abstractions into a working software application. At first this may sound overly restrictive, but when we consider everything that the term abstraction can mean (variables, classes, objects, etc.), we see that we are afforded quite a bit of flexibility for interpreting what software design means. In practice, the design process is essentially one of decision making. Should we use a list or a stack? What services should this interface offer? Where should this error be handled? Considering design as decision making leads to the concept of a design space. A design space can be imagined as an n-dimensional geometric space where each dimension corresponds to a design quality attribute. Typical de- sign quality attributes for software include understandability, reusability, and ease of implementation. Within such a design space, each specific design decision (or co- herent set of decisions) corresponds to a coordinate in the space that represents the consequence of the decision. Figure 1.3 illustrates the idea with two dimensions. In practice, any design decision is likely to be good in some dimension, but less good in other dimensions, something we call a design trade-off. Two partitions of the design space that are useful to consider are the space of possible solutions, and the space of acceptable solutions. We can observe that the ideal solution, which is optimal in all dimensions, is unlikely to be possible. In other words, given a design problem, there is not necessarily a single solution that is the “right answer”, only solutions that are better or worse in some dimensions (but including some solutions that are pretty bad in most dimensions). The concept of a design space may make it look like selecting a design deci- sion is a systematic process. This is not the case. Where the analogy breaks down is that a geometric space is completely defined, whereas the reality of software design is rife with uncertainty. First, not all possible decisions are known and, in com- plex situations, there may be an infinity of them. Second, estimating to what extent a design decision fulfills a given quality attribute (e.g., understandability) is an ap- proximate process. Consequently, there is no standard formula for arriving at a point in the design space. In most realistic software development contexts, it will not be the case that to design and implement a software requirement, we can follow a pre- determined set of steps. Software design is a heuristic process: it consists of iterative problem-solving guided by experience and know-how (see Section 1.4). In fact, the heuristic nature of the software design process is what makes it an exciting creative activity. The quality attributes that constitute the dimensions of the design space also correspond to the general goals of design. One of the most important goals for soft-
51.1 Defining Software Design Dimension A D im en si on B Optimality Possible Acceptable Fig. 1.3 A hypothetical design space ware design is to reduce the complexity of software, which means making it easier to understand. Cleanly-designed code that is easy to understand is less error-prone and easier to modify. In contrast, messy code obscures the important decisions of its original developers. When developers ignore existing design constraints, they risk modifying code in a way that does not agree with the original structure, and thereby introduce errors and generally degrade the quality of the code. The problem of modifying code in a way that does not respect the original structure has been called ignorant surgery. In general, the relative importance of design goals depends on the context in which a piece of software is being designed. A design context (or problem) is a specific set of requirements and constraints within a domain in which a design solu- tion must be found and integrated. For example, because of economic or contractual reasons, it may be required to design a particular piece of software to maximize its reusability. Or, if a piece of software is intended to be integrated into safety-critical applications, it may be more important to prioritize robustness (i.e., resilience to errors). In this book, I give a lot of importance to the understandability quality at- tribute. I try to emphasize designs where the code itself reveals the underlying design decisions and the intent behind these design decisions. The idea of having design decisions be self-evident in code is a property I call sustainability. If we consider that the design process is a series of decisions-making activities about software abstractions, then it follows that a good definition for a design is a cohesive collection of these decisions. This definition for a design artifact is suffi- ciently general to avoid dictating the medium in which the design is captured. In formal software development settings, this could be an official standardized design
6 1 Introduction document. In less formal contexts, design decisions could be stored in the code, di- agrams, or various documentation pages associated with the project. In the extreme, design decisions could exist only in the mind of the developers who made them. Because people tend to forget or misremember, this latter approach is best kept to a minimum. Section 1.3 provides an overview of how design knowledge can be captured. 1.2 Design in the Software Development Process Design is only one of the many activities that take place during the development of a software system. There is an abundant literature on different process models for software development. A process model describes (and sometimes prescribes) how the different steps required to create a system are organized. Different process mod- els offer different ways of doing things for different reasons. In the early days of the software engineering discipline it was believed that a planning-heavy process, exemplified by the waterfall software process model, was the desirable way to build high-quality software. However, in the mid-1990s this belief was challenged by a movement towards a more organic approach to software development, also called agile development. In practice, ideas about how to best develop software keep evolv- ing, and in the end the important things are to have a development process in the first place, and for that process to be well-adapted to the type of system being developed and the organization that develops it. For example, the process used by an organiza- tion to develop a prototype for a video game would probably be different from the process used to develop banking or aeronautical software. The issue of devising, adapting, or even following a software development pro- cess is not the main focus of this book. However, even when learning about software design, it is useful to have a general idea of software development processes, if only to stay oriented in the wide and buzzword-laden realm of technology. One concept of the software development process literature that is related to soft- ware design is the idea of a software development practice. A practice is a well- understood way of doing something to achieve a certain benefit. An example of a practice many programmers are familiar with is version control (the use of software tools to keep track of changes to software development artifacts). Another example of software development practice is pair programming (writing code as a team of two in front of a single computer). In this book I refer to a number of software de- velopment practices that directly support good design, including the use of coding conventions (see Appendix B) and refactoring (see below). Another concept of software development processes that is relevant to software design is that of the iteration. As discussed in Section 1.1, when searching for a design solution, it is usual to iterate over various alternatives. However, iterations also take place at a more macroscopic level in software development, in the sense that the design of the system may be periodically extended, reviewed, and/or im- proved. In some cases, the design can even be improved without any change to the
71.3 Capturing Design Knowledge observable behavior of the system. Improving the design of code without changing its functionality is the software development practice known as refactoring. There are various reasons why refactoring can become necessary or desirable. One reason is that the original developer did not really get it right, and after working with the code for a while it becomes apparent that a different design would be better. Another reason is that we might want to add modules and features that do not integrate well with the existing design, so we first refactor the design to prepare it so that it better supports the later addition of new code. A third reason is to reduce accumulated design weaknesses. As part of maintaining the code (e.g., to fix bugs), developers occasionally implement quick and dirty solutions that do not align properly with the existing design. This phenomenon is known as accumulating technical debt. By not investing the effort necessary to code a clean solution, the team effectively borrows development effort from the future. If allowed to accumulate, too much technical debt can threaten the viability of the project, just like the risk of bankruptcy incurred by excessive borrowing in the financial sense. When technical debt is incurred in a project, refactoring is a way to pay it back, and good software development teams will periodically refactor their code. Thus, software design is in continual evolution. 1.3 Capturing Design Knowledge A design (or design solution) is a collection of decisions, each of which is the result of a search process through a design space for a particular design problem, or con- text. In practice, a design decision is a statement about how to organize abstractions to meet a requirement, ideally associated with the reason for this statement. A sim- ple example could be: We will store the appointments in a list because we need to know in what order they were added. For this decision to even exist, it has to be in at least one developer’s mind at some point. We thus have a first medium for stor- ing design decisions: a person’s mind. For small projects, this could be sufficient. However, given that human memory is unreliable, it can be worthwhile to record important design decisions externally. This raises the question of how to capture de- sign knowledge. The following is a concise summary of the options for externalizing design knowledge: • Source code: Many design decisions can be captured directly in the source code. The example above, of selecting a list as a data structure, would be one case. The advantage of source code is that it is a formal language whose rules are checked by the compiler. Unfortunately, source code is not a good substrate for capturing the rationale for design decisions. For this purpose, code comments can be of some assistance. • Design documents and diagrams: Design decisions can be captured in docu- ments specifically aimed at capturing such design decisions. There exists a wide variety of formats for documents about software, from standardized design doc-
8 1 Introduction uments to blog posts. Design documents may also include diagrams, which are another way to represent design decisions. • Communication and collaboration tools: Design information can be captured in email and comments stored in tools used for software development, such as issue management systems and version control systems. • Specialized models: In certain software development projects, developers use formal models to specify various aspects of the software. These models can then be automatically converted into code in a programming language. Such an ap- proach is called generative programming or model-driven development (MDD). In model-driven development, the models serve as design documents. As a soft- ware construction approach, model-driven design and development is outside the scope of this book. Because the level of design abstraction covered by this book remains close to the source code, many of the design decisions discussed will be at least partly reflected in the code. Subsequent chapters will also contain many diagrams and accompany- ing text that document design decisions. The Unified Modeling Language There will often be situations where we need to discuss design problems and solu- tions that are impractical to describe using either source code or natural language. For this purpose we can use a specialized modeling language. This situation is not limited to software. For example, describing instrumental music in plain language is near-impossible: instead, we use musical notation. Historically, many different modeling languages and notations have been devel- oped for representing, at an abstract level, various aspects of a software system. This disparity was, however, an obstacle to adoption because of the overhead involved in interpreting models expressed in an unfamiliar notation. Thankfully, in the mid- 1990s the main software modeling notations were merged into a single one, the Unified Modeling Language (UML), which was subsequently adopted as a standard by the International Organization for Standardization (ISO). The UML is a modeling language organized in terms of different types of dia- grams intended to illustrate different aspects of software. Examples of design in- formation than can be neatly captured in the UML include relationships between classes (e.g., A inherits from B), changes in the state of an object (e.g., the list ob- ject goes from Empty to Non-Empty when the first element is added), and sequences of calls dispatched on objects (e.g., a.m1() results in a call to b.m2()). Not all development teams use the UML. However, those who do can use it in different ways for different reasons. For example, UML can be used to produce formal design documentation in waterfall-type development processes. Others use the UML to describe enough of the software to be able to automatically generate the code from the models, following the idea of generative programming. In this book, I use the UML simply for sketching design ideas. The diagrams included in this book are not expected to be automatically transformable into code. I also use
Comments 0
Loading comments...
Reply to Comment
Edit Comment