Software development is a very young field, particularly when you compare it to, say, medicine or law. Despite this, there’s no shortage of wisdom pearls, which accumulated in the decades that preceded us.
One interesting phenomenon I’ve observed in myself over the years—and I’m sure there’s a name for it—is that some of these sayings sound like they must be right, even if I don’t really understand them the first time I hear them. For instance, in my post about the SOLID principles, I mentioned how the SRP’s definition—”each class should have just one reason to change”—just ticks the right boxes for me in some way that I can’t even pinpoint.
Unfortunately, just hearing a phrase and acknowledging that it kind of sounds right doesn’t do much to really make you understand the topic, right?
Then why do so many in our industry act like that’s the case? I’ve lost count of how many times I’ve seen experienced developers toss around catchphrases like this as if they’re able to automatically inject the necessary information into beginners’ heads.
In this spirit, I’ve decided to demystify one of these catchphrases that happens to be one of my favorites: “Separation of Concerns.” What does that mean and why should you care? That’s what today’s post is all about.
First of All: What Are Concerns?
Before we get to explain why concerns are best off when they’re separated, we should take a step back and understand what “concern” even means in the context of software development.
The current Wikipedia definition says this:
In computer science, a concern is a particular set of information that has an effect on the code of a computer program.
Frankly, I think that’s quite vague and not particularly useful. So instead, let’s try to come up with some examples. Think about a boring line-of-business application, such as a payroll application. What are its concerns?
- Interaction with the user.
- Generation of charts, graphs, and reports of different kinds.
- Calculation of employee’s salaries, benefits, severance packages and so on.
- Persistence of all the relevant data into some persistence storage.
In short, each one of the areas that an application covers and does something is a concern.
Concerns in the Software World: Why Keep’ Em Apart?
Suppose you and your team successfully release an application. You’re getting great feedback and all is nice in the world.
Then, like they always do, a requirement for a new feature comes in. And for the sake of the argument, this is a feature you absolutely have to do. You can’t refuse it (let’s say the competitor’s product already has it).
What would the potential risks be when you add this feature? We could cite a lot of them, but it all boils down to two things in the end really. First, it’s possible that the feature could be very hard to write because you’d have to write a lot of code in a lot of different places. You also run the risk of breaking current features that paying customers already depend on.
Keeping your concerns separated will decrease the above risks. If all the code related to a certain concern is kept together (for example, in the same layer) it becomes easier to change it. You don’t have to make a myriad of changes scattered throughout the code base. You don’t have to “look for” where a certain task is implemented since the code is organized according to its concerns.
Finally, you don’t risk breaking code unrelated to what you’ve implemented since this other code doesn’t even reside in the same place in the application.
Let’s get back to our boring payroll example. Think about the concerns we’ve identified for it. Would it make sense that a request to support Oracle Database beyond the current PostgreSQL could cause the app to miscalculate salaries? Or could a change in the location of a GUI element make a SQL query stop working? By keeping each concern as separated as possible, we can prevent those things from happening.
Let’s now see an example of what it looks like when concerns are not separated.
Desperation of Concerns—A Quick Example
Let’s write a toy app based on Roy Osherove’s String Calculator Kata. It’s going to be a very simple WinForms app with just one text field and one button. The user should input integer numbers separated by a comma and then click the button. The application will then calculate and display the sum of the inputted numbers. Only non-negative integers are allowed, though. If the input string contains any character that is not a non-negative integer, then the sum is aborted and an error message should be displayed along with the list of offending characters.
The following images depict a successful and an unsuccessful sum, respectively:
It isn’t that hard to write code for the application you can see above. The following listing shows the code for the “Add” button:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
private void AddbButton_Click(object sender, EventArgs e) { var input = NumbersTextBox.Text; var pieces = input.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var sum = 0; var invalid = new List<string>(); var negativeIntegers = new List<string>(); var error = false; foreach (var piece in pieces) { if (!int.TryParse(piece, out int number)) { invalid.Add(piece); error = true; continue; } if (number < 0) { invalid.Add(piece); error = true; continue; } sum += number; } ResultsLabel.Visible = true; if (error) { ResultsLabel.Text = "Only non-negative integers are allowed!"; ResultsLabel.BackColor = Color.Yellow; ResultsLabel.ForeColor = Color.Red; ResultsLabel.Font = new Font(ResultsLabel.Font, FontStyle.Bold); InvalidInputsLabel.Text = "Invalid characters: " + string.Join(", ", invalid); InvalidInputsLabel.Visible = true; return; } ResultsLabel.BackColor = SystemColors.Control; ResultsLabel.ForeColor = SystemColors.ControlText; ResultsLabel.Font = new Font(ResultsLabel.Font, FontStyle.Regular); ResultsLabel.Text = sum.ToString(); InvalidInputsLabel.Visible = false; } |
As you can see, the developer hasn’t kept concerns separated! We have error-handling, business logic, and UI code all in one big mess. How can we make things better? Why should we? It’s not hard to imagine that a new requirement could come in and ask to deploy the application as a console app. How could we do that without duplicating a lot of code? It should be very easy to do if concerns were separated. If all the string calculator logic itself was contained in an isolated location, it’d be a matter of adding a new project—a console app—to our solution and writing a few lines of code.
And how could we make this example a little bit better? First, I’ll add a new project to the solution. This new project will be of the type “Class Library.” Then, I’ll add a new class to this project, which I’ll name “StringCalculator.” The code in the class should be as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using System; using System.Collections.Generic; namespace SoCExample.Domain { public static class StringCalculator { public static (string invalidCharacters, int result) Add(string numbers) { if (numbers == null) throw new ArgumentNullException(); var pieces = numbers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var sum = 0; var invalid = new List<string>(); var error = false; foreach (var piece in pieces) { if (!int.TryParse(piece, out int number)) { invalid.Add(piece); error = true; continue; } if (number < 0) { invalid.Add(piece); error = true; continue; } sum += number; } return (error ? string.Join(", ", invalid) : null, sum); } } } |
All you need to do now is to edit the form’s code in order to use the new class. I’ll leave that as an exercise for the reader.
Write Software As If Your Users Were Blind. Teach As If You’re a Beginner.
Recently, I came up with this sentence: “Write software as if your users were blind.” I’ve been using this phrase as a mental framework for thinking about separation of concerns, particularly regarding decoupling logic from presentation. When I spot some code in the application’s domain layer mentioning visual aspects such as color names, I ask myself, “Would this still make sense if the people who’ll use this app were blind?” (And they might as well be, why not?).
If the answer is “no,” then I know I must move that code to the presentation layer, keeping in only the domain layer code related to the concepts themselves.
When teaching someone, you should always be aware of the curse of knowledge. Try to have empathy with your students. Put yourself in their shoes and try to remember when you were a beginner yourself. Don’t just toss catchphrases around; take the time to turn them into valuable lessons that will inform and empower the developers of tomorrow.