Tuesday, 18 January 2022

A Philosophy of Software Design - Chapter 1

 A Philosophy of Software Design is it's author's attempt to teach an often neglected core skill in software development: How to design and create high quality software. This is not done in the way it is often attempted, where a method of programming like OOP is explained and codified and good practices are listed. Rather the book attempts to explore and explain the principles that underlie these good practices so that the reader can then apply them to other problems they will encounter day to day.

The author is John Ousterhout, who currently teaches the first class on Software Design at Stanford University. In the preface he talks about how Software Design is both a fairly static subject in academia and not taught enough and that the book contains both lessons learned during his own extensive code writing career and the classes he has taught at Stanford University.

Chapter One: Introduction

The first chapter describes complexity as the biggest limitation in writing programs. The larger and more interconnected the program is the larger the cognitive load of adding to it becomes. There are two ways of managing complexity: First by simplifying the code, and second by hiding it inside an encapsulation (called a module) like a function or class. 

Unlike traditional design work code is highly malleable, this allows the designing of the program to be an ongoing iterative part of programming. The contrast between Waterfall and Agile is noted and should be familiar to any programmer out there, but the book set reducing complexity as one of if not the most important goal of software design.

The book focusses mostly on describing complexity and its solutions at a very high level. Unlike most programming books there is little actual code printed and very few images. Instead the focus is on more narrative examples rather than the nitty-gritty written code.

Book Reviews

PS: 2nd Edition

I'm reading the first edition of the book released in 2018, the second edition has reworked chapter 6, added some sections comparing and contrasting with Clean Code and added a new chapter. Luckily these sections have been released for free here as a sort of errata to the first edition.

Monday, 3 January 2022

Planning the first sprint part 1: What are we making?

Sprints in the Solo Project are split up by feature, where the sprint is finished when the feature is implemented. So what is the feature to be implemented in the first?

The core purpose of Omnihedron is selecting random items from a user-defined list, where the randomness is generated via virtual dice rolls. As such the first requirement is the ability to roll dice to generate random (or rather pseudo-random) results, seeing as the other features rely on this functionality.

Feature: The user can roll dice. ⬅ A good feature, but somewhat vague and unclear.

 There already exists structured notation for describing dice-rolls that is widely used by roleplaying games and roleplaying communities: Wikipedia: Dice Notation, as well as programming libraries able to evaluate and execute dice rolls. From both a user and developer point of view it makes sense to implement such existing familiarity and tools into Omnihedron.

Feature: The user can make dice rolls described via dice notation. ⬅ The use of dice notation can be argued to be a implementation detail, but seeing as it is user facing I'll allow it.

What kind of dice rolls should we support? If we want to make the program as widely useful as possible we should support as much of the commonly used dice notation as is possible, preferentially even a superset thereof. Most important are:

  • NdM: Where N is the number of dice and M the number of faces on those dice.
  • Mathematical Operators: Specifically +, -, /, and *.
  • Comparisons: Checking the rolled value against a given value.
  • Reroll: Reroll a die if a comparison succeeds. The ability to rerolls 1's is common in games.
  • Keep/Drop: Discard dice until N dice are left.

In all honesty, these options are taken from the library I'll be using, and as such could be considered programming to an implementation.

With that we have a feature and a series of examples of that feature, finishing the discovery phase of the BDD process.

Behaviour Driven Development

BDD is a re-implementation or expansion of Test Driven Development that is intended to be less focussed on the tests and more on the purpose of tests in the entire software development cycle. Tests in traditional TDD is heavily focussed on the programmer. The programmer decides what is tested and what the tests expect from the program.

In contrast BDD involves the rest of the software development team with deciding what is expected from the program, and how the program should behave under specific circumstances, which in turn is used to define the tests.

This is accomplished by bringing together the product owner, programmers and testers to together define requirements in a structured manner. The first part of this post is an example of the first phase: Discovery, where a feature to be worked on is selected and discussed so that the meaning, purpose and expectations from that feature are agreed upon.

When doing this on your own it is important to remember that you cannot be just the programmer and product owner, you also have to be the tester. This is something that developers find difficult, as it is our jobs to make things work, while when you are a tester your job is to break a poor programmer's hard work. And indeed, I forgot to put on a tester's hat during the above discovery.

Employing BDD should keep this from happening

So from there we have another feature:

  • Malformed dice notation should return an error message to the user.

Formulation

Once the features and examples have been decided on, the next step is to formulate them into formalized documentation. By using a formal language to describe features they are easy to understand for both humans and automated tools. The most common language used for this is Gherkin, which is designed to be human readable.

Gherkin used a Given, When, Then structure to describe scenarios, these are analogous to the Arrange, Act, Assert structure used in unit tests. Given describes the prerequisite state for starting the scenario, When indicates actions taken and Then describes the expected results.

Like all tests and related scenarios should be short and separate from the code. Don't reference buttons, commands or specific classes, but rather specify desired functionality and abstract user actions. This makes certain it does not need to change as the program changes and keeps it relevant as documentation for the program's desired functionality.

With formulation done we have a clear picture of what functionality we want to implement in the first sprint, we know the end goal and have specified clear acceptance criteria and know when the sprint is finished. In the next post I'll continue with planning the first sprint, now focusing on planning the classes themselves.

A Philosophy of Software Design - Chapter 1

 A Philosophy of Software Design is it's author's attempt to teach an often neglected core skill in software development: How to des...