Get to know constexpr

I did my first run with C++ before modern C++ was a thing. That is, before C++11 and C++14. Now that I’m once again developing application code using C++ I’ve learned to master great things like smart pointers, move semantics, and lambda expressions. One modern C++ concept that I haven’t put so much thought to is the keyword constexpr. But now it’s time, let’s get to know constexpr.

First, let’s break the word itself. It is short for constant expression and was introduced in C++11 and improved in C++14. Constant means something that does not change, and expression is a sequence of operands and operators. A really simple example is assigning a value to a variable that is not allowed to be changed:

constexpr auto n = 42;

This sets n to the value 42 and does not allow n to be changed to anything else. This allows the compiler to optimize away n and insert the value 42 in all places where n is used. We can take this a step further and use n to calculate other constexpr values:

constexpr auto half_n = n / 2;

Now, both n and half_n are calculated at compile time and no CPU cycles needs to be used during runtime to perform this calculation.

You can also use constexpr with functions, i.e. you can mark a function with constexpr, like this:

constexpr int pow(int base, unsigned exp) {
  if (exp == 0) return 1;
  return base * pow(base, exp-1); // Note the recursive call
}

With this function you can calculate things like 216 during compile time, as long as the arguments given to pow are also constexpr (or literals). But what if you want to use pow with values that are not known at compile time, say for example with values requested from the user or read from a data source? Great news! It is perfectly fine to use pow just like a regular function, if the argument values are not constexpr or literals then pow will be called at runtime instead:

constexpr int pow(int base, unsigned exp) {
  if (exp == 0) return 1;
  return base * pow(base, exp-1);
}

int main() {
  constexpr auto bits_per_byte = pow(2, 3); // Calculated at compile time
  int n;
  std::cin >> n;
  const auto user_value = pow(2, n); // Calculated at runtime
}

Since bits_per_byte is calculated at compile time it can be used in constructs that requires constant values, for example when defining a std::array: std::array<bool, bits_per_byte> byteArray{};

So far we have only used simple built-in types with constexpr, but it can also be used with user-defined types. For example we can define a Point3D type that holds three integer values representing coordinates on the x-, y-, and z-axis:

class Point3D {
public:
  constexpr Point3D(int x = 0, int y = 0, int z = 0) noexcept
    : x{x}, y{y}, z{z} {}

  constexpr int getX() const noexcept { return x; }
  constexpr int getY() const noexcept { return y; }
  constexpr int getZ() const noexcept { return z; }

  constexpr void setX(int new_x) noexcept { x = new_x; }
  constexpr void setY(int new_y) noexcept { y = new_y; }
  constexpr void setZ(int new_z) noexcept { z = new_z; }
private:
  int x, y, z;
};

constexpr Point3D reflectionXY(const Point3D& p) {
  Point3D res;
  res.setX(p.getX());
  res.setY(p.getY());
  res.setZ(-p.getZ());
  return res;
}

int main() {
  constexpr Point3D p{-1, 2, -3};
  constexpr auto reflected = reflectionXY(p);
}

Notice that even the setters can be constexpr, they are used in reflectionXY. As long as the values used for x, y, and z are constants that are known during compile time the Point3D class can be used by the compiler during compile time and you don’t get any runtime overhead for creating and freeing up objects. As with functions it works just as well with values not known during compile time as well.

That concludes everything I wanted to say about constexpr. Now go write some code!

When an int is not an int

The more I learn about C++ the more I understand that it is considered a difficult language to master. It has all these quirks and things that are far from obvious which may lead to unforeseen behavior. As an example, look at the following code:

template <typename T>
void func(T&& t) {
  if (std::is_same<T, int>::value) {
    std::cout << t << " is an int!\n";
  } else {
    std::cout << t << " is not an int!\n";
  }
}

int main() {
  int one = 1;
  func(one);
}

From the headline of this post and the introductory text you probably guessed that the output when running this program is 1 is not an int!

But what happens when I change the calling code to this:

int main() {
  func(1);
}

The program will now output 1 is an int! What’s going on here? How can 1 be an integer in one case but not in the other? To understand this we need to learn two things, a little bit about how type deduction works and which qualifications in a type that std::is_same takes into account.

