Fortran 77 – Does It S.O.L.I.D.?

My journey through time to see how the software design principles I teach on Codemanship courses could have been applied in the past continues to 1979, 40 years ago. Although it was already 22 years old by then, Fortran was still one of the most popular languages – particularly in scientific and engineering computing.

The language had an upgrade in 1977, but was still recognisably the very procedural language that was first envisaged in 1957. I used Fortran 77 in my degree studies, and I last used it 27 years ago. How many of the design principles I recommend to developers could I have been applying in it?

Let’s start with the 4 principles of Simple Design:

  1. Should Fortran 77 code work? Well, I think so. Don’t you? In the early 1990s when I was writing code to do computational maths, I was a pretty naive programmer. I wrote no automated tests, and typically wrote all the code for a program – usually just 100-200 lines of it – before trying t to see if it gave the answers I expected. Can you automically test Fortran 77 code? Of course you can. If you can write code that calls code, you can write automated tests.
  2. Should Fortran 77 code clearly communicate its intent? Again, does anyone believe that readability doesn’t matter in Fortran 77? The language has all the mechanisms we need to endow our code with meaning – i.e., opportunities to name things.
  3. Should Fortran 77 code be free of duplication (unless that makes it harder to undersand)? The question is really one of language design: does Fortran 77 allow us to reuse code instead of repeating it? Yes, it does. Functions and subprocedures can encapsulate repeated code. And – in most implementations – code can be split into separate reusable “modules”. In the GNU77 version I used for this example, multiple “library” files can be separately compiled and linked to a main program, allowing for relatively easy reuse.
  4. Should Fortran 77 code be composed out of the simplest parts? It’s entirely possible to write Fortran 77 programs that are made out of functions and subprocedures that are very simple, if you choose to. Whether the language scales to very large composed programs of tens of thousands of lines of code is an interesting challenge. There are some limitations on naming in particular (Fortran 77 names aren’t case-sensitive, and cannot be qualified with namespaces or module names, so more thought is needed to prevent us running out of unique and meaningful names as we add more and more parts. But in 1979, this was less of a problem because of hardware limitations)

By and large, Simple Design is perfectly feasible in Fortran 77.

What about Tell, Don’t Ask? Let’s consider a familar example of a function that knows too much.

real function quote(width, length, pricePerSqMtr, roundUp)
real width, length, pricePerSqMtr
logical roundUp
real area
area = width * length
if (roundUp) then
area = ceil(area)
endif
quote = area * pricePerSqMtr
return
end

view raw
quote.for
hosted with ❤ by GitHub

The dialect of Fortran 77 implemented for the GNU77 compiler doesn’t support data structures (other dialects, like the one I used at university running on Sun hardware did). So we have to pass in all of the data about the room and the carpet as individual parameters to calculate a quote. We are somewhat limited in what we can do as far as data encapsulation is concerned as a result.

If this was Fortran 90 or later (or Sun F77), we could have user-defined types for the room’s dimensions and the carpet’s pricing data, and we could encapsulate their creation in the same modules that access that data. In F90, we also have some control over visibility of module features. (See how similar language features enabled data encapsulation in C in a previous post.) So, if this was Fortran 90, things would be much easier.

In F77, we have a teeny bit of wiggle room to represent an “object” (e.g., a room) as a single entity that quote() doesn’t need to know the internal details of: we could represent the room’s dimensions as an array.

real function quote(room, pricePerSqMtr, roundUp)
real pricePerSqMtr
logical roundUp
real area
area = area_of_room(room)
if (roundUp) then
area = ceil(area)
endif
quote = area * pricePerSqMtr
return
end

view raw
quote.for
hosted with ❤ by GitHub

real function area_of_room(room)
real room(2)
real width, length
width = room(1)
length = room(2)
area_of_room = width * length
return
end

view raw
room.for
hosted with ❤ by GitHub

And our test client just passes in the room array.

program CarpetQuoteTest
implicit none
real quote
real price
real room(2)
room(1) = 2.5
room(2) = 2.5
price = quote(room, 10.0, .FALSE.)
print*, "Quote (not rounded) = ", price
price = quote(room, 10.0, .TRUE.)
print*, "Quote (rounded) = ", price
end

view raw
test.for
hosted with ❤ by GitHub

It’s not so easy for the carpet pricing data. Fortran 77 arrays can only be of a single data type, so we can’t easily represent a real and boolean value in the same array without adding considerable complexity (e.g., a function for translating 0.0 an 1.0 into FALSE and TRUE). Is it worth it?

But it would be worth extracting a function in its own module for calculating the price of a carpet for an area of room, so that each module has a Single Responsibility.

real function price_of_carpet(area, pricePerSqMtr, roundUp)
real area, pricePerSqMtr
logical roundUp
if (roundUp) then
area = ceil(area)
endif
price_of_carpet = area * pricePerSqMtr
return
end

view raw
carpet.for
hosted with ❤ by GitHub

So quote() can be simplied to:

real function quote(room, pricePerSqMtr, roundUp)
quote = price_of_carpet(area_of_room(room), pricePerSqMtr, roundUp)
return
end

view raw
quote.for
hosted with ❤ by GitHub

Now, could we make these dependencies swappable? Well, surprisingly for this language, we can. Fortran 77 allows function references to be passed as parameters. So we could, for example, swap our area_of_room() function with a different implementation that has the same signature.

What if we also want to calculate the area of carpet required to fill a circular room?

real function area_of_circular_room(room)
real room(1)
real radius
radius = room(1)
area_of_circular_room = (radius * 2)**2
return
end

view raw
circular_room.for
hosted with ❤ by GitHub

If we add a function parameter to quote()…

real function quote(room, pricePerSqMtr, roundUp, area_function)
quote = price_of_carpet(area_function(room), pricePerSqMtr, roundUp)
return
end

view raw
quote.for
hosted with ❤ by GitHub

…we can substitute this implementation by injection from the test program.

program CarpetQuoteTest
implicit none
real quote
real price
real rectangular_room(2)
real circular_room(1)
external area_of_rectangular_room
external area_of_circular_room
rectangular_room(1) = 2.5
rectangular_room(2) = 2.5
price = quote(rectangular_room, 10.0, .FALSE., area_of_rectangular_room)
print*, "Quote for rectangular room (not rounded) = ", price
price = quote(rectangular_room, 10.0, .TRUE., area_of_rectangular_room)
print*, "Quote for rectangular room (rounded) = ", price
circular_room(1) = 2.5
price = quote(circular_room, 10.0, .FALSE., area_of_circular_room)
print*, "Quote for circular room (not rounded) = ", price
end

view raw
test.for
hosted with ❤ by GitHub

This is only possible if the data required by each implementation either doesn’t change – so the parameters stay the same – or can be encapsulated (e.g., in an array) as a single parameter. Fortran 77 has very little support for data encapsulation, and this limits the scope for swappability. So I give it 50% for swappability.

Finally, since Fortran 77 has no explicit concept of modules, interfaces and visibility, we can’t control what features are exposed to a client. We can control what features that client uses, of course. But in my G77 set-up, if I make any change to any module, all the modules that depend on it have to be recompiled, even if they don’t use the feature I changed.

So, to sum up: Fortran 77 ticks quite a few of my boxes and has some limited SOLID credentials, but it’s not quite there. Fortran 90 fixed some of these problems, with derived types, explicit modules and explicit interfaces, making it more like C in those respects.

I give Fortran 77 6.5/10 for ease of applying these design principles.

 

You can find the complete source code at https://github.com/jasongorman/fortran-77-SOLID

 

 

 

S.O.L.I.D. in 1969?

For a talk I’m preparing on “Timeless Design Principles”, I thought it would be fun to journey back through the decades and demonstrate how the design principles I teach on the Codemanship courses could have been applied with the technology available at the time.

I’ve already show how it’s possible in C, so that takes us back to the 1989 – 30 years ago. But what if we travel back another 20 years to 1969: could we have written SOLID code then?

The short answers is “Yes, but…” And I’ll get to the “but…” at the end. For now, suffice to say that programming languages did exist in the late 1960s that enable SOLID code. The first object oriented language is Simula 67.

Here’s our now-familiar carpet quote example in Simula 67.

class CarpetQuote(r, c); ref(Room) r; ref(Carpet) c;
begin
Real procedure quote;
begin
Real area;
area := r.width * r.length;
if c.roundUp then
area := ceiling(area);
quote := c.pricePerSqrMetre * area;
end;
end;

view raw
carpetquote.sim
hosted with ❤ by GitHub

