© Adam Singer 2022
A. B. SingerPractical C++ Designhttps://doi.org/10.1007/978-1-4842-7407-1_1

1. Defining the Case Study

Adam B. Singer1  
(1)
The Woodlands, TX, USA
 

1.1 A Brief Introduction

This book is about programming design. However, unlike many books on this topic, this book teaches design by exploration rather than design by instruction. Typically, most authors writing about some aspect of design establish principles they wish to convey, lay out these principles in the abstract, and then proceed to give examples supporting the current points. This is not such a book. Rather, this book defines a practical problem to be solved and proceeds to examine its solution in detail. That is, instead of deciding on a topic and creating trivial examples to support its teaching, I have defined a hard problem and then let the solution of this problem dictate what topics should be discussed.

Interestingly enough, the preceding approach is exactly how I would tell someone not to learn a subject. I always stress that people should learn broad fundamentals first and subsequently apply these principles to solving problems. However, this is not a book meant to teach the principles of design. Rather, this is a book meant for someone who already knows the fundamentals but wishes to deepen their knowledge of practice. This is a book meant to teach someone the craft of designing and implementing a realistic, albeit small, program from start to finish. This process involves more than knowing elements of design. It involves understanding when and how to use what you know, understanding how to decide between seemingly equivalent approaches, and understanding the long-term implications of various decisions. This book is not comprehensive in its coverage of data structures, algorithms, design patterns, or C++ best practices; volumes of books exist to cover these topics. This is a book about learning how to apply this knowledge to write code that is organized, cohesive, sensible, purposeful, and pragmatic. In other words, this book is about learning to write code that both gets the job done now (development) and allows others to continue to get the job done in the future (maintenance). This, I have termed practical design.

In order to explore practical design, we need a case study. Ideally, the case study problem should be
  • Large enough to be more than trivial

  • Small enough to be tractable

  • Familiar enough to not require domain-specific expertise

  • Interesting enough to maintain the reader’s attention for the duration of the book

After taking the preceding criteria into consideration, I decided to select a stack-based, Reverse Polish Notation (RPN) calculator as the case study. The details of the calculator’s requirements will be defined in the following. I believe that the code for a fully functioning calculator is significant enough that the detailed study of its design provides sufficient material to cover a book. Yet the project is small enough that the book can be a reasonable length. Certainly, specialized domain expertise is not required. I suspect every reader of this book has used a calculator and is well versed in its basic functionality. Finally, I hope that making the calculator RPN provides a suitable twist to stave off boredom.

1.2 A Few Words About Requirements

No matter how big or how small, all programs have requirements. Requirements are those features, whether explicit or implicit, functional or nonfunctional, to which the program must comply. Entire books have been written on gathering and managing software requirements (see, e.g., [36] or [28]). Typically, despite one’s best efforts, it is practically impossible to gather all of the requirements up front. Sometimes, the effort required is economically infeasible. Sometimes, domain experts overlook what seem like obvious requirements to them, and they simply neglect to relate all of their requirements to the development team. Sometimes, requirements only become apparent after the program begins to take shape. Sometimes, the customer does not understand their own requirements well enough to articulate them to the development team. While some of these dilemmas may be mitigated using agile development methods, the fact remains that many design decisions, some of which may have far-reaching implications, must occur before all of the requirements are known.

In this book, we will not study techniques for gathering requirements; rather, our requirements are simply given up front. Well, most of them will be given up front. A few of the requirements have been explicitly reserved until a later chapter so that we can study how our design might change to accommodate unknown future expansion. Certainly, one could justly argue that since the author knows how the requirements will change, the initial design will correctly “predict” the unforeseen features. While this criticism is fair, I nonetheless argue that the thought process and discussion behind the design decisions are still relevant. As a software architect, part of your job will be to anticipate future requests. Although any request is possible, incorporating too much flexibility at the outset is not economical. Designing for future expansion must always be considered as a trade-off between the cost difference for expressly accommodating expandability up front vs. modifying the code later if a change is requested. Where a design should land in the spectrum between simplicity and flexibility must ultimately be measured against the likelihood of a feature request materializing and the feasibility of adding a new feature if its incorporation is not considered at the beginning.

1.3 Reverse Polish Notation (RPN)

I presume that anyone reading this book is familiar with the typical operation of a calculator. However, unless you grew up using a Hewlett-Packard calculator, you may be unfamiliar with how a stack-based RPN calculator functions (see [10] if you are unfamiliar with how a stack works). Simply stated, input numbers are pushed onto a stack, and operations are performed on the numbers already on the stack. A binary operator, such as addition, pops the top two numbers from the stack, adds the two numbers, and then pushes the result onto the stack. A unary operator, such as the sine function, pops one number from the top of the stack, uses this number as the operand, and pushes the result onto the stack. For those familiar with basic compiler lingo, RPN functions as the postfix notation of the operation (see [4] for a detailed discussion of postfix notation). The following list describes my opinion of just a few of the advantages of Reverse Polish Notation over conventional syntax:
  • All operations can be expressed parentheses-free.

  • Multiple inputs and outputs can be visualized simultaneously.

  • Large calculations can be trivially decomposed into multiple, simple operations.

  • Intermediate results can be trivially retained and reused.