Let’s start with the type deduction. You might think that T&& in the code above denotes an rvalue reference, however this is not true in this case. If the function was an ordinary function and not a function template this would be true. However, for function templates, a T&& denotes a Forwarding Reference, which can be either an rvalue reference or an lvalue reference. Now, in the first case where we have a variable named one of type int with the value 1, one is an lvalue. In order for the function func to be able to accept one as an argument it must accept an lvalue reference. The code after type deduction looks like this:

void func(int& t) {
  if (std::is_same<int&, int>::value) {
    std::cout << t << " is an int!\n";
  } else {
    std::cout << t << " is not an int!\n";
  }
}

Note that both T (used in std::is_same) and T&& (used as the parameter type) is deduced to int&. Now it should be obvious that std::is_same<int&, int>::value is false, since int is not the same as int&.

What about the second case? In that the argument to func is an rvalue so func will take an rvalue reference as argument:

void func(int&& t) {
  if (std::is_same<int, int>::value) {
    std::cout << t << " is an int!\n";
  } else {
    std::cout << t << " is not an int!";
  }
}

In this case T is deduced to int and T&& to int&&. This is the way forwarding references work, they can deduce to either rvalue or lvalue references. By deducing T to an int we get a function that takes an rvalue reference as parameter (just replace T with int and we get func(int&& t)). This is why std::is_same<int, int>::value is true in this case.

The rules also states that T&&& becomes T&, so by deducing T to int& in the first case, void func(T&&) becomes void func(T& &&) which becomes void func(T&) and we get a function that accepts and lvalue reference.

I understand that this is mind boggling and sometimes C++ is far from intuitive, but stick in there!

Happy coding!

Hidden conditions

One way of making code easy to understand and reason about is to try to ensure that it does exactly what you expect it to do. Sometimes however, there are conditions secretly hidden in places of the code that leaves you scratching your head and forces you into a quest for answers down the deepest of rabbit holes. For example, let’s assume that the application that you develop handles purchase orders. When you scroll through the code you see a call to a method named cancel that is part of the PurchaseOrder class. The natural thing to assume is that calling this function cancels the order, since that is exactly what the name says. However, things looks a bit odd since the call is made from inside of removeItem. How can it be that removing an item from an order unconditionally cancels it? Did you just find a bug in the code?

/* orderHandling.cpp */
void OrderHandling::handleItemRemoved(PurchaseOrder& order, const Item& removedItem) {
  order.removeItem(removedItem);
}

/* purchaseOrder.cpp */
void PurchaseOrder::removeItem(const Item& itemToRemove) {
  m_items.erase(itemToRemove);
  this.updateOrderSum();
  this.cancel(); // Why is the order cancelled?
}

To understand what is going on you take a look at the cancel method:

void PurchaseOrder::cancel() {
  if (m_orderItems.size() > 0) {
    return; // All items must be removed before cancelling
  }
  // Code that cancels the order goes here
  this.m_state = PurchaseOrderState::cancelled;
}

Aha! There is a condition hidden in the cancel method that makes calling it do nothing if there are any items in the order list. Sneaky! (In real world applications these conditions are usually many layers down and sometimes spread out over several different functions).

When you show the code to a senior colleague he informs you that the purchase order is supposed to be cancelled if all items are removed and this is how the business logic was implemented. Now things makes sense, but it required that you put in quite a lot of time and effort. Wouldn’t it be better if the code was cleaner?

If you paid attention to the code earlier you noticed that there is something called OrderHandling. This is a good place to add business logic such as cancelling of orders when the last item is removed. Let’s change the code a bit:

void OrderHandling::handleItemRemoved(PurchaseOrder& order, const Item& removedItem) {
  order.removeItem(removedItem);
  if (order.isEmpty()) {
    order.cancel();
  }
}

This should make it more clear. Now with this change in place we can remove the call to cancel from the removeItem method:

void PurchaseOrder::removeItem(const Item& itemToRemove) {
  m_items.erase(itemToRemove);
  this.updateOrderSum();
}

And finally, after checking and testing that there is no other place in the code that is depending on the condition in the cancel method to only cancel out empty orders it can be updated as well:

void PurchaseOrder::cancel() {
  m_state = PurchaseOrderState::cancelled;
}

Nice!

The next time you find a hidden condition like this secretly lurking in the depths of the code. Pull it out into the light! Make it really visible and obvious under what conditions certain functions should be called.