This CarpetQuote class takes two constructor parameters for a Room and a Carpet. They are simple data classes (“records”).

class Carpet(pricePerSqrMetre, roundUp); Real pricePerSqrMetre; Boolean roundUp;
begin
end;
class Room(width, length); Real width, length;
begin
end;

view raw
carpetquote.sim
hosted with ❤ by GitHub

What we’ve got here is a classic case of a method that does more than one thing, as well as Feature Envy for the fields of Room and Carpet. Let’s fix that by extracting methods for calculating room area and carpet price, and moving them to where the data is.

class Carpet(pricePerSqrMetre, roundUp); Real pricePerSqrMetre; Boolean roundUp;
begin
Real procedure price(area); Real area;
begin
if roundUp then
area := ceiling(area);
price := pricePerSqrMetre * area;
end;
end;
class Room(width, length); Real width, length;
begin
Real procedure area;
begin
area := width * length;
end;
end;

view raw
carpetquote.sim
hosted with ❤ by GitHub

So now CarpetQuote knows nothing about the details of how these calculations are done, and is greatly simplified into a composed method.

class CarpetQuote(r, c); ref(Room) r; ref(Carpet) c;
begin
Real procedure quote;
begin
quote := c.price(r.area);
end;
end;

view raw
carpetquote.sim
hosted with ❤ by GitHub

So that the S in SOLID, and Tell, Don’t Ask ticked off our list of modular design principles. What about swappability (the O, L and D in SOLID)?

The designers of Simula 67 provided a simple mechanism for this. If we declare them as virtual in a base class.

class Carpet;
virtual: procedure price is Real procedure price(area); Real area;;
begin
end;
class Room;
virtual: Real procedure area();
begin
end;

view raw
carpetquote.sim
hosted with ❤ by GitHub

And we can implement them in subclasses.

Carpet class StandardCarpet(pricePerSqrMetre, roundUp); Real pricePerSqrMetre; Boolean roundUp;
begin
Real procedure price(area); Real area;
begin
if roundUp then
area := ceiling(area);
price := pricePerSqrMetre * area;
end;
end;
Room class RectangularRoom(width, length); Real width, length;
begin
Real procedure area;
begin
area := width * length;
end;
end;

view raw
carpetquote.sim
hosted with ❤ by GitHub

CarpetQuote binds to the abstract types Room and Carpet still, so if we wanted to – for example – swap in a circular room, it’s a doddle.

Room class CircularRoom(radius); Real radius;
begin
Real procedure area;
begin
area := (radius * 2)**2;
end;
end;

view raw
carpetquote.sim
hosted with ❤ by GitHub

Our test client can now pass in whichever shape of room it chooses, with no need to change CarpetQuote.

ref(Carpet) c;
ref(Room) r;
ref(CarpetQuote) q;
r :- new RectangularRoom(2.5, 2.5);
c :- new StandardCarpet(10.0, False);
q :- new CarpetQuote(r, c);
OutText("Quote for rectangular carpet (not rounded) = ");
OutFix(q.quote, 5, 10);
Outimage;
c :- new StandardCarpet(10.0, True);
q :- new CarpetQuote(r, c);
OutText("Quote for rectangular carpet (rounded to nearest sq m) = ");
OutFix(q.quote, 5, 10);
Outimage;
r :- new CircularRoom(2.5);
c :- new StandardCarpet(10.0, True);
q :- new CarpetQuote(r, c);
OutText("Quote for carpet for circular room (rounded to nearest sq m) = ");
OutFix(q.quote, 5, 10);
Outimage;

view raw
carpetquote.sim
hosted with ❤ by GitHub

So we’ve ticked the Tell, Don’t Ask box, and the S, O, L and D boxes. What about Interface Segregation? Well, Simula 67 offers very limited support to achieve client-specific interfaces. We can hide methods a client doesn’t need to see using base classes that only declare those methods, but it’s a one-interface-per-class deal as Simula 67 doesn’t support multiple inheritance.

Having said that, if our classes only do one job, then I suspect the need for multiple interfaces to support multiple clients would be quite limited.

On the whole, it’s good news for our design principles in Simula 67. Now for the bad news…

The observant among you may have noticed that all of these code gists have the same file name. As far as I’ve been able to learn, with very limited – and often conflicting – documentation for a language I don’t think more than a handful of people have used in decades, the GNU Cim Simula compiler only accepts a single .sim source file. So true modularity isn’t possible with the tools available today.

I suspect in 1967 that – with very limited computing power – Simula programs didn’t get so big that they necessarily needed to be split multiple source files. There is some limited support for a kind of modularity (classes inside classes or “packages”), but that seems to be in the same single file only.

With more time and bit of work, someone could probably knock up a simple inline #INCLUDE pre-processor that could pull in code from other files, but that’s beyond the scope of my mini-adventure.

This has been fun and educational, though. I’d read about Simula but never tried it. Throwing together this little program was hard work, but seeing a 52-yeear-old language resurrected on my Windows laptop was rewarding – like hearing the engine of a classic car revving into life after it’s been rusting in someone’s garage since the 1980s.

If you’d like to have a go at some Simula yourself, here are a few resources to get you started:

  • GNU Cim Simula compiler (on modern Windows, follow the instructions for Win NT) – generates C code and then compiles with GCC
  • Introduction to Simula 67 – warning: the code in this guide almost certainly was not actually run on a computer
  • Simula 67 grammar – you’re going to need this, because almost all the guides I found online are not correct Simula 67

And you can find the complete source listing here.

Without syntax-directed support in my editor, and with the often not-very-helpful guides and compiler error messages, this little exercise involved a certain amount of trial and error to figure out exactly what the syntax is. In that sense, it was quite a nostalgic trip down memory lane. This is how things were 30+ years ago: learning to code from printed listings and misleading documentation by trial and error. It’s also the reason why I never publish code that I haven’t seen compile and run correctly. Academics: I’m looking at you!

 

 

 

S.O.L.I.D. in Kotlin

My journey of demonstrating how S.O.L.I.D. design principles can be applied in a range of programming languages going back 50+ years gets bang up-to-date with an example in Kotlin.

Now, you could probably argue that Kotlin is a no-brainer where this is concerned. Anything I can do in Java I can do in Kotlin, if I choose to. Kotlin has classes, interfaces and constructors. We can make data private just as easily as in Java. But still, I hear objections from developers doing pure FP in Kotlin that either:

a. “OO” design principles don’t apply (which is why I’ve stopped calling them that – they’re modular design principles), or…

b. We don’t need to apply S.O.L.I.D. to functional programming, because FP is innately S.O.L.I.D. (Spoiler Alert: it isn’t.)

Whereas with older languages like C and Fortran 77, I’m working harder to get around some language limitations, with languages like Kotlin and Clojure, I’m having to work harder to get around cultural limitations. To be fair, this is not a new phenomenon. I can clearly recall programmers telling me – in the heydey of OOP in the mid-to-late 90s – that you didn’t need to think about things like modularity because OOP is innately modular. (Spoiler Alert: it wasn’t.) Give a C programmer C++ and they’ll write you procedural C++ code. And, as my previous post illustrated, give a C++ programmer C and they’ll find a way to create objects with it.

I define code that’s effectively modular by three key properties:

  • It’s made of discrete parts that do one job each
  • Those parts know as little as possible about each other
  • Those parts are easily swappable

There’s no programming language on Earth that forces us to write code that ticks all three boxes. You have to tick the boxes yourself by the design choices you make.

Granted, there are things we need from a programming language to enabe effective modularity:

  • The ability to break code up into discrete reusable units (i.e., modules)
  • The ability to control what client code can see of a module (or – in the case of Ruby, Python, JS etc – the ability to make that not matter with dynamic binding)
  • The ability to dynamically substitute a different implementation without re-writing the client code

These days, the vast majority of programming languages available to us score 3/3. There are some older languages that offer no mechanism for polymorphism, but you’re very probably not using one of them on a regular basis. You can even do it to a limited extent in Fortran 77. Any language that allows us to pass a function or procedure pointer/reference as a parameter is technically polymorphic.

Anyhoo, here we are in 2019, and the shiny new kid on the code block is JetBrain’s Kotlin. It’s a derivative of Java, with spiffy FP sensibilities. To an old Java hand like me, it takes no time at all to learn. Here’s the carpet quote example in Kotlin.

fun quote(room : Room, carpet : Carpet) : Double{
var area = room.width * room.length
if(carpet.roundUp)
area = Math.ceil(area)
return carpet.pricePerSqrMetre * area
}

view raw
CarpetQuote.kt
hosted with ❤ by GitHub

