- Published on
The ambiguity of code
- Authors
- Name
- Damian Płaza
- @raimeyuu
Dear Reader, for the purpose of this tiny tale, domains and business areas, that were used as examples, I excavated from a nice chat with ChatGPT.
Bear that in mind while reading - I am not an expert in those areas!
Just code stuff?
Imagine that we were challenged with fixing couple of bugs and adding a new capability to already built system (legacy?) that we weren't contributing to before.
Clothing production - sounds so interesting!
Some time passed, sprints were not sprints but rather slow walks, and eventually we were diving into the "code".
"The code" was having all the required design patterns - there were factories, template methods and various other approaches.
We tried to hook into some services, adapters, and other cool technical shinies - still it wasn't enough.
While being vastly interesting, turned out that clothing production, as area of human activity, is quite complex too.
Somehow it wasn't that easy to grasp what some part of "the code" do.
It should supposed to be "just code", right?
Only hero can save us!
But imagine we had a great luck - during lunch we chatted with an clothing production expert.
Jeff, that was his name, knew everything about this business as he worked in this area for almost 30 years.
Imagine we asked for a 2 hour intro and that's what Jeff did - he invited to a room with a whiteboard where all the magic happened.
Jeff wrote the following:
Textile Manufacturing
Pattern Making and Design
Garment Construction
Quality Control and Assurance
Supply Chain Management
Sustainable Practices
Technology and Innovation
Fashion Forecasting and Trends
Customization and Personalization
E-commerce and Retail
The more we went into the discussion, the more unknown terms and lingo started becoming familiar.
One of the bugs was related to incorrect mapping of seams and stitches.
The "aha" moment came.
"Garment Construction
!", we yelled.
We scared Jeff a bit but after a while we described our Eureka time.
Pattern Making and Design
. That's my favorite one. I started my journey as a designer. What's wrong with it? We: Turns out that for some customers pattern grading logic is incorrectly working. Jeff: Sounds like Customization and Personalization
problem? I think you should ask Henry, he knows a lot regarding that area.Those were very intense three hours.
We didn't touch "the code", which feels a bit weird.
Just talking, we hope no one would accuse us of not wasting time.
Code?
Somehow when we came back to the screen and keyboard, it wasn't perfect but we could relate to the terms and language used in the code.
Not in all the places "the code" was super "clean" (what does it really mean?), and of course there were moments when we needed to "focus" and understand what part of the given subdomain was supposed to be represented.
Nevertheless, we found the place where stitches and seams mapping was mainly done.
It turned out that "it was just a wrong if" - fabrics-related logic had wrong argument passed. The method looked as follows:
public Task<Stitch> SelectStitchByFabric(string fabric)
{ /*...*/ }
Fix itself was very trivial, knowing what subarea of business we work in currently.
We even consulted Jeff.
COTN#080#B
- this means that we're dealing with cotton, having thinckness 0.8mm, having density B. We: (...silence...)We got a bit confident with our understanding so we refactored "the code" from:
public Task<Stitch> SelectStitchByFabric(string fabric)
{ /*...*/ }
to:
public Task<Stitch> SelectStitchByFabric(Fabric fabric)
{ /*...*/ }
From now on, a concept of a Fabric
has started to be used by other people too.
Suddenly, glitches in other parts of the system happened as this little Fabric
caused great thread (pun intended) on coding fabrics.
As for grading rules and customer problems - Jeff was right - it was mainly related to configuration of available grading rules, not applying them.
As Jeff advised, this time we consulted Henry.
Grading rules configuration was coded like this:
public record GradingRuleConfiguration(string Id, string CustomerId, string SizePatterns, bool IsDefault, bool IsPremium);
With usage in a ConfigurationService:
public class ConfigurationService
{
private GradingRuleConfigurationRepository _rulesRepository;
public ConfigurationService(GradingRuleConfigurationRepository rulesRepository)
{
_rulesRepository = rulesRepository;
}
public async Task ConfigureGradingRuleConfigurations(string customerId, string customerTier, List<GradingRuleConfiguration> incomingRules)
{
var defaults = await _rulesRepository.QueryAsync(rule => rule.IsDefault); // ❌ can you see a bug?
var finalRules = incomingRules.Concat(defaults);
await _rulesRepository.UpdateAsync(finalRules);
}
}
We understand the problem now.
There are defaults and premium defaults.
The fix is easy now:
public class ConfigurationService
{
private GradingRuleConfigurationRepository _rulesRepository;
public ConfigurationService(GradingRuleConfigurationRepository rulesRepository)
{
_rulesRepository = rulesRepository;
}
public async Task ConfigureGradingRuleConfigurations(string customerId, string customerTier, List<GradingRuleConfiguration> incomingRules)
{
var defaults = await _rulesRepository.QueryAsync(rule => rule.IsDefault && rule.IsPremium); // 👈️ easy fix!
var finalRules = incomingRules.Concat(defaults);
await _rulesRepository.UpdateAsync(finalRules);
}
}
Probably it should be enough to close one Jira ticket but we wanted to confirm it with Henry.
It's clear - we understood the logic there.
Code-wise, it was so easy.
Fix applied, move on, right?
It's very tempting to leave it as it is right now - avoiding "overengineering".
But we learned something, didn't we?
At least now we have the understanding that there are platinum customers which always get premium defaults. But when we look at "the code" - this understanding isn't there.
Yeah, the system behaves as expected, but it feels under the skin that something is missing.
Now we know what this code means but what will happen in the future?
Ok, let's Slow down.
Let's introduce something that will represent premium default rules:
public record PremiumDefaultGradingRuleConfigurations(IReadOnlyList<GradingRuleConfiguration> Rules);
We know that when configuring rules for platinum customer, those need to be included in incoming premium rules:
public record IncomingPlatinumGradingRuleConfigurations(IReadOnlyList<GradingRuleConfiguration> Rules)
{
public PlatinumGradingRuleConfigurations IncludeDefaultRules(PremiumDefaultGradingRuleConfigurations defaults) =>
PlatinumGradingRuleConfigurations.CreateFrom(defaults.Rules, this.Rules);
}
So revisting our little service:
public class ConfigurationService
{
private GradingRuleConfigurationRepository _rulesRepository;
public ConfigurationService(GradingRuleConfigurationRepository rulesRepository)
{
_rulesRepository = rulesRepository;
}
public async Task ConfigureGradingRuleConfigurations(string customerId, string customerTier, IncomingPlatinumGradingRuleConfigurations incomingRules)
{
var defaults = new PremiumDefaultGradingRuleConfigurations(await _rulesRepository.QueryAsync(rule => rule.IsDefault && rule.IsPremium));
var finalRules = incomingRules.IncludeDefaultRules(defaults);
await _rulesRepository.UpdateAsync(finalRules.ToList());
}
}
Yey, it's working as previously! (we have tests, right?!).
But wait, when getting rules using a repository, do we care at this point how they are retrieved?
Hm, no. We are interested in a specific capability - getting premium defaults.
We should require "someone" who knows how to provide them.
Let's introduce a responsibility (what do I mean by that? If you're curious, please check The ambiguity of interfaces):
public interface IProvidePremiumDefaultGradingRuleConfigurations
{
public Task<PremiumDefaultGradingRuleConfigurations> Provide()
}
Now, time to introduce our new friend:
public class ConfigurationService
{
private GradingRuleConfigurationRepository _rulesRepository;
private IProvidePremiumDefaultGradingRuleConfigurations _premiumDefaultGradingRuleConfigurationsProvider;
public ConfigurationService(GradingRuleConfigurationRepository rulesRepository, IProvidePremiumDefaultGradingRuleConfigurations premiumDefaultGradingRuleConfigurationsProvider)
{
_rulesRepository = rulesRepository;
_premiumDefaultGradingRuleConfigurationsProvider = premiumDefaultGradingRuleConfigurationsProvider;
}
public async Task ConfigureGradingRuleConfigurations(string customerId, string customerTier, IncomingPlatinumGradingRuleConfigurations incomingRules)
{
PremiumDefaultGradingRuleConfigurations defaults = await _premiumDefaultGradingRuleConfigurationsProvider.Provide();
PlatinumGradingRuleConfigurations finalRules = incomingRules.IncludeDefaultRules(defaults);
await _rulesRepository.UpdateAsync(finalRules.ToList());
}
}
It seems like "the code" expresses our current state of the knowledge about this problem.
Of course, during the review process, we got "accussed" of overengineering and building citadels of too many classes (or records).
For sure, there is more code.
One might say it's pointless to be that specific as it might change - then we will have so much work to do!
What "Agile" and "Waterfall" have in common?
"Can't you just code stuff?" - have you ever heard something similar, dear Reader?
"I just want to code stuff" - or this?
I had this kind of mindset - "a glorified typist".
We could put "just coding stuff" in a single axis.
It forms a spectrum with "Agile"-like and "Big Up Front Design"-like extremas on sides.
We could highlight "the common" part.
Stuff?
I like definitions.
Not because I can boast during a party, but because I can enrich my understanding by chopping stuff until I find the deeper meaning.
Let's look at the meaning of "code":
a system of words, letters, figures, or symbols used to represent others, especially for the purposes of secrecy.
According to this definition, it helps representing "a thing" by "other things".
Of course, it is one of the perspectives that one might have on "the code".
It's quite typical to say "coding" but it's more fun to say "representing a thing by other things", right?
Ok, what are those things then?
During our tiny tale, to complete the bug fixes, we were lacking one precious resource - the knowledge.
The understanding.
The context.
We of course could apply some semi-random moves and apply "fixes".
Sometimes there is no other way (due to various reasons).
But as we saw, meeting Jeff and Henry enabled building the understanding.
And as you saw, dear Reader, after each fix, we spend some time on "refactoring the code", right?
We often hear that term, e.g. when we want to clean thing up after some sketchy work.
In our tiny tale, we grew our understanding and eventually we decided to represent that knowledge using code.
Sometimes "it's just a class" and sometimes it might require introducing much "more code".
The representation of knowledge
If we get back to our spectrum for a while.
We were able to represent our understanding of underlying...Yeah, what exactly?
We had discussions both with Jeff and Henry, which eventually made us aware of certain concepts living in the area of clothing production business.
Some say those are "just words" or "this is just a language".
From one perspective - yeah, that might be true.
Words and language convey the meaning.
The meaning carries rules whenever it goes.
When we don't pay enough attention, it might hurt us later.
The spectrum might indicate that there is no single representation.
"The code", "architecture" and similar concepts (from the IT area of human activities) also have the meaning encapsulated (you might be interested in The ambiguity of encapsulation) and details abstracted away (you might be interested in The ambiguity of abstraction).
They are (just) representations.
A code is one of the representations of our understanding of the concepts and ideas living inside of a certain context.
Code fetish
The central part of software engineering work is the activity of coding - using "the code" to represent the understanding we acquired or grew through time about "something".
It reminds me the first sentence from the great book, written by Craig Larman, "Applying UML and Patterns" (paraphrasing):
Programming is fun, but building maintainable software is hard.
But when taken as a goal itself, one might lose the purpose of using code as a tool for representing the knowledge of a particular problem.
It is vastly important to know what is the context we operate in and what are problems we are supposed to deal with.
This whole journey, as we saw in our tiny tale, might lead us From concepts to architecture.
One could say that we organized the ideas living in the area of clothing production (what do I mean by "organizing"? See The ambiguity of software architecture).
When we start representing our understanding, we might eventually land up with much more "things" getting created (e.g. in the form of the code).
"More code" might look not simple but remember that Simple isn't easy.
So instead of treating it as a "my code is better than yours" kind of a fetish, we should orient our thinking into "understanding fetish".
Breath it.
Speak it.
What's next?
Of course I am exaggerating a bit when it comes to putting "understanding representation" in the central place.
We might eventually get onto the right side of the visited spectrum, which might not be the desirable state.
Next time, dear Reader, when someone says "let's jump into coding", expand details abstracted away with the concept of "coding", and counter it with "you mean we should jump into representing the understanding of the given problem using code, right?".
Fun time ahead!
It might anchor you back into the question "do we know what we do?" kind of a state.
Do you have enough understanding?
Do you know how to represent it?
What is the representation level we should utilize? (levels? You might be interested in Modeling Maturity Levels)
Nevertheless, everything starts with understanding, not with the representation.