Happy coding!

The art of keeping things simple

As a software developer you go through different phases in your personal development. Many, but far from all, end up valuing simple code. Simple does not imply that the code doesn’t do what it needs to do, it means that the code is written in a way that puts minimal cognitive load on the human brain while working with it.

For some, striving for simplicity seems to come natural and they appear to have the ability to envision other developers reading their code and how to make it easy for them to follow along. Other developers – for unknown reasons – do not appear to reach this level of insight, even after many years.

Let me take an example, a few days back I was reading through a code forum where a solution to a toy problem was being discussed. The problem was a simple one: write a function – to_robberlang – that converts it’s input to ”The Robber Language”. I thought that I would write my own version of the function first and then compare it with the suggested solution. My code looked like this:

std::string to_robberlang(const std::string& in) {
    std::stringstream result;

    std::for_each(begin(in), end(in), [&](char c) {
        result << c;
        if (!isvowel(c)) {
            result << 'o' << c;
        }
    });

    return result.str();
}

I did not include the code for isvowel but it is a simple helper function that checks if c – in lowercase – is one of ’a’, ’e’, ’i’, ’o’, or ’u’. To me this was a simple, straight forward solution that should do the trick. The suggested solution posted in the forum however was a beast!

The first thing I noticed was that the suggested solution used templates. It did not say clearly but my understanding is that since the problem statement did not mention the type of the input parameter, the author of the solution thought it was a good idea to make it generic (which then also applied to the type of the return value).

Secondly the author wanted the function to work given a slice of a range of characters, for example only a substring or the characters between index 5 and 10 in a large vector. For this to work he made the to_robberlang function take two iterators as input arguments. The first iterator was expected to point to the starting character in the range, the second one past the ending character in the range.

Thirdly the author wanted the caller to be able to supply a back_inserter iterator pointing to the start of the resulting output. This would enable the caller to decide the type of the output container and where inside it to place the output.

The function’s prototype looked something like this (full implementation left out for brevity):

template <typename TIn, typename TOut>
TOut to_robberlang(TIn&& begin, const TIn& end, const TOut& outputbegin);

To me this is a classic case of what I like to call the ”What if”-syndrome. The author probably started out with the simple std::string case in his mind, and then a series of ”What if”:s came over him. ”What if the input is not a std::string?”, ”What if the caller just want to transform a slice of the range?”, ”What if the caller wants to place the output into an existing collection?”. I have seen this happen many times in the development of real world applications. Usually the development team has a quite good understanding of the requirements and the constraints that apply, but then someone says ”But what if someone wants to <insert non-existing use case here> in the future?” and before you know it things have started to spin out of control. When this happens you need to stop it. My response to this concern is usually something in the lines of ”We’ll make sure to make the code easy to change so that we can add support for that use case when it is needed.” This is usually accepted by the team.

So how flexible and generic should you make the code? A good principle to follow comes from Ron Jeffries:

”Do the simplest thing that could possibly work”

https://ronjeffries.com/xprog/articles/practices/pracsimplest/

Going back to the to_robberlang function. Imagine that it is part of a library that you are using and that you have a piece of text that you want to transform to Robber Language. How would you prefer that the interface looked like?

// As a user of the to_robber function, would you prefer calling it like this:
auto encrypted{ to_robberlang("Secret text") };
// or like this:
const std::string original{ "Secret text" };
std::string encrypted;
to_robberlang(begin(original), end(original), std::back_inserter(encrypted));

I would go for the first one any time of the day.

To finish off this post I would like end with a final quote. The KISS principle, coined by Kelly Johnson, says:

”Keep it simple, stupid”

https://en.wikipedia.org/wiki/KISS_principle

Happy coding!

I found a case for a ”C/C++ developer”

What are your thoughts when you see a job ad where they are looking for a C/C++ developer? Personally I have always wondered what ”C/C++” means. Do they know that C and C++ are different languages? That C is a procedural language and C++ is an object oriented language and writing modern C++ code is very different from writing C code. Or maybe they have different products, some written in C and some in C++ and they are looking for developers that know both C and C++?