Again, the first problem that leaps out at me is that this function is doing more than one thing. It breaks the Single Responsibility principle. Let’s refactor each reason to change into its own function in its own module.

data class Room(val width: Double, val length: Double)
fun area(room: Room) = room.width * room.length

view raw
Room.kt
hosted with ❤ by GitHub

data class Carpet(val pricePerSqrMetre : Double, val roundUp : Boolean)
fun price(carpet: Carpet, roomArea: Double): Double {
var carpetArea = roomArea
if (carpet.roundUp)
carpetArea = Math.ceil(carpetArea)
return carpet.pricePerSqrMetre * carpetArea
}

view raw
Carpet.kt
hosted with ❤ by GitHub

fun quote(room : Room, carpet : Carpet) : Double{
return price(carpet, area(room))
}

view raw
CarpetQuote.kt
hosted with ❤ by GitHub

The next thing that’s bothering me is that our data classes Room and Carpet are unencapsulated. That’s always bugs me. In OO design, we say that data classes are a code smell. They hurt us in FP, too. A dependency’s a dependency. Let’s refactor our area() and price() functions into closures that hide the data from quote().

fun room(width: Double, length: Double) : () > (Double) {
fun area() : Double {
return width * length
}
return ::area
}

view raw
Room.kt
hosted with ❤ by GitHub

fun carpet(pricePerSqrMetre: Double, roundUp: Boolean): (Double) > (Double) {
fun price(roomArea : Double) : Double {
var carpetArea = roomArea
if (roundUp)
carpetArea = Math.ceil(carpetArea)
return pricePerSqrMetre * carpetArea
}
return ::price
}

view raw
Carpet.kt
hosted with ❤ by GitHub

@Test
fun quoteForRectangularRoomWithNoRounding(){
assertEquals(62.5, quote(room(2.5, 2.5), carpet(10.0, false)));
}
@Test
fun quoteForRectangularRoomWithRounding(){
assertEquals(70.0, quote(room(2.5, 2.5), carpet(10.0, true)));
}

view raw
CarpetQuoteTest.kt
hosted with ❤ by GitHub

fun quote(area: () > (Double), price: (Double) > (Double)) : Double{
return price(area())
}

view raw
CarpetQuote.kt
hosted with ❤ by GitHub

And yes, I would just as readily use a class instead of a closure. I’m not an FP purist.

This refactoring has killed two birds with one stone: we’re hiding the data from quote() and now we can easily swap in a different implementation for calculating room area and carpet price without changing quote().

For example, what about a circular room?

fun circularRoom(radius: Double) : () > (Double) {
fun area() : Double {
// a square carpet of length 2x radius is required to cut out a circle
return (radius * 2) * (radius * 2)
}
return ::area
}

view raw
CircularRoom.kt
hosted with ❤ by GitHub

@Test
fun quoteForCircularRoomWithNoRounding(){
assertEquals(250.0, quote(circularRoom(2.5), carpet(10.0, false)));
}

view raw
CarpetQuoteTest.kt
hosted with ❤ by GitHub

That ticks the O, the L and the D in S.O.L.I.D. – our dependencies are now easily swappable. So, so far, we’ve covered S.O.L.D. as well as Tell, Don’t Ask.

What about Interface Segregation? Well, unlike many languages that support FP, Kotlin also has direct support for classes that implement multiple client-specific interfaces. If we can do it in Java, we can do it in Kotlin.

Tick.

 

You can view the source files at https://github.com/jasongorman/kotlin_solid

C is for S.O.L.I.D.

In my last post, I dusted off my C skills  – it’s been a very long time – to demonstrate how we might have achieved encapsulation in C thirty years ago. (Indeed, this is how I was taught to do it by engineers who’d been working in C in the 1980s.)

That ticks one box for the software design principles I teach on the Codemanship course: modules should Tell, Don’t Ask.

What about the other design principles? Well, can we all agree that Simple Design applies in any programming language?

  • C code should work
  • C code should clearly communicate its intent
  • C code should not repeat itself (unless repeating itself makes it easier to understand)
  • C code should be composed out of the simplest parts

That leaves the S.O.L.I.D. principles from the course. Let’s go through them one letter at a time and see how (or if, or why) they could be applied in C.

 

Single Responsibility

Modules should only have one reason to change

In the example I used last time, we started with a carpet_quote module that “knew too much”. It calculated the area of a room based on the room’s dimensions, and it calculated a total price for a fitted carpet based on the carpet’s price per square metre, and whether we should round up to the nearest square metre.

float quote(struct Room *room, struct Carpet *carpet){
float area = room->length * room->width;
if(carpet->roundUp){
area = ceil(area);
}
return area * carpet->pricePerSqrMetre;
}

view raw
carpet_quote.c
hosted with ❤ by GitHub

What has calculating the total price got to do with how we calculate the area of the room? I can easily imagine wanting to change how we do either of those calculations independently of the other. For example, what about L-shaped rooms? What if we need to apply a discount for larger rooms? Arguably, these two pieces of logic belong in two separate modules. We split them up to help carpet_quote Tell, Don’t Ask, and here’s another good reason why we should have split them up.

 

Open Closed

Modules should be open to extension and closed to modification

In the refactored Tell, Don’t Ask version of the carpet quote example code, we ended up with carpet_quote using a room and a carpet module inside which the data and the details of the calculations were hidden. What if I wanted to extend this design to price carpets for circular rooms? carpet_quote binds directly to the current room.h header.

#include "carpet_quote.h"
#include "room.h"
#include "carpet.h"
float quote(struct Room *room, struct Carpet *carpet){
return price(carpet, area(room));
}

view raw
carpet_quote.c
hosted with ❤ by GitHub

Whether or not I can swap in a different implementation without modifying carpet_quote.c will depend on what’s exposed in room.h.

#ifndef ENCAPSULATION_ROOM_H
#define ENCAPSULATION_ROOM_H
typedef struct Room Room;
Room* new_room(float width, float length);
float area(struct Room *room);
#endif //ENCAPSULATION_ROOM_H

view raw
room.h
hosted with ❤ by GitHub

There are two blockers here if we want the two kinds of room to co-exist in the same code: first, the “constructor” function new_room(). It’s signature would need to change for a circular room (e.g., new_room(float radius) ), and any modules importing room.h would be affected by that change.

What we need here is a clean separation of the abstractions of a room from the details of how the room is created.

So let’s define two implementations of room in separate modules, with their own constructor functions.

#ifndef ENCAPSULATION_RECTANGULAR_ROOM_H
#define ENCAPSULATION_RECTANGULAR_ROOM_H
#include "room.h"
Room* new_rectangular_room(float width, float length);

view raw
rectangular_room.h
hosted with ❤ by GitHub

#include <stdlib.h>
#include "rectangular_room.h"
struct Room {
float width;
float length;
};
Room* new_rectangular_room(float width, float length){
Room* room = malloc(sizeof(Room));
room->width = width;
room->length = length;
return room;
}

view raw
rectangular_room.c
hosted with ❤ by GitHub

Note here that, because the Room struct is only implemented internally, we can implement it again in a circular_room.c file without any naming conflicts.

Rinse and repeat for a circular room.

#ifndef ENCAPSULATION_CIRCULAR_ROOM_H
#define ENCAPSULATION_CIRCULAR_ROOM_H
#include "room.h"
Room* new_circular_room(float radius);

view raw
circular_room.h
hosted with ❤ by GitHub

struct Room {
float radius;
};
Room* new_circular_room(float radius){
Room* room = malloc(sizeof(Room));
room->radius = radius;
return room;
}

view raw
circular_room.c
hosted with ❤ by GitHub

So far, so good. But both of these modules can’t implement the area() function defined in room.h, or we get a naming conflict. How can we have two implementations of area() co-exist in a language that doesn’t explicitly support polymorphism?

The simplest solution is to use a function pointer in carpet_quote that matches the signature of area(), instead of directly invoking area().

float quote(struct Room *room, float (*area)(Room*), struct Carpet *carpet){
return price(carpet, area(room));
}

view raw
carpet_quote.c
hosted with ❤ by GitHub

Then rectangular_room and circular_room can have their own unique functions for calculating room area.

float rectangular_area(struct Room *room) {
return room->length * room->width;
}

view raw
rectangular_room.c
hosted with ❤ by GitHub

float circular_area(struct Room *room) {
/* a circular room requires cutting a square carpet of length/width 2 * radius */
return (2 * room->radius) * (2 * room->radius);
}

view raw
circular_room.c
hosted with ❤ by GitHub

