Foundations

We’ll set our goal by presenting several elements that make up a complete Problem Statement: a context in which the problem arises, the problem, the forces that influence the choice of solution, the solution that balances the forces, and some consequences of the chosen solution.

Based on the problem statement, we’ll present the high-level use case that this software implements. This will be Our Simulation Application.

In Solution Approach we’ll summarize the approach to the solution, describing the overall strategy that we will follow. This is a kind of overall design pattern that we’ll use to establish some areas of responsibility.

We’ll look at some issues in Methodology, Technique and Process that are not technical in nature. They’re more procedural and provide some direction on development, testing and writing documentation.

In Additional Topics: Non-Functional Requirements we’ll look at issues like Quality, Rework, Reuse, and Design Patterns.

Finally, in Deliverables_, we’ll address the kinds of deliverables that should be produced. These kinds of deliverables form the basis for each chapter.

Problem Statement

We’ll start with a big-picture overview of our problem. We’ll present the context in which the problem arises, a summary of the problem, and a “business use case”. This will show how our application is used.

We can then dig into the details of our application.

Important

Fools Rush In

It’s important not to rush in to programming.

Be sure you understand the problem being solved and how software solves that problem.

Context. Our context is the “classic” casino table games played against the house, including Roulette, Craps and Blackjack. We want to explore the consequences of various betting strategies for these casino games.

Questions include “How well does the Cancellation strategy work?” “How well does the Martingale strategy works for the Come Line odds bet in Craps?” “How well does this Blackjack strategy I found on the Internet compare with the strategy card I bought in the gift shop?”

A close parallel to this is exploring variations in rules and how these different rules have an influence on outcomes. Questions include “What should we do with the 2x and 10x odds offers in Craps?” “How should we modify our play for a single-deck Blackjack game with 6:5 blackjack odds?”

Our context does not include exploring or designing new casino games. Our context also excludes multi-player games like poker. We would like to be able to include additional against-the-house games like Pai Gow Poker, Caribbean Stud Poker, and Baccarat.

Problem. Our problem is to answer the following question: For a given game, what player strategies produce the best results?

Forces. There are a number of forces that influence our choice of solution. First, we want an application that is relatively simple to build. Instead of producing an interactive user interface, we will produce raw data and statistical summaries. If we have little interaction, a command-line interface will work perfectly. We can have the user specify a player strategy and the application respond with a presentation of the results. If the results are tab-delimited or in comma-separated values (CSV) format, they can be pasted into a spreadsheet for further analysis.

Another force that influences our choice of solution is the need to be platform and language agnostic. In this case, we have selected an approach that works well on POSIX-compliant operating systems (i.e., Linux, MacOS, and all of the proprietary UNIX variants), and also works on non-compliant operating systems (i.e., all of the Windows versions).

We also need to strike a balance between interesting programming, probability theory, and statistics. On one hand, the simplicity of these games means that complete analyses have been done using probability theory. However, that’s not a very interesting programming exercise, so we will ignore the pure probability theory route in favor of learning OO design and programming.

Another force is the desire to reflect actual game play. While a long-running simulation of thousands of invidual cycles of play will approach the theoretical results, people typically don’t spend more than a few hours at a table game. If, for example, a Roulette wheel is spun once each minute, a player is unlikely to see more that 480 spins in an eight-hour evening at a casino. Additionally, many players have a fixed budget, and the betting is confined by table limits. Finally, we need to address the subject of “money management”: a player may elect to stop playing when they are ahead. This structures our statistical analysis: we must simulate sessions of play that are limited in time, the amount lost and the amount won.

High-Level Use Case. The high-level (or “business”) use case is an overall cycle of investigation . From this overall view, the actor’s goal is to find an optimal strategy for a given game.

Here’s the scenario we’re imagining.

Business Use Case

  1. Actor. Researches alternative strategies. Uses IDE to build new classes for a simulator.
  2. IDE. Creates new classes for the simulator.
  3. Actor. Runs the simulator with selection of game and strategy.
  4. Simulator. Responds with statistical results.
  5. Actor. Evaluates the results. Uses a spreadsheet or other tool for analysis and visualization.

Consequences. We’re going build the simulator application that supports this high-level (or “business”) use case.

We’re not going to build the IDE to build the new classes. Any IDE should work.

Additionally, we won’t address how to analyze the results.

One of the most important consequences of our solution is that we will build an application into which new player betting strategies can be inserted. Clever gamblers invent new strategies all the time.