Just the other day when I was browsing some code it hit me, this code is C/C++ code! What I was looking at was product code that was originally written in C. At one point in time the entire code base was pure C, no C++ at all. Then at some point it was decided to introduce C++ and continue developing the product using C++ instead of C. Of course, the existing code was not re-written from scratch but left as is.

Fast forward to today. The code is now a mix of C and C++ code. New code is mostly written in C++ but there are still some developers that work almost exclusively with the older C code and they seem to stick with C, even when extending the existing functionality. There are also some external libraries being used that are written in C and hence have C-style interfaces. Anyone that is going to work with this code needs to be a ”C/C++ developer”.

Ways to pass Arguments in C++

In C++ there are many ways to pass an argument to a function. The following code shows some commonly used variants. It is also possible to add the const keyword at different places when passing by pointers to make either the pointer const or the value the pointer is pointing at const, or both.

You can also pass a reference to a pointer or a pointer to a pointer. I won’t cover these in detail but it is worth knowing that they do occur.

// Pass by value
void foo(Bar bar);
// Pass by reference
void foo(Bar& bar);
// Pass by const reference
void foo(const Bar& bar);
// Pass by rvalue reference
void foo(Bar&& bar);
// Pass by pointer
void foo(Bar* bar);

With all these possibilities it can be hard to know when to use which. Let’s try to find some use case for each of the examples above.

Pass by value

When you pass an argument by value a copy of the original value will be created and supplied to the function. Creating this copy is expensive for large objects but there are at least two use cases where pass by value is the best fit.

Passing primitive data types

Examples of primitive data types are bool, char, int, long, float, and double. These are small and cheap to copy and there is no winning in creating a reference or a pointer to them instead of just creating a copy of the actual value.

Passing enum values

Enumerations, or enums for short are usually represented by integers that can be passed by value without any extra overhead.

enum class Color {
  Red,
  Green,
  Blue,
  Black,
  White
};

void setBackground(Color color) {
  // Some implementation goes here...
}

int main() {
  setBackground(Color::Green);
  // ...
}

Passing movable types for copying

This use case is a bit trickier to explain. From C++ 11 a type can be movable. This means that the ownership of its underlying data can be transferred – moved – to another object. One commonly used type that is movable it std::string. Without going into move semantics and rvalue references we can just say that moving the ownership of the data is in many cases a lot cheaper than creating a copy and freeing up the original memory.

Now, since passing by value will create a copy it is possible to use this fact in cases where you would want to create a copy anyway and then move the ownership of the data in the copy to the new object. Sounds confusing? Let’s take a look at an example:

class Person {
std::string m_name;
public:
  Person(std::string name)
    : m_name{ std::move(name) } {}
};

Here a std::string is passed in to the constructor by value, then the ownership of the underlying data is transferred from the the argument name to the member variable m_name. If you are a seasoned C++ developer you might think that the correct way to do this would be to pass name as const reference instead. That would however not take advantage of the fact that a std::string is movable which allows for some optimization. An example:

int main() {
  std::string name{ "John" };
  Person p{ name }; // Creates a copy of 'name' as argument
  Person p2{ "Danny" }; // Optimized. No copy is created.
};

In the creation of p2 in the code above the compiler will be able to generate optimized code that does not create any copy of ”Danny”. In order for the same code and optimization to be possible using references two constructors would be needed, one taking the argument as const reference and another taking the argument as an rvalue reference.

Pass by reference

This option is to be used when you need to somehow modify the argument passed in, and let the caller have access to the modified object. I try to avoid using this since it can be unclear when reading the calling code that the object passed in is actually being changed. Still there might be some cases where you want to do this. My advice would then be to name the functions so that it is clear that the object is changed:

enum class CreditScores {
  Low,
  Medium,
  High
}

void setCreditScoreOn(Person& p) {
  p.creditScore = CreditScores.Low;
}

int main() {
  Person dylan{ "Dylan" };
  setCreditScoreOn(dylan); // Should be obvious that dylan is modified
  if (dylan.creditScore == CreditScores.Low) {
    std::cout << "Loan rejected\n";
  }
}

Pass by const reference

This option should be the default way of passing user defined types that you do not wish to modify or copy, or user defined types that you do wish to copy but are not movable. Note that user defined types does include a lot of types in the standard library such as std::string, std::vector, std::map, and so on. The exception from the rule is enums. These can be passed by value since they are represented by a primitive data type.