Now our client just needs to create the correct kind of Room and pass in the associated area calculation function.

int main() {
Carpet* carpet = new_carpet(10.0, false);
Room* rectangular_room = new_rectangular_room(2.5, 2.5);
float total = quote(rectangular_room, &rectangular_area , carpet);
printf("Price of rectangular carpet 2.5m x 2.5m at 10.0 p/sqm with no rounding up is %g\n", total);
Room* circular_room = new_circular_room(2.5);
total = quote(circular_room, &circular_area, carpet);
printf("Price of circular carpet of radius 2.5m at 10.0 p/sqm with no rounding up is %g\n", total);
return 0;
}

view raw
main.c
hosted with ❤ by GitHub

This is, of course, nasty. We’re holding the client responsible for making this type safe. If we try to apply rectangular_area() to a circular room, we’ll get an error. How can we ensure that the right area function is applied to any room?

We’re in luck. If, in C, functions can be data, then structs can contain functions.

#ifndef ENCAPSULATION_ROOM_H
#define ENCAPSULATION_ROOM_H
typedef struct Dimensions Dimensions;
typedef struct Room Room;
struct Room {
Dimensions *dimensions;
float (*area)(struct Room*);
};
#endif //ENCAPSULATION_ROOM_H

view raw
room.h
hosted with ❤ by GitHub

We can assign a reference to the appropriate area function inside each constructor.

struct Dimensions {
float width;
float length;
};
Room* new_rectangular_room(float width, float length){
Dimensions* dimensions = malloc(sizeof(Dimensions));
Room* room = malloc(sizeof(Room));
dimensions->width = width;
dimensions->length = length;
room->dimensions = dimensions;
room->area = &rectangular_area;
return room;
}
float rectangular_area(struct Room *room) {
Dimensions *dimensions = room->dimensions;
return dimensions->length * dimensions->width;
}

view raw
rectangular_room.c
hosted with ❤ by GitHub

struct Dimensions {
float radius;
};
struct Room* new_circular_room(float radius){
Dimensions* dimensions = malloc(sizeof(Dimensions));
dimensions->radius = radius;
Room* room = malloc(sizeof(Room));
room->dimensions = dimensions;
room->area = &circular_area;
return room;
}
float circular_area(struct Room *room) {
/* a circular room requires cutting a square carpet of length/width 2 * radius */
float radius = room->dimensions->radius;
return (2 * radius) * (2 * radius);
}

view raw
circular_room.c
hosted with ❤ by GitHub

Then we can re-write the quote() function to use the attached area() function.

float quote(Room *room, struct Carpet *carpet){
return price(carpet, room->area(room));
}

view raw
carpet_quote.c
hosted with ❤ by GitHub

Now the client doesn’t need to know anything about the area() function. It just decides what kind of room to create, as before.

This refactored design allows us to add new kinds of room (new kinds of dimensions) and new ways of calculating the area of carpet required without making any changes to the existing modules.

Yes: it’s hard work in C! But it can be done. It can be done in any programming language that supports function pointers.

 

Liskov Substitution

An instance of any type can be substituted with an instance of any of its subtypes.

This design principle is all about contracts. If we define an abstraction for calculating the area of carpet required for a room, the client will have expectations about how to use that function and what they should get from it that must hold regardless of which implementation is being used at runtime.

In practical terms, if we wrote a contract test for the area() function – e.g., when the deminsions are positive numbers the output should be a positive number greater than or equal to a “base dimension” – then every implementation of area() must pass that test.

void test_area(float base_dimension, Room *room){
assert(room->area(room) >= (base_dimension));
}
int main() {
test_area(2.5, new_rectangular_room(2.5, 2.5));
test_area(2.5, new_circular_room(2.5));
return 0;
}

view raw
main.c
hosted with ❤ by GitHub

 

Interface Segregation

Modules should present client-specific interfaces

Here we’re talking about what modules make visible to the client modules that use them. This is more important in a statically-typed language like C than it is in dynamically-typed languages like JavaScript. I guess the advice here is to make good use of .h files to control what clients see.

Taking carpet_quote.c as an example, it references room.h and carpet.h.

#include "carpet_quote.h"
#include "room.h"
#include "carpet.h"
float quote(Room *room, struct Carpet *carpet){
return price(carpet, room->area(room));
}

view raw
carpet_quote.c
hosted with ❤ by GitHub

It needs to know about the type Room, and it needs to know a Room struct has an area() function. What’s exposed in room.h?

typedef struct Dimensions Dimensions;
typedef struct Room Room;
struct Room {
Dimensions *dimensions;
float (*area)(struct Room*);
};

view raw
room.h
hosted with ❤ by GitHub

carpet_quote doesn’t need to know about dimensions. How could we refactor this so that carpet_quote is only exposed to what it uses? The honest answer is “not easily”. Once we’ve defined a Room struct with an area() function, we can’t redefine it to have additional features.

If we extracted Dimensions into it’s own .h file, we’d just have to include it here anyway. Importantly, the details of what Dimensions contains is hidden from carpet_quote, because we encapsulated the implementations inside rectangular_room.c and circular_room.c.

So 100% client-specific interfaces is tricky in C. But at least we can control what functions clients are exposed to through the use of header files, which gets us much of the way there. carpet_quote.c knows nothing about how rooms and carpets are created, knows nothing about what data they contain and doesn’t know about the room-specific functions for calculating areas.

 

Dependency Inversion

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Abstractions shouldn’t depend on details. Details shoud depend on abstractions.

Putting it all together, we’ve achieved a kind of dependency inversion in the way that carpet_quote only depends on an abstract definition of a Room. The details of that room’s internal dimensions, and the way we calculate the area of carpet required to fill it, are hidden from carpet_quote. Room could be thought of as an abstract class in this sense, and implementations of are injected as an argument of the quote() function. (Or, as we did before, we can inject the area() function implementation directly. This is very similar to the way we achieved dependency inversion in functional JavaScript or Ruby in previous posts.)

The way to know if we have inverted dependencies is to examine the imports: what files does carpet_quote.c need to include?

#include "carpet_quote.h"
#include "room.h"
#include "carpet.h"

view raw
carpet_quote.c
hosted with ❤ by GitHub

Aside from its owner header file, it only imports the abstractions in room.h and carpet.h. This high-level modules doesn’t depend on low-level modules, nor does it depend on details.

When we use dependency injection to wire our collaborating modules together, the tendency is for the details – the dependencies on implementations – to bubble to the top of the call stack. Good modular architectures wear their implementation dependencies on the outside.

Examining the imports in main.c, this appears to be exactly what has happened.

#include <stdio.h>
#include <string.h>
#include "carpet_quote.h"
#include "rectangular_room.h"
#include "circular_room.h"
#include "bool.h"
#include <assert.h>
void test_area(float base_dimension, Room *room){
assert(room->area(room) >= (base_dimension));
}
int main() {
test_area(2.5, new_rectangular_room(2.5, 2.5));
test_area(2.5, new_circular_room(2.5));
return 0;
}

view raw
main.c
hosted with ❤ by GitHub

We might then go through a similar process to abstract the way that carpet price are calculated. (I’ve left this for you to do.)

 

So there you have it. S.O.L.I.D. – for the most part – can be applied in C. And, back in the day, I routinely applied it when C++ was not an option. If you’ve got function pointers, you can SOLID.

 

You can view a complete copy of the finished code at https://github.com/jasongorman/solid_c

Tell, Don’t Ask in C

I’m preparing a keynote on “Timeless Design Principles”, with the aim of demonstrating how the principles I try to instil in developers on my Codemanship courses could have been applied just as readily 30 years ago or even 50 years ago in programming languages of the time.

In 1989, C ruled the world. A common misconception among inexperienced developers is that design principles like S.O.L.I.D. and Tell, Don’t Ask only apply to OO languages like C++.

Nothing could be further from the truth, though. Let’s start with Tell, Don’t Ask.

Consider this simple C function that calculates a quote for a fitted carpet:

float quote(struct Room *room, struct Carpet *carpet){
float area = room->length * room->width;
if(carpet->roundUp){
area = ceil(area);
}
return area * carpet->pricePerSqrMetre;
}

view raw
carpet_quote.c
hosted with ❤ by GitHub

The quote() function has to ask for the room’s dimensions, and then has to ask for the carpet’s price pr square metre and whether it should round up to the nearest square metre.

Although Room and Carpet aren’t classes, as far as I’m concerned this is still Feature Envy. Room and Carpet are completely unencapsulated. The carpet_quote module knows how the area of a room is calculated, and it knows how to calculate the price of the carpet in that room. If those details change, carpet_quote breaks. Or, more simply, carpet_quote knows too much.