We will not know all of the available strategies in advance, so we will not be able to fully specify all of the various design details in advance. Instead, we will find ourselves reworking some parts of the solution, to support a new player betting strategy. This forces us to take an Agile approach to the design and implementation.

Our Simulation Application

The previous section was a fluffy overview of what we’re trying to accomplish. It sets some goals and provides a detailed context for who’s using this application and why.

Armed with that information, we can look at the simulation application we’re going to write.

Our simulation application will allow a programmer to experiment with different casino game betting strategies. We’ll build a simple, command-line simulator that provides a reliable, accurate model of the game. We need to be able to easily pick one of a variety of player betting strategies, play a number of simulated rounds of the game, and produce a statistical summary of the results of that betting strategy.

This leads us to a small essential use case. There is a single actor, the “investigator”. The actor’s goal is to see the expected results of using a particular strategy for a particular game. The typical scenario is the following.

Essential Use Case

  1. Actor. Specifies which game and betting strategy to test.

    The game may require additional parameters, like betting limits.

    The strategy may need additional parameters, like an initial budget, or stake.

  2. System. Responds with a statistical summary of the outcomes after a fixed number of cycles (spins, or throws or hands). The number of cycles needs to be small (on the order of 200, to reflect only a few hours of play).

On Simplicity. Yes, this use case is very simple. It’s a command-line application: it’s supposed to be simple.

The point is to explore OO design, not development of a fancy GUI or web application.

Simplicity is a virtue. We can add a fancy GUI or web presentation of the results later. First, create some results.

Soapbox on Use Cases

We feel that the use case technique is abused by some IT organizations. Quoting from [Jacobson95]. “A use case is a sequence of transactions in a system whose task is to yield a result of measurable value to an individual actor of the system.”

A use case will clearly identify an actor, define the value created, and define a sequence of transactions. A use case will be a kind of system test specification. A use case will define the system’s behavior, and define why an actor bothers to interact with it.

Use cases are often simplified to user stories: “As a <role>, I need <feature> so that <business value>”.

A use case is not a specification, and does not replace ordinary design. We have had experiences with customers who simply retitle their traditional procedural programming specifications as “use cases”. We hypothesize that this comes from an unwillingness to separate problem definition from solution definition. The consequence is a conflation of use case, technical background, design and programming specifications into gargantuan documents that defy the ability of programmers or users to comprehend them.

There are a number of common problems with use cases that will make the design job more difficult. Each of these defects should lead to review of the use case with the authors to see what, if anything, they can do to rework the use case to be more complete.

  • No Actor. Without an actor, it’s impossible to tell who is getting value from the interaction. A catch-all title like “the user” indicates that the use case is written from the point of view of a database or the application software, not an actual person.

    An actor can be an interface with other software, in which case, the actual software needs to be named. Without knowing the actor, we have trouble deciding which classes are clients and which classes provide the lower-level services of the application.

  • No Value Proposition. There are two basic kinds of value: information for decision-making or actions taken as the result of decision-making. People interact with software because there are decisions the software cannot make or there are actions the actor cannot take. Some use cases include value-less activities like logging in, or committing a transaction, or clicking “Okay” to continue. These are parts of operating scenarios, not statements of value that show how the actor is happier or more successful. Without a value proposition, we have no clue as to what problem the software solves, or what it eventually does for the actor.

  • No Interactions. If the entire body of the use case is a series of steps the application performs, we are suspicious of the focus. We prefer a use case to emphasize interaction with the actor. Complex algorithms or interface specifications should be part of an appendix or supplemental document. Without any interaction, it isn’t clear how the actor uses the software.

We also try to make a distinction between detailed operating scenarios and use cases. We have seen customers write documents they call “detailed use cases” that describe the behavior of individual graphical user interface widgets or panels. We prefer to call these scenarios, since they don’t describe measurable business value, but instead describe technical interactions.

Solution Approach

From reading the problem and use case information, we can identify at least the following four general elements to our application.

  • The game being simulated. This includes the various elements of the game: the wheel, the dice, the cards, the table, and the bets.
  • The player being simulated. This includes the various decisions the player makes based on the state of the game, and the various rules of the betting system the player is following.
  • The statistics being collected.
  • An overall control component which processes the game, collects the statistics, and writes the details or the final summary.

When we look at common design patterns, the Model-View-Control pattern often helps to structure applications. A more sophisticated, transactional application may require a more complex structure. However, in this case, the game, the player, and the statistics are the model. The command line selection of player and the reporting of raw data is the view. A control component creates the various objects to execute the simulation and write the results.

