Published on

Language of the problem

Authors

What happens when superman does not use his powers?

We are living in the world of constant changes and new tech emerging every minute (I know, I am exaggerating. Did it purposefully!).

Our software should evolve even faster. Today's architecture might be obsolete in weeks, if we want to support new shiny use case.

Code must follow landscape that shapes itself. Code - a basic building block of the software systems.

We, developers and architects, can create things out of thin air. It fells almost unnatural!

Then, is the code our secret superpower?

What is the problem with a language of the problem?

Imagine a simple scenario: we are working with a collection of e-mails (valid and invalid ones) and we would like to get only invalid ones.

The example will be in C# and should be pretty straightforward.

// somewhere in a method of the service...
public void DoCoolStuffWithEmails(List<Email> emails)
{
    var invalidEmails = 
        emails
        .Where(email => !email.IsValid)
        .ToList();
    //...consume it somehow
}

Looks unharmful, right?

It is so typical that it looks innocently.

"What is your problem?"

What problem can I see?

I don't like the language used.

One could ask this question - why I am so nitpicky?

Why using computational power of the brain to even think about such a naive scenario?

Initially, we were challenged to solve the problem of getting invalid emails.

Above we can see a suggested solution (a model). In "Essentially bounded, Accidentally unlimited", I shared this observation:

Conclusion 🔍

There is no single solution, but there is a single problem.

Avid peer developer could say: "oh, you want to be more expressive?"

New hope

Here's the next version that typically appears in the wild:


private List<Email> GetInvalidEmailsFrom(List<Email> emails) =>
    emails
        .Where(email => !email.IsValid)
        .ToList();
        
// somewhere in a method of the service...
public void DoCoolStuffWithEmails(List<Email> emails)
{
    var invalidEmails = GetInvalidEmailsFrom(emails)
    //...consume it somehow
}

Great, readability increased. Intention is explicit now.

Still, I don't like it.

"The dot" will tell you

Bear with me, for a while (I know you will, because you are already spending your energy to read this!).

We already agreed (or at least I enforced it, my speachless reader) that our approach is one of the possible models.

I like to think that a model is materializing a certain capability, or responsibility.

Let pause for a while and ask a question:

Question 🤔

What responsibility do we want to get from our model?

It should be responsible for giving us only invalid e-mails.

How could we check what it can do?

We can just hit "the dot" (.) on our keyboard and we will get, of course, all of the available operations (i.e. methods).

What methods do we get in return, after hitting "the dot"?

Here are just some of them:

  • Any
  • Add
  • Where
  • Select
  • GroupBy
  • etc.

Let's be honest - are we getting the desired responsibility?

Paying attention to the language

We were pretty clear with our "question" to the model - we stated WHAT we want to get, but instead we got HOW.

Still, the language, a breakpoint that triggered entire "discussion" (in fact it is a monologue, because I am the only one talking, but let's pretend it is a discussion!), isn't right.

We didn't mention all those technical operations.

A remedy?

Stop coding, start modelling

Let's suggest another model.

A model where the concept of e-mails is really visible, and real.

public class Emails
{
  // I am empty :-(
}

Here it is. Our new, powerful model.

Of course, when we use "the dot", we are not getting any capabilities.

It's pretty useless, isn't it?

Ok, let's "teach" it some cool skillz. The first one of course will be giving us a list of invalid emails.

public class Emails
{
  public List<Email> GetInvalidEmails()
  {
    return new(); // look how cool I am, I know new syntax
  }
}

Impressive, but it's not working. We could ask ourselves: to satisfy this responsibility, what does Emails need to know?

Easy.

public class Emails
{
  private List<Email> _emails; // details

  public Emails(List<Email> emails) =>
    _emails = emails; // ctor

  public List<Email> GetInvalidEmails() =>
    _emails
    .Where(email => !email.IsValid)
    .ToList();
}

Woah, outstanding. Who did expect such evolution? Nevertheless, our work isn't finished.

We need to utilize our new friend. I got rid of unused method from previous attempt.

// somewhere in a method of the service...
public void DoCoolStuffWithEmails(List<Email> emails)
{
    var emailz = new Emails(emails);
    var invalidEmails = emailz.GetInvalidEmails();
    //...consume it somehow
}

How cool is that?!

What the hell have just happened? 😲

You might wonder: "Is this dude crazy? Why freaking with a simple class?"

Well, it might sound strange, but I don't see only a simple class.

I see a materialized concept, not just "a class".

It carries the context with it, binding a language, which is valid only within this context. (isn't it one of the definitions of Bounded Context? 🤔)

It satisfies the responsibility, we wanted it to get. When "dotting" it, we see what is possible. Nothing more, nothing less.

Conclusion 🔍

A concept carries the context with it, binding a language, which is valid only within this context.

We taught it what it needs to know.

Nihil novi

This, of course, isn't something new. Encapsulation, single responsibility, object-orientation, etc.

Name it as you want.

Mentioned heuristics, are yet again "technical" terms. And they are highly useful.

Thinking in terms of "concepts", about what, deferring how, expresses the right language.

It (the language), gives the meaning, determining the rules how one can use a given concept.

Such a modelling approach is very friendly, because of making it easier, for other programming folks, to get the intention faster. Documenting and capturing the context, so that it can evolve within its boundaries.

What about flexibility?

But actually, is it all flowers and butterflies?

Nope, one could argue we "lost" the flexibility of underlying data structure (a list).

We can't just rush to extract other details, use some interesting transformations. There's an additional effort to achieve so.

And that's fine.

Now it requires discipline, thus thought process, to pause and analyze, is it the right direction.

When new requirements are in place, we would be "encouraged" to stop and start asking (examplary) questions:

  • What other responsibilities it needs to know?
  • When it is too much?
  • Does it need help from external collaborators?
  • etc.

Our job is not to "code", but to solve problems, preferably by providing models. Modelling.

Why limiting yourself?

I like what grug "said":

Apex predator of grug is complexity.

I want to keep the apex predator in the single place. The best place for apex predator is in the cage. Or in a small box.

I want to exchange this box with something else, when necessary.

Yes, we are putting limits on ourselves. That's deliberate. Such discipline gives us some interesting properties.

Ease of reasoning (I already tried to discuss "The value of Value Objects").

Ease of giving the meaning to a technical data structures.

Ease of communication.

Every limit is a possibility, every possibility is a limit.

Conclusion 🔍

Stop coding, start modelling.

What's more, I think it's wise to stop lying to ourselves that things are simpler than they really are.

One could easily call such an approach, which I am trying to present, "unnecessarily complex".

What if such "complexity", of the concept we try to model (with its context), is hidden in the author's head?

Aren't we trying to turn our heads around, when unintentionally hiding in our heads the context and intentions, and stating "nothing to see here"?

A bonus: are we dealing with any email?

Let's recall our suggested model:

public class Emails
{
  private List<Email> _emails; // details

  public Emails(List<Email> emails) =>
    _emails = emails; // ctor

  public List<Email> GetInvalidEmails() => // is it ANY email?
    _emails
    .Where(email => !email.IsValid)
    .ToList();
}

Avid reader might notice that GetInvalidEmails returns a list of Emails. Method itself says it is giving out only invalid emails.

Are we missing something? What do you think?