A good first step to fixing that would be to move those two pieces of logic into their own functions.

float area(const struct Room *room) {
return room->length * room->width;
}
float price(const struct Carpet *carpet, float area) {
if(carpet->roundUp){
area = ceil(area);
}
return area * carpet->pricePerSqrMetre;
}
float quote(struct Room *room, struct Carpet *carpet){
return price(carpet, area(room));
}

view raw
carpet_quote.c
hosted with ❤ by GitHub

Now our quote() function knows a lot less. But the carpet_quote module still knows it all. So the next step would be to move the area() and price() functions to the modules where the Room and Carpet structs are declared.

#include "room.h"
float area(const struct Room *room) {
return room->length * room->width;
}

view raw
room.c
hosted with ❤ by GitHub

#include <math.h>
#include "carpet.h"
float price(const struct Carpet *carpet, float area) {
if(carpet->roundUp){
area = ceil(area);
}
return area * carpet->pricePerSqrMetre;
}

view raw
carpet.c
hosted with ❤ by GitHub

carpet_quote now knows less about the details, which are neatly encapsulated inside carpet and room.

#include "carpet_quote.h"
#include "room.h"
#include "carpet.h"
float quote(struct Room *room, struct Carpet *carpet){
return price(carpet, area(room));
}

view raw
carpet_quote.c
hosted with ❤ by GitHub

The data is still accessible from the outside, though. So we’ve got a little more work to do to complete this refactoring. At the moment, the Room and Carpet structs are declared in completeness in the header files room.h and carpet.h, so any module can create and set their field values directly.

#ifndef ENCAPSULATION_ROOM_H
#define ENCAPSULATION_ROOM_H
struct Room {
float width;
float length;
};
float area(const struct Room *room);
#endif //ENCAPSULATION_ROOM_H

view raw
room.h
hosted with ❤ by GitHub

#ifndef ENCAPSULATION_CARPET_H
#define ENCAPSULATION_CARPET_H
#include "bool.h"
struct Carpet {
float pricePerSqrMetre;
boolean roundUp;
};
float price(const struct Carpet *carpet, float area);
#endif //ENCAPSULATION_CARPET_H

view raw
carpet.h
hosted with ❤ by GitHub

In our client code, we instantiate these “objects” directly, setting their field values externally. This gives me the screaming heebie-jeebies. (Every bit a smuch as “data classes in Python, or JSON objects in JavaScript.)