While RPN will likely seem incredibly awkward at first, once you’ve become accustomed to it, you will curse every calculator that does not employ it when you are tasked with performing anything more complicated than simple arithmetic.

To ensure that the operation of an RPN calculator is clear, let’s examine a short example. Suppose we wish to evaluate the following expression:
$$ frac{left(4+7
ight)ast 3+2}{7} $$
On a typical, non-RPN calculator, we would type ((4 + 7) 3 + 2)/7 and then press the = key. On an RPN calculator, we would instead type 4 7 + 3 2 + 7 /, where there is an enter command following each number in order to push the input onto the stack. Note that for many calculators, to reduce key entry, operations such as + may also function to implicitly enter the previous number on the stack. Figure 1-1 shows the preceding calculation performed step by step on an RPN calculator.
../images/454125_2_En_1_Chapter/454125_2_En_1_Fig1_HTML.png
Figure 1-1

An example calculation performed on an RPN calculator showing intermediate steps. Counterintuitively, the top of the stack is at the bottom of the screen

1.4 The Calculator’s Requirements

Once you understand the nature of Reverse Polish Notation, the rest of the calculator’s functionality should be straightforward from the requirements description. If RPN is still unclear, I recommend spending some time clarifying this concept before proceeding. Given that caveat, the requirements of the calculator are now defined as follows:
  • The calculator will be stack based; the stack size should not be hard-coded.

  • The calculator will use RPN to perform operations.

  • The calculator will exclusively operate on floating-point numbers; a technique for entering input numbers (including scientific notation) should be implemented.

  • The calculator will have the ability to undo and redo operations; the undo/redo stack sizes should be conceptually unlimited.

  • The calculator will have the ability to swap the top two elements of the stack.

  • The calculator will be able to drop (erase) an element from the top of the stack.

  • The calculator will be able to clear the entire stack.

  • The calculator will be able to duplicate the element from the top of the stack.

  • The calculator will be able to negate the element from the top of the stack.

  • The calculator will implement the four basic arithmetic operations: addition, subtraction, multiplication, and division. Division by 0 is impermissible.

  • The calculator will implement the three basic trigonometric functions and their inverses: sin, cos, tan, arcsin, arccos, and arctan. Arguments to the trigonometric functions will be given in radians.

  • The calculator will implement functions for yx and $$ sqrt[x]{y} $$.

  • The calculator will implement a runtime plugin structure to expand the operations the calculator can perform.

  • The calculator will implement both a command line interface (CLI) and a graphical user interface (GUI).

  • The calculator will not support infinity or imaginary numbers.

  • The calculator will be fault tolerant (i.e., it will not crash if the user gives bad input) but does not need to handle floating-point exceptions.

Now that the calculator has requirements, it deserves a name. I chose to call the calculator pdCalc, short for practical design calculator. Please accept my apologies to you for my lack of naming creativity.

The remainder of this book will examine, in detail, the complete design of a calculator that satisfies the preceding requirements. In addition to describing the decisions made for the final design, we will also discuss alternatives to understand why the final decisions were made and what could have been the consequences of different decisions. I’ll note that the final design presented in this book is not the only design that will meet the requirements, and it may not even be the best design that meets the requirements. I encourage the ambitious reader to experiment with alternate designs and extend the calculator to meet their own needs and interests.

1.5 The Source Code

Throughout the text of this book, we will be examining a lot of code snippets as we design our calculator. Most of these code snippets are taken directly from pdCalc’s GitHub source repository (see Appendix A for instruction for downloading the source code). I will point out any significant differences between the code in the text and the code in the repository. Occasionally, code snippets are comprised of small, contrived examples. These code snippets are not part of pdCalc’s source repository. All of the code is made available under the GPL version 3 [12]. I highly encourage you to experiment with the source code and modify it in any way you see fit.

In order to build pdCalc, you will need access to a C++20-compliant compiler, Qt (version 5 or 6), and CMake. In order to not introduce additional dependencies, the unit tests are performed with Qt’s QtTest. At the time this edition was written, Microsoft’s Visual C++ (MSVC) was the only compiler with sufficient C++20 conformance to build pdCalc. Hopefully, GCC and clang will reach C++20 maturity soon. However, due to the inability of GCC or clang to build pdCalc, I have only built and tested the program in Windows using MSVC. However, as additional compilers reach a sufficient level of C++20 maturity, the code should also build and execute on additional systems with little or no source code modification. Some tweaks to the CMake project files will be necessary for porting to a different platform, although I have at least provided hooks to get one started with Linux using either GCC or clang. Because I expect that the audience for this book leans toward developers with years of experience, I suspect that building the code from source will be a fairly trivial task. However, for completeness, I have included build guidance in Appendix A. Additionally, I have included Appendix B to explain the organization of pdCalc’s source code, libraries, and executables. Although these two appendices appear at the end of the book, you may wish to read them first if you intend to build pdCalc and explore its full implementation while reading the text.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.169.68