std::string getFullName(const Person& p) {
  return p.firstName() + " " + p.lastName();
}

By using the const keyword the compiler can apply certain optimizations since it ”knows” that the Person object will not be modified when getFullName() is called.

Pass by rvalue reference

The double ampersand, &&, identifies an rvalue reference. An rvalue is a value that does not have a name, and therefore you can’t get the address of it or put it on the left side of an assignment:

int n{3}; // 3 is an rvalue and n is an lvalue. Since n has a name
n = 4;    // it can be put on the left side of an assignment

But if something does not have a name, how do you add it as an argument to a function? Well, you define it directly in the argument list, like this:

void print(const std::string& str);  // Normal reference
void print(const std::string&& str); // R-value reference

int main() {
  std::string str{ "Hello" };
  print(str); // Invokes the first print function
  print(std::string{ ", World!" }); // Invokes the 2:nd print function
}

The example above works but it is not something that you would normally do. What rvalue references really do is to enable move semantics. I will not go into details of move semantics here since it is a large topic that requires a post of its own, but I will mention that it is possible to cast a value to an rvalue reference with std::move.

Pass by pointer

In most cases passing raw pointers around should be avoided in modern C++. The reason for avoiding them is that it is easy to make mistakes – like forgetting to check if the pointer is a nullptr, accidentally de-referencing freed memory, or reading or writing outside of the allocated memory – which can lead to really nasty bugs that are next to impossible to find. With that said, there are still some use cases where pointers are needed. But when choosing between using a reference or a pointer, use references when you can and pointers when you have to.

One case when you might need to use a pointer is when interacting with a library written in C (there are no references in C) or when you have a need to indicate a special condition which you can do with a nullptr. In the case of a raw pointer without any const you are free to alter both the pointer and the object that the pointer is pointing to:

void zeroUntil(int* values, int sentinel) {
  if (!values) return;
  while (*values != sentinel) {
    *values = 0;
    values++; // Note that 'values' here is a copy of the pointer in the calling code. Incrementing it has no effect on the pointer in the caller.
  }
}

int main() {
  int values[]{ 1, 2, 3, 4, 5, 6, 7 };
  zeroUntil(values, 5);
  // values is [0, 0, 0, 0, 5, 6, 7]
}

Adding const either restricts modification of the pointer or the value it points to:

void foo(const Bar* bar); // bar is a pointer to a Bar object that is const
void foo(Bar* const bar); // bar is a const pointer to a Bar object that is not const
void foo(const Bar* const bar); // bar is a const pointer to a Bar object that is const

Ending words

Hopefully this article gave you a good overview of when to use pass by value, pass by reference, and pass by pointer. It does not cover all variants that you might encounter but enough to get a good understanding of the different possibilities.

Live long and code well!

Representing missing value in C++

One thing that I have bothered me quite a lot when coding in C# is a good way to indicate the absence of a value. There are a few different alternatives how this can be done, the most common being using null. However, using null instead of an actual type quite often leads to crashes and bugs due to NullReferenceExceptions being thrown when a null check is forgotten somewhere in the code.

string GetName(int personId)
{
    var p = repository.Find(personId);
    return p.FirstName + " " + p.LastName; // What if p is null?
}

string GetNameWithNullCheck(int personId)
{
    var p = repository.Find(personId);
    if (p == null) return null; // The caller needs to check if null is returned
    return p.FirstName + " " + p.LastName;
}

As can be seen in the code above it is easy to forget to check for null and there is nothing in the last method that informs the caller that he GetNameWithNullCheck might return null.

Functional languages like Haskell and (functional first) F# have a different approach to representing the absence of a value; Maybe in Haskell and Option in F#. These can both be seen as a container that can either be empty or contain a single value. As you might have guessed, an empty container represents the absence of a value.

let getName personId =
    match find(personId) with
    | Some(p) -> Some(p.firstName + " " + p.lastName)
    | None -> None

In the code above, both find and getName returns an Option. By matching against Some and None it can be determined if find returned a value and act accordingly. The big difference here, comparing to the C# methods, is that the returned value must be checked if it has a value or the code will not even compile. Compile errors should always be preferred to runtime errors.