int main() {
struct Room room = {2.5, 2.5};
struct Carpet carpet = {10.0, false};
float total = quote(&room, &carpet);

view raw
main.c
hosted with ❤ by GitHub

How can we hide the data of a Room and a Carpet inside their respective modules? Luckily, C gives a mechanism: partial declarations. We can partially declare a struct in a .h file, defining its type but omitting its data.

#ifndef ENCAPSULATION_ROOM_H
#define ENCAPSULATION_ROOM_H
typedef struct Room Room;
Room* new_room(float width, float length);
float area(struct Room *room);
#endif //ENCAPSULATION_ROOM_H

view raw
room.h
hosted with ❤ by GitHub

Then we can declare the struct with all its data fields in the .c file, so that they can only be accessed internally. Then we add a factory method – essentially a “constructor” – for that type.

#include <stdlib.h>
#include "room.h"
struct Room {
float width;
float length;
};
Room* new_room(float width, float length){
Room* room = malloc(sizeof(Room));
room->width = width;
room->length = length;
return room;
}
float area(struct Room *room) {
return room->length * room->width;
}

view raw
room.c
hosted with ❤ by GitHub

Now the only way a client can get a handle on an instance of the struct is via its module, and it can’t access the data directly.

int main() {
Room* room = new_room(2.5, 2.5);
Carpet* carpet = new_carpet(10.0, false);
float total = quote(room, carpet);

view raw
main.c
hosted with ❤ by GitHub

 

Refactoring To Higher-Order Functions

In my last post, I demonstrated how we might refactor a simple object oriented piece of code into a functional style with a JavaScript example. The focus of that example was about how to get from instance methods that access mutable fields to stateless functions that use immutable data structures.

I wanted to follow that up with a slightly more sophisticated example to illustrate how we might refactor from an OO design that uses dependency injection to an FP design that uses higher-order functions.

Let’s do it in Ruby this time.

class ResponseWriter
def write(customer, serializer, writer)
writer.write(serializer.serialize(customer))
end
end
customer = Customer.new("Mr", "Jason", "Gorman")
writer = ResponseWriter.new
writer.write(customer, HtmlSerializer.new(), ConsoleWriter.new)
writer.write(customer, XmlSerializer.new(), LogFileWriter.new("C:\test\testlog.txt"))
writer.write(customer, StringSerializer.new(), NoSqlWriter.new("mongodb", "localhost", "admin", "password123"))

view raw
response_writer.rb
hosted with ❤ by GitHub

Here we have a class that writes customer data in a variety of formats – XML, HTML and andstrings – to a variety of output destinations – console, log file and NoSql database.

The serializers all present the same interface, with a write() method that accepts a customer parameter. A good first step might be to pass in lambda that invokes the serialize() method instead of invoking it on the serializer instance inside write().

class ResponseWriter
def write(customer, serialize, writer)
writer.write(serialize.call(customer))
end
end
customer = Customer.new("Mr", "Jason", "Gorman")
writer = ResponseWriter.new
writer.write(customer, lambda {|c| HtmlSerializer.new().serialize(c)}, ConsoleWriter.new)
writer.write(customer, lambda {|c| XmlSerializer.new().serialize(c)}, LogFileWriter.new("C:\test\testlog.txt"))
writer.write(customer, lambda {|c| StringSerializer.new().serialize(c)}, NoSqlWriter.new("mongodb", "localhost", "admin", "password123"))

view raw
response_writer.rb
hosted with ❤ by GitHub

So far, so ugly. Next we can make all our serialize() methods unique.

customer = Customer.new("Mr", "Jason", "Gorman")
writer = ResponseWriter.new
writer.write(customer, lambda {|c| HtmlSerializer.new().to_html(c)}, ConsoleWriter.new)
writer.write(customer, lambda {|c| XmlSerializer.new().to_xml(c)}, LogFileWriter.new("C:\test\testlog.txt"))
writer.write(customer, lambda {|c| StringSerializer.new().to_string(c)}, NoSqlWriter.new("mongodb", "localhost", "admin", "password123"))

view raw
response_writer.rb
hosted with ❤ by GitHub

Then we can clean things up by turning these instance methods into standalone functions. e.g.

def to_html(customer)
return "<table><tr><td>Name</td><td>" + customer.title + " " + customer.first_name + " " + customer.last_name + "</td></tr></table>"
end

view raw
html_serializer.rb
hosted with ❤ by GitHub

…allows us to re-write the client code more cleanly.

writer.write(customer, method(:to_html), ConsoleWriter.new)

view raw
response_writer.rb
hosted with ❤ by GitHub

We can rinse and repeat for the output writers. Start by passing in lambdas that invoke their write() methods.

class ResponseWriter
def write(customer, serialize, write)
write.call(serialize.call(customer))
end
end
customer = Customer.new("Mr", "Jason", "Gorman")
writer = ResponseWriter.new
writer.write(customer, method(:to_html), lambda {|o| ConsoleWriter.new().write(o)})
writer.write(customer, method(:to_xml), lambda {|o| LogFileWriter.new("C:\test\testlog.txt").write(o)})
writer.write(customer, method(:to_string), lambda {|o| NoSqlWriter.new(
"mongodb",
"localhost",
"admin",
"password123").write(o)})

view raw
response_writer.rb
hosted with ❤ by GitHub

Then make each write() method unique.

customer = Customer.new("Mr", "Jason", "Gorman")
writer = ResponseWriter.new
writer.write(customer, method(:to_html), lambda {|o| ConsoleWriter.new().write_console(o)})
writer.write(customer, method(:to_xml), lambda {|o| LogFileWriter.new("C:\test\testlog.txt").write_logfile(o)})
writer.write(customer, method(:to_string), lambda {|o| NoSqlWriter.new(
"mongodb",
"localhost",
"admin",
"password123").write_nosql(o)})

view raw
response_writer.rb
hosted with ❤ by GitHub

Now, the next part is a little fiddlier. We want to turn these methods into standalone functions. For the console writer, it’s simple because write_console() is stateless, so we don’t have any fields to worry about.

def write_console(output)
puts output
end

view raw
console_writer.rb
hosted with ❤ by GitHub

writer.write(customer, method(:to_html), method(:write_console))

view raw
response_writer.rb
hosted with ❤ by GitHub

But write_logfile() and write_nosql() access fields that are set in constructors. In the previous post, I illustrated how we can refactor from there. All the information those methods need can be passed in as arguments.

class LogFileWriter
def write_logfile(output, file_path)
# pretend to write string to log file, but this is actually a dummy
end

view raw
log_file_writer.rb
hosted with ❤ by GitHub

class NoSqlWriter
def write_nosql(output, db_type, url, user_name, password)
# pretend to write string to a NoSQL DB, but this is actually a dummy
end

view raw
no_sql_writer.rb
hosted with ❤ by GitHub

customer = Customer.new("Mr", "Jason", "Gorman")
writer = ResponseWriter.new
writer.write(customer, method(:to_html), method(:write_console))
writer.write(customer, method(:to_xml), lambda {|o| LogFileWriter.new().write_logfile(o, "C:\test\testlog.txt")})
writer.write(customer, method(:to_string), lambda {|o| NoSqlWriter.new().write_nosql(
o,
"mongodb",
"localhost",
"admin",
"password123")})

view raw
response_writer.rb
hosted with ❤ by GitHub

Now we can make them standalone functions.

customer = Customer.new("Mr", "Jason", "Gorman")
writer = ResponseWriter.new
writer.write(customer, method(:to_html), method(:write_console))
write_logfile = lambda {|o| write_logfile(o, "C:\test\testlog.txt")}
writer.write(customer, method(:to_xml), write_logfile)
write_nosql = lambda {|o| write_nosql(
o,
"mongodb",
"localhost",
"admin",
"password123")}
writer.write(customer, method(:to_string), write_nosql)

view raw
response_writer.rb
hosted with ❤ by GitHub

And a final bit of tidying up: if we turn our write_logfile() and write_nosql() into closures, with the outer functions acepting all the messy extra parameters, we can simplify our client code.

def write_nosql(db_type, url, user_name, password)
return -> (output) {
# pretend to write string to a NoSQL DB, but this is actually a dummy
}
end

view raw
no_sql_writer.rb
hosted with ❤ by GitHub

def write_logfile(file_path)
return -> (output) {
# pretend to write string to log file, but this is actually a dummy
}
end

view raw
log_file_writer.rb
hosted with ❤ by GitHub

Last, but not least, we get rid of the ResponseWriter class, making its write() method a standalone function.

def write(customer, serialize, write)
write.call(serialize.call(customer))
end
customer = Customer.new("Mr", "Jason", "Gorman")
write(customer, method(:to_html), method(:write_console))
write(customer, method(:to_xml), write_logfile("C:\test\testlog.txt"))
write(customer, method(:to_string), write_nosql("mongodb",
"localhost",
"admin",
"password123"))

view raw
response_writer.rb
hosted with ❤ by GitHub

 

 

 

 

 

Refactoring to Functions

While I’ve been porting the Codemanship Software Design Principles code examples to JavaScript – in both OO and FP styles – I’ve been thinking a lot about the relationship between those two programming styles.

Possibly the best way to illustrate might be to refactor an object oriented code example into a functional example that’s logically equivalent. This might also serve to illustrate how we might move from one style to the other in a disciplined way, without breaking the code.

This is the simple class I’m going to start with.

function BankAccount() {
this.balance = 0;
this.credit = function (amount) {
this.balance += amount
}
this.debit = function (amount) {
if (amount > this.balance) {
throw "Insufficient funds error";
}
this.balance -= amount;
}
}

view raw
bank_account.js
hosted with ❤ by GitHub

And these are its tests.

const BankAccount = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
const account = new BankAccount();
account.credit(50);
expect(account.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const account = new BankAccount();
account.credit(50);
account.debit(50);
expect(account.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const account = new BankAccount();
account.credit(50);
expect(() => account.debit(51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

The first refactoring step might be to make each method of the class properly stateless (i.e., they don’t reference any fields).

To achieve this, we’ll have to add a parameter to each method that accepts an instance of BankAccount. Then we replace this with a reference to that parameter. This will work if the BankAccount we pass in is the exact same object this refers to.

function BankAccount() {
this.balance = 0;
this.credit = function (account, amount) {
account.balance += amount
}
this.debit = function (account, amount) {
if (amount > account.balance) {
throw "Insufficient funds error";
}
account.balance -= amount;
}
}

view raw
bank_account.js
hosted with ❤ by GitHub

So, in our tests, we pass in the BankAccount object we were invoking credit() and debit() on.

const BankAccount = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
const account = new BankAccount();
account.credit(account, 50);
expect(account.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const account = new BankAccount();
account.credit(account, 50);
account.debit(account, 50);
expect(account.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const account = new BankAccount();
account.credit(account, 50);
expect(() => account.debit(account, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

Now we can pull these instance methods out of BankAccount and turn them into global functions.

function BankAccount() {
this.balance = 0;
}
const credit = function (account, amount) {
account.balance += amount
}
const debit = function (account, amount) {
if (amount > account.balance) {
throw "Insufficient funds error";
}
account.balance -= amount;
}
module.exports = {BankAccount, credit, debit};

view raw
bank_account.js
hosted with ❤ by GitHub

The tests can now invoke them directly.

const {BankAccount, credit, debit} = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
const account = new BankAccount();
credit(account, 50);
expect(account.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const account = new BankAccount();
credit(account, 50);
debit(account, 50);
expect(account.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const account = new BankAccount();
credit(account, 50);
expect(() => debit(account, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

One last piece of business: the BankAccount data object. We can replace it in two steps. First, let’s use a JSON version instead that matches the schema credit() and debit() expected. To make this the smallest change possible (so we don’t have to re-write those functions yet), let’s make them mutable.

const {BankAccount, credit, debit} = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
let account = {balance: 0};
credit(account, 50);
expect(account.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
let account = {balance: 0};
credit(account, 50);
debit(account, 50);
expect(account.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
let account = {balance: 0};
credit(account, 50);
expect(() => debit(account, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

Then we can re-write credit() and debit() to return mutated copies.

const credit = function (account, amount) {
return {account, balance: account.balance + amount};
}
const debit = function (account, amount) {
if (amount > account.balance) {
throw "Insufficient funds error";
}
return {account, balance: account.balance amount};
}

view raw
bank_account.js
hosted with ❤ by GitHub

This will require us to re-write the tests to use the mutated copies.

describe('bank account', () => {
it('credit account', () => {
const credited = credit({balance: 0}, 50);
expect(credited.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const debited = debit(credit({balance: 0}, 50), 50);
expect(debited.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const credited = credit({balance: 0}, 50);
expect(() => debit(credited, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

So, there you have it: from OO to FP (well, functional-ish, maybe) for a simple class with no collaborators. In the next post, I’ll refactor some a code example that involves several related classes so we can examine the relationshi between dependency injection and high-order functions.

 

Tell, Don’t Ask in Functional Programming

In my last blog post, I illustrated how S.O.L.I.D. design principles can be applied to functional programming with five JavaScript examples.

There’s more to modular design than SOLID, though. For example, what about encapsulation?

As I continue to port the Java and C# examples for the Codemanship software design principles course, I’ve reached Tell, Don’t Ask. Take a look at the example code for calculating how much to charge for a new carpet.

function area(room) {
return room.length * room.width;
}
function priceForRoom(roomArea, carpet) {
let sqrMetres = roomArea;
if(carpet.roundUp){
sqrMetres = Math.ceil(sqrMetres);
}
return sqrMetres * carpet.pricePerSqrMetre;
}
function quote(room, carpet) {
return priceForRoom(area(room), carpet);
}
module.exports = quote;

view raw
carpet_quote.js
hosted with ❤ by GitHub

const quote = require("../../src/tell_dont_ask/carpet_quote");
test("quote for carpet without rounding up", () => {
const room = {length: 2.5, width: 2.5};
const carpet = {pricePerSqrMetre: 10.0, roundUp: false};
expect(quote(room, carpet)).toBe(62.5);
})
test("quote for carpet with rounding up", () => {
const room = {length: 2.5, width: 2.5};
const carpet = {pricePerSqrMetre: 10.0, roundUp: true};
expect(quote(room, carpet)).toBe(70.0);
})

view raw
carpet_quote.test.js
hosted with ❤ by GitHub

Here’s the thing about functions: when they act on data structures (like JSON objects or tuples), the relationship between data and the logic that acts on that data inherently becomes unencapsulated. That’s just a fancy way of saying that carpet_quote.js knows too much. It knows how to calculate the area of a room based on the length and the width. It knows how to round up the area of a carpet if required. And it knows how to combine these things to get a price for that carpet in that room.

How could we encapsulate each of these jobs so that carpet_quote knows less? The answer may lie in closures.

We can encapsulate calculating the area of a room inside an outer function that takes the length and width as parameters.

function room(length, width) {
function area() {
return length * width;
}
return area;
}
module.exports = room;

view raw
room.js
hosted with ❤ by GitHub

And we can encapsulate the knowledge of calculating the price for that area of carpet inside an outer function that takes the price per square metre and whether or not to round up to the nearest square metre as parameters.

function carpet(pricePerSqrMetre, roundUp) {
function priceForRoom(area) {
let sqrMetres = area;
if (roundUp) {
sqrMetres = Math.ceil(sqrMetres);
}
return sqrMetres * pricePerSqrMetre;
}
return priceForRoom;
}
module.exports = carpet;

view raw
carpet.js
hosted with ❤ by GitHub

Then we can rewrite quote() as a higher-order function (just a fancy way of saying “inject the functions it uses”) that knows a lot less.

function quote(area, priceForRoom) {
return priceForRoom(area());
}
module.exports = quote;

view raw
carpet_quote.js
hosted with ❤ by GitHub

The data required is no longer exposed to carpet_quote. Instead, it’s passed to the outer functions of the closures in the client code – acting effectively as constructors. (Yes, closures are a lot like classes, dontcha think?)

const quote = require("../../src/tell_dont_ask/carpet_quote");
const room = require("../../src/tell_dont_ask/room");
const carpet = require("../../src/tell_dont_ask/carpet");
test("quote for carpet without rounding up", () => {
expect(quote(
room(2.5, 2.5),
carpet(10.0, false))
).toBe(62.5);
})
test("quote for carpet with rounding up", () => {
expect(quote(
room(2.5, 2.5),
carpet(10.0, true))
).toBe(70.0);
})

view raw
carpet_quote.test.js
hosted with ❤ by GitHub

Each module now only has one job, and has no direct implementation dependencies. And it makes the logic of calculating room areas and prices swappable. This design ticks all three boxes of good modular design:

  1. Each unit does one job
  2. Each hides its inner workings (especially its data)
  3. Their dependencies are swappable (by injection)

Let’s say we wanted to calculate the areas of more complex rooms (e.g., L-shaped, circular). In the refactored design we can extend the solution without having to rewrite carpet_quote. So this design is S.O.L.I.D., too.

 

If you’d like to have a crack at refactoring the JavaScript design princples examples, you can find the source code at https://github.com/jasongorman/JS_design_principles

Functional S.O.L.I.D. Explained In 5 Examples

I’m in the process of porting the Codemanship course materials to Python and JavaScript. After I did the S.O.L.I.D. examples from the Software Design Principles workshop in JS, I thought it might be useful to illustrate how these “object oriented” principles can be applied to a more functional style of programming.

Single Responsibility

“Classes should have one reason to change.”

The rationale behind the SRP is simple: editing classes risks breaking the code in them. Once code is tested and out there working, we’d ideally prefer to leave it that way. If a class contains code that changes at different times for different reasons, the risk is of breaking code that really didn’t need to change just because it’s in the same class as code that did. (There’s another, much more compelling reason for our classes – and methods and functions – to do only one job, which is explained on the course.)

But “class” is a red herring here. Really, it’s sources files. (Or modules.) Editing code in a source file risks breaking other code in the same file.

For example:

const {debit, credit} = require("./bank_account");
const transfer = (payer, payee, amount) => {
return {
payer: debit(payer, amount),
payee: credit(payee, amount),
amount: amount
};
}
const toXml = (transferRecord) => {
return "<BankTransfer amount='" + transferRecord.amount + "'>" +
"<Payer>" + transferRecord.payer.id + "</Payer>" +
"<Payee>" + transferRecord.payee.id + "</Payee>" +
"</BankTransfer>";
}
module.exports = {transfer, toXml};

view raw
bank_transfer.js
hosted with ❤ by GitHub

Here I can easily imagine wanting to change the XML output without changing the logic of a bank transfer. This module has two reasons to change. So we should split it up.

const {debit, credit} = require("./bank_account");
const transfer = (payer, payee, amount) => {
return {
payer: debit(payer, amount),
payee: credit(payee, amount),
amount: amount
};
}
module.exports = transfer;

view raw
bank_transfer.js
hosted with ❤ by GitHub

const toXml = (transferRecord) => {
return "<BankTransfer amount='" + transferRecord.amount + "'>" +
"<Payer>" + transferRecord.payer.id + "</Payer>" +
"<Payee>" + transferRecord.payee.id + "</Payee>" +
"</BankTransfer>";
}
module.exports = toXml;

view raw
xml_output.js
hosted with ❤ by GitHub

Open-Closed

“Classes should be open to extension and closed to modification”

Once a class is tested and released, modifying it risks breaking it – and any code that depends on it. A safer way to add functionality to an existing system is to extend the existing code without editing it.

This means our code needs to be designed in a way that makes extending easy. Now, this design principle arguably belongs more in the days of C++ and a few other statically-typed languages where, if you want a class to be open to extension, you have to design it a certain way (e.g., any methods you plan to override need to be declared as virtual. The love for pure interfaces for everything was born of this era.)

In the modern era of dynamic languages and duck typing, it’s very easy to swap one implementation with another – provided the client hasn’t directly referenced that implementation (which is what the D in SOLID is all about.)

Back in the day, we no doubt had inheritance in mind. But that has very much fallen out of favour in recent years, and now many developers prefer composition instead. A subclass that presents the same interface as a base class, and delegates method calls to an instance of the base class internally is logically the same as implementation inheritance.

The functional equivalent of that would be a function with the same signature as the “base” function that internaly delegates to it.

Imagine we wanted to extend a function for borrowing videos from a library so that we can prevent people from borrowing titles they’re too young to see:

const borrow = (customer, video) => {
const borrowed = customer.borrowedVideos;
return {
customer: {customer, borrowedVideos: borrowed.concat([video]) },
video: {video, onLoan: true, borrower: customer.id }
}
}
module.exports = borrow;

view raw
video_library.js
hosted with ❤ by GitHub

All we have to do is create a new function that has an identical signature, that calls the original borrow() function.

const base = require("./video_library");
const borrow_rated = (customer, video) => {
if(customer.age < video.rating.minAge){
throw "Customer is too young for this title";
}
return base(customer, video);
}

Liskov Substitution

“An instance of a class can be substituted with an instance of any of its subclasses”

The example above solves the problem of syntactic swappability of a function with an extended version that has the same signature. But… it’s not quite as simple as just syntax in many cases of extension.

First of all, you may have noticed that the extended function relies on customers having an age, and videos having a rating. We didn’t just extend the function, we extended the data structure (in this case, the JSON object) the function accesses. Any client passing in the old data structure will cause an unhandled exception.

Also, the client could be in for a nasty surprise if the customer is too young for the video they’re borrowing, in the shape of an unexpected error. We’d have to rewrite the client code. Ths is how ripples start in our code that can spread from the module we changed/extended to the rest of the code, making even the smallest changes very expensive.

Ideally, we want to be able to extend the software without having to rewrite client code. And that means, in practice, that – as far as existing clients are concerned – the contracts for calling functions must still hold.

The original borrow() function has no precondition. Any customer can borrow any video. The extended version requires the customer be old enough. So there are scenarios for using borrow() the client thinks are valid that no longer are. (Imagine turning up to the airport with your ticket and your passport, and not being allowed to board the flight because, on your way to the airport, they added a requirement to bring the pilot a bunch of bananas.)

Consider this simple bank account module:

function credit(account, amount) {
return {account, balance: account.balance + amount};
}
function debit(account, amount) {
if (amount > account.balance) {
throw "Insufficient funds error"
}
return {account, balance: account.balance amount};
}
module.exports = {credit, debit};

view raw
bank_account.js
hosted with ❤ by GitHub

We’re asked to extend it so that customers can withdraw beyond the balance up to an agreed overdraft limit. In an FP style, this just means “overriding” the debit() function.

const debit = (account, amount) => {
if(amount > account.balance + account.limit){
throw "Insufficient funds error";
}
return {account, balance: account.balance amount};
}
module.exports = debit;

view raw
overdraft_account.js
hosted with ❤ by GitHub

So far, soo good. This works syntactically. Anywhere clients expect the original debit function, we could substitute the new version. But have we broken the original contract?

One way to check would be to somehow run the original bank account tests against the new implementation. Right now, they look like this:

const {credit, debit} = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
const account = {id: 1, balance: 0};
const credited = credit(account, 50);
expect(credited.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const account = {id: 1, balance: 50};
const debited = debit(account, 50);
expect(debited.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const account = {id: 1, balance: 50};
expect(() => debit(account, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

As they are, it’s not possible to swap in the new implementation because the tests directly reference the old one. What if we refactored the tests to look like this?

const {credit, debit} = require("../../src/liskov_substitution/bank_account");
const bankAccountTest = (account, credit, debit) => {
it('credit account', () => {
const credited = credit({account, balance: 0}, 50);
expect(credited.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const debited = debit({account, balance: 50}, 50);
expect(debited.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
expect(() => debit({account, balance: 50}, 51)).toThrow('Insufficient funds error');
})
}
describe("bank account", () => {
const {credit, debit} = require("../../src/liskov_substitution/bank_account");
const account = {id: 1, balance: 0};
bankAccountTest(account, credit, debit);
})
describe("bank account with overdraft facility", () => {
const overdraft_debit = require("../../src/liskov_substitution/overdraft_account");
const overdraft_account = {id: 1, balance: 0, limit: 1000};
bankAccountTest(overdraft_account, credit, overdraft_debit);
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

Now we can run the same tests with two different implementations of debit(), and two different versions of the account object. Now, I wonder what happens when I run these tests…

 

Interface Segregation

“Classes should present client-specific interfaces”

Back in the days when C++ ruled the world, if I changed a class’s interface (e.g., renamed a method), all the code that referenced that class had to be recompiled, re-tested and re-deployed. Unavoidable if those clients use that renamed method, but totally avoidable if they don’t, by extracting interfaces that only include the methods they actually do use.

In FP, there are no interfaces. Or, rather, every funtion can be thought of as an interface with only one method. Add to that dynamic binding, and the problem goes away. So, arguably, a discussion about Interface Segregation is moot.

But in the heat of battle, as functions evolve and move between modules, and dependencies change, it’s entirely possible for one module to end up directly referencing functions and modules they no longer use. The effect is the same.

const {rating, rate, summarize} = require("./book");
const booksWithRating = (number, books) => {
return books.filter((book) => rating(book) == number);
}
module.exports = booksWithRating;

view raw
book_stats.js
hosted with ❤ by GitHub

Although this module only uses the rating() function from book.js, it references two other exported functions. If I were to, say, move summarize() to a different module, this code breaks.

So, in FP, we might re-frame Interface Segregation as:

“Modules should only reference things from other modules they actually use”

Dependency Inversion

“High-level modules should not depend on low-level modules. Both should depend on abstractions.

Abstractions should not depend on details. Details should depend on abstractions.”

This is a rather hifalutin way of saying that dependencies between modules should be swappable.

Consider this example:

const toXml = require("./xml_serializer");
const toHtml = require("./html_serializer");
const toString = require("./string_serializer");
const ResponseKind = {
XML: 1,
HTML: 2,
STRING: 3
}
const write = (customer, responseKind) => {
if(responseKind == ResponseKind.HTML) {
return toHtml(customer);
} else {
if(responseKind == ResponseKind.XML){
return toXml(customer);
}
}
return toString(customer);
}
module.exports = {ResponseKind, write};

view raw
response_writer.js
hosted with ❤ by GitHub

This is a poor design. How do we add new kinds of output format without modifying this write() function? That breaks the Open-Closed principle. OO languages give us a simple mechanism for making choices without the nastiness of enums and conditionals: polymorphism.

FP gives us exactly the same mechanism. Two functions with the same signatures can be invoked by the same client, without the client knowing which implementation it’s using. From the outside, they look exactly the same.

If we refactor write() to remove direct references to the implementations, and inject the output function from the outside, we can make that dependency easily swappable and our design easily extensible.

const write = (customer, output) => {
return output(customer);
}
module.exports = write;

view raw
response_writer.js
hosted with ❤ by GitHub

Now the client decides which output function to use:

const write = require("../../src/dependency_inversion/response_writer");
const toXml = require("../../src/dependency_inversion/xml_serializer");
const toHtml = require("../../src/dependency_inversion/html_serializer");
const toString = require("../../src/dependency_inversion/string_serializer");
describe('response writer outputs', () => {
it('outputs XML when selected', () => {
expect(write({name:""}, toXml)).toMatch("<customer>");
})
it('outputs HTML when selected', () => {
expect(write({name:""}, toHtml)).toMatch("<html>");
})
it('outputs as string when selected', () => {
expect(write({name:""}, toString)).toMatch("Customer:");
})
})

Notice that all the implementation references are at the top of our call stack now. This is the effect that composing functions (and objects in OOP) by dependency injection tends to have.

Dependency Inversion (and dependency injection that enables it) is as much a foundation of good, extensible FP as it is of good OOP. You may have noticed how many, many staples of the FP paradigm – e.g., map(), filter() and reduce() – work by allowing us to pass in functions that are invoked inside without having to anything about the function being called other than its signature.

 

So there you have it: S.O.L.I.D. applied to functional JavaScript in five examples. If you want to take a more detailed look at the example code and try the refactorings for yourself, it’s all on https://github.com/jasongorman/JS_design_principles

And if you’d like Software Design Principles training and coaching for your team, you know where to find me.

 

S.O.L.I.D. JavaScript – OO Version

A little while back I wrote a post on the old blog about how we could apply the same design principles – near enough – to functional programming as we might to object oriented programming, using JavaScript examples.

That encouraged a couple of people to get in touch saying “But we don’t do FP in JavaScript!”, and suggesting therefore that – strangely – these principles don’t apply to them. The mind boggles.

But, for completeness, here’s how I might apply S.O.L.I.D. principles to OO JavaScript code. To make things backwards compatible, I’ve not used the class syntax of later versions of JS.

First of all, the big tomale: swappable dependencies (Dependency Inversion).

Consider this snippet of code for a simplistic shopping basket:

function Basket(customer, items){
this.add = function(item) {
items.push(item);
}
this.checkout = function(){
const payments = new PayPalPayments();
return payments.pay(this.total(), customer.creditCard)
}
this.total = function() {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}

view raw
basket.js
hosted with ❤ by GitHub

The problem here is what happens if we want to change the way we process payments? Maybe we don’t want to use PayPal any more, for example. Or what if we don’t want to use a real payment processor in a unit test? In this design, we’d have to change the Basket class. That breaks the Open-Closed Principle of SOLID (classes should be open to extension, but closed for modification.)

If we inject the payment processor, then it becomes easy to swap the implementation for whatever purpose (in this example, to stub the processor for a test.)

function Basket(customer, items){
this.add = function(item) {
items.push(item);
}
this.checkout = function (payments) {
return payments.pay(this.total(), customer.creditCard)
}
this.total = function() {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}
test('should process payment on checkout', () => {
let basket = new Basket({creditCard: '1234'}, []);
basket.add({price: 10.0, quantity: 10});
expect(basket.checkout(new PaymentsStub(true))).toBe(true);
});

view raw
basket.js
hosted with ❤ by GitHub

And there we have it: three fifths of SOLID is about making dependencies swappable – Open-Closed, Liskov Substitution and Dependency Inversion. (or “OLD”, if you like.)

And can we agree classes should have a Single Responsibility? That’s not really an OO principle. The same’s true of functions and modules and microservices and any other discrete unit of executable code.

Finally, the Interface Segregation Principle: classes should present client-specific interfaces. That is, interfaces should only include the methods a client uses. With duck typing, it doesn’t really matter of a class presents methods a client doesn’t use. This is true whether we’re talking about methods of classes, or functions in modules.

It might help to make the code easier to understand of we document protocols by explicitly defining pure abstract classes that describe what methods any implementation would need to support. But it’s not necessary for our code to compile and run.

But, as with the functional examples I used, there is a case for saying that modules shouldn’t reference implementations they’re not using. Let’s suppose that after I refactored my Basket to use dependency injection, I forgot to remove the import for PayPalPayments:

const paypalPayments = require('./paypalpayments.js');
function Basket(customer, items){
this.add = function(item) {
items.push(item);
}
this.checkout = function (payments) {
return payments.pay(this.total(), customer.creditCard)
}
this.total = function() {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}

view raw
basket.js
hosted with ❤ by GitHub

It’s important to remember to clean up your imports regularly to avoid situations where changes to things we don’t use could break our code.

So, the sum up: the same principles apply in JavaScript regardless of whether you’re doing FP or OOP.

No excuses!