While interesting, we will not pursue the design of a general-purpose simulation framework. Nor will we use any of the available general frameworks. While these are handy and powerful tools, we want to focus on developing application software “from scratch” (or de novo) as a learning exercise.

Our solution will depend heavily on desktop integration: the actor will use their IDE to create a strategy and build a new version of the application program. Once the application is built, the actor can run the application from the command line, collecting the output file. The statistical results file can be analyzed using a spreadsheet application. There are at least three separate application programs involved: the IDE (including editor and compiler), the simulator, the spreadsheet used for analysis.

A typical execution of the simulator will look like the following example.

Sample Execution

python3 -m casino.craps --Dplayer.name="Player1326" >details.log
  1. We select the main simulator control using the package casino and the module craps.
  2. We define the player to use, player.name="Player1326". The main method will use this parameter to create objects and execute the simulation.
  3. We collect the raw data in a file named details.log.

We are intentionally limiting our approach to a simple command-line application using the default language libraries.

There are a number of more technical considerations that we will expand in Deliverables. These include the use of an overall application framework and an approach for unit testing.

Among the topics this book deals with in a casual – possibly misleading – manner are probability and statitics. Experts will spot a number of gaps in our exposition. For example, there isn’t a compelling need for simulation of the simpler games of Craps and Roulette, since they can be completely analyzed. However, our primary objective is to study programming, not casino games, therefore we don’t mind solving known problems again. We are aware that our statistical analysis has a number of deficiencies. We will avoid any deeper investigation into statistics.

Methodology, Technique and Process

We want to focus on technical skills; we won’t follow any particular software development methodology too closely. We prefer to lift up a few techniques which have a great deal of benefit.

  • Incremental Development. Each chapter is a “sprint” that produces some collection of deliverables. Each part is a complete release.
  • Unit Testing. We don’t dwell on test-driven development, but each chapter explicitly requires unit tests for the classes built. Ideally, one writes the test cases first.
  • Embedded Documentation. We provide appendices on how to use Sphinx or epydoc to create usable API documents.

The exercises are presented as if we are doing a kind of iterative design with small deliverables. We present the exercises like this for a number of reasons.

  1. We find design is helped by immediate feedback. While we present the design in considerable detail, we do not present the final code. Programmers new to OO design will benefit from repeated exposure to the transformation of problem statement through design to code.
  2. This presentation parallels the way software is developed. A project may emphasize larger collections of deliverables. However, the actual creation of working eventually decomposes into classes, fields and methods.

For developers enamored of a strict waterfall methodology – with all design work completed before any programming work – the book can be read in a slightly different order. From each exercise chapter, read only the overview and design sections. From that information, integrate the complete design. Then proceed through the deliverables sections of each chapter, removing duplicates and building only the final form of the deliverables based on the complete design.

This will show how design rework arises as part of a waterfall methodology. It will show that rework doesn’t go away with a Big Design Up Front (BDUF) approach. The rework is merely shifted around a bit.

Making Technical Decisions

Many of the chapters will include some lengthy design decisions that appear to be little more than hand-wringning over nuances. We need to emphasize our technique for doing appropriate hand-wringing over OO design. We call it “Looking For The Big Simple”, and find that managers don’t often permit the careful enumeration of all the alternatives and the itemization of the pros and cons of each choice.

We have worked with managers who capriciously play their “schedule” or “budget” trump cards, stopping useful discussion of alternatives. This may stem from a fundamental discomfort with the technology, and a consequent discomfort of appearing lost in front of team members and direct reports.

Our suggestion in this book can be summarized as follows:

Important

Good Design

Good OO design comes from a good process for technical decision-making.

First, admit what we don’t know, and then take steps to reduce our degrees of ignorance.

A phrase like “work smarter not harder” is useless because it doesn’t provide the time and budget to actually get smarter.

The learning process, as with all things, must be managed. This means there must be time budgeted for exploring the bad designs before arriving at a good design.

A little more time spent on design can result in considerable simplification, which will reduce overall development and maintenance costs.

It’s also important to note that no one in the real world is omniscient. Some of the exercises include intentional dead-ends. As a practical matter, we can rarely foresee all of the consequences of a design decision.

Additional Topics: Non-Functional Requirements