With this covered, lets investigate how the case of a missing value can be handled in the C++ language. The old way of handling the absence of value was probably to use pointers and in the case of no value, a nullptr. This is by no means better than null in C#. Dereferencing a nullptr can lead to all kinds of errors in an application and should be avoided at all costs. This is why it was happily surprised when I discovered that an optional type had been added to the C++ standard library as of C++17.

#include <optional>

std::optional<std::string> getName(int personId) {
    auto p = repository.find(personId);
    if (p)
        return p.getFirstName() + " " + p.getLastName();
    return std::nullopt;
}

Using optional has one major advantage over the other ways of handling a missing value; it is absolutely clear just by looking at the function’s signature that it may or may not return a value. You do not need to spend any time reading and understanding the function’s body to understand this.

The caller of the getName function will have to check whether the returned optional contains a value or not. This can be done in a couple of different ways depending on what fits best for the situation.

// n1 is assigned the name of the person or "No name" if there is no person with id 123
auto n1 = getName(123).value_or("No name");
// prints a greeting if a person with id 234 exists
auto o1 = getName(234);
if (o1)
    std::cout << "Hello " << *o1 << std::endl;
// throws an exception if no person with id 345 exists
auto o2 = getName(345);
if (!o2.has_value())
    throw std::invalid_argument("Invalid user id provided");

To conclude, whenever there is a place in the code that may or may not have a value, prefer using optional to clearly indicate that this is the case and help avoiding misunderstandings and bugs.

Typing speed and accuracy

Recently I have been practicing my typing speed and accuracy. When you do a lot of typing on a daily basis it can be a good idea to ramp up your typing speed. It is said that the average typing speed is about 40 words per minute (WPM). For this measure a ”word” is standardized as five letters och key strokes.

When you first start out practicing typing you learn where the keys are on the keyboard and how to type while looking at the screen instead of the keys, I assume most professional office workers know how to ”touch type” like this.

The next thing to improve is accuracy. Correcting mistakes will slow you down a lot when trying to type fast. So before attempting to improve your speed you need to practice accuracy. A good goal is to consistently reach above 97% accuracy when practicing typing. A few sites that help you with this is http://www.typingclub.com and https://www.keybr.com.

Once you can type at about 50 – 60 WPM with > 97% accuracy you can switch over to other web sites. There are three main sites I recommend, they all serve different purposes. First off, speed. To build speed you can use https://www.nitrotype.com where you can race other players with your race car. To finish first you need to type correctly and fast.

Next is accuracy. Here you want to practice typing normal texts, with capital letters, punctuation, numbers, and such. For this you can use https://www.typeracer.com. As with Nitrotype you race other players on this site. Typeracer forces you to go back and correct mistakes, which encourages you to not make any in the first place.

The third site is https://www.monkeytype.com which helps you measure and track your speed over time. Here you do not type any longer texts, instead you are presented with a list of words to type as accurate and fast as possible. Do a couple of these each day to get a good indication of your progress.

My current speed and accuracy on Monkeytype

Some final words, it is easy to go all like, like I have, and practice a lot for a couple of days and then just stop practicing. As with every skill, the key to great progress is continuous practice. So try to schedule some time, like 20-30 minutes each day to visit the different sites I have listed in this post.

Being able to type fast and accurate will save you a lot of time if you are an office worker that spends lot of your time in front of the keyboard.

Teach Yourself Computer Science

In high school I studied electronics and computer systems, and then I continued on with software engineering for a short period before switching over to electrical engineering where I received my master’s degree in electronic system design. After graduation I wrote software for large embedded systems for a number of years before switching over to more high-level programming in .NET.

During my studies at the university I took some programming courses, but I never took any courses on data structures, algorithms, databases, computer networking, an similar courses that are related to computer science. However, during the years after graduation I have spent many hours studying these topics.

In Sweden, where I live, you do not have to pay a tuition fee to study at the university as long as you are a member of the European Union. Also, several universities offer single, stand-alone, courses that you can take remotely in pretty much your own pace. I have taken a few courses using this set up, most recently I took a hands-on course on relational databases given by the University of Dalarna.

I have also studied algorithms and data structures via Coursera. The course Algorithms, Part I, offered by Princeton University with instructors Robert Sedgewick and Kevin Wayne is great. It is quite challenging so if you consider taking it you should be prepared to put in quite a lot of work.