We can decompose software requirements into two broad categories. The Functional Requirements are the things the software must do; the use cases should address this completely. The Non-Functional Requirements are all of the supporting ideals and principles that make good software. The number of non-functional features of software is large. We’ll talk about a few of them, specifically:

  • Quality, in general
  • Rework
  • Reuse
  • Design Patterns

On Quality

Our approach to overall quality assurance is relatively simple. We feel that a focus on unit testing and documetation covers most of the generally accepted quality factors. The Software Engineering Institute (SEI) published a quality measures taxonomy. While officially “legacy”, it still provides an exhaustive list of quality attributes. These are broadly grouped into five categories. Our approach covers most of those five categories reasonably well.

  • Need Satisfaction. Does the software meet the need? We start with a problem statement, define the use case, and then write software which is narrowly focused on the actor’s needs. By developing our application in small increments, we can ask ourself at each step, “Does this meet the actor’s needs?” It’s fairly easy to keep a software development project focused when we have use cases to describe our goals.

  • Performance. We don’t address this specifically in this book. However, the presence of extensive unit tests allows us to alter the implemention of classes to change the overall performance of our application. As long as the resulting class still passes the unit tests, we can develop numerous alternative implementations to optimize speed, memory use, input/output, or any other resource.

  • Maintenance. Software is something that is frequently changed. It changes when we uncover bugs. More commonly, it changes when our understanding of the problem, the actor or the use case changes. In many cases, our initial solution merely clarifies the actor’s thinking, and we have to alter the software to reflect a deeper understanding of the problem.

    Maintenance is just another cycle of the iterative approach we’ve chosen in this book. We pick a feature, create or modify classes, and then create or modify the unit tests. In the case of bug fixing, we often add unit tests to demonstrate the bug, and then fix our classes to pass the revised unit tests.

  • Adaptation. Adaptation refers to our need to adapt our software to changes in the environment. The environment includes interfaces, the operating system or platform, even the number of users is part of the environment. When we address issues of interoperability with other software, portability to new operating systems, scalability for more users, we are addressing adaptation issues.

    We chose Python to avoid having interoperability and portability issues; this language give admirable support for many scalability issues. Generally, a well-written piece of software can be reused. While this book doesn’t focus on reuse, Python is biased toward writing reusable software.

  • Organizational. There are some organizational quality factors: cost of ownership and productivity of the developers creating it. We don’t address these directly. Our approach, however, of developing software incrementally often leads to good developer productivity.

Our approach (Incremental, Unit Testing, Embedded Documentation) assures high quality in four of the five quality areas. Incremental development is a way to focus on need satisfaction. Unit testing helps us optimize resource use, and do maintenance well. Our choices of tools and platforms help us address adaptation.

The organizational impact of these techniques isn’t so clear. It is easy to mis-manage a team and turn incremental development into a quagmire of too much planning for too little delivered software. It is all too common to declare that the effort spent writing unit test code is “wasted”.

Ultimately, this is a book on OO design. How people organize themselves to build software is beyond our scope.

On Rework

In Problem Statement, we described the problem. In Solution Approach, we provided an overview of the solution. The following parts will guide you through an incremental design process; a process that involves learning and exploring. This means that we will coach you to build classes and then modify those classes based on lessons learned during later steps in the design process. See our Soapbox on Rework for an opinion on the absolute necessity for design rework.

We don’t simply present a completed design. We feel that it is very important follow a realistic problem-solving trajectory so that beginning designers are exposed to the decisions involved in creating a complete design. In our experience, all problems involve a considerable amount of “learn as you go”.

We want to reflect this in our series of exercises. In many respects, a successful OO design is one that respects the degrees of ignorance that people have when starting to build software. We will try to present the exercises in a way that teaches the reader how to manage ignorance and still develop valuable software.

For some, the word rework has a negative connotation. If you find the word distasteful, please replace every occurance with any of the synonyms: adaptation, evolution, enhancement, mutation. We prefer the slightly negative connotation of the word rework because it helps managers realize the importance of incremental learning and how it changes the requirements, the design and the resulting software.

Since learning will involve mistakes, good management plans for the costs and risks of those mistakes. Generally, our approach is to manage our ignorance; we try to create a design such that correcting a mistake only fixes a few classes.

We often observe denial of the amount of ignorance involved in creating IT solutions. It is sometimes very difficult to make it clear that if the problem was well-understood, or the solution was well-defined there would be immediately applicable off-the-shelf or open-source solutions. The absence of a ready-to-hand solution generally means the problem is hard. It also means that there are several degrees of ignorance: ignorance of the problem, solution and technology; not to mention ignorance of the amount of ignorance involved in each of these areas.

We see a number of consequences of denying the degrees of ignorance.

  • Programmers. For programmers, experienced in non-OO (e.g. procedural) environments, one consequnece is that they find learning OO design is difficult and frustrating. Our advice is that since this is new, you have to make mistakes or you won’t learn effectively. Allow yourself to explore and make mistakes; feel free to rework your solutions to make them better. Above all, do not attempt to design a solution that is complete and perfect the very first time. We can’t emphasize enough the need to do design many times before understanding what is important and what is not important in coping with ignorance.
  • Managers. For managers, experienced in non-object implementation, the design rework appears to be contrary to a fanciful expectation of reduced development effort from OO techniques. The usual form for the complaint is the following: “I thought that OO design was supposed to be easier than non-OO design.” We’re not sure where the expectation originates, but good design takes time, and learning to do good design seems to require making mistakes. Every project needs a budget for making the necessary mistakes, reworking bad ideas to make them good and searching for simplifications.
  • Economics. Often, management attempts the false economy of attempting to minimize rework by resorting to a waterfall methodology. The idea is that having the design complete before attempting to do any development somehow magically prevents design rework. We don’t see that this waterfall approach minimizes rework; rather, we see it shifting the rework forward in the process. There are two issues ignored by this approach: how we grow to understand the problem domain and providing an appropriate level of design detail.

We find that any initial “high-level” design can miss details of the problem domain, and this leads to rework. Forbidding rework amounts to mandating a full understanding of the problem prior to any code.

In most cases, our users do not fully understand their problem any more than our developers understand our users. Generally, it is very hard to understand the problem, the technology, and the solution. We find that hands-on use of preliminary versions of software can help more than endless conversations about what could be built.

There was a time when programming languages were so primitive that deep, detailed design was required. In a language that lacks the built-in collection classes, a great deal of design was required just to introduce the collections required to do useful work.

Because Python comes with so many features built in, it’s often more efficient to write a draft version of a class in Python than it is to write an elaborate design document prior to writing a preliminary draft.

On Reuse

While there is a great deal of commonality among the three games, the exercises do not start with an emphasis on constructing a general framework. We find that too much generalization and too much emphasis on reuse is not appropriate for beginning object designers. See Soapbox on Reuse for an opinion on reuse.

Additionally, we find that projects that begin with too-lofty reuse goals often fail to deliver valuable solutions in a timely fashion. We prefer not to start out with a goal that amounts to boiling the ocean to make a pot of tea.

On Design Patterns

These exercises will refer to several of the “Gang of Four” design patterns in [Gamma95]. The Design Patterns book is not a prerequisite; we use it as reference material to provide additional insight into the design patterns used here. We feel that use of common design patterns significantly expands the programmer’s repertoire of techniques. We note where they are appropriate, and provide some guidance in their implementation.

In addition, we reference several other design patterns which are not as well documented. These are, in some cases, patterns of bad design more than patterns of good design.

Deliverables

Each chapter defines the classes to be built and the unit testing that is expected. A third category of deliverable – documentation – is merely implied.

The purpose of each chapter is to write the source files for one or more classes, the source files for one or more unit tests, and assure that a minimal set of API documentation is available.

  • Source Files. The source files are the most important deliverable. In effect, this is the working application program. Generally, we will be running this application from within the Integrated Development Environment (IDE). We can, of course, create a stand-alone program.

    In the case of Python, the “program” is simply the packages of .py files. There really isn’t much more to deliver. The interested student might want to look at the Python distutils and setuptools to create a distribution kit, or possibly a Python .egg file.

  • Unit Test Files. The deliverables section of each chapter summarizes the unit testing that is expected, in addition to the classes to be built. We feel that unit testing is a critical skill, and emphasize it throughout the inividual exercises. We don’t endorse a particular technology for implementing the unit tests. There are several approaches to unit testing that are in common use.

    For formal testing of some class, X, we create a separate class, TestX, which creates instances of X and exercises those instances to be sure they work. The unittest package is the mechanism for doing formal unit tests. Additionally, many Python developers also use the doctest module to assure that the sample code in the docstrings is actually correct. We cover these technologies in the appendices.

  • Documentation. The job isn’t over until the paperwork is done. The internal documentation is generally built from specially formatted blocks of comments within the source itself. We can use Epydoc (or sphinx) to create documentation based on the code.