If you are looking for a courses on a specific framework or computer language there are tons out there. I worked through some courses on Pluralsight some time ago, which was nice. Just remember that they are often very specific to a particular programming language or framework.

Quite recently I found the site https://teachyourselfcs.com/ which is a superb resource for anyone looking for tips on what to study when you wish to learn Computer Science. I was happily surprised to find that I already had some of the recommended literature, namely the classic Structure and Interpretation of Computer Programs, The Algorithm Design Manual, and Designing Data-Intensive Applications. All of which are great books.

Today I also received a copy of Computer Networking: A top-down approach which is the recommended book on the topic Computer Networking.

If you haven’t studied Computer Science but are interested to learn, I highly recommend that you take a look at teachyourselfcs.com. The resources provided on that page contains information that will be useful your whole career and not just until the next hype.

Enough writing, time for me to dive into the next chapter of Computer Networking: A top-down approach.

Visualizing software architecture using the C4 model

Recently I stumbled across a model called ”C4” that is designed to help visualize software architecture. After reading through the C4 website, https://c4model.com/, and looking at the conference talk by Simon Brown, I felt that this was something I should explore more in depth. My, very personal, opinion is that we as software designers have lost our ability to communicate software architecture in clear and concise way. Maybe it was lost somewhere in our hunt for being ”Agile” and valuing Working Software over comprehensive documentation, as stated in the Manifesto for Agile Software Development, and we need to rediscover how it should be done.

Now, I don’t have anything against being agile, I’ve suffered through the test and integration phases of a large waterfall project and that is one experience I never wish to have again. However, valuing Working Software does not mean that our systems should not be documented, and in many cases claiming that The code is our documentation just does not cut it (if you don’t believe me, try telling your, not so tech-savvy, manager to clone the Git repo and look at the source code when you get a question about the system and see what happens).

What really spoke to me when I learned about the C4 model are the clear and distinct levels, Context, Containers, Components, and Code. These are ordered by level of abstraction. At the highest level, Context, the system is presented as a single box and the diagram shows people and external systems that in some way interact with the system. As an example I made up a used cars dealership backend system.

System context diagram for the imaginary system

It should be easy to understand the role of the system and the context of which it functions. In the diagram there are two Persons, or roles, a Car Dealer and a System Admin. The dealer has access to a locally hosted system which in turn interacts with the backend system via REST. The system admin has direct access to the backend system via some sort of graphical user interface (GUI). The backend system also interacts with the National vehicle owner registry via remote procedure calls (RPC).

On the next level, the Container level, we open up the box of the Used Cars Dealership Backend system and take a look inside. Note that containers in this context have nothing to do with Docker. Containers in this context are different applications that makes up the system. You can imagine a Web API application that handles REST request and some sort of admin application that the system administrator has access to. There is probably also one or two databases that contains information about the cars and the connected dealers. It could look something like the diagram below.

Container diagram for the imaginary system

On the third level, Components, we zoom in on a single container and peek inside. I didn’t go that deep in this example but you can imagine a component to correspond to a .NET project and the connections between components being project references. As you can imagine, the components level can change quite rapidly in new projects and it can be worth looking at ways to generate a diagram in this level instead of manually keeping it updated.

For the final level, Code, no diagrams should be manually constructed. The Code level is represented by classes and interfaces, which can be constructed using UML. These diagrams should either be skipped entirely or generated by a tool.

Conclusion

The C4 model can be really useful for describing a software system’s architecture from a high-level perspective. The first two levels, Context and Containers, should be quite stable during the lifetime of a system and creating and keeping those diagrams up to date should not add much work. The diagrams can be used when communicating the architecture both within a development team and with other parts of the organization.

The lower two levels, Components and Code, are more volatile and also more targeting software developers and architects actively working with developing the system. They should be generated from the existing code rather than being written manually by hand.

For documentation on Wiki pages, PDF files and the likes I would only use Context and Container diagrams. Components and Code can be generated when needed, or automatically generated as part of the build and automatically uploaded to a shared area.

There are pretty good tooling available for creating the diagrams using simple text and have the diagrams generated. For the diagrams above I used PlantUML (https://plantuml.com/) with a C4 add-on (https://github.com/RicardoNiepel/C4-PlantUML). The text files can be easily stored in the same source control repository as the system’s source code.