<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://sebastian-hans.de/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Sebastian&#39;s blog</title>
    <link>https://sebastian-hans.de/</link>
    <atom:link href="https://sebastian-hans.de/rss.xml" rel="self" type="application/rss+xml" />
    <description>Hi, I am Sebastian Hans, a software architect and developer with about 20 years of professional experience. I started this blog to get some ideas out of my head that have been bouncing around in there for some time now. I plan to write mainly about work stuff.</description>
    <language>en</language>
    
    <item>
      <title>Feel the force</title>
      <link>https://sebastian-hans.de/blog/feel-the-force/</link>
      <description>&lt;p&gt;An important principle in both &lt;a href=&quot;https://sebastian-hans.de/blog/software-development-and-martial-arts/&quot;&gt;martial arts and software development&lt;/a&gt;
is “feeling the force”.
By this I mean sensing the strength and direction of the force an opponent is
applying (or is about to apply) as well as the opponent&#39;s stability in various
directions you might consider applying force in.
Or, in software architecture, seeing all the forces at play when preparing for a
decision.
In both cases, feeling the force enables you to anticipate how it might impact
you (in a literal or figurative sense), and allows you to find the best (martial
arts or software architecture) move in the situation.&lt;/p&gt;
&lt;h2 id=&quot;feeling-the-force-in-martial-arts&quot; tabindex=&quot;-1&quot;&gt;Feeling the force in martial arts&lt;/h2&gt;
&lt;p&gt;In martial arts, the better you can judge the speed and angle of – and the
strength behind – an attack, the better you can position yourself outside the
direct line of attack and perform a counter movement that neutralizes it.
In software architecture, the better you can judge the social, political, and
technical forces affecting the system you are designing, the better you can
adapt the shape of the system to go along with or withstand these forces as
appropriate.&lt;/p&gt;
&lt;p&gt;A classic diagram in martial arts shows how to destabilize your typical
two-legged opponent.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/feel-the-force-diagram.svg&quot; alt=&quot;A diagram showing two footprints connected with a line. A double arrow on the line pointing towards the feet is labelled “stable”. A double arrow perpendicular to the line is labelled “unstable”.&quot; width=&quot;300em&quot; /&gt;
&lt;figcaption&gt;“Cross” diagram of an opponent&#39;s stance&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;You apply force perpendicular to the line connecting the two feet.
Pulling or pushing in parallel to this line only serves to stabilize
your opponent because you end up pushing him onto his own foot,
so the force is easily redirected into the ground.
However, there is no support on either side of this line, so applying
just a little bit of perpendicular force immediately destabilizes your opponent,
forcing him to adjust his position or fall over.&lt;/p&gt;
&lt;p&gt;Now, in most practical situations, you won&#39;t have time to draw a diagram, or
to ask opponents to stand still so you can get a good look at their foot position.
You have to get the direction right immediately.
To get to the point where you can do that, you need to practice. A lot.
By doing this, you develop a sense of where the opponent&#39;s feet are without
looking at them, of the momentum of the opponent&#39;s body (which enables you to
decide to which side to move), and of the best point in space to position your own
body so the opponent has no chance to lean on you to stabilize himself.
This is what I mean by feeling the force.
You practice until this becomes second nature.
At this point, it feels effortless, and it may look like magic to an unskilled
observer.&lt;/p&gt;
&lt;h2 id=&quot;feeling-the-force-in-software-architecture&quot; tabindex=&quot;-1&quot;&gt;Feeling the force in software architecture&lt;/h2&gt;
&lt;p&gt;In software architecture, “feeling the force” means being aware of the
&lt;a href=&quot;https://ishanul.medium.com/system-constraints-in-software-architecture-4750233c3dbf&quot;&gt;constraints&lt;/a&gt;
bearing on decisions you are about to make, of the current
trajectory of the system under development and surrounding systems that might
potentially have an impact on those decisions, and of the
&lt;a href=&quot;https://donellameadows.org/archives/leverage-points-places-to-intervene-in-a-system/&quot;&gt;leverage points&lt;/a&gt;
at your disposal to deal with them.
This awareness lets you come up with solutions that fit the constraints and
guide the system in the right direction.
It can feel as if the architecture “flows” naturally from the context.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/feel-the-force/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;For example: Picture an old enterprise with a heterogeneous IT landscape
(partially on prem and partially in the cloud) managed by a strong, centralized
IT department, where in-house development is focused on Java/Spring Boot and
TypeScript/Angular.
The enterprise as a whole is highly focused on compliance such as data protection regulation.
Now imagine a situation in which one business unit of this
enterprise, a mostly agile bubble in this large, not-so-agile organization, is
tasked with developing a new customer landing page with some custom
functionality, while it is at the same time trying to build an internal
integration platform to support several budding value streams.
The landing page is important politically, and there is a deadline – not too
tight, but not comfortable either.
The business unit has a couple of employees who know just enough Python to be dangerous
and has already developed some ideas for
building the integration platform in the cloud (even though the requirements are very hazy)
before the project has come to the attention of the IT department,
which is – of course – stretched for resources.
One of the ideas is to use the new landing page as a pilot for the integration platform
(which doesn&#39;t yet exist).&lt;/p&gt;
&lt;p&gt;This is a real situation I found myself in a couple of years ago,
when I, as part of said IT department, was in the position to establish myself
as a solution architect for this project by dint of being in the right place at the right time.
So what does “feeling the force” look like in this situation?&lt;/p&gt;
&lt;p&gt;It was clear from the outset that letting the business unit loose with an
account on the cloud platform by themselves was not going to work out well.
At the same time, we (the IT) didn&#39;t have spare resources to staff the project ourselves.
Nevertheless, positioning yourself as a blocker is never a good start.
So the first thing I did was to talk to them and listen to their ideas.
This was to the detriment of other commitments, but it enabled me to establish
a friendly working relationship, to gather further information, and to get a
feeling for the forces they felt (political pressure, promises they had made
to their leadership, motivation to get their hands dirty coding themselves etc).
I was observing their stance, so to speak.
While this may sound obvious when you read about it (“just talk to them”),
other IT architects at the company got quite upset about the plans of the business unit
and wanted to have an internal discussion about how “IT should position itself”
before confronting the business unit with this position. This is the opposite of
feeling the force – trying to react before really knowing anything.
Feeling the force requires establishing contact first.&lt;/p&gt;
&lt;p&gt;Talking to my colleagues in IT was also part of “feeling the force”.
One aspect of this was the self-image of certain colleagues, which boiled down to,
“We are the IT experts!”
Another was the availability – or rather, unavailability – of spare resources.
Some others like the IT tech strategy and security policy I already knew.&lt;/p&gt;
&lt;p&gt;After some internal negotiation, I managed to secure a part-time commitment from one of our IT cloud
architects for the business unit to ensure a reasonable base setup in the
public cloud without any major security issues.
I also offered to accompany the project as a solution architect, while at the
same time juggling with my other commitments to make this work.
The people in the business unit accepted this gladly because I framed it as
extending a helpful hand rather than coming down on them with compliance and
security regulations.
In the eyes of my IT colleagues I was reining in the rogue business unit,
which served to appease them.&lt;/p&gt;
&lt;p&gt;After some back and forth, we settled on a microservices architecture
based on Python for the integration platform part (to enable experimentation by
the business unit without too much IT involvement, at least in the beginning)
and a backend for frontend built by myself and a fresh graduate of our internal
training program on the approved tech stack.
This backend for frontend served the dual purpose of&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;providing a simple facade to the frontend for the functionality it needed and&lt;/li&gt;
&lt;li&gt;shielding the potentially not as secure (built by less experienced people
using an unfamiliar tech stack) integration services from external access.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I also freed up time by delegating other work so I could take the lead writing
the mandatory security concept (including threat modelling and risk assessment)
and getting it approved with minimum fuss.
In the end, we did manage to get the landing page up in time for the deadline,
and development continued within this architectural frame.&lt;/p&gt;
&lt;h2 id=&quot;how-to-practice&quot; tabindex=&quot;-1&quot;&gt;How to practice&lt;/h2&gt;
&lt;p&gt;In martial arts, you learn how to feel the force by practicing the same sequence
again and again. The feedback cycle is very quick: you push or pull and see the
effect immediately. Did it work? Remember what it felt like and try to reproduce
it. It didn&#39;t work? Make a slight change to your position, stance, or direction,
repeat, and observe the effect. Usually, an instructor will help if you get
stuck or have questions.&lt;/p&gt;
&lt;p&gt;In software architecture, the feedback cycle is typically
much longer, and you get much less repetition. Also, the situation is often not
repeatable in the same way – unless you are prepared to discard at least part of
the work that has already been done and start over – and how often do you get
that chance? Another consequence of this is that there is no instructor who has
done the same thing a thousand times before and can tell you what you are doing
wrong.&lt;/p&gt;
&lt;p&gt;Despite all this, I think you can – and should – practice feeling the forces in
software architecture, too.
Here are a few ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Close the feedback loop.&lt;/strong&gt; Although feedback is often slow to come, it is still
valuable. Get feedback on your architecture decisions after
enough time so that their effects have made themselves felt. In his lightning
talk &lt;a href=&quot;https://vimeo.com/1173187152&quot;&gt;Tech debt nomads and slash-and-burn development&lt;/a&gt;,
Einar Høst suggests that this time is about 3 years. From my personal
experience, I&#39;d say it&#39;s more like 2 years for bigger decisions if you
are sensitive to feedback. Feedback on smaller decisions can come much faster,
but you still have to look for it. It doesn&#39;t always smack you on the head.
In any case, make sure to look for feedback, and to learn from it.
If I leave a team before the trajectory becomes visible, I try to get in touch
with someone on the team later and ask them how the architecture has held up.
This is not always possible, but when it is, it&#39;s always interesting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Try multiple versions.&lt;/strong&gt; For smaller decisions like, for example, whether to
implement a certain feature using inheritance or composition, it is often
quite possible to try both versions and see where this takes you.
Writing a little bit of code is cheap, and even if you will discard half of
it, what you learned will stay with you and make you a little bit smarter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recycle past situations.&lt;/strong&gt; You may not be able to recreate a situation to
practice it again for real, but thinking is cheap.
You can always recreate it in your mind and reflect on it.
What happened? How did you handle it? What happened as a result?
What do you know now that you didn&#39;t know then that turned out to be
important? How could you have known at the time? In retrospect, what signs
were there that you might have missed? Also: What went well, and why?
Were you well prepared, or did you get lucky? What could have gone
differently, and how would you have reacted to the change – with your knowledge
then and with your knowledge now? How could it have gone even better?
Asking these questions turns you into your own instructor because having lived
through the situation already allows you to see it much clearer, and to come
up with your own advice.
If you can talk the situation through with someone else (a peer, a mentor),
this can help, too, but even if you can&#39;t, a little reflection can take you a
long way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stress-test your decisions.&lt;/strong&gt; You can exercise feeling the force in your
current situation, too, by taking a step back before committing to a decision
and asking challenging questions like, “Why are we even doing this?”
This is only half a joke. Surprisingly often, the answer is not really clear.
But even if it is, you can play around with the parameters the answer likely
contains. What if this requirement changes? What if this assumption turns out to be wrong?
What would need to happen for this to blow up politically? Etc.
Posing these questions can help you feel out the trajectory you are on
and bring hidden forces to the surface so you can deal with them.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/feel-the-force/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do systems thinking exercises.&lt;/strong&gt; If you want to get a broader perspective,
or if you are new to this and don&#39;t have a long history of past decisions to
draw on, you can do systems thinking exercises like
&lt;a href=&quot;https://www.ruthmalan.com/Advent/About.html&quot;&gt;Ruth Malan&#39;s Advent(ure) in System Seeing&lt;/a&gt;.
The early exercises use generic topics to get you used to various kinds of
questions to ask, while the later exercises can be applied to a situation of
your own choosing. This can be a work situation, but it doesn&#39;t have to be.
The advantage of these exercises is that some people make their “solutions”
public so you can compare them with your own and with each other and examine the differences.
Ruth has links to some examples on her site. My own reflections from last year
can be found &lt;a href=&quot;https://sebastian-hans.de/blog/aiss-2025/&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Feeling the force is an important skill for both martial artists and software
architects. Noticing all the forces at play in an encounter enables you to
respond effectively, while expending only the necessary effort.&lt;/p&gt;
&lt;p&gt;If you get this right, in software architecture as in martial arts,
it will feel as if you aren&#39;t actually doing very much and things are
going fine nonetheless.
This might make you nervous.
You might feel your training partner is not really attacking properly,
or that you should be “architecting more”.
But don&#39;t be fooled! The goal is not to do much.
The goal is to do the right things to ensure the result you want.
Indeed, having to exert much effort for little gain is one of the surest signs
you are not sensing the forces well (or are not responding appropriately).
If you find yourself getting hit again and again despite your best efforts,
or if you find yourself mired in complexity despite having already done much
architectural work, take a step back and reflect!
You might not be taking into account the forces around you.
Maybe it is time to practice feeling the force a bit more.&lt;/p&gt;
&lt;p&gt;Martial artists practice this skill regularly and with quick feedback that is
not easy to ignore (like a fist in your mouth). It&#39;s more difficult for software
architects due to long feedback cycles and because negative feedback is easier
to miss (hopefully!).
Nevertheless, you can and should practice feeling the force as a software
architect, and after reading this, you don&#39;t have an excuse not to. 😁&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;But
consider verbalizing this context and writing it down, e.g., in the form of
Architecture Decision Records! Unlike the application of a martial arts
technique, which is ephemeral, a software architecture decision is durable and
should be documented and understandable later. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/feel-the-force/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;
This is similar to how
&lt;a href=&quot;https://www.infoq.com/news/2025/10/architectures-residuality-theory/&quot;&gt;Residuality Theory&lt;/a&gt;
tries to come up with better software architectures by applying random
simulations of stress to potential decisions, and adapting them to survive
these stresses. If you haven&#39;t encountered Residuality Theory before,
I highly encourage you to take a look. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/feel-the-force/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Mon, 30 Mar 2026 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/feel-the-force/</guid>
    </item>
    
    
    <item>
      <title>Pattern Radar</title>
      <link>https://sebastian-hans.de/blog/pattern-radar/</link>
      <description>&lt;p&gt;In this post, I introduce the idea of the “Pattern Radar” as a means of
incrementally improving a legacy code base.
If you want to skip right to the practical part, go &lt;a href=&quot;https://sebastian-hans.de/blog/pattern-radar/#pattern-radar&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;intro%3A-working-with-legacy-code&quot; tabindex=&quot;-1&quot;&gt;Intro: working with legacy code&lt;/h2&gt;
&lt;p&gt;Have you ever had the “pleasure” of working on a code base that has been in
development for 15+ years, has had various attempts at improvement perpetrated
on it, has survived them all, and is now ready. For. A. New. Feature?
To be implemented by you, who just joined the project?&lt;/p&gt;
&lt;p&gt;If you haven&#39;t, let me give you a taste of what it&#39;s like.
You start out thinking it can&#39;t be too hard;
it&#39;s only a new attribute in a database entity and a bit of logic over here.
Or is it over there? Wait, why are there 5 classes with almost identical names
in 3 packages?
OK, let&#39;s look at the call chain for this one to see where it is used.
Invoke the &lt;em&gt;Find Usages&lt;/em&gt; function in my favourite IDE, and …
what&#39;s this? 30 call sites?
Erm, the first one doesn&#39;t look plausible at all, it&#39;s actually calling a
completely different service.
WTF?
Oh, I see, the services are implementing the same interface, and the IDE lists
all uses of the interface.
So I actually have to look at all of them to see which are the ones I want.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Fast forward a bit.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So, I have finally found the correct place to change. I think.
Is there a test for this class?
Yes, there is. It&#39;s an … integration test that connects to an external test
database?
Hm, OK.
What does it do?
There are two test methods, one named &lt;code&gt;editObject()&lt;/code&gt;, and one named
&lt;code&gt;editObjectNegative()&lt;/code&gt;.
What do they do?
They call the &lt;code&gt;editObject()&lt;/code&gt; on the service and assert various things.
OK, looks like the “negative” one is testing error scenarios,
and the other one is testing the happy path, but I&#39;m not entirely sure
because some of the assertions seem only tangentially related to the logic
under test. My team mates can&#39;t remember why they are there either. O. K.&lt;/p&gt;
&lt;p&gt;How do they do test setup?
Where, for example, is the data coming from? &lt;em&gt;Search, search.&lt;/em&gt;
Hm, seems to be magically there.
A few conversations with veteran team mates later, I know that the test data is
maintained in a giant SQL file in a different repository.
Now, if I add the data I need there, do I break all the existing tests?
No, good, I only broke 50 or so of them.&lt;/p&gt;
&lt;p&gt;That&#39;s enough of that.
This was just to introduce you to the environment that sparked the following
idea.
Because the thing is, the team working on the application is actually quite
motivated.
They want to improve the code base.
They have already made some attempts at this in the past.
For example, I found two different &lt;a href=&quot;https://hibernate.org/orm/&quot;&gt;Hibernate&lt;/a&gt;
entities representing the same class of data, one of which was much cleaner than
the other one, which was marked as deprecated.
But which one do you think is used by most of the code? Right.&lt;/p&gt;
&lt;p&gt;For me as a newcomer, these partial improvements sometimes actually made things worse.
Having seen two or three ways of solving the same problem in the (pretty large) code base,
how do you decide which one to use when you encounter the same problem again?
In the case of the entity class, it was at least clear what the plan was.
In other cases, not so much.&lt;/p&gt;
&lt;h2 id=&quot;so-how-to-improve-this%3F&quot; tabindex=&quot;-1&quot;&gt;So how to improve this?&lt;/h2&gt;
&lt;p&gt;For some improvements, we wrote backlog items and implemented them in one fell
swoop, just to get some big blockers out of the way.
For example, we had a mixture of JUnit 4 and JUnit 5 tests in one of the Java
backend services, and I used &lt;a href=&quot;https://docs.openrewrite.org/&quot;&gt;OpenRewrite&lt;/a&gt; to
convert all of them to JUnit 5 (which worked like a charm, by the way;
OpenRewrite is a fantastic tool for this kind of work).&lt;/p&gt;
&lt;p&gt;If you can do that, it&#39;s great.
However, addressing all code quality issues in this fashion is unrealistic.
We would be busy for half a year doing nothing but quality improvements –
and step on each other&#39;s toes for the whole time because most of these
improvements, if done in a big bang, touch large parts of the code base,
so this would produce an endless stream of merge conflicts.
We needed a way to improve the code gradually.
This means we needed to ensure&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;that team members had (i.e., took) time to make improvements during their normal work and&lt;/li&gt;
&lt;li&gt;that the team agreed on what actually constituted an improvement.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As to 1., we had already instituted the &lt;em&gt;Boy Scout Rule&lt;/em&gt;, “Leave the code better
than you found it,” and it was kind of working.
If we could just find a way to align on 2., we&#39;d be in a good place.
This is where the Pattern Radar comes in.&lt;/p&gt;
&lt;h2 id=&quot;pattern-radar&quot; tabindex=&quot;-1&quot;&gt;Introducing the Pattern Radar&lt;/h2&gt;
&lt;p&gt;The Pattern Radar is inspired by &lt;a href=&quot;https://www.thoughtworks.com/radar&quot;&gt;Thoughtworks&#39; Technology Radar&lt;/a&gt;.
It lists recurring patterns that we find (or would like to find) in our code base,
and classifies them according to how we want to deal with them.
Our classifications are as follows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TRIAL&lt;/strong&gt;: We want to try this. It&#39;s not yet clear whether the pattern is
suitable for general use.&lt;br /&gt;
&lt;em&gt;Expected behaviour: Present trial to the team and together decide how to proceed. Don&#39;t apply directly outside of the trial; wait for the experience report. If it turns out not to suit us, we&#39;ll roll back the trial.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ADOPT&lt;/strong&gt;: Tried and tested. We want to apply this broadly.&lt;br /&gt;
&lt;em&gt;Expected behaviour: Use when appropriate. Prefer this to patterns in other categories except for special circumstances. Refactor occurrences of PURGE patterns into this when you come across them (Boy Scout Rule).&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PURGE&lt;/strong&gt;: Was used in the past and may still be found in the code base (maybe even broadly), but should be replaced by a different pattern.&lt;br /&gt;
&lt;em&gt;Expected behaviour: Don&#39;t use in new code! If you come across it, replace it with an ADOPT pattern (Boy Scout Rule).&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KEEP&lt;/strong&gt;: May be found in the code base in certain places. It&#39;s OK there, but it shouldn&#39;t be used more broadly.&lt;br /&gt;
&lt;em&gt;Expected behaviour: Don&#39;t use in new code! If you come across it, leave it alone!&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BURN&lt;/strong&gt;: Doesn&#39;t occur in the code base, and we don&#39;t want to see it ever again.&lt;br /&gt;
&lt;em&gt;Expected behaviour: Don&#39;t use in new code! If spotted anywhere, replace immediately!&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have not strictly defined what counts as a pattern except that they should be
things that are found recurringly in our code base.
Some examples are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hibernate Typed Queries with JPQL: ADOPT&lt;/li&gt;
&lt;li&gt;Hibernate Criteria Queries: PURGE (replace with Typed Queries)&lt;/li&gt;
&lt;li&gt;Application logic in mapper classes: PURGE&lt;/li&gt;
&lt;li&gt;Composition over inheritance: ADOPT&lt;/li&gt;
&lt;li&gt;Testcontainers for DB: ADOPT&lt;/li&gt;
&lt;li&gt;Mockist tests (in which &lt;em&gt;all dependencies&lt;/em&gt; are mocked): PURGE&lt;/li&gt;
&lt;li&gt;Reactive forms (in our Angular frontend): ADOPT&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We started by gathering these and other patterns in a table on a wiki page and
discussing them one by one. We used the following six columns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: A short name for the pattern (as shown in the examples above)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Classification&lt;/strong&gt;: TRIAL, ADOPT, PURGE, KEEP, or BURN&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Last change&lt;/strong&gt;: The date when we added or updated the pattern&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Description&lt;/strong&gt;: Description of the pattern in one sentence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rationale&lt;/strong&gt;: Brief outline of the reasoning behind the classification (1-3
sentences)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comments&lt;/strong&gt;: Additional commentary, e.g., suggested replacements for PURGE
patterns or reasons you might not want to use an ADOPT pattern&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A full example might look like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Name&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Classification&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Last change&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Rationale&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Comments&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Testcontainers for DB&lt;/td&gt;
&lt;td&gt;ADOPT&lt;/td&gt;
&lt;td&gt;19.02.2026&lt;/td&gt;
&lt;td&gt;Use testcontainers for integration tests that need a database.&lt;/td&gt;
&lt;td&gt;We want our tests to be independent from shared infrastructure (pipeline reliability!)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composition over inheritance&lt;/td&gt;
&lt;td&gt;ADOPT&lt;/td&gt;
&lt;td&gt;23.02.2026&lt;/td&gt;
&lt;td&gt;Reuse code via composition rather than via inheritance.&lt;/td&gt;
&lt;td&gt;Composition tends to be more explicit and easier to understand. The code base relies on template methods rather heavily but this has confused devs.&lt;/td&gt;
&lt;td&gt;Template methods can still be used in moderation, but we prefer composition.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;benefits&quot; tabindex=&quot;-1&quot;&gt;Benefits&lt;/h2&gt;
&lt;p&gt;We started this about 2 weeks ago and have already observed a few benefits while
discussing the patterns that came up during our initial collection.
In some cases, it simply made clear the reason these patterns existed in the code
base and helped us align on proper and improper uses.&lt;/p&gt;
&lt;p&gt;In other cases, someone described a pattern that irritated them and suggested to
purge it, and someone else went, “Oh yes, I&#39;ve noticed that too, we should
probably get rid of that,” allowing us to boy scout them away with a clear
conscience.&lt;/p&gt;
&lt;p&gt;Once, we discussed a pattern that was very prevalent in the code base and
someone suggested a replacement.
What a relief!
I had been propagating the pattern because consistency in a code base is
valuable (sometimes more valuable than small, isolated improvements) and had
been unaware that this had been discussed in the past, and the team had already
decided to replace it.&lt;/p&gt;
&lt;p&gt;Writing down a brief rationale forced us to articulate why exactly a given
pattern was to be adopted or to be purged, providing important context,
especially to newer team members.&lt;/p&gt;
&lt;p&gt;And the discussion of one or two patterns highlighted deep-seated differences in
opinion among team members. We still have to resolve these, and I believe this
won&#39;t be easy, but having put them on the table at least gives us the chance to have
a meaningful discussion instead of quietly continuing to work at cross-purposes.&lt;/p&gt;
&lt;h2 id=&quot;why-not-adrs%3F&quot; tabindex=&quot;-1&quot;&gt;Why not ADRs?&lt;/h2&gt;
&lt;p&gt;The Pattern Radar captures our decisions about how to evolve our code base.
Does this sound similar to &lt;a href=&quot;https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions&quot;&gt;Architecture Decision Records&lt;/a&gt;?
In a way, it is.
Actually, we use ADRs, too, but those are more focused on the high-level
structures of our system; you know, the things that are hard to change and for
the most part only occur once.
If we want to change the service decomposition, for example, we will initiate an
ADR, discuss options in the team, decide together, finalize the ADR, and then
implement it, probably in an incremental fashion, but still in a quite
straightforward manner.&lt;/p&gt;
&lt;p&gt;In contrast, I envision the Pattern Radar as a light-weight, evolving catalog of
the recurring lower-level structures in our code base.
To phase out Hibernate Criteria Queries does not feel ADR-worthy.
It is a smaller decision that can be implemented in many small, local steps as
we go along.
The Pattern Radar gives us a place to discuss and record such decisions,
and to review them regularly, without introducing much overhead.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot; tabindex=&quot;-1&quot;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Setting up a Pattern Radar is very simple.
You don&#39;t need more than a place where you can write down a simple list or table
and share it with your team.
This can be a wiki page, a Google doc, a virtual whiteboard, a Markdown file in
your source code repository, or wherever your team likes to record things.&lt;/p&gt;
&lt;p&gt;Begin by writing down patterns you have noticed in your code base, and ask your
team mates to do the same. Then get together and talk about the problems these
patterns solve or create, and agree on a classification.
That&#39;s all you need in the beginning.
You can always add more patterns and details or visualizations later.&lt;/p&gt;
&lt;p&gt;To actually let the Pattern Radar come to life, however, you also need to adopt
the Boy Scout Rule, i.e., agree to make little changes in line with the Pattern
Radar as you go along.&lt;/p&gt;
&lt;h2 id=&quot;what&#39;s-next%3F&quot; tabindex=&quot;-1&quot;&gt;What&#39;s next?&lt;/h2&gt;
&lt;p&gt;Currently, our Pattern Radar consists of about two dozen patterns, half of which
we have already classified.
The format is a simple table as described above.
We have not yet created a visualization (the classical radar picture with
rings).
I&#39;m not sure we will ever do that. It looks nice for the Tech Radar, but as long
as we are going to use the Pattern Radar only internally in our team, I don&#39;t
think it would add much value.&lt;/p&gt;
&lt;p&gt;We have not yet defined quadrants because we don&#39;t have that many patterns.
We will probably do that once we reach a number of patterns where it makes sense
to add a bit more structure.
Looking at the patterns we have already collected, I can see how quadrants for
“backend code”, “frontend code”, and “testing” might make sense.
Maybe we&#39;ll call them “sectors” or something if we don&#39;t get exactly four.&lt;/p&gt;
&lt;p&gt;Although we have defined TRIAL, KEEP, and BURN as classifications, we have not
used them yet.
I imagine TRIAL becoming useful as time goes on and we decide to try new
patterns.
And as patterns we classified as PURGE slowly vanish from our code base, we
might reclassify them as KEEP for patterns that make sense to keep for a limited
scope, or as BURN for patterns we have successfully eliminated.
I think it will be useful to keep those on the radar lest someone try to introduce
them again, but make them easy to filter out so they don&#39;t clutter up our table
too much.&lt;/p&gt;
&lt;p&gt;We will see how it works out.
If anything interesting comes of it, I will write a follow-up post.&lt;/p&gt;
&lt;p&gt;If you decide to try it out, I&#39;d like to hear about your experience, so please
drop me a line!&lt;/p&gt;
</description>
      <pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/pattern-radar/</guid>
    </item>
    
    
    <item>
      <title>Reflections on Advent(ure) in System Seeing 2025</title>
      <link>https://sebastian-hans.de/blog/aiss-2025/</link>
      <description>&lt;p&gt;These are my reflections on the last &lt;a href=&quot;https://www.ruthmalan.com/Advent.html&quot;&gt;Advent(ure) in System Seeing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Advent(ure) in System Seeing is a series of daily challenges by Ruth Malan,
focusing on understanding systems, and playing out from December 1st to December
24th.
I enjoyed following along for the second year now and recommend it to anyone who
is interested in trying their hand at system thinking.&lt;/p&gt;
&lt;p&gt;I posted my results and reflections on Mastodon under
&lt;a href=&quot;https://hachyderm.io/tags/adventofsystemseeing&quot;&gt;#AdventOfSystemSeeing&lt;/a&gt;,
but thought I&#39;d collect them here for ease of reference –
and because Ruth asked me to share a PDF of my zine that was the culmination of
last year&#39;s effort. You can find it at the &lt;a href=&quot;https://sebastian-hans.de/blog/aiss-2025/#zine&quot;&gt;bottom of this post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thank you, Ruth, for your prompts and the effort you put into them!&lt;/p&gt;
&lt;h2 id=&quot;day-1-%E2%80%93-draw-a-bicycle-(and-sketch-a-key-mechanism)&quot; tabindex=&quot;-1&quot;&gt;Day 1 – Draw a bicycle (and sketch a key mechanism)&lt;/h2&gt;
&lt;p&gt;Day one was about drawing a bicycle from memory
(find the full prompt &lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day1.html&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I did steps 2 (sketch), 3 (explore key capability), and 4 (reflect).
Here are my pictures.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Pencil drawing of a bicycle. Shows the frame, wheels, saddle, pedals, gearwheels, chain, handlebar, lights, luggage carrier, brake levers, mudguards. Doesn&#39;t show the brake mechanism, multiple gears, source of electricity.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day1-1-300w.jpeg?v=db66f676b342&quot; width=&quot;900&quot; height=&quot;624&quot; srcset=&quot;https://sebastian-hans.de/img/day1-1-300w.jpeg?v=db66f676b342 300w, https://sebastian-hans.de/img/day1-1-600w.jpeg?v=518646aa6757 600w, https://sebastian-hans.de/img/day1-1-900w.jpeg?v=7f1958d93b18 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
My drawing of a bicycle
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Pencilled illustration of the propulsion system of a bicycle with annotations in German. Shows a foot stepping on a pedal, the pedal moving the first gearwheel, the first gearwheel pulling the chain by hooking the chain links with its teeth, the chain driving the second gearwheel in the same way, the second gearwheel driving the wheel via the hub and spokes, and the wheel propelling the bike forward due to friction with the ground. Elements are colored grey. Cause and effect are indicated with blue arrows. Friction is shown in red.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day1-2-300w.jpeg?v=7e28fb0d937f&quot; width=&quot;900&quot; height=&quot;477&quot; srcset=&quot;https://sebastian-hans.de/img/day1-2-300w.jpeg?v=7e28fb0d937f 300w, https://sebastian-hans.de/img/day1-2-600w.jpeg?v=760f1f4d98de 600w, https://sebastian-hans.de/img/day1-2-900w.jpeg?v=e123d674630e 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Focusing on the propulsion system
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;What I noticed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I paid more attention to how the parts were connected this time around.&lt;/li&gt;
&lt;li&gt;Drawing made me notice I don&#39;t know the exact angles between parts of the frame. My drawing looks slightly skewed.&lt;/li&gt;
&lt;li&gt;I see what I didn&#39;t draw.&lt;/li&gt;
&lt;li&gt;Just because you interact with a system often this doesn&#39;t mean you understand it.&lt;/li&gt;
&lt;li&gt;There&#39;s always another layer. I did note the necessity of friction, but I didn&#39;t describe how this comes about (gravity, usually).&lt;/li&gt;
&lt;li&gt;The system only works at all in connection with its environment. Without the ground, the bicycle doesn&#39;t move.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;day-2-%E2%80%93-zoom-out-to-see-the-world-around-a-bicycle&quot; tabindex=&quot;-1&quot;&gt;Day 2 – Zoom out to see the world around a bicycle&lt;/h2&gt;
&lt;p&gt;This was about exploring the context around bicycles
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day2.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Didn&#39;t have much time today, but I managed to knock out a simple context diagram.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Concept map around the topic bicycle&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day2-300w.jpeg?v=7b6b74912119&quot; width=&quot;900&quot; height=&quot;1089&quot; srcset=&quot;https://sebastian-hans.de/img/day2-300w.jpeg?v=7b6b74912119 300w, https://sebastian-hans.de/img/day2-600w.jpeg?v=fd0e2397ea69 600w, https://sebastian-hans.de/img/day2-900w.jpeg?v=8b40772b6769 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Concept map around the topic bicycle
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;What I noticed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My map is not hierarchical. There are many connections across contexts.&lt;/li&gt;
&lt;li&gt;Some contexts have more depth than others, maybe because I have more experience riding a bike than building one.&lt;/li&gt;
&lt;li&gt;Some contexts are more … organized (at scale) than others, e.g., I imagine bike factories to be more strictly organized than how individuals use their bikes recreationally. I had no way to indicate this in the map. Maybe I could have color-coded it?&lt;/li&gt;
&lt;li&gt;I realize I forgot to include regulation and inspection (like traffic regulations and the TÜV).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;day-3-%E2%80%93-explore-impact-of-change&quot; tabindex=&quot;-1&quot;&gt;Day 3 – Explore impact of change&lt;/h2&gt;
&lt;p&gt;Today&#39;s task was to draw a Futures Wheel to explore how one change in the
context of cycling would ripple out into the environment
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day3.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;A Futures Wheel around the trend of building more bike lanes in Munich (not a big trend, mind you; green bubble). First order effects (blue bubbles): cycling becomes more attractive; less conflict between cars and bicycles; less space for cars and/or pedestrians. Second order effects (yellow bubbles): increased demand for bicycle parking spaces; less demand for other modes of transportation; more cyclists on the road, which may lead to more traffic accidents; more aggressive drivers? (ditto); also, maybe less traffic accidents (due to reduced conflict)&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day3-300w.jpeg?v=ae6f626d9d2f&quot; width=&quot;900&quot; height=&quot;889&quot; srcset=&quot;https://sebastian-hans.de/img/day3-300w.jpeg?v=ae6f626d9d2f 300w, https://sebastian-hans.de/img/day3-600w.jpeg?v=64742a949874 600w, https://sebastian-hans.de/img/day3-900w.jpeg?v=fc5c1ce4a1fa 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Futures Wheel for the change “building more bike lanes”
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;What I noticed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;More than one first order effect may contribute to a second order effect.&lt;/li&gt;
&lt;li&gt;Different first order effects can lead to opposite second order effects.&lt;/li&gt;
&lt;li&gt;Second order effects are tricky.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;day-4-%E2%80%93-going-behind-the-scenes&quot; tabindex=&quot;-1&quot;&gt;Day 4 – Going behind the scenes&lt;/h2&gt;
&lt;p&gt;Drawing a rich picture of all that happens when you fill a pot with water
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day4.html&quot;&gt;full prompt&lt;/a&gt;)
was the exercise on day 4.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Bad drawing showing clouds, rain, rivers, a well, municipal utilities, the city council controlling it, pipes, spout, my pan, me, and my plumber.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day4-300w.jpeg?v=14be0d651ac3&quot; width=&quot;900&quot; height=&quot;1298&quot; srcset=&quot;https://sebastian-hans.de/img/day4-300w.jpeg?v=14be0d651ac3 300w, https://sebastian-hans.de/img/day4-600w.jpeg?v=55e89de87d97 600w, https://sebastian-hans.de/img/day4-900w.jpeg?v=78822e78211c 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Rich picture of me filling a pot with water
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;What I noticed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I know where the water in Munich comes from, but I don&#39;t know the exact mechanics.&lt;/li&gt;
&lt;li&gt;The plumber pays taxes in the city, which I didn&#39;t draw.&lt;/li&gt;
&lt;li&gt;Each of the elements in the picture is a complex system in its own right.&lt;/li&gt;
&lt;li&gt;I really don&#39;t like drawing that much.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;day-5-%E2%80%93-characterizing-systems&quot; tabindex=&quot;-1&quot;&gt;Day 5 – Characterizing systems&lt;/h2&gt;
&lt;p&gt;This day&#39;s task was to design an introductory page of a zine on systems
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day5.html&quot;&gt;full prompt&lt;/a&gt;).
Here&#39;s what I came up with.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Systems intro page. Top left: Sketch of airplane parts. Note: “They lie.” An arrow labelled “combine” points toward the center. At the center, the airplane is assembled and labelled with “system” and “It flies!” Top right: A drawing of earth surrounded by lines suggesting flight connections, labelled “purpose”. An arrow labelled “gives meaning” points at the airplane in the center. Bottom left: Sketch of a gangway not quite connecting the door of an airplane to a building. The gangway is labelled “boundaries” and below are the words “separate and connect”. Bottom right: Cross-section of an airplane wing with air streams above and below providing lift, and the ground below providing  gravity. Label: “environment”. The green arrow indicating lift is labelled “enables”, the red arrow indicating gravity is labelled “and constrains”.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day5-300w.jpeg?v=8b775ddf2046&quot; width=&quot;900&quot; height=&quot;558&quot; srcset=&quot;https://sebastian-hans.de/img/day5-300w.jpeg?v=8b775ddf2046 300w, https://sebastian-hans.de/img/day5-600w.jpeg?v=56dc26ba1cda 600w, https://sebastian-hans.de/img/day5-900w.jpeg?v=6d83681dcc0e 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Zine page on the characterization of systems
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;I spent waaaaay too much time on this one, but I am pleased with the result.&lt;/p&gt;
&lt;p&gt;Deciding what to put on the sheet was the easy part. Coming up with a balanced arrangement was harder, and I didn&#39;t quite manage it. That this was supposed to be a zine cover made me want to achieve a much more polished design, which took a long time and a few false starts. I also wanted to tie the concepts together with a unifying theme, the airplane, which worked quite well, I think.&lt;/p&gt;
&lt;h2 id=&quot;day-6-%E2%80%93-causal-loops-with-meadows&quot; tabindex=&quot;-1&quot;&gt;Day 6 – Causal loops with Meadows&lt;/h2&gt;
&lt;p&gt;This day was about watching part of a &lt;a href=&quot;https://youtu.be/XL_lOoomRTA&quot;&gt;lecture by Donella Meadows&lt;/a&gt; and taking sketchnotes
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day6.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This is a very interesting lecture. Here are my notes on minutes 18 to 36. I&#39;ll have to watch the whole thing when I have more time. The point that stuck with me the most is that when there are loops within loops, an interesting question is when which of them will dominate the system because this will influence the overall direction the system will take.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;My notes on Donella Meadows&#39; lecture “A Philosophical Look at System Dynamics”, minutes 18 to 36. Mostly reproducing the diagrams she drew on the blackboard. You&#39;ll be better off listening to her than reading this alt text.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day6-300w.jpeg?v=ac94ed097779&quot; width=&quot;900&quot; height=&quot;1217&quot; srcset=&quot;https://sebastian-hans.de/img/day6-300w.jpeg?v=ac94ed097779 300w, https://sebastian-hans.de/img/day6-600w.jpeg?v=0d16c8b94def 600w, https://sebastian-hans.de/img/day6-900w.jpeg?v=9b66e8419ffd 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Sketchnotes on minutes 18 to 36 of Donella Meadows&#39; lecture,
“A Philosophical Look at System Dynamics”
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;day-7-%E2%80%93-give-causal-loop-diagrams-a-spin&quot; tabindex=&quot;-1&quot;&gt;Day 7 – Give causal loop diagrams a spin&lt;/h2&gt;
&lt;p&gt;After watching Donella Meadows draw a few causal loops,
this day was spent on drawing one myself
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day7.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I chose to explore technical debt. This one is interesting because increasing technical debt initially increases development velocity (that&#39;s the whole point), but once a certain threshold is crossed, it begins to decrease it (red arrow). Perceived sucess tends to increase feature priority (over engineering priority), but so does a reduction in perceived success (red arrow). In both cases (initial low technical debt as well as later high technical debt) this can result in a self-reinforcing loop leading to the collapse of the system. Thus, the critical point of leverage seems to be how we approach feature vs. engineering priority in the face of some level of perceived success. At this point, we can break the self-reinforcing dynamic by exercising sound judgement.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Causal loop diagram exploring technical debt&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day7-300w.jpeg?v=cdaa00631879&quot; width=&quot;900&quot; height=&quot;530&quot; srcset=&quot;https://sebastian-hans.de/img/day7-300w.jpeg?v=cdaa00631879 300w, https://sebastian-hans.de/img/day7-600w.jpeg?v=e8bc1939ff32 600w, https://sebastian-hans.de/img/day7-900w.jpeg?v=8b6d9652f0d4 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Causal loop diagram exporing technical debt
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;day-8-%E2%80%93-context-changes-everything&quot; tabindex=&quot;-1&quot;&gt;Day 8 – Context changes everything&lt;/h2&gt;
&lt;p&gt;Drawing a second page for a zine on systems was the task of day 8
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day8.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I went with context as the topic and chose a more abstract visualization this time (mostly to keep to the time box).&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Design for a zine page about system context. Shows a “system bubble” at the center, surrounded by: another system, with which it interacts; people, who have expectations of it; a source of energy providing power to it; a wall of constraints limiting what it can do; a feedback loop, of which it is part; “consequences” arrows pointing from the system to some of the above&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day8-300w.jpeg?v=3fe2f9148f76&quot; width=&quot;900&quot; height=&quot;554&quot; srcset=&quot;https://sebastian-hans.de/img/day8-300w.jpeg?v=3fe2f9148f76 300w, https://sebastian-hans.de/img/day8-600w.jpeg?v=24277a67d2fd 600w, https://sebastian-hans.de/img/day8-900w.jpeg?v=82c3f61f8c3b 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Zine page on system context
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;day-9-%E2%80%93-relationships-and-interactions&quot; tabindex=&quot;-1&quot;&gt;Day 9 – Relationships and interactions&lt;/h2&gt;
&lt;p&gt;Today, there were two tasks:
to watch a video about relationships and exchanges among trees and fungi in mycorrhizal networks,
and to draft another zine page on relationships or interactions
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day9.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I didn&#39;t come up with a good focal point for the zine page within my timebox, so I just jotted down relationships and interactions between systems as they occurred to me.
If I had more time, I would try to bring them in some kind of order, maybe by similarity or something.
What I now notice I missed are indirect relationships, e.g., mediated by some other system.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Sketch of a forest with mycelium network and exchanges of sugar, nutrients, and information among trees and between trees and fungi. Below it, a rough pencilled draft of a zine page showing systems and how they can relate to each other: create; destroy; produce/consume; part/whole; exchange; coexist; independent; symbiotic&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day9-300w.jpeg?v=2d6a929ba16c&quot; width=&quot;900&quot; height=&quot;1260&quot; srcset=&quot;https://sebastian-hans.de/img/day9-300w.jpeg?v=2d6a929ba16c 300w, https://sebastian-hans.de/img/day9-600w.jpeg?v=181d76a4d400 600w, https://sebastian-hans.de/img/day9-900w.jpeg?v=b5710c61700e 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Sketch of the tree video and draft of a zine page on relationships and interactions
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;day-10-%E2%80%93-draw-your-org-3-ways&quot; tabindex=&quot;-1&quot;&gt;Day 10 – Draw your org 3 ways&lt;/h2&gt;
&lt;p&gt;As the title says, this day was about drawing an organization I have been part
of at least three different ways
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day10.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I drew my work organization, anonymized so I can share it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The (almost) classic org chart. As a consultant, I am part of 2 orgs, one of which is my employer (red), and the other one is the customer I am currently working for (blue).&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Org chart showing me as part of two organizations. The first, colored red, is the company that is employing me. Units from top to bottom: Group, Company in Germany, Consulting division, me. The second, colored blue, is the customer I am currently working for. Units from top to bottom: Government, Gov. Agency, IT, 2 subdivisions, me.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day10-1-300w.jpeg?v=0ae45932369e&quot; width=&quot;900&quot; height=&quot;499&quot; srcset=&quot;https://sebastian-hans.de/img/day10-1-300w.jpeg?v=0ae45932369e 300w, https://sebastian-hans.de/img/day10-1-600w.jpeg?v=c7dacc752357 600w, https://sebastian-hans.de/img/day10-1-900w.jpeg?v=8f3dab34324c 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
My organization shown as an org chart
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;The teams, working groups, and communities of practice in which my actual work happens. None of these appear in the official org chart because they are either too small (the Scrum team) or cross organizational borders (the communities).&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Diagram showing “working groups” I am part of as bubbles with the common overlap being me: Java CoP, Arch. CoP, Int. CoP (all red); Scrum team, DevOps CoP (blue).&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day10-2-300w.jpeg?v=298314d77bec&quot; width=&quot;900&quot; height=&quot;565&quot; srcset=&quot;https://sebastian-hans.de/img/day10-2-300w.jpeg?v=298314d77bec 300w, https://sebastian-hans.de/img/day10-2-600w.jpeg?v=ca54ba5a16cc 600w, https://sebastian-hans.de/img/day10-2-900w.jpeg?v=22d15e005062 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
My organization shown as a Venn diagram of more informal working groups
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;A calendar view that roughly shows where my time is spent. Unlike the other charts, this one shows the relative importance of various activities. Because I work for a consulting firm, work for the customer dominates my calendar, although I am also engaged in various internal activities.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Calendar view of one month showing a rough allocation of my work time. Most of it is blue (work for the customer), some days are partially, and one day completely red (internal work for my employer). Weekends are free.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day10-3-300w.jpeg?v=8c37f9a3af71&quot; width=&quot;900&quot; height=&quot;588&quot; srcset=&quot;https://sebastian-hans.de/img/day10-3-300w.jpeg?v=8c37f9a3af71 300w, https://sebastian-hans.de/img/day10-3-600w.jpeg?v=de2ef52d6706 600w, https://sebastian-hans.de/img/day10-3-900w.jpeg?v=46edf20e878a 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
A calendar view of what I spend my time on
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;day-11-%E2%80%93-describe-your-focal-situation&quot; tabindex=&quot;-1&quot;&gt;Day 11 – Describe your focal situation&lt;/h2&gt;
&lt;p&gt;On this day, we moved from generic systems exercises and ruminations about
common systems to exploring a specific situation in our own lives
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day11.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Reflecting on this, I find that deciding on my focal situation for the upcoming days was easy.
I&#39;ve been thinking intensely about the situation the team in my current customer project is facing anyway.
On one hand, writing down all the things bouncing around in my head was slightly depressing because it showed how many unresolved issues – major issues – there are.
On the other hand, I can see none of them are unsolvable, even if it will take a long time to address them.&lt;/p&gt;
&lt;h2 id=&quot;day-12-%E2%80%93-sketch-the-situation&quot; tabindex=&quot;-1&quot;&gt;Day 12 – Sketch the situation&lt;/h2&gt;
&lt;p&gt;Today, the task was to either draw a rich picture or an actor map of the focal
situation chosen yesterday
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day12.html&quot;&gt;full prompt&lt;/a&gt;).
I chose the actor map.&lt;/p&gt;
&lt;p&gt;What I noticed:
Drawing the situation – the software system we are responsible for, a number of construction sites within the system, and the actors surrounding and complementing it – made me think a bit harder about where to place each actor in relation to the whole.
I realized the motivation of some of the actors is not that clear (to me).
Most of the contractors don&#39;t seem to have strong motivation apart from ensuring their contracts are extended.
This is both a boon and a hindrance.
A boon because they are unlikely to oppose potential improvements; a hindrance because they are equally unlikely to contribute much energy.
This means what leverage I need I&#39;ll have to find in the others, and I “just” have to make the changes I want easy for the contractors to go along with.
Well then, problem solved. 😉&lt;/p&gt;
&lt;h2 id=&quot;day-13-%E2%80%93-what-if-you-do-nothing-(different)%3F&quot; tabindex=&quot;-1&quot;&gt;Day 13 – What if you do nothing (different)?&lt;/h2&gt;
&lt;p&gt;The task was to reflect on the “characteristics of the current state of things”
of my situation and to think about what was likely to happen if I didn&#39;t
actively change anything
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day13.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;In my case, the most relevant factors influencing the default future situation likely are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reverting to the mean,&lt;/li&gt;
&lt;li&gt;institutional inertia,&lt;/li&gt;
&lt;li&gt;central policy forcing certain issues,&lt;/li&gt;
&lt;li&gt;cost of change for established communication patterns,&lt;/li&gt;
&lt;li&gt;distribution of experience in the team.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;day-14-%E2%80%93-how-we-got-here&quot; tabindex=&quot;-1&quot;&gt;Day 14 – How we got here&lt;/h2&gt;
&lt;p&gt;Today was about exploring how the situation came to be, either in textual form,
or in the form of a graphical history
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day14.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I tried to assemble a graphical history.
This was difficult because the history of  the project I&#39;m working on goes back more than 10 years and there have been many changes to the team.
The documentation is … not great as far as significant decisions are concerned, so the historical information is disjoint at best, and in large parts unrecoverable.
I myself joined 6 months ago and my efforts to uncover the rationale behind certain … interesting decisions were not very successful.
This in itself is telling.&lt;/p&gt;
&lt;h2 id=&quot;day-15-%E2%80%93-mapping-value-flows&quot; tabindex=&quot;-1&quot;&gt;Day 15 – Mapping value flows&lt;/h2&gt;
&lt;p&gt;Today, we focused on value exchanges in the our focal situation,
and visualized them in the form of a Value Network Map
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day15.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Mapping the value flows for my current project was interesting because it is an internal service that provides basic data to many other services.
Hence, on the surface, the primary value flows are very simple.
But actually, the team providing this service is part of a rather large network of other teams and communities, some of which provide direct support to each other, some are formally responsible for certain aspects (e.g., a centralized architecture team), and some are more informal in nature.
It is not always obvious (to me) what each party gets out of the interactions I have observed so far.
Putting this on paper clarified some things.
I&#39;ll have to dig deeper on the others.&lt;/p&gt;
&lt;h2 id=&quot;day-16-%E2%80%93-another-distillation&quot; tabindex=&quot;-1&quot;&gt;Day 16 – Another distillation&lt;/h2&gt;
&lt;p&gt;Taking a break from our focal situation,
this day was about designign another page for the systems zine
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day16.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I followed the suggestion of illustrating constraints.
I found this rather hard – constrained by my drawing skills, time, and mind space more than by the medium.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Draft of a zine page about constraints. The page is divided into 2 parts by a diagonal line from the bottom left to the top right. The line is labelled “constraints”. The upper left part illustrates 3 instances of deliberately setting constraints: “support” is shown by a baking tin constraining a bread from all sides so it can rise; “direction” is shown by a train on rails; “creativity” is shown by a framed drawing of a horse. The lower right part illustrates 4 instances of “using and overcoming” constraints: “using” is shown by the cross section of a wing using physics (the ultimate constraint) for lift; “respecting” is shown by a door with a sign reading “Do not enter”; “breaking” is shown by a broken rope; “overcoming” is shown by a climber hanging from an overhanging rock.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day16-300w.jpeg?v=b54a844b72ea&quot; width=&quot;900&quot; height=&quot;556&quot; srcset=&quot;https://sebastian-hans.de/img/day16-300w.jpeg?v=b54a844b72ea 300w, https://sebastian-hans.de/img/day16-600w.jpeg?v=e2d824cec8b6 600w, https://sebastian-hans.de/img/day16-900w.jpeg?v=2cd372718269 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
A zine page on constraints in systems
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;day-17-%E2%80%93-systems-and-boundaries&quot; tabindex=&quot;-1&quot;&gt;Day 17 – Systems and boundaries&lt;/h2&gt;
&lt;p&gt;Back to our focal situation, today&#39;s task was to
“identify systems, list them, or draw (and name) tne systems, and their
interrelationships”
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day17.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The page I used to draw my diagram quickly got too small.
In the end, I touched on everything from the technical components of my system of interest over the team composition, organisational structures of the German government, technical infrastructure down to the electrical grid, my own company, the labor market which enables me to work for it, the contracting system which allows me to work for this customer, the economy underpinning both, the food supply chain (gotta eat something!), the (US) companies providing the software I use to work on the system remotely, and world politics up to the solar system and astrophysics.
After all, where would we be without the sun?&lt;/p&gt;
&lt;p&gt;Oh, and, yes, we have users, too. 😉&lt;/p&gt;
&lt;p&gt;I&#39;m not sure if I should be glad or sad that, tomorrow, I&#39;ll probably be focusing on breaking the dependency between our unit tests and a centrally provided test database. 😵‍💫&lt;/p&gt;
&lt;h2 id=&quot;day-18-%E2%80%93-exploring-capabilities-and-boundaries&quot; tabindex=&quot;-1&quot;&gt;Day 18 – Exploring capabilities and boundaries&lt;/h2&gt;
&lt;p&gt;This day&#39;s task was to draw the network of capabilities that enable the system
identified yesterday “to be viable as a system with that purpose”
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day18.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Reflecting on this task, I realize that most of the capabilities required for the system to fulfil its purpose are external to the system – or at least out of scope of the team nominally responsible for the system.
This has already made some things more difficult than they should be.&lt;/p&gt;
&lt;h2 id=&quot;day-19-%E2%80%93-exploring-constraints-and-forces&quot; tabindex=&quot;-1&quot;&gt;Day 19 – Exploring constraints and forces&lt;/h2&gt;
&lt;p&gt;The focus for today was on action – to identify an area where we wanted to
&lt;strong&gt;do something&lt;/strong&gt; (the problem), and to explore relevant constraints, forces,
assumptions, and approaches
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day19.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Reflecting on this task, I can say mind mapping on paper does not work for me.
I always end up needing more space in places where I did not plan for it, resulting either in very crammed corners or spaghetti connections.
So I made a table instead, with columns for constraints, forces, assumptions, and approaches.
I linked constraints with the forces they generate and assumptions I made about them.
I marked each force assisting my endeavor with a green plus sign and each resisting force with a red minus sign.
I also linked forces and assumptions with possible approaches that build upon them or try to counter them, and I included my assessment of the efficacy of each approach.
I did this iteratively, adding entries to all columns as they occurred to me.&lt;/p&gt;
&lt;p&gt;This is what it looked like:&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Anonymized photo of a table of constraints, forces, assumptions, and approaches pertaining to a sociotechnical problem. Entries are linked with lines across columns. Entries in the column “Forces” are marked with (+) and (-). Each entry in the column “Approaches” features an arrow followed by my assessment of its efficacy.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day19-300w.jpeg?v=e74ff3dccb84&quot; width=&quot;900&quot; height=&quot;1283&quot; srcset=&quot;https://sebastian-hans.de/img/day19-300w.jpeg?v=e74ff3dccb84 300w, https://sebastian-hans.de/img/day19-600w.jpeg?v=59cf9f5c7070 600w, https://sebastian-hans.de/img/day19-900w.jpeg?v=1779089b47db 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Table of constraints, forces, assumptions, and approaches pertaining to a
particular problem
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Initially, I came up with a lot of resisting forces, until I realized that I myself and my current role in the team could count as forces, too.
Now it&#39;s even, which matches my gut feeling.
It could go either way.&lt;/p&gt;
&lt;p&gt;Filling the first three columns enabled me to think about possible approaches in a more structured way.
Not all approaches are mutually exclusive, and they address different subsets of the forces.
Most importantly, I noticed an important gap: some of the positive forces hadn&#39;t been properly utilized yet.
Thinking about this enabled me to come up with a new approach that might have the best chance of success.&lt;/p&gt;
&lt;p&gt;This may well have been the most immediately helpful exercise up to now.&lt;/p&gt;
&lt;h2 id=&quot;day-20-%E2%80%93-reflection&quot; tabindex=&quot;-1&quot;&gt;Day 20 – Reflection&lt;/h2&gt;
&lt;p&gt;The task was to go back over the work of the past 19 days and extract concepts
related to systems; to identify the lenses we&#39;ve used in system seeing;
to come up with questions and a next step; and to think about what topic we
would like to add
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day20.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I drew a concept map.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Concept map of system concepts and lenses&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day20-300w.jpeg?v=f2d51d31facb&quot; width=&quot;900&quot; height=&quot;1252&quot; srcset=&quot;https://sebastian-hans.de/img/day20-300w.jpeg?v=f2d51d31facb 300w, https://sebastian-hans.de/img/day20-600w.jpeg?v=ee26da4eddb3 600w, https://sebastian-hans.de/img/day20-900w.jpeg?v=7033bff34420 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Concept map of system concepts and lenses
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Two things I added to my map that weren&#39;t explicitly in focus on days 1 through 19 is seeing what&#39;s there and seeing what&#39;s missing.
Seeing what&#39;s there means not being content with the first impression and jumping to conclusions but looking closer for what&#39;s really there, e.g., do we really have cause and effect here or is it just correlation?
Seeing what&#39;s missing means noticing gaps in our understanding or in arguments, and working to fill them.&lt;/p&gt;
&lt;h2 id=&quot;day-21-%E2%80%93-system-concepts%2C-take-n%2B1&quot; tabindex=&quot;-1&quot;&gt;Day 21 – System concepts, take n+1&lt;/h2&gt;
&lt;p&gt;Today was about designing another page or two for the systems zine
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day21.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;At this point, I was getting a bit fed up with all the drawing,
but in for a penny, in for a pound, right?&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Draft of a zine page on the topic of boundaries. Concepts depicted: separate vs. connect as a circle, its line separating it from its surroundings, and a dashed line connecting its two halves; well-defined vs. fuzzy as a sharp fine separating two colors and two colors  blending ino each other; implicit vs. explicit with two colors separated by an explicit line and the same colors next to each other without a line; fixed vs. changing as a marble and a river; structural vs. behavioral as a tree and a dog marking its territory&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day21-300w.jpeg?v=61c78d2cb974&quot; width=&quot;900&quot; height=&quot;566&quot; srcset=&quot;https://sebastian-hans.de/img/day21-300w.jpeg?v=61c78d2cb974 300w, https://sebastian-hans.de/img/day21-600w.jpeg?v=76b7557327e2 600w, https://sebastian-hans.de/img/day21-900w.jpeg?v=de5c0127afbb 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Draft of a zine page on boundaries
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;day-22-%E2%80%93-shifting-outcomes&quot; tabindex=&quot;-1&quot;&gt;Day 22 – Shifting outcomes&lt;/h2&gt;
&lt;p&gt;The task was to draw an Impact Map for a challenge in our focal situation
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day22.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This exercise felt very similar to “Exploring Constraints and Forces” on day 19.
When I squint a bit, the goal here looks like the problem there, the actors here form a subset of the constraints there, the impacts here are roughly equivalent to the forces there, and the responses here look like the approaches there.
Assumptions are not explicitly called out here, although some of the impacts I listed are indeed assumptions rather than proven facts.&lt;/p&gt;
&lt;p&gt;In order to not just copy everything over, I chose a different goal to explore today.&lt;/p&gt;
&lt;p&gt;What&#39;s new here is the last paragraph where we ask about side-effects and explore further beyond the right side of the diagram.
We could do that with the other one, too, however.&lt;/p&gt;
&lt;h2 id=&quot;day-23-%E2%80%93-notice-what-we-add&quot; tabindex=&quot;-1&quot;&gt;Day 23 – Notice what we add&lt;/h2&gt;
&lt;p&gt;Focusing on a recent conversation, the task was to write down what was actually
said in one column and what I heard/thought in a second column, and to reflect
on this
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day23.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;There were definitely “differences between [my] external dialogue and internal thoughts and feelings.” I think this is necessary to a certain degree.
In general, you don&#39;t want to bleat out the first thing that comes to mind in a professional setting.
In the situation I had in mind, I had certain suspicions about the motives of the other party, but this was not the moment to address them.
There will be a meeting at the beginning of the new year where the cards will be put on the table.
We will prepare for this and hopefully have a fruitful discussion then.&lt;/p&gt;
&lt;h2 id=&quot;day-24-%E2%80%94-adventure-zine&quot; tabindex=&quot;-1&quot;&gt;Day 24 — Adventure zine&lt;/h2&gt;
&lt;p&gt;The final exercise was to turn the zine pages designed over the past days into a
physical zine
(&lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day24.html&quot;&gt;full prompt&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Doing this on Christmas Eve with 2 kids? No chance!
On the 25th, they are busy with their presents, and I have some free time.
I admit, I groaned every time I encountered a “make a zine page” exercise because drawing does not come naturally to me, but now I already had 5 pages I wanted to finish it.
So I added a title page and created two additional pages to illustrate all the tools we used during the course of our Advent(ure).
I am quite pleased with the result.
I intend to keep it on my desk as a tangible reminder to practice system seeing more often, and as a quick reference.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Photo of the front page of my zine featuring a drawing of a Christmas tree and the title “Advent(ure) in System Seeing”. There are 4 red balls on the tree, with the digits 2, 0, 2, and 5 on them. There are electric lights, too, and the picture shows the cable and where it is plugged into the wall socket.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day24-1-300w.jpeg?v=1b1cecb507c1&quot; width=&quot;900&quot; height=&quot;512&quot; srcset=&quot;https://sebastian-hans.de/img/day24-1-300w.jpeg?v=1b1cecb507c1 300w, https://sebastian-hans.de/img/day24-1-600w.jpeg?v=ccfab3f945e5 600w, https://sebastian-hans.de/img/day24-1-900w.jpeg?v=3bc6ebbbb16b 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Front cover
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Photo of a double page of my zine showing the system overview and the relationships and interactions page (from days 5 and 9).&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day24-2-300w.jpeg?v=453c95f1f098&quot; width=&quot;900&quot; height=&quot;1072&quot; srcset=&quot;https://sebastian-hans.de/img/day24-2-300w.jpeg?v=453c95f1f098 300w, https://sebastian-hans.de/img/day24-2-600w.jpeg?v=27df1fdd43cb 600w, https://sebastian-hans.de/img/day24-2-900w.jpeg?v=c845bc4d6c79 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Pages 1 and 2
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Photo of a double page of my zine showing the boundaries and the context pages (from days 21 and 8).&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day24-3-300w.jpeg?v=dddc5bf0b632&quot; width=&quot;900&quot; height=&quot;1066&quot; srcset=&quot;https://sebastian-hans.de/img/day24-3-300w.jpeg?v=dddc5bf0b632 300w, https://sebastian-hans.de/img/day24-3-600w.jpeg?v=48600523e118 600w, https://sebastian-hans.de/img/day24-3-900w.jpeg?v=070fd19965d7 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Pages 3 and 4
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Photo of a double page of my zine showing the constraints page (from day 16) and a new one that illustrates these 6 methods: bubble diagram; futures wheel; rich picture; causal loop; actor map; ideal present canvas&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day24-4-300w.jpeg?v=ea1ed495afb0&quot; width=&quot;900&quot; height=&quot;1087&quot; srcset=&quot;https://sebastian-hans.de/img/day24-4-300w.jpeg?v=ea1ed495afb0 300w, https://sebastian-hans.de/img/day24-4-600w.jpeg?v=5fb11ddf1753 600w, https://sebastian-hans.de/img/day24-4-900w.jpeg?v=718501713d5d 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Pages 5 and 6
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Photo of the final page of my zine illustrating these 6 methods: graphical history; value network map; capability map; constraints/forces/assumptions/approaches; impact mapping; left column/right column&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/day24-5-300w.jpeg?v=8ba7664c1b42&quot; width=&quot;900&quot; height=&quot;511&quot; srcset=&quot;https://sebastian-hans.de/img/day24-5-300w.jpeg?v=8ba7664c1b42 300w, https://sebastian-hans.de/img/day24-5-600w.jpeg?v=780c8ca93c74 600w, https://sebastian-hans.de/img/day24-5-900w.jpeg?v=d298d47f205a 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Back cover
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;zine&quot; tabindex=&quot;-1&quot;&gt;Advent(ure) in System Seeing zine&lt;/h2&gt;
&lt;p&gt;If you want to print my zine, here is a PDF file with all pages in the correct
order: &lt;a href=&quot;https://sebastian-hans.de/files/Adventure_in_System_Seeing_2025_Zine.pdf&quot;&gt;Adventure_in_System_Seeing_2025_Zine.pdf&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because my printer can only handle A4 pages,
the PDF file contains 2 pages.
I printed them on A4 paper, cut off the margins, and taped the pages together with transparent tape.
The right border of the first page (with the title and constraints pages)
must be attached to the left border of the second page (with the context and
system pages).
The resulting large page can be folded into an 8 page zine by following the
instructions from &lt;a href=&quot;https://www.ruthmalan.com/Advent/2025/Day24.html&quot;&gt;Ruth&#39;s prompt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Have fun with it, and let me know what you think!&lt;/p&gt;
</description>
      <pubDate>Sun, 25 Jan 2026 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/aiss-2025/</guid>
    </item>
    
    
    <item>
      <title>Clean the f… up!</title>
      <link>https://sebastian-hans.de/blog/ctfu/</link>
      <description>&lt;p&gt;My new motto is, &lt;strong&gt;Clean the f… up!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The “f” is for files, of course.&lt;/p&gt;
&lt;p&gt;By cleaning up, I mean not leaving obsolete, unused, temporary stuff lying around.
A house is finished not when the walls are complete but when the scaffolding has
been removed and the dirt has been swept up so people can live in it without
constantly tripping over rubble.
Similarly, software is finished not when the tests pass but when all temporary
experiments, exploratory meanderings, and temporary workarounds for unfinished
parts have been cleaned up – deleted, maybe archived, but in any case removed
from the sight of the maintainer who will have to work with the results.&lt;/p&gt;
&lt;p&gt;A refactoring is finished not when the new code is operational, but
when the old code has been deleted. Not commented out! You &lt;em&gt;are&lt;/em&gt; using a version
control system, aren&#39;t you? If so, deleted code is not lost anyway. There is no
point in unused code cluttering up your workspace.&lt;/p&gt;
&lt;p&gt;I&#39;ve heard people say we should hang on to old stuff because there is no harm in
it, and “what if we need it again some time?”
Well, let me show you some examples.&lt;/p&gt;
&lt;p&gt;Just recently, I wanted to make a change to the monitoring setup at a customer project.
The change went smoothly in the dev environment, but deployment failed in the
production environment.
This is a Kubernetes cluster,
and applications are installed via Helm.
The Helm charts are pulled from an internal registry.
The step that failed was the login to this registry.
An hour and conversations with 3 different people later, the problem was solved.
One of the people I talked to had sent me down the wrong road by suggesting that
the credentials we had configured were obsolete; we should authenticate with this other
user instead.
Actually, the solution was even simpler.
You see, during a platform migration half a year ago, the systems had been wired up in such
a way as to render the explicit login unnecessary.
It had kept working for a time, but apparently now there had been a permission
cleanup, and the login stopped working.
Simply deleting the login step made the deployment work again.&lt;/p&gt;
&lt;p&gt;Actually, the team knew about this. One of the people I talked to was an
engineer on my team, and he gave me the hint that allowed me to fix the problem.
Cleaning up the deployment process at the time of the migration half a year ago
would have cost 5 minutes of engineering time. They had to change the very same
file anyway.
Doing it now cost about 2 hours and blocked other work. As I said, …&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean the f… up!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another thing I am currently working on is improving the performance and stability of the test suite.
This project uses a whole lot of integration tests with a containerized
database. The tests are based almost exclusively on a common set of test data in
the form of a large (large!) SQL file. The file has gotten pretty big and
unwieldy (did I mention it was large?),
and if a test fails, finding the relevant input data is a major pain
in the ass. It is also slower to load into the database than I would like.
I&#39;m pretty sure most of the data in the reference data SQL file is not actually
used, but the team has a culture of keeping stuff around “in case it is needed
again.”
Due to this, we are now at a point where it is difficult to tell which parts
are needed and which parts aren&#39;t. Keeping something like this manageable is
&lt;em&gt;much&lt;/em&gt; easier if you don&#39;t keep unused stuff lying around, so …&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean the f… up!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A code base I know uses Hibernate ORM at the persistence layer.
One of the entities exists twice in the code base.
Both implementations read the same database table, but contain a different set
of relations.
One of them has been deprecated &lt;em&gt;2 years ago&lt;/em&gt;
and sports a comment stating that it has evolved into
a &lt;a href=&quot;https://en.wikipedia.org/wiki/God_object&quot;&gt;god class&lt;/a&gt; and should not be used;
usages should be replaced by the other class.
Can you guess in how many places this class is used versus the other one?
Right.&lt;/p&gt;
&lt;p&gt;Eliminating usage of the deprecated class is not trivial, I grant you that.
The replacement is not a drop-in; you need to refactor any code that uses it,
which is not a fun exercise.
But you know what&#39;s worse than sticking with a badly designed class?
Having to deal with a badly designed class &lt;em&gt;plus&lt;/em&gt; another class for the same thing
in the long term.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean the f… up!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One customer uses a vulnerability scanner to scan all containers running on the
production Kubernetes cluster. One day two months ago, we got an alert for a
dependency. The normal process after receiving such an alert is to update the
dependency to a non-vulnerable version. In this particular case, the alert was
for an indirect dependency, and there was no clear upgrade path because it was
no longer actively maintained.
Figuring out what to do took some time.
In the end, I noticed that the dependency was only required by obsolete code
(a part of the service that was no longer in use).
The solution was to remove the dead code – and the dependency, of course.
Again, figuring out which parts were really unused and which parts were still
necessary was not trivial. It would have been much easier if this had been done
at the time when the part of the service had been decommissioned by the people
who were working on it.
It took me much longer, plus we had vulnerable software in production for no good reason.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean the f… up!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Keeping obsolete, unused, temporary stuff around is &lt;em&gt;not free&lt;/em&gt;!
The cost may not be immediately obvious because leaving it there feels like being faster in
the moment.
But the cost (in terms of engineering effort) of cleaning up later
is almost certainly higher than the cost of cleaning up right away.
So do yourself – and your organization – a favour and &lt;strong&gt;clean the f… up!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Thank you.&lt;/p&gt;
</description>
      <pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/ctfu/</guid>
    </item>
    
    
    <item>
      <title>Transcript: Programming as Theory Building and the Not Invented Here Syndrome (english)</title>
      <link>https://sebastian-hans.de/blog/patbatnihs-en/</link>
      <description>&lt;p&gt;This is the translated transcript of my talk “Programming as Theory Building and the
Not Invented Here Syndrome” at the &lt;a href=&quot;https://www.infodays.de/sa&quot;&gt;InfoDays: Softwarearchitektur&lt;/a&gt; 2025.
It is a rather direct translation, so expect some rough edges.&lt;/p&gt;
&lt;p&gt;(Das deutsche Originaltranskript ist &lt;a href=&quot;https://sebastian-hans.de/blog/patbatnihs-de/&quot;&gt;hier&lt;/a&gt;.)&lt;/p&gt;
&lt;h2 id=&quot;intro&quot; tabindex=&quot;-1&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;Hello, welcome to my talk “Programming as Theory Building and the Not Invented Here Syndrome”!
My name is Sebastian Hans.
I have been working in software development for 20 years, most of the time as a software architect and developer in development teams; and this is the setting for the ideas you are about to hear from me.&lt;/p&gt;
&lt;p&gt;Currently, I am a Senior Technical Consultant at bbv and accompany software and digitalization projects for our customers.
When you do this for a while, sometimes you see things go well, and sometimes they get a little bumpy, occasionally due to technology, but often due to people, too.
And that is what we will deal with in this talk, “Programming as Theory Building and the Not Invented Here Syndrome”.
In it, I will first briefly introduce the essay “Programming as Theory Building”, then the Not Invented Here Syndrome, and then I will examine the connections and show situations in which they influence the daily life of development teams.&lt;/p&gt;
&lt;h2 id=&quot;programming-as-theory-building&quot; tabindex=&quot;-1&quot;&gt;Programming as Theory Building&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/onlurking/fc5c81d18cfce9ff81bc968a7f342fb1&quot;&gt;Programming as Theory Building&lt;/a&gt; is an essay by Peter Naur.
It is 40 years old now, but its topic is still just as relevant as it was in the 80s.
I recommend to everyone to read it themselves some time; there is a lot of wisdom in it.
For our purposes here, it is enough to know the core thesis:
a program does not consist of its source code, but of a theory.
And programming does not consist of translating facts from the real world into source code, but of building this theory.
The theory – and thus the program – lives in the heads of the developers.&lt;/p&gt;
&lt;p&gt;(A small side note: In the following, I am going to talk about “programs” because Naur&#39;s essay uses this term.
But everything applies to systems and system compositions as well – just to make that clear.)&lt;/p&gt;
&lt;p&gt;Therefore, to really understand a program in this sense, reading the source code is not enough; and neither is looking at the documentation; you somehow have to get at this theory.&lt;/p&gt;
&lt;p&gt;The term “theory” that is used here does not only refer to factual knowledge, e. g., what the code does, where to find which functionality and so on, but also the ability to explain it, to justify it, and to connect it to the real world.
You can imagine the code to be like the shadow of a real object.
Here we see a shadow.
If I only look at this, I can see it is a circle.
But this on its own does not constitute a theory, because I have no idea what creates the shadow.
And there can be quite different theories.
It could be a sphere, for example. Or a cylinder. Or a cone or an Easter egg.
If I am in possession of the theory, I can explain &lt;em&gt;why&lt;/em&gt; the shadow is circular.
Let&#39;s take the cylinder as the theory. Then the reason is because it is being illuminated along its axis.
And if someone comes along and says, “Look, our requirements have changed by 90°, make sure the shadow matches!”, then I only ask for the axis, and if it is the vertical axis, then I say: “OK”, and – boom – I am done.
Someone who is not in possession of the theory and only sees the circle cannot know what needs to be done – and they also cannot somehow reconstruct this knowledge from the circle.
That simply is not possible.&lt;/p&gt;
&lt;p&gt;Whether this theory is present or not is especially noticable when changes or extensions are needed.
There are usually many ways to somehow shoehorn in a requirement, but often only a few that fit the theory.
If you choose one of those, the change is relatively easy and the result remains easy to change and maintain.
If you do it differently, you create an unmaintainable pile of code that might
even work if you are lucky.&lt;/p&gt;
&lt;h2 id=&quot;an-example&quot; tabindex=&quot;-1&quot;&gt;An example&lt;/h2&gt;
&lt;p&gt;An example from my own practice:
For a few years, I was in a team that was responsible for a payment component.
This component provided a unified interface through which apps could process payments from customers.
It had different payment service providers connected on the back end for different payment methods.
The payment component has a ports-and-adapters architecture, which means, in short, it separates domain logic from technical connections to surrounding systems, which live inside adapters.&lt;/p&gt;
&lt;p&gt;At one time, a new colleague joined the team and was given the task of implementing a new, additional payment flow.
To do this, he made extensions in the domain and in the adapter for the affected payment service provider.
Now I have to say that, for various reasons, the boundary between generic domain logic and specifics for the payment service provider, which belong in the adapter, is not that sharp.
Of course, the ports-and-adapters architecture itself is documented, but that does not help you decide exactly which piece of the implementation to put in which box.
This is part of the theory that lives in the heads of the developers and that you cannot directly find anywhere else.
Long story short, the new colleague did not fit the feature in the way it was intended.
He pulled too many adapter specifics into the domain code, making this part too complex, and we then rearranged it together quite a bit to simplify subsequent changes.
Doing this, we achieved two things.
On the one hand, we brought the changes into alignment with the theory of the payment component and thus quite practically improved the maintainability of the software.
And on the other hand, the colleague brought another piece of the theory into his head and can thus better drive the development forward within the bounds of the theory.&lt;/p&gt;
&lt;p&gt;So again: The core thesis of Programming as Theory Building is that a program is more than the source code plus documentation.
Rather, the program consists of the underlying theory and lives in the heads of the developers.
Source code and documentation show, at best, a small part of it.&lt;/p&gt;
&lt;p&gt;So much for “Programming as Theory Building”.
Let&#39;s move on to the other concept, the Not Invented Here Syndrome.&lt;/p&gt;
&lt;h2 id=&quot;the-not-invented-here-syndrome&quot; tabindex=&quot;-1&quot;&gt;The Not Invented Here Syndrome&lt;/h2&gt;
&lt;p&gt;Not Invented Here refers to the tendency to reject things that one has not discovered or invented oneself.
In the context of software development, these can be tools, processes, programming paradigms, or indeed also a theory that underlies a program.
This is not a formally defined term but rather an observation from practice, but there are studies that show that the effect is real.
For example, &lt;a href=&quot;https://journals.aom.org/doi/abs/10.5465/amj.2012.0458&quot;&gt;this study from 2014&lt;/a&gt;.
In it, researchers investigated how organizations conduct crowdsourcing, that is, ask the “crowd” – external people, customers, website visitors, etc. – for improvement suggestions;
and in particular, how they then evaluate and implement them.
To do so, they measured various types of distance and found, for example, that suggestions that were closer to what the organizations were already doing anyway received more attention than genuinely new suggestions that came from &lt;em&gt;somewhere&lt;/em&gt; in the crowd – which were Not Invented Here.
What is Not Invented Here is not that interesting to organizations.&lt;/p&gt;
&lt;p&gt;I have experienced this myself as well.
One of the clients of the payment component (the one I already mentioned) was
an SAP system.
But it was not connected the same way as all the other clients.
When this connection was being planned, there was a dispute between those responsible for the payment component and those responsible for the SAP system because the SAP people said that the flow as the payment component team envisioned it (and as the other clients had implemented it) would not work with SAP; it would contradict the SAP logic.
Not Invented Here.
We can&#39;t do that and we don&#39;t want to.&lt;/p&gt;
&lt;p&gt;Because this Not Invented Here reaction is usually adverserial, it has a somewhat bad reputation.
It is also called “Not Invented Here &lt;em&gt;Syndrome&lt;/em&gt;”, and a syndrome is fundamentally something bad.
But the Not Invented Here &lt;em&gt;reaction&lt;/em&gt;, as I would like to call it more neutrally, does not necessarily have to be bad.
A small dose of Not Invented Here is good and even necessary, especially if you take Programming as Theory Building seriously.&lt;/p&gt;
&lt;h2 id=&quot;not-invented-here-as-a-sensor&quot; tabindex=&quot;-1&quot;&gt;Not Invented Here as a sensor&lt;/h2&gt;
&lt;p&gt;To understand this, I would like to return to the idea of distance used by the study.
Very close is almost Invented Here, far away is definitely Not Invented Here.
For the purpose of this talk, I will take a closer look at the distance of an idea or concept to the theory of the program.
I&#39;ll call it &lt;em&gt;“conceptual distance”&lt;/em&gt;.
The distance is 0 if an idea lies within the bounds of the theory.
And if a concept does not fit in with the theory at all, then I say the two have a high conceptual distance.
From the perspective of the theory of the program, such an idea would be Not Invented Here.&lt;/p&gt;
&lt;p&gt;If you then think of a program – in the sense of theory + code – and an idea for a change, then it would be good if the idea had as little conceptual distance as possible to the theory.
Because then it would usually be easy to implement, relatively low-risk, and would leave the program in a good, maintainable state.
Whereas a change that was far away from the theory would be rather difficult to implement and error-prone and could potentially destroy the conceptual integrity of the program and thus render it unmaintainable.
Hence, it is completely rational to view ideas with a high conceptual distance to the theory of the program more critically than those that are closer to it –
which is exactly what “Not Invented Here” is about.
From the perspective of the development team, Not Invented Here is basically a protective shield for the conceptual integrity of our theory.
Or a sensor that tells me, “Watch out, this is something that does not fit our theory.”&lt;/p&gt;
&lt;p&gt;Does that mean Not Invented Here is always good and we should make a habit of rejecting ideas that are Not Invented Here?
Of course not. Blanket statements are always wrong.
For one thing, there are many different types of distance where one could cry “Not Invented Here”; distance based on group membership, for example.
Something like that is usually bad.
That is not what I mean here.
I exclusively mean conceptual distance.&lt;/p&gt;
&lt;p&gt;And there is another reason why I should not categorically reject things that are Not Invented Here:
the theory of the program may not be perfect.
From time to time, the team learns something new and adapts the theory.
This is only possible by integrating ideas that lie outside the bounds of the theory.&lt;/p&gt;
&lt;h2 id=&quot;dealing-with-new-ideas&quot; tabindex=&quot;-1&quot;&gt;Dealing with new ideas&lt;/h2&gt;
&lt;p&gt;So what do I do if I am a developer in an established development team and someone comes along with an idea or does something in a way that triggers my Not Invented Here sensor?
First, I quickly check whether it has triggered for a good reason.
Is it the conceptual distance from the theory?
If it is not, then maybe it is a false alarm and I should mute it and just continue listening to the idea.
But if I find that what I am hearing or seeing really &lt;em&gt;is&lt;/em&gt; far away from the theory of my program, then I can rejoice because then the sensor has worked.
It has prevented us from blindly putting something into our program that will cause problems later.&lt;/p&gt;
&lt;p&gt;The simplest solution is to say, “No, we won&#39;t do that.”
This saves time and money, and keeps the program tidy and small.
But of course you cannot and do not always want to do that, because behind many ideas there is a good intention.
Then the task consists of reducing the distance to the theory as much as possible.
Is there a way to represent what you actually wanted to achieve with the idea within the existing bounds of the theory?
This is the best case; then you should just do it that way.
Remember the new colleague in the payment component team.
He wanted to build a feature and tried it in a way that did not fit the theory, and the team then remodeled it together to work well within the existing theory.&lt;/p&gt;
&lt;p&gt;If we cannot bring the idea conceptually closer to the theory, but we still want to implement it, we can do that, but it will be more work.
Because then we have to adapt the theory to the idea.
And that means not just writing new code, because the theory is more than just code.
We have to bring the whole team along and together work out a new, consistent mental model that reconciles the existing theory and the new idea; and then we can implement that.
The main task here is to establish a shared understanding of the new theory.
Writing the code to match afterwards is usually much easier.&lt;/p&gt;
&lt;h3 id=&quot;another-example&quot; tabindex=&quot;-1&quot;&gt;Another example&lt;/h3&gt;
&lt;p&gt;I also have an example for that, from the same payment component.
We once had to replace a payment service provider.
The contract with the old one had expired, a new one had been found as part of a tender process and had to be integrated, without downtime.
We already had a way to configure the payment service provider per app, to send the payments for each app to the appropriate adapter.
That means, following the theory of the program, we could have switched the apps over via a simple configuration change at the time of migration, and we would have been done.
But since many customers were affected, a lot of money was at stake, and big-bang migrations are quite risky, I had the idea of performing the migration per customer instead.
This way, we would have much more fine-grained control, and we could significantly reduce the risk.&lt;/p&gt;
&lt;p&gt;However, the conceptual distance to the existing theory was relatively large, and so –
even though the idea came from me, that is, from within the team –
we carefully considered whether to introduce it like that and how to marry this concept of migration per user with what was already there.
Ultimately, we decided to completely remove the configuration per app, implement the migration per user, and remove it (along with the old payment service provider) after the migration was finished.
The last part may surprise you, but it had to do with the new theory we had formed on the subject of routing payments to payment service providers.&lt;/p&gt;
&lt;p&gt;Old theory:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Routing is not really proper logic. It is enough to have a routing table somewhere
that can be changed arbitrarily at runtime.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;New theory:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Routing is a core function of the payment component,
and changing it is a non-trivial undertaking
that will probably involve custom logic and edge cases that are best represented in code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we had not taken the time to adapt the theory, but simply bolted the migration per user on top …
I don&#39;t even want to imagine what that would have come of that.
Certainly nothing that would have been easy to maintain and extend.&lt;/p&gt;
&lt;h2 id=&quot;dealing-with-existing-theories&quot; tabindex=&quot;-1&quot;&gt;Dealing with existing theories&lt;/h2&gt;
&lt;p&gt;Let&#39;s look at the whole thing from a different perspective, namely from the point of view of someone who wants to or has to deal with someone else&#39;s theory.
This could be, for example, a piece of software that someone else has developed and where something needs to be changed now.
And let&#39;s also assume that the conceptual distance to what I am otherwise used to is medium to high.
Then I will initially have difficulties understanding the theory behind it.
I won&#39;t be able to carry out the change to the software in such a way that I can be sure the result will be good.
The theory is too far away.
In other words, my Not Invented Here sensor is being triggered.
But that does not mean I should fall into the Not Invented Here defensive posture.
Because the consequence of the Theory Building thesis is:
if I don&#39;t want a half-baked kludge, then I first have to obtain the theory of the program.
I have to get it into my head.
How to do that depends on the situation.&lt;/p&gt;
&lt;h3 id=&quot;onboarding-to-an-active-development-team&quot; tabindex=&quot;-1&quot;&gt;Onboarding to an active development team&lt;/h3&gt;
&lt;p&gt;One possibility is that I am joining an established development team.
Then I can assume that the people already have a theory in their heads, but I don&#39;t know it yet.
I only see the source code at first.
Then the first thing to do is to get out of the defensive stance!
The most unproductive thing to do would be to say things like,
“That&#39;s strange! How could anyone come up with such an idea? I would have done this completely differently.”
And so on.
This would only prevent me from completing my main task, which is to internalize the theory of the program as quickly as possible.
What helps is&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;talking to the team,&lt;/li&gt;
&lt;li&gt;reading documentation,&lt;/li&gt;
&lt;li&gt;talking to the team,&lt;/li&gt;
&lt;li&gt;reading code,&lt;/li&gt;
&lt;li&gt;talking to the team,&lt;/li&gt;
&lt;li&gt;maybe starting with coding, using pair programming if possible, but at first less to adapt the code, and more with the goal of learning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oh yes, and talking to the team.&lt;/p&gt;
&lt;p&gt;I&#39;ve encountered such a situation recently with a service that handles historicized data.
In this service, attributes of some entities can change over time and they are stored with “from” and “to” dates.
Others are not.
Some relationships between entities are historicized, too, and some aren&#39;t.
I can see in the code that this is so, and the internal wiki contains some amount of documentation,
but I only really understood the why behind it in conversation with the team.
I could have said directly, “This is all way too complicated, I would have built it completely differently.”
Not Invented Here.
But that would not have helped me understand the topic.&lt;/p&gt;
&lt;p&gt;So whenever I&#39;m new in a team,
I switch off the Not Invented Here defensive posture and try to absorb the theory as quickly as possible.&lt;/p&gt;
&lt;h3 id=&quot;dealing-with-legacy-software&quot; tabindex=&quot;-1&quot;&gt;Dealing with legacy software&lt;/h3&gt;
&lt;p&gt;A different situation would be taking over responsibility for an existing system where nobody is available to answer questions.
This can have different reasons.
Maybe it is an old system where nothing has had to be done for a long time, and the people with knowledge of it have since moved on.
And now there is something to do again and I&#39;m the lucky guy who gets to do it.
Or maybe a team exists, but it no longer consists of the original developers but of people who joined later and have not internalized the theory themselves.
I know the experience – maybe you know it too – of joining a team that does not know its own system – or only parts of it.
Then it gets difficult; then you have to reconstruct the theory. This is hard work.&lt;/p&gt;
&lt;p&gt;And with this work, too, my Not Invented Here sensor or the idea of conceptual distance can help.
Because it tells me what I still need to investigate.
From the start, I already have a certain idea of what the theory could look like, even if it is only my intuition based on my experience with other programs.
When I then look at the material that is there – code, diagrams, documentation, whatever – I see things where I think, “Yes, OK, fine.”
And I see things where my distance sensor triggers – the one for conceptual distance.
Then I think, “Wow, that does not fit at all with what I have pieced together so far!”
Some developers then fall into the Not Invented Here defensive stance and grumble about the legacy crap they have to deal with.
I take that as a signal to take a closer look, because this is a place where my theory is obviously still wrong or at least incomplete.
So I dig in further and try to understand exactly that spot and reconstruct the original theory a bit further.&lt;/p&gt;
&lt;p&gt;This works up to a certain point. But usually not completely.
When you have reached that point, then you can – and should – begin to rebuild the theory to suit the active developers, to get out of this “I have no idea what I am doing” mode.
The result is a theory that is actually alive in the heads of the developers, and you can continue working with it.&lt;/p&gt;
&lt;p&gt;An example of this is a developer team that inherited a Java library consisting of five layers.
Nobody knew the purpose of these layers, and some developers had exactly this Not Invented Here reaction where they didn&#39;t want to touch the thing at all.
When I joined the team, we looked at what problems we actually had and where layers helped us and where they didn&#39;t;
and we have now started to change the code according to our new theory.&lt;/p&gt;
&lt;h2 id=&quot;reprise&quot; tabindex=&quot;-1&quot;&gt;Reprise&lt;/h2&gt;
&lt;p&gt;So you see, there is a connection between the idea of Programming as Theory Building by Peter Naur and Not Invented Here.
To summarize briefly, Programming as Theory Building asserts that the essence of programming consists not of writing code, but of building a theory.
This theory lives in the heads of the developers and is the actual program.
The code is part of it, but it only ensures that the program becomes executable on a machine.
Changes to the code that are in harmony with the theory are much easier to carry out and lead to better results than changes that are made without regard to the theory – because the program consists mainly of the theory.&lt;/p&gt;
&lt;p&gt;Not Invented Here is a reaction to ideas that come from far away, where the distance can lie in different dimensions.
This reaction is often dismissive and counterproductive, but it does not have to be that way.&lt;/p&gt;
&lt;p&gt;One dimension that is particularly important for development is the conceptual distance.
How far apart are two concepts or ideas or theories?
According to the Theory Building thesis, program changes with a high conceptual distance to the theory are precisely the problematic ones.
And this is why the Not Invented Here reaction can be useful, because it alerts me to this situation.&lt;/p&gt;
&lt;p&gt;When I have learned to get rid of the automatic defensive stance that is often associated with it, and view it as a signal instead, then I am free to consider whether and how I want to reduce the conceptual distance.
Maybe I don&#39;t want to do this at all and say, “No, I won&#39;t do that!”
But if I want to do it, I have essentially three possibilities:
I can adapt one side or the other, or I adapt both and land somewhere in the middle.&lt;/p&gt;
&lt;p&gt;We have looked at a few situations:
when onboarding a new developer, the two relevant concepts are the theory of the program and the preconceived ideas and notions in the developer&#39;s head.
In this case, I usually want the developer to first understand the theory of the program and &lt;em&gt;later&lt;/em&gt; bring in their own experience to improve the theory.&lt;/p&gt;
&lt;p&gt;With change proposals, two things can happen.
Either you manage to implement the requirements within the bounds of the theory of the program – that is the best case – or you actually have to adapt the theory of the program to integrate the solution idea.
But then really change the theory, not just isolated pieces of code!&lt;/p&gt;
&lt;p&gt;When taking over a legacy system where the theory is no longer accessible, you will often be unable to fully reconstruct it.
But you should try, and then adapt the remaining parts to your own ideas and form a new theory so that you can work with it properly.&lt;/p&gt;
&lt;p&gt;So, the next time you encounter a Not Invented Here reaction, in yourself or in others, think about what caused it and whether it might not be a signal to engage more deeply with the topic!&lt;/p&gt;
&lt;p&gt;Thank you and happy programming! Or theory building!&lt;/p&gt;
</description>
      <pubDate>Sun, 28 Dec 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/patbatnihs-en/</guid>
    </item>
    
    
    <item>
      <title>Transkript: Programming as Theory Building and the Not Invented Here Syndrome (deutsch)</title>
      <link>https://sebastian-hans.de/blog/patbatnihs-de/</link>
      <description>&lt;p&gt;Dies ist ein Transkript meines Vortrags „Programming as Theory Building and the
Not Invented Here Syndrome“, den ich auf den
&lt;a href=&quot;https://www.infodays.de/sa&quot;&gt;InfoDays: Softwarearchitektur&lt;/a&gt; 2025
gehalten habe (leicht bearbeitet).&lt;/p&gt;
&lt;p&gt;(English translation &lt;a href=&quot;https://sebastian-hans.de/blog/patbatnihs-en/&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;
&lt;h2 id=&quot;intro&quot; tabindex=&quot;-1&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;Hallo, willkommen zu meinem Vortrag „Programming as Theory Building and the Not Invented Here Syndrome“!
Mein Name ist Sebastian Hans.
Ich bin seit 20 Jahren in der Software-Entwicklung tätig, den allergrößten Teil davon als Software-Architekt und Entwickler in Entwicklungsteams, und das ist auch das Setting für die Ideen, die ihr gleich von mir hören werdet.&lt;/p&gt;
&lt;p&gt;Aktuell bin ich als Senior Technical Consultant bei der bbv und begleite Software- und Digitalisierungsprojekte bei Kunden.
Wenn man sowas länger macht, dann sieht man Sachen, die gut laufen, und Sachen, die holpern, manchmal in der Technik, oft aber auch bei den Menschen.
Und mit denen beschäftigen wir uns eben genau in diesem Vortrag „Programming as Theory Building and the Not Invented Here Syndrome“.
Dazu werde ich zuerst kurz das Essay „Programming as Theory Building“ vorstellen, dann das Not Invented Here Syndrome und dann werde ich die Zusammenhänge untersuchen und Situationen aufzeigen, in denen die in der Praxis von Entwicklungsteams eine Rolle spielen.&lt;/p&gt;
&lt;h2 id=&quot;programming-as-theory-building&quot; tabindex=&quot;-1&quot;&gt;Programming as Theory Building&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/onlurking/fc5c81d18cfce9ff81bc968a7f342fb1&quot;&gt;Programming as Theory Building&lt;/a&gt; ist ein Essay von Peter Naur, das mittlerweile 40 Jahre alt ist, aber inhaltlich immer noch genauso gültig wie in den 80ern.
Ich kann jedem nur empfehlen, es bei Gelegenheit selbst zu lesen, da steckt eine ganze Menge Weisheit drin.
Für unsere Zwecke hier reicht es, die Kernthese zu kennen. Die lautet nämlich, dass ein Programm nicht aus seinem Source-Code besteht, sondern aus einer Theorie.
Und programmieren besteht nicht darin, dass man Sachverhalte aus der echten Welt in Source-Code übersetzt, sondern darin, dass man diese Theorie aufbaut. Die Theorie – und damit das Programm – lebt in den Köpfen der Entwickler.&lt;/p&gt;
&lt;p&gt;(Kleine Randnotiz: Ich werde im Folgenden immer von „Programmen“ reden, weil Naurs Essay diesen Begriff verwendet. Das gilt aber immer auch für Systeme und Systemverbunde – nur, damit das klar ist.)&lt;/p&gt;
&lt;p&gt;Um ein Programm in diesem Sinne wirklich zu verstehen, reicht es also nicht, den Quellcode zu lesen, und auch nicht, die Dokumentation anzuschauen, sondern man muss irgendwie an diese Theorie kommen.&lt;/p&gt;
&lt;p&gt;Der Begriff der Theorie, der da verwendet wird, bezeichnet nicht nur Faktenkenntnis, zum Beispiel, was tut der Code, wo finde ich welche Funktionalität und so weiter, sondern darüberhinaus auch die Fähigkeit, das zu erklären, zu begründen und einen Bezug zur realen Welt herzustellen.
Man kann sich das so vorstellen, dass der Code wie der Schatten eines realen Objekts ist.
Hier haben wir einen Schatten.
Wenn ich mir nur den anschaue, kann ich sagen, das ist ein Kreis. Damit habe ich aber noch keine Theorie, weil ich keine Ahnung habe, was den Schatten erzeugt.
Und es kann ganz verschiedene Theorien geben. Es könnte zum Beispiel eine Kugel sein. Oder ein Zylinder. Oder ein Kegel oder ein Osterei.
Wenn ich die Theorie kenne, dann kann ich erklären, &lt;em&gt;warum&lt;/em&gt; der Schatten kreisförmig ist.
Nehmen wir mal den Zylinder als Theorie, dann liegt es daran, dass er entlang seiner Achse beleuchtet wird.
Und wenn jemand daherkommt und sagt: „Pass auf, unsere Anforderungen haben sich um 90° gedreht, sorg dafür, dass der Schatten dazu passt!“, dann frage ich nur, um welche Achse, und wenn es die vertikale Achse ist, dann sage ich: „OK“, und – zack – bin ich fertig.
Jemand, der die Theorie nicht kennt und nur den Kreis sieht, der kann nicht wissen, was zu tun ist – und er kann das Wissen auch nicht irgendwie aus dem Kreis rekonstruieren. Das geht einfach nicht.&lt;/p&gt;
&lt;p&gt;Ob diese Theorie vorhanden ist oder nicht, merkt man also vor allem, wenn Änderungen oder Erweiterungen gebraucht werden.
Es gibt meistens viele Möglichkeiten, eine Anforderung irgendwie reinzupfriemeln, aber oft nur wenige, die zur Theorie passen.
Wählt man eine davon, ist es relativ einfach und das Ergebnis ist auch weiterhin gut änderbar und wartbar.
Wenn man es anders macht, erzeugt man einen unwartbaren Haufen Code, der, wenn man Glück hat, funktioniert.&lt;/p&gt;
&lt;h2 id=&quot;ein-beispiel&quot; tabindex=&quot;-1&quot;&gt;Ein Beispiel&lt;/h2&gt;
&lt;p&gt;Ein Beispiel aus meiner eigenen Praxis:
Ich war ein paar Jahre lang in einem Team, das eine Zahlungskomponente verantwortet hat.
Diese Komponente hat eine einheitliche Schnittstelle bereitgestellt, über die Apps Zahlungen von Endkunden abwickeln konnten, und sie hatte hintendran unterschiedliche Zahlungsdienstleister für unterschiedliche Zahlungsmittel angebunden.
Die Zahlungskomponente hat eine Ports-and-Adapters-Architektur, das heißt, kurz gesagt, sie unterscheidet zwischen der Domänenlogik und technischen Anbindungen an Umsysteme; die stecken in den Adaptern drin.&lt;/p&gt;
&lt;p&gt;Im Laufe der Zeit ist ein neuer Kollege ins Team gekommen und hat die Aufgabe bekommen, einen neuen, zusätzlichen Zahlungsablauf zu implementieren, und er hat dazu Erweiterungen in der Domäne und in dem Adapter für den betroffenen Zahlungsdienstleister vorgenommen.
Nun muss ich dazusagen, dass die Aufteilung zwischen generischer Domänenlogik und Spezifika für den Zahlungsdienstleister, die in den Adapter gehören, hier aus verschiedenen Gründen nicht so ganz eindeutig ist.
Die Ports-and-Adapters-Architektur ist natürlich dokumentiert, aber das hilft einem nicht bei der Entscheidung, was man genau in welches Kasterl reinsteckt.
Das ist Teil der Theorie, die in den Köpfen der Entwickler lebt und die man nicht direkt irgendwo anders herauslesen kann.
Langer Rede, kurzer Sinn, der neue Kollege hat das Feature natürlich nicht so geschnitten, wie es gedacht war.
Er hat zu viele Adapter-Spezifika in den Domänencode gezogen, sodass es da zu komplex geworden ist, und wir haben dann gemeinsam nochmal einiges umgebaut, um nachfolgende Änderungen zu vereinfachen.
Damit haben wir zweierlei erreicht.
Einerseits haben wir die Änderungen mit der Theorie der Zahlungskomponente in Einklang gebracht und damit ganz praktisch die Wartbarkeit der Software verbessert.
Und andererseits hat der Kollege ein weiteres Stück der Theorie in seinen Kopf gebracht und kann so die Entwicklung im Rahmen der Theorie beser weitertreiben.&lt;/p&gt;
&lt;p&gt;Also nochmal: Die Kernthese von Programming as Theory Building ist, dass ein Programm mehr ist als der Quellcode plus Dokumentation.
Das Programm ist vielmehr die dahinterstehende Theorie und die lebt in den Köpfen der Entwickler.
Quellcode und Dokumentation sind im besten Fall ein kleiner Ausschnitt davon.&lt;/p&gt;
&lt;p&gt;So weit zu „Programming as Theory Building“.
Kommen wir zu dem anderen Konzept, dem Not Invented Here Syndrome.&lt;/p&gt;
&lt;h2 id=&quot;das-not-invented-here-syndrome&quot; tabindex=&quot;-1&quot;&gt;Das Not Invented Here Syndrome&lt;/h2&gt;
&lt;p&gt;Not Invented Here bezeichnet die Tendenz, Dinge abzulehnen, die man nicht selbst entdeckt oder erfunden hat.
Im Kontext von Software-Entwicklung können das Werkzeuge sein, Prozesse, Programmierparadigmen oder eben auch eine Theorie, die hinter einem Programm steckt.
Das ist kein formal definierter Begriff sondern eher eine Beobachtung aus der Praxis, aber es gibt Untersuchungen, die belegen, dass der Effekt real ist.
Zum Beispiel eine &lt;a href=&quot;https://journals.aom.org/doi/abs/10.5465/amj.2012.0458&quot;&gt;Studie aus dem Jahr 2014&lt;/a&gt;.
In der haben Wissenschaftler untersucht, wie Organisationen Crowdsourcing betreiben, also die „Crowd“ – irgendwelche externen Personen, Kunden, Webseitenbesucher usw. – nach Verbesserungsvorschlägen fragen, und insbesondere, wie sie die dann auswerten und umsetzen.
Dabei haben sie verschiedene Arten von Distanz gemessen und zum Beispiel festgestellt, dass Vorschläge, die inhaltlich näher an dem dran waren, was die Organisationen sowieso schon gemacht haben, mehr Aufmerksamkeit bekommen haben als wirklich neue Vorschläge, die &lt;em&gt;irgendwo&lt;/em&gt; aus der Crowd kamen, die also Not Invented Here waren.
Was Not Invented Here ist, interessiert Organisationen anscheinend nicht so sehr.&lt;/p&gt;
&lt;p&gt;Das habe ich auch selbst schon erlebt.
An die Zahlungskomponente, die ich schon erwähnt habe, war auch SAP als Client angeschlossen.
Aber natürlich nicht so wie alle anderen Systeme.
Als diese Anbindung angebahnt wurde, haben sich die Verantwortlichen für die Zahlungskomponente und das fragliche SAP-System in die Wolle gekriegt, weil die SAP-Leute gesagt haben, der Ablauf, wie ihn sich das Zahlungskomponententeam vorstellt und die anderen Clients ihn auch realisiert haben, würde mit SAP nicht funktionieren, der widerspräche der SAP-Logik.
Not Invented Here. Das können wir nicht und wollen wir nicht.&lt;/p&gt;
&lt;p&gt;Weil diese Not Invented Here-Reaktion meistens negativ ist, hat sie einen ein bisschen schlechten Ruf.
Man sagt auch: „Not Invented Here &lt;em&gt;Syndrome&lt;/em&gt;“, und ein Syndrom ist grundsätzlich was Schlechtes.
Aber die Not Invented Here-&lt;em&gt;Reaktion&lt;/em&gt;, wie ich sie jetzt mal neutral nennen will, muss nicht unbedingt schlecht sein.
Eine kleine Dosis Not Invented Here ist gut und sogar notwendig, ganz besonders, wenn man Programming as Theory Building ernst nimmt.&lt;/p&gt;
&lt;h2 id=&quot;not-invented-here-als-sensor&quot; tabindex=&quot;-1&quot;&gt;Not Invented Here als Sensor&lt;/h2&gt;
&lt;p&gt;Um das zu verstehen, möchte ich die Idee der Distanz nochmal aufgreifen, die in der Studie angewandt wurde.
Ganz nah dran ist fast Invented Here, weit weg ist definitiv Not Invented Here.
Für den Zweck dieses Vortrags schaue ich mir mal die Distanz einer Idee oder eines Konzepts zur Theorie des Programms an.
Ich nenne sie hier &lt;em&gt;„konzeptionelle Distanz“&lt;/em&gt;.
Dabei ist die Distanz 0, wenn sich eine Idee innerhalb der Theorie bewegt.
Und wenn ein Konzept richtig quer liegt, sodass es überhaupt nicht zur Theorie passt, dann sage ich, die beiden haben eine hohe konzeptionelle Distanz und die Idee wäre aus Sicht der Theorie des Programms Not Invented Here.&lt;/p&gt;
&lt;p&gt;Wenn man sich dann ein Programm vorstellt – im Sinne von Theorie + Code – und eine Idee für eine Änderung, dann wäre es doch gut, wenn die Idee eine möglichst geringe konzeptionelle Distanz zur Theorie hätte.
Denn dann wäre sie normalerweise leicht durchzuführen, relativ risikoarm und würde das Programm in einem guten, wartbaren Zustand hinterlassen, wohingegen eine Änderung, die weit weg von der Theorie wäre, eher schwierig umzusetzen wäre und fehleranfällig und eventuell die konzeptionelle Integrität des Programms zerstören und es somit unwartbar machen würde.
So gesehen, ist es völlig rational, Ideen, die konzeptuell weit weg von der Theorie des Programms sind, kritischer zu betrachten als welche, die näher dran sind – also genau „Not Invented Here“ zu betreiben.
Aus Sicht des Entwicklungsteams ist Not Invented Here quasi ein Schutzschild für die konzeptionelle Integrität unserer Theorie.
Oder ein Sensor, der mir sagt: „Obacht, hier haben wir was, das nicht zu unserer Theorie passt.“&lt;/p&gt;
&lt;p&gt;Heißt das, Not Invented Here ist uneingeschränkt gut und wir sollten Ideen, die Not Invented Here sind, grundsätzlich ablehnen?
Natürlich nicht. Pauschalaussagen sind immer falsch.
Zum einen gibt es viele unterschiedliche Arten von Distanz, bei denen man „Not Invented Here“ rufen kann, z. B. auch solche, die auf Gruppenzugehörigkeit beruhen.
Sowas ist meistens schlecht. Das meine ich hier nicht. Ich meine ausschließlich die konzeptionelle Distanz.&lt;/p&gt;
&lt;p&gt;Und es gibt noch einen anderen Grund, warum ich Dinge, die Not Invented Here sind, nicht pauschal ablehnen sollte:
Die Theorie des Programms muss ja nicht perfekt sein.
Manchmal lernt das Team was dazu und entwickelt die Theorie weiter.
Das kann eigentlich nur passieren, indem man Ideen, die außerhalb der Theorie liegen, integriert.&lt;/p&gt;
&lt;h2 id=&quot;umgang-mit-neuen-ideen&quot; tabindex=&quot;-1&quot;&gt;Umgang mit neuen Ideen&lt;/h2&gt;
&lt;p&gt;Was mache ich also, wenn ich Entwickler in einem eingespielten Entwicklungsteam bin und jemand kommt mit einer Idee daher oder macht etwas auf eine Art und Weise, die meinen Not Invented Here-Sensor triggert?
Zuerst check ich kurz, ob er aus einem guten Grund getriggert hat.
Ist es die konzeptionelle Distanz zur Theorie? Wenn nicht, dann ist es vielleicht ein Fehlalarm und ich sollte den mal stummschalten und mir die Idee einfach weiter anhören.
Wenn ich aber feststelle, dass das, was ich da höre oder sehe, wirklich weit weg von der Theorie meines Programms ist, dann kann ich mich freuen, denn dann hat der Sensor funktioniert. Er hat verhindert, dass wir blind etwas in unser Programm einbauen, das später Probleme verursachen wird.&lt;/p&gt;
&lt;p&gt;Das Einfachste ist dann zu sagen: „Nein, das machen wir nicht.“
Das spart Zeit und Geld und hält das Programm schön klein.
Aber das kann und will man natürlich nicht immer machen, denn hinter vielen Ideen steckt ja eine gute Absicht.
Dann besteht die Aufgabe darin, die Distanz zur Theorie möglichst zu verringern.
Gibt es eine Möglichkeit, das, was man mit der Idee eigentlich erreichen wollte, im bestehenden Rahmen der Theorie abzubilden?
Das ist der beste Fall, dann sollte man das eben so tun.
Wir erinnern uns an den neuen Kollegen bei der Zahlungskomponente.
Der wollte ein Feature bauen und hat es auf eine Art und Weise versucht, die nicht zur Theorie gepasst hat, und das Team hat es dann gemeinsam so umgemodelt, dass es im bestehenden Rahmen gut funktioniert.&lt;/p&gt;
&lt;p&gt;Wenn wir es nicht schaffen, die Idee konzeptionell an die Theorie ranzubringen, und wir sie trotzdem umsetzen wollen, dann geht das auch, aber es wird mehr Arbeit.
Dann müssen wir nämlich die Theorie an die Idee anpassen.
Und das heißt, nicht nur neuen Code schreiben, denn die Theorie ist ja mehr als nur Code.
Wir müssen das ganze Team mitnehmen, uns gemeinsam ein neues, konsistentes mentales Modell überlegen, das die bisherige Theorie und die neue Idee in Einklang bringt, und das dann umsetzen.
Dabei ist die Hauptaufgabe, dieses gemeinsame Verständnis von der neuen Theorie herzustellen.
Dann den passenden Code zu schreiben, ist meistens deutlich leichter.&lt;/p&gt;
&lt;h3 id=&quot;noch-ein-beispiel&quot; tabindex=&quot;-1&quot;&gt;Noch ein Beispiel&lt;/h3&gt;
&lt;p&gt;Dazu habe ich auch ein Beispiel, und zwar aus derselben Zahlungskomponente.
Da mussten wir einmal einen Zahlungsdienstleister austauschen.
Der Vertrag mit dem alten ist ausgelaufen, im Rahmen einer Ausschreibung wurde ein neuer gefunden und der musste angebunden werden, und zwar ohne Downtime.
Es gab schon eine Möglichkeit, die Zahlungsdienstleister pro angebundener App zu konfigurieren, um die Zahlungen für eine App an den jeweils passenden Adapter zu schicken.
Das heißt, der Theorie des Programms folgend, hätten wir zum Migrationszeitpunkt die Apps konfigurativ umstellen können und wären fertig gewesen.
Da es aber um viele Kunden und um viel Geld ging und solche Big-Bang-Migrationen ziemlich risikobehaftet sind, hatte ich die Idee, die Umstellung doch lieber pro Kunde vorzunehmen.
So ließe sie sich viel feingranularer steuern und wir könnten das Risiko deutlich senken.&lt;/p&gt;
&lt;p&gt;Die konzeptionelle Distanz zur bestehenden Theorie war da allerdings schon relativ groß und obwohl die Idee von mir, also aus dem Team selbst heraus kam, haben wir uns genau überlegt, ob wir das so einführen wollen und wie wir dieses Konzept der Migration pro User mit dem verheiraten, was schon da war.
Letztlich haben wir uns gemeinsam dafür entschieden, diese Konfiguration pro App komplett auszubauen, die Migration pro User zu implementieren und die nach der Migration zusammen mit dem alten Zahlungsdienstleister auch wieder auszubauen.
Letzteres mag überraschen, aber das hatte mit der neuen Theorie zu tun, die wir zum Thema Routing von Zahlungen an Zahlungsdienstleister gebildet hatten.&lt;/p&gt;
&lt;p&gt;Alte Theorie:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Das Routing ist eigentlich keine richtige Logik. Da reicht es, wenn man irgendwo eine Tabelle hat, und man kann das Routing praktisch beliebig zur Laufzeit ändern.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neue Theorie:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Das Routing ist eine Kernfunktion der Zahlungskomponente und wenn man das ändert, ist das ein nicht-triviales Vorhaben, das wahrscheinlich mit Custom-Logik und Edge-Cases verbunden ist, die man am besten mit Code abbilden kann.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hätten wir uns nicht die Zeit genommen, die Theorie anzupassen, sondern die Migration pro User einfach zusätzlich reingefrickelt … das will ich mir gar nicht vorstellen, was dabei rausgekommen wäre.
Bestimmt nichts, was sich leicht weiterentwickeln ließe.&lt;/p&gt;
&lt;h2 id=&quot;umgang-mit-bestehenden-theorien&quot; tabindex=&quot;-1&quot;&gt;Umgang mit bestehenden Theorien&lt;/h2&gt;
&lt;p&gt;Betrachten wir das Ganze doch mal aus einer anderen Perspektive, nämlich aus der Sicht desjenigen, der mit einer Theorie von jemand anderem umgehen will oder muss.
Das kann zum Beispiel ein Stück Software sein, die jemand anders entwickelt hat und an der es jetzt etwas zu ändern gibt.
Und nehmen wir auch mal an, dass die konzeptionelle Distanz zu dem, was ich sonst so gewohnt bin, mittel bis hoch ist.
Dann werde ich anfangs Schwierigkeiten haben, die Theorie dahinter zu verstehen.
Ich kann dann die Änderung an der Software nicht so durchführen, dass ich mir sicher sein kann, dass das Ergebnis gut ist.
Die Theorie ist zu weit weg. In anderen Worten, mein Not Invented Here-Sensor löst aus.
Das heißt aber nicht, dass ich in die Not Invented Here-Abwehrhaltung verfallen sollte.
Denn die Konsequenz aus der Theory-Building-These ist ja:
Wenn ich keine halbgare Frickellösung haben will, dann muss ich mir zuerst die Theorie des Programms beschaffen.
Die muss in meinen Kopf rein.
Wie das geht, hängt von der Situation ab.&lt;/p&gt;
&lt;h3 id=&quot;onboarding-in-ein-aktives-entwicklungsteam&quot; tabindex=&quot;-1&quot;&gt;Onboarding in ein aktives Entwicklungsteam&lt;/h3&gt;
&lt;p&gt;Eine Möglichkeit ist: Ich komme in ein eingespieltes Entwicklungsteam.
Dann kann ich davon ausgehen, dass die Leute eine Theorie in ihren Köpfen haben, die ich aber noch nicht kenne.
Ich sehe erst mal nur den Source-Code.
Dann heißt es als erstes: Die Abwehrhaltung abschalten!
Das Unproduktivste, was ich machen kann, ist, bei jeder Gelegenheit zu sagen: „Das ist aber komisch. Wie kann man nur auf so eine Idee kommen? Ich hätte das ganz anders gemacht.“ Und so weiter.
Das hindert mich nur daran, meine Hauptaufgabe zu erledigen, die da wäre, möglichst schnell die Theorie des Programms zu verinnerlichen.
Dabei hilft:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mit dem Team reden.&lt;/li&gt;
&lt;li&gt;Doku lesen.&lt;/li&gt;
&lt;li&gt;Mit dem Team reden.&lt;/li&gt;
&lt;li&gt;Code lesen.&lt;/li&gt;
&lt;li&gt;Mit dem Team reden.&lt;/li&gt;
&lt;li&gt;Auch mit dem Coden anfangen, wenn möglich im Pair-Programming, aber zuerst weniger, um den Code anzupassen, sondern eher mit dem Ziel, zu lernen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ach ja, und mit dem Team reden.&lt;/p&gt;
&lt;p&gt;Ich hatte das erst vor kurzem bei einem Service, der mit historisierten Daten hantiert.
Da können sich Attribute von manchen Entitäten über die Zeit hinweg ändern und sie werden mit „von“ und „bis“ gespeichert.
Andere nicht. Und auch Beziehungen zwischen Entitäten sind teilweise historisiert, teilweise nicht.
Ich kann im Code sehen, &lt;em&gt;dass&lt;/em&gt; das passiert, und es steht auch ein bisschen was darüber im internen Wiki, aber richtig verstanden, warum das so ist, wie es ist, habe ich erst im Gespräch mit dem Team.
Ich hätte auch gleich sagen können: „Das ist doch alles viel zu kompliziert, ich hätte das ganz anders gebaut.“ Not Invented Here. Aber das hätte mir nicht dabei geholfen, das Thema zu verstehen.&lt;/p&gt;
&lt;p&gt;Also wenn ich irgendwo neu reinkomme: Die ablehnende Not Invented Here-Reaktion abschalten und möglichst schnell die Theorie aufsaugen.&lt;/p&gt;
&lt;h3 id=&quot;umgang-mit-legacy-software&quot; tabindex=&quot;-1&quot;&gt;Umgang mit Legacy-Software&lt;/h3&gt;
&lt;p&gt;Eine andere Situation wäre: Ich übernehme Verantwortung für ein bestehendes System, bei dem niemand mehr verfügbar ist, dem ich Fragen stellen kann.
Das kann unterschiedliche Gründe haben.
Vielleicht ist es ein altes System, an dem lange nichts mehr zu tun war, und die Leute mit Ahnung sind inzwischen weitergezogen.
Und jetzt ist wieder etwas zu tun und ich habe das große Los gezogen.
Aber vielleicht ist auch ein Team da, aber es sind nicht mehr die ursprünglichen Entwickler sondern welche, die später dazugekommen sind und die Theorie selbst nicht verinnerlicht haben.
Ich habe das schon erlebt – vielleicht ihr auch – dass ich in ein Team gekommen bin, das das eigene System nicht kennt – oder nur Teile davon.
Dann wird es schwierig, dann muss man die Theorie rekonstruieren. Das ist Arbeit.&lt;/p&gt;
&lt;p&gt;Und auch bei dieser Arbeit kann wieder mein Not Invented Here-Sensor bzw. die Idee der konzeptionellen Distanz hilfreich sein.
Die sagt mir nämlich, womit ich mich noch beschäftigen muss.
Ich habe ja schon, wenn ich anfange, eine gewisse Vorstellung davon, wie die Theorie aussehen könnte, und wenn es nur meine Intuition ist, die auf meiner Erfahrung mit anderen Programmen beruht.
Wenn ich mir dann das Material anschaue, das da ist – Code, Bildchen, Doku, whatever – dann gibt es Dinge, da denke ich mir: „Ja, OK, klar.“
Und es gibt Dinge, da löst mein Entfernungssensor aus – also der für die konzeptionelle Distanz.
Da denke ich mir dann: „Hui, das passt gar nicht zu dem, was ich mir bisher zusammengereimt habe!“
Manche Entwickler verfallen dann in die Not Invented Here-Abwehrhaltung und grummeln über den Legacy-Scheiß, mit dem sie sich rumschlagen müssen.
Ich nehme das als Signal, genauer hinzuschauen, denn das ist eine Stelle, an der meine Theorie offensichtlich noch falsch oder zumindest unvollständig ist.
Also grabe ich mich da rein und versuche, genau die Stelle zu verstehen und die ursprüngliche Theorie noch ein Stück weiter zu rekonstruieren.&lt;/p&gt;
&lt;p&gt;Das geht bis zu einem gewissen Punkt. Aber meistens nicht vollständig.
Wenn man an dem Punkt angelangt ist, dann kann – und sollte – man anfangen, die Theorie zugunsten der aktiven Entwickler umzubauen, um aus diesem „ich habe keine Ahnung, was ich da tue“-Modus rauszukommen.
Dann hat man wieder eine lebende Theorie, die tatsächlich in den Köpfen der Entwickler drinsteckt, und kann damit weiterarbeiten.&lt;/p&gt;
&lt;p&gt;Ein Beispiel dafür ist ein Entwickler-Team, das eine Java-Library geerbt hat, die aus fünf Schichten aufgebaut ist.
Niemand kannte den Sinn dieser Schichten und da gab es genau diese Not Invented Here-Reaktion von einigen Entwicklern und sie wollten das Ding am liebsten gar nicht anfassen.
Als ich dazugekommen bin, haben wir uns angeschaut, welche Probleme wir tatsächlich haben und wo uns Schichten helfen und wo eher nicht, und wir haben jetzt angefangen, den Code gemäß unserer neuen Theorie zu ändern.&lt;/p&gt;
&lt;h2 id=&quot;reprise&quot; tabindex=&quot;-1&quot;&gt;Reprise&lt;/h2&gt;
&lt;p&gt;Ihr seht also, es gibt einen Zusammenhang zwischen der Idee des Programming as Theory Building von Peter Naur und Not Invented Here.
Nochmal kurz zusammengefasst, sagt Programming as Theory Building, dass die Essenz des Programmierens nicht das Schreiben von Code ist, sondern das Aufbauen einer Theorie.
Diese Theorie lebt in den Köpfen der Entwickler und ist das eigentliche Programm.
Der Code gehört auch dazu, aber der sorgt nur dafür, dass das Programm auf einer Maschine ausführbar wird.
Änderungen am Code lassen sich im Einklang mit der Theorie viel leichter durchführen und führen zu besseren Ergebnissen als Änderungen, die ohne Rücksicht auf die Theorie vorgenommen werden, weil das Programm eben hauptsächlich aus der Theorie besteht.&lt;/p&gt;
&lt;p&gt;Not Invented Here ist eine Reaktion auf Ideen, die von weit weg kommen, wobei die Entfernung in unterschiedlichen Dimensionen liegen kann.
Oft ist diese Reaktion ablehnend und kontraproduktiv, aber das muss nicht so sein.&lt;/p&gt;
&lt;p&gt;Eine für die Entwicklung besonders wichtige Dimension ist die konzeptionelle Distanz.
Wie weit liegen zwei Konzepte oder Ideen oder Theorien auseinander?
Laut der Theory-Building-These sind Programmänderungen mit einer hohen konzeptionellen Distanz zur Theorie genau die problematischen.
Und deswegen kann die Not Invented Here-Reaktion nützlich sein, weil sie mich auf so etwas hinweist.&lt;/p&gt;
&lt;p&gt;Wenn ich gelernt habe, die automatische Abwehrhaltung loszuwerden, die oft damit verbunden ist, und sie stattdessen als Signal zu betrachten, dann bin ich frei, mir zu überlegen, ob und wie ich die konzeptionelle Distanz verringern will.
Vielleicht will ich das gar nicht und sage tatsächlich: „Nein, das mach ich nicht!“
Aber wenn ich es machen will, gibt es im Wesentlichen drei Möglichkeiten: Ich kann die eine Seite anpassen oder die andere oder ich tue beides und lande irgendwo in der Mitte.&lt;/p&gt;
&lt;p&gt;Wir haben uns ein paar Situationen dazu angeschaut:
Beim Onboarding eines neuen Entwicklers sind die beiden Konzepte die Theorie des Programms und die vorgefertigten Ideen und Vorstellungen im Kopf des Entwicklers. Da möchte ich in der Regel, dass der Entwickler zuerst mal die Theorie des Programms versteht und &lt;em&gt;später&lt;/em&gt; seine eigenen Erfahrungen einfließen lässt, um die Theorie zu verbessern.&lt;/p&gt;
&lt;p&gt;Bei Änderungsvorschlägen kann zweierlei passieren. Entweder man schafft es, die Anforderungen im Rahmen der Theorie des Programms umzusetzen – das ist der beste Fall –, oder man muss tatsächlich die Theorie des Programms anpassen, um die Lösungsidee zu integrieren. Aber dann auch wirklich die Theorie, nicht nur punktuell den Code!&lt;/p&gt;
&lt;p&gt;Und wenn man ein Legacy-System übernimmt, bei dem die Theorie nicht mehr greifbar ist, wird man oft die Situation haben, dass man sie nicht ganz rekonstruieren kann. Man sollte es aber versuchen und dann die verbleibenden Teile an die eigenen Vorstellungen anpassen und eine neue Theorie bilden, damit man vernünftig damit arbeiten kann.&lt;/p&gt;
&lt;p&gt;Wenn ihr also das nächste Mal auf eine Not Invented Here-Reaktion stoßt, bei euch selbst oder bei anderen, dann überlegt euch mal, was dahintersteckt und ob das nicht ein Signal sein könnte, euch tiefer mit dem Thema zu beschäftigen!&lt;/p&gt;
&lt;p&gt;Danke schön und Happy Programming! Oder Theory-Building!&lt;/p&gt;
</description>
      <pubDate>Sun, 28 Dec 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/patbatnihs-de/</guid>
    </item>
    
    
    <item>
      <title>Unit testing vs. integration testing</title>
      <link>https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/</link>
      <description>&lt;p&gt;Unit tests and integration tests are often compared – and usually presented as opposites.
In this post, I will examine these two types of tests from a different point of
view and show how this perspective can help us write better tests.&lt;/p&gt;
&lt;h2 id=&quot;a-plea-for-a-differentiated-view&quot; tabindex=&quot;-1&quot;&gt;A plea for a differentiated view&lt;/h2&gt;
&lt;p&gt;Unit tests are usually characterized as “small”, “fast”, and “reliable”,
while integration tests are typically seen as “large”, “slow”, and “unreliable”.
If you compare the test for one method of one class that doesn&#39;t do much
with the test of the complete system including an external database, this is probably true.
In the former case, most developers would probably agree that it is a unit test,
and in the latter case, that it is an integration test.
This is certainly not wrong. However, it&#39;s also not the whole truth.
Let&#39;s take a look at a test for a Java class that solves the universally beloved
&lt;a href=&quot;https://en.wikipedia.org/wiki/Fizz_buzz&quot;&gt;FizzBuzz&lt;/a&gt; problem.&lt;/p&gt;
&lt;p&gt;(All the code from this post is available in the GitHub repo
&lt;a href=&quot;https://github.com/sebhans/fizzbuzz-testing-example&quot;&gt;fizzbuzz-testing-example&lt;/a&gt;
to play around with.)&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IntStream&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rangeClosed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapToObj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;FizzBuzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Buzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fizzBuzz &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzzTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;oneIs1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt; fizzBuzz &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// + several similar test cases&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Probably everyone would agree that this is a unit test.
It instantiates the class, calls a method that doesn&#39;t interact with other components,
and checks the result, nothing more … but is that really true?
What about &lt;code&gt;IntStream&lt;/code&gt;? What about &lt;code&gt;String&lt;/code&gt; and &lt;code&gt;Integer&lt;/code&gt;? What about the Java compiler?
And the Java runtime? All of this is necessary to actually execute the method
&lt;code&gt;FizzBuzz.go(int)&lt;/code&gt;. If we leave out any of this, the program is no longer executable.
A bug in any of these components could cause the test to fail.
The test only runs successfully if all of these components work without error.&lt;/p&gt;
&lt;p&gt;Let that sink in for a moment.&lt;/p&gt;
&lt;p&gt;“But those things aren&#39;t mine! If my test depends on 3rd-party stuff,
does that mean &lt;em&gt;this&lt;/em&gt; is an &lt;em&gt;integration test&lt;/em&gt;!?”&lt;/p&gt;
&lt;p&gt;Yes, that&#39;s exactly what it means.
More precisely: &lt;em&gt;every&lt;/em&gt; test is an integration test, because every test integrates something;
the only question is what. Thus, every unit test is also an integration test.
It integrates everything that is part of the unit and leaves out everything that is not part of the unit.&lt;/p&gt;
&lt;p&gt;In the example above, the test integrates the external dependencies I already mentioned,
but also the methods &lt;code&gt;FizzBuzz.go(int)&lt;/code&gt; and &lt;code&gt;FizzBuzz.fizzBuzz(int)&lt;/code&gt;.
And the methods of the standard library, too.&lt;/p&gt;
&lt;p&gt;So the interesting question is …&lt;/p&gt;
&lt;h2 id=&quot;what-is-a-unit%3F&quot; tabindex=&quot;-1&quot;&gt;What is a unit?&lt;/h2&gt;
&lt;p&gt;For our purposes, a &lt;em&gt;unit&lt;/em&gt; is a self-contained functional element
with defined dependencies and inputs and outputs.
Units can work together as part of a larger unit.&lt;/p&gt;
&lt;p&gt;We&#39;ll expand the FizzBuzz example a bit to highlight some possibilities:&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; number&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;number&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzzOutput&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;BUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Buzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;FIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;FizzBuzz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; rep&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;FizzBuzzOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; rep&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rep &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rep&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; rep&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Selector&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Does&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; divisor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;divisor&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Does&lt;/span&gt; does&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Selector&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Streamer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AscendingStreamer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Selector&lt;/span&gt; selector&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Streamer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IntStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rangeClosed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapToObj&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Streamer&lt;/span&gt; streamer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; streamer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toUpperCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fizzBuzz &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AscendingStreamer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Does&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new solution divides the code into several classes with distinct responsibilities.
The class &lt;code&gt;FizzBuzz&lt;/code&gt; now uses a &lt;code&gt;Streamer&lt;/code&gt; that determines what number to start with
and in what order to process the numbers.
&lt;code&gt;Streamer&lt;/code&gt; in turn uses a &lt;code&gt;Selector&lt;/code&gt; to determine which number should actually be output.
&lt;code&gt;Selector&lt;/code&gt; in turn builds on the helper class &lt;code&gt;Does&lt;/code&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;,
which handles the divisibility check.
The outputs are returned in typed form.
&lt;code&gt;FizzBuzz&lt;/code&gt; itself now only needs to convert the &lt;code&gt;Stream&lt;/code&gt; of &lt;code&gt;Output&lt;/code&gt;s into a list of &lt;code&gt;String&lt;/code&gt;s to keep the interface unchanged.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;However, we&#39;ve built in one extension:
we want to SHOUT “FizzBuzz” in capital letters and
introduced an additional mapping in the &lt;code&gt;FizzBuzz&lt;/code&gt; class for this purpose.&lt;/p&gt;
&lt;p&gt;To plug the individual parts together, we use constructor-based dependency injection.
The use of interfaces allows us to easily swap out implementations.
It should be noted, however, that this separation of concerns
is an internal implementation detail of the &lt;code&gt;FizzBuzz&lt;/code&gt; class
and has no impact on the interface it exposes to callers or its functionality.
Except for the initialization code, the &lt;code&gt;main&lt;/code&gt; method looks exactly the same as before.&lt;/p&gt;
&lt;p&gt;For the purpose of testing this implementation, what are the relevant units here?
The reflexive answer would probably be, “Each class is a unit.”
For unit testing, this means we test each class individually and leave its dependencies out of the test.
We use &lt;a href=&quot;https://site.mockito.org/&quot;&gt;Mockito&lt;/a&gt; for this and also
&lt;a href=&quot;https://assertj.github.io/doc/&quot;&gt;AssertJ&lt;/a&gt;, to simplify list and stream handling
in the tests.
Here are a few example tests:&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExtendWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MockitoExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelectorTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Mock&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Does&lt;/span&gt; does&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelector&lt;/span&gt; selector&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@BeforeEach&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        selector &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;modNothingYieldsNumber&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mod3AndMod5YieldsFizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This test checks the selection logic.
It does so more directly than the test in the previous version
because it no longer has to extract the result from a list.
The class &lt;code&gt;Does&lt;/code&gt; is mocked away.
There are separate tests for that one.&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExtendWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MockitoExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AscendingStreamerTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Mock&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Selector&lt;/span&gt; selector&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Streamer&lt;/span&gt; streamer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@BeforeEach&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        streamer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AscendingStreamer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;returnsSelectedOutputsInOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streamer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsExactly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This test only checks the behavior of our &lt;code&gt;Streamer&lt;/code&gt; implementation.
The &lt;code&gt;Selector&lt;/code&gt; is mocked away.&lt;/p&gt;
&lt;p&gt;And last but not least, the test of &lt;code&gt;FizzBuzz&lt;/code&gt; checks the behavior of only this class,
with the output of the &lt;code&gt;Streamer&lt;/code&gt; specified by Mockito:&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExtendWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MockitoExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzzTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Mock&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Streamer&lt;/span&gt; streamer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt; fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@BeforeEach&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        fizzBuzz &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;aggregates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsExactly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shoutsFIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsExactly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;FIZZBUZZ&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tests all run successfully, and when we execute &lt;code&gt;Main.main()&lt;/code&gt;,
we see that everything also works together.&lt;/p&gt;
&lt;p&gt;The approach “unit=class” (or sometimes also “unit=method”) is widespread among Java developers.
I&#39;ve seen this kind of test (at the class level with mocked dependencies) many times in different projects.
And dependency chains are quite common.
An example in a microservice based on the ports-and-adapters architecture could be:
Web Controller
→ Driving Port
→ Application Service
→ other Application Service
→ Domain Object
→ another Domain Object
→ Driven Port
→ JDBC Adapter
(arrows are runtime dependencies).&lt;/p&gt;
&lt;p&gt;Of course, FizzBuzz in its entirety is also a unit.
In the first implementation, all the code is in one class;
in the second it is distributed across multiple classes, but it still forms a functional unit.
In this example, the unit as a whole is quite small and can well be tested in its entirety,
but in larger code bases such whole-system tests can be quite slow
(looking at you, &lt;code&gt;@SpringBootTest&lt;/code&gt;).
This is the point at which most developers begin to bandy about the word “integration test”,
and negative vibes can be felt.&lt;/p&gt;
&lt;p&gt;So, mocking it is.
But what happens to tests in this style if we begin to make changes?&lt;/p&gt;
&lt;h2 id=&quot;a-small-change&quot; tabindex=&quot;-1&quot;&gt;A small change&lt;/h2&gt;
&lt;p&gt;Let&#39;s assume that the customer for our industry-leading FizzBuzz solution wants to
pave the way for a bright future in which FizzBuzz can not only
“fizz” and “buzz”, but also “zoom” and “boom” and much more –
and not just for the numbers 3 and 5, but for any other number.
To achieve this, we will probably need to combine multiple numbers, or rather
their mapped &lt;code&gt;Output&lt;/code&gt;s.
This is where our enum-based approach will hit its limits.
We would have to add not only &lt;code&gt;ZOOM&lt;/code&gt; and &lt;code&gt;BOOM&lt;/code&gt; as values, but also all possible combinations.
To avoid combinatorial explosion, we introduce a &lt;code&gt;CombinedOutput&lt;/code&gt; instead:&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CombinedOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; outputs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CombinedOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; outputs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;outputs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; outputs
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;joining&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And use this in our &lt;code&gt;SimpleSelector&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-arrow deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt;         if (does.divide(3, n) &amp;amp;&amp;amp; does.divide(5, n)) return FIZZBUZZ;
&lt;/span&gt;&lt;span class=&quot;token coord&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token inserted-arrow inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;&gt;&lt;/span&gt;         if (does.divide(3, n) &amp;amp;&amp;amp; does.divide(5, n)) return new CombinedOutput(FIZZ, BUZZ);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in its test:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-arrow deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt;         assertThat(selector.select(1)).isEqualTo(FIZZBUZZ);
&lt;/span&gt;&lt;span class=&quot;token coord&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token inserted-arrow inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;&gt;&lt;/span&gt;         assertThat(selector.select(1)).isEqualTo(new CombinedOutput(FIZZ, BUZZ));
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We run the entire test suite and see that all tests pass.
Great! That would be that.
We&#39;re now well equipped for the introduction of &lt;code&gt;ZOOM&lt;/code&gt; at 7 or whatever else may come our way.
Quickly push to production and the sprint goal is achieved.
Or the deliverable for this release can be signed off.
Or something like that. In any case, we&#39;re done.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;PAUSE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before reading on, take a moment to think about what just broke!
Because, yes, something did break.
But I didn&#39;t lie.
All tests ran without errors.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Found the mistake? No? Well, then, let&#39;s take a look. What happens when we run &lt;code&gt;Main.main()&lt;/code&gt;?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
2
Fizz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking good so far. It&#39;s fizzing.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4
Buzz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buzzing, too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&#39;s the “FizzBuzz”.
But – wait a minute! Shouldn&#39;t it be “FIZZBUZZ”?
You remember the SHOUTING requirement?
And I know for sure there&#39;s a dedicated test for it.
Where was it again?
Ah, in &lt;code&gt;FizzBuzzTest&lt;/code&gt;. Let&#39;s take a closer look at it now.&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shoutsFIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsExactly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;FIZZBUZZ&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the streamer returns &lt;code&gt;FIZZBUZZ&lt;/code&gt;, “FIZZBUZZ” should come out.
So what&#39;s the problem?
The problem is that in production the streamer no longer returns &lt;code&gt;FIZZBUZZ&lt;/code&gt; at all,
because we changed the selector to return a &lt;code&gt;CombinedOutput&lt;/code&gt;.
&lt;em&gt;If&lt;/em&gt; the selector were still outputting &lt;code&gt;FIZZBUZZ&lt;/code&gt;, the class &lt;code&gt;FizzBuzz&lt;/code&gt; would still SHOUT.
But it no longer does.&lt;/p&gt;
&lt;p&gt;Because we focused each test so narrowly on its respective unit –
or what we identified as a unit, i.e., the class –
none of them was able to uncover this mismatch.
A comprehensive integration test would have helped,
but those are expensive and slow and are therefore only executed relatively late in the development cycle –
or omitted entirely if the test coverage is already good. And it is good
(100%).&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;a-real-example&quot; tabindex=&quot;-1&quot;&gt;A real example&lt;/h2&gt;
&lt;p&gt;Of course, this is a contrived example.
But the same kinds of errors also occur in real development projects.
A case I&#39;ve personally encountered concerned a duplicate check in a REST interface.
There was a test for the database adapter that ensured a &lt;code&gt;DuplicatePaymentException&lt;/code&gt;
was thrown when a unique constraint was violated during insertion into the database.
And there was a test at the API layer that ensured
that in case of a &lt;code&gt;DuplicatePaymentTransactionException&lt;/code&gt; an appropriate response
(HTTP status code 409) was sent to the client.
This project relied heavily on mocking to isolate the “units” in tests.
Each of the tests looked plausible when viewed individually, and they were all successful.
But clients in production kept getting HTTP status code 500
(which indicates an internal server error) instead of the expected
duplicate error message, because the &lt;code&gt;DuplicatePaymentException&lt;/code&gt; was not caught
and handled.
I only found the cause when I looked at both tests side by side.
That&#39;s when I noticed that different (but similarly named) exception types were being used.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-of-too-narrow-unit-tests&quot; tabindex=&quot;-1&quot;&gt;The problem of too narrow unit tests&lt;/h2&gt;
&lt;p&gt;The fundamental problem here is the same as in the FizzBuzz example:
the test of one unit makes assumptions about the behavior of another unit
and hardcodes them into the test instead of verifying them as well.
&lt;em&gt;If&lt;/em&gt; the streamer returns &lt;code&gt;FIZZBUZZ&lt;/code&gt;, then &lt;code&gt;FizzBuzz&lt;/code&gt; does the right thing.
But if it doesn&#39;t, the test is meaningless.
&lt;em&gt;If&lt;/em&gt; the adapter throws a &lt;code&gt;DuplicatePaymentTransactionException&lt;/code&gt;,
the API delivers the correct response.
But if it throws a &lt;code&gt;DuplicatePaymentException&lt;/code&gt; instead, we lose.&lt;/p&gt;
&lt;p&gt;In both cases, we tried to make things independent of each other that are not independent of each other in reality.
But if the behavior of one of our unit&#39;s dependencies changes,
it would certainly be good to have a test that fails if the change invalidates our assumptions.&lt;/p&gt;
&lt;p&gt;So back to giant integration tests after all?
Not necessarily. Instead, I propose overlapping tests.&lt;/p&gt;
&lt;h2 id=&quot;overlapping-tests&quot; tabindex=&quot;-1&quot;&gt;Overlapping tests&lt;/h2&gt;
&lt;p&gt;Such tests are unit tests, too. We just choose the unit a bit differently.
So far, we have used exactly one class as a unit, with boundaries upwards (to the caller)
and downwards (to the dependencies).
If we draw a picture of the scopes of these tests, they are completely separated from each other.
In the following diagram, each test frames the classes it covers, i.e., exactly one each.
The arrows between the classes represent runtime dependencies.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/test-scopes-separate.svg&quot; alt=&quot;The diagram shows the classes FizzBuzz, AscendingStreamer, SimpleSelector and Does as well as the corresponding test classes. FizzBuzzTest frames FizzBuzz. AscendingStreamerTest frames AscendingStreamer. SimpleSelectorTest frames SimpleSelector and DoesTest frames Does. The dependency arrow from FizzBuzz points to AscendingStreamer, the one from AscendingStreamer to SimpleSelector and the one from SimpleSelector to Does.&quot; /&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;As we have seen, the problem with this strict separation is that the interactions remains untested.
Overlapping tests solve this by expanding the scope of the tests
(i.e., enlarging the unit under consideration),
so that the interactions between the classes are included in the tests.
Figuratively speaking, each arrow is contained in at least one test scope.
For our project “Over-engineered FizzBuzz”, it could look like this:&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/test-scopes-overlapping.svg&quot; alt=&quot;The diagram shows the classes FizzBuzz, AscendingStreamer, SimpleSelector and Does as well as the corresponding test classes. FizzBuzzTest frames FizzBuzz, AscendingStreamer and SimpleSelector. AscendingStreamerTest frames AscendingStreamer, SimpleSelector and Does. SimpleSelectorTest frames SimpleSelector and Does, and DoesTest frames only Does. The dependency arrows from FizzBuzz to AscendingStreamer, from AscendingStreamer to SimpleSelector, and from SimpleSelector to Does now lie within the test frames.&quot; /&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;And here is the code:&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelectorTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelector&lt;/span&gt; selector &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Does&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;oneIs1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// …&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fifteenIsFizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CombinedOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;Does&lt;/code&gt; is no longer mocked away, which even makes the test code shorter.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AscendingStreamerTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;returnsSelectedOutputsInOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; streamer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AscendingStreamer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Does&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streamer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsExactly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FIZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FIZZBUZZ&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the selector is no longer mocked
(and neither is &lt;code&gt;Does&lt;/code&gt; – we could mock it, but we wouldn&#39;t gain any advantage by doing so).
The test must cover all possible outputs that are important to us, which is why it goes up to 15.
That&#39;s where &lt;code&gt;FIZZBUZZ&lt;/code&gt; appears for the first time.
This ensures that the streamer works for all &lt;code&gt;Output&lt;/code&gt;s that &lt;code&gt;SimpleSelector&lt;/code&gt;
returns.
In the “mocking” test implementation, this was pointless,
since the test itself specified what the streamer returned.
It didn&#39;t contain the arrow, so to speak. This test does.&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExtendWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MockitoExtension&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzzTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Mock&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Does&lt;/span&gt; does&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt; fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@BeforeEach&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        fizzBuzz &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FizzBuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AscendingStreamer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;aggregates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsExactly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Fizz&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shoutsFIZZBUZZ&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;does&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fizzBuzz&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;containsExactly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;FIZZBUZZ&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this test, &lt;code&gt;Does&lt;/code&gt; is mocked,
so the test doesn&#39;t reach all the way to the end of the dependency chain
(otherwise it would be a complete integration test).
However, it must reach far enough to cover the &lt;em&gt;interesting&lt;/em&gt; behavioral differences
in the dependencies.
In our case, this means the selector must be included, since it determines which &lt;code&gt;Output&lt;/code&gt;s can arrive in &lt;code&gt;FizzBuzz&lt;/code&gt;.
This way, we get a test that fails when we change the selector
result from &lt;code&gt;FizzBuzzOutput.FIZZBUZZ&lt;/code&gt; to &lt;code&gt;CombinedOutput&lt;/code&gt;
and thereby break our SHOUTING feature.&lt;/p&gt;
&lt;p&gt;Exactly how to draw the scope boundaries for the tests to find a good middle ground between
sharply delineated (too narrow) tests and all-encompassing integration tests is a matter of experience.
However, there are a few useful heuristics.&lt;/p&gt;
&lt;p&gt;A dependency with a stable interface can be mocked without giving up too much safety.
The interface of &lt;code&gt;Does&lt;/code&gt; is very stable since it is based on mathematical rules,
and can therefore be mocked without running the risk of being surprised by changes.
In this concrete example, this isn&#39;t actually all that useful,
since the implementation is fast enough and we don&#39;t really gain anything through mocking.
But if we imagine a web interface that&#39;s slow and a bit unreliable,
but has a stable interface contract, it makes much more sense.&lt;/p&gt;
&lt;p&gt;On the other hand, mocking a dependency with interesting behavior should not be
taken lightly.
In the case of FizzBuzz, the &lt;code&gt;Selector&lt;/code&gt; implementation contains the most
interesting behavior (selecting what to output).
As you can see in the picture above, we could mock it in &lt;code&gt;FizzBuzzTest&lt;/code&gt; and
still have all arrows covered by at least one test.
We do not, however, because its behavior arguably has a
larger influence on the operation of &lt;code&gt;FizzBuzz&lt;/code&gt; than, e.g.,
that of the &lt;code&gt;Streamer&lt;/code&gt; implementation.
By mocking the &lt;code&gt;Selector&lt;/code&gt; implementation, we would run the risk of missing
interesting interactions.
Replacing &lt;code&gt;FizzBuzzOutput.FIZZBUZZ&lt;/code&gt; with &lt;code&gt;CombinedOutput&lt;/code&gt;
in the &lt;code&gt;Selector&lt;/code&gt; would still cause &lt;code&gt;AscendingStreamerTest&lt;/code&gt; to fail,
so we &lt;em&gt;would&lt;/em&gt; notice that this change has some knock-on effects.
But we would not directly see the most interesting one.&lt;/p&gt;
&lt;h2 id=&quot;a-larger-example&quot; tabindex=&quot;-1&quot;&gt;A larger example&lt;/h2&gt;
&lt;p&gt;The principle of overlapping tests can be applied not only to unit tests within a single piece of software,
but also to integration tests between multiple services.
An example of its successful application was an integration of multiple services
with payment service providers via a dedicated payment component.&lt;/p&gt;
&lt;p&gt;Each service had tests for itself, of course, and the payment component also had tests for itself.
Large integration tests,
where a service was integrated with the payment component &lt;em&gt;and&lt;/em&gt; the payment service providers,
were difficult because the connection to the payment service providers&#39; test environments
wasn&#39;t particularly stable and therefore such tests were often disrupted by temporary problems.
That&#39;s why we, as developers of the payment component,
agreed with the service teams that they would use fake payment methods for the majority of their tests.
These fake payment methods were not hooked up to the payment service providers.
This way, service tests could proceed without disruption.
Since the test scopes overlapped, as shown in the following diagram,
it was still ensured that the system as a whole worked as intended.&lt;/p&gt;
&lt;p&gt;In this diagram,
tests shown in orange were the responsibility of the payment component team,
and tests shown in blue were the responsibility of the service teams.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/payment-tests.svg&quot; alt=&quot;The diagram shows the overlapping test scopes of the payment component. Each individual module of the payment component has its own unit tests. Internal end-to-end tests cover the interaction of the internal building blocks with each other and with the database. Integration tests performed by the payment component team cover the entire payment component and the connections to the payment service providers, which unfortunately only work semi-reliably in the test environment. Connected services also have tests that are performed by the respective service teams. In these, however, the payment service providers and their adapter implementations within the payment component are disconnected and replaced by a fake payment method implementation. The diagram shows that even without an all-encompassing integration test, each dependency arrow between components is covered by at least one test.&quot; /&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;In addition to the tests shown in the diagram,
there were further overlapping tests within the payment component.
And each building block in the diagram also consisted of multiple units,
which were tested partly with sharp boundaries and partly overlapping.
Overall, the payment component achieved very good test coverage this way –
much better than could be achieved with completely isolated unit tests,
even supplemented with “large” integration tests.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Unit tests and integration tests cannot be sharply separated from each other –
and are certainly not mutually exclusive concepts.
Units can be of different sizes and contain sub-units.
On the one hand, a unit is distinct and separated from other units
(at the same level of granularity),
and on the other hand, it integrates its own sub-units.
Considering only units at one level (e.g., only unit=class)
and testing them in isolation leads to test gaps and consequently to bugs.&lt;/p&gt;
&lt;p&gt;Overlapping tests close these test gaps and at the same time help to avoid elaborate,
slow and expensive &lt;em&gt;all-encompassing&lt;/em&gt; integration tests.&lt;/p&gt;
&lt;p&gt;Happy testing!&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;
The names &lt;code&gt;Does&lt;/code&gt; and &lt;code&gt;divide&lt;/code&gt; may seem a bit strange,
but they enable the client code to say &lt;code&gt;if (does.divide(3, n))&lt;/code&gt;.
This is a trade-off I&#39;m willing to make.
Anyway, it doesn&#39;t have a bearing on the point of this post. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;
I use &lt;code&gt;record&lt;/code&gt; instead of &lt;code&gt;class&lt;/code&gt; for &lt;code&gt;SimpleSelector&lt;/code&gt;, &lt;code&gt;AscendingStreamer&lt;/code&gt; and &lt;code&gt;FizzBuzz&lt;/code&gt;
for syntactic brevity only; not because I think these classes make good records. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;
Yes, the error would have been noticed if we had immediately deleted the superfluous enum &lt;code&gt;FIZZBUZZ&lt;/code&gt;.
Then &lt;code&gt;FizzBuzzTest&lt;/code&gt; would no longer have compiled.
But in large projects changes often aren&#39;t rolled out atomically across the entire code base,
and it is common for old classes or values to stay around for a while. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;
The test of &lt;code&gt;Does&lt;/code&gt; itself remains the same. It doesn&#39;t have any dependencies anyway. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;
In the diagram, the payment service providers lie only partially in the test scope,
since their test environments come with various limitations that make it impossible
to cover 100% of the functionality in integration tests.
Yes, this caused problems. Yes, we also tested in production. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Tue, 04 Nov 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/unit-testing-vs-integration-testing/</guid>
    </item>
    
    
    <item>
      <title>Excellence is a habit</title>
      <link>https://sebastian-hans.de/blog/excellence-is-a-habit/</link>
      <description>&lt;p&gt;In martial arts training, you spend a lot of time repeating certain movements,
sequences, and reactions over and over again. When learning a defense, one
partner often executes a predetermined attack, and the other one practices the
defensive technique. Usually (hopefully!) your training partner is not really trying to hurt
you.
Also, very few people would be able to go full-throttle
for a whole training session, even if they wanted to.
As a consequence, practice is mostly done at some level of reduced force.&lt;/p&gt;
&lt;p&gt;Now, the thing is, deflecting a blow dealt by someone who isn&#39;t really trying to
hurt you and who is also conserving strength is relatively easy.
You don&#39;t really need much in the way of technique for that.
Unfortunately, this means that it is very easy to become negligent.
You don&#39;t step as far aside as you should because you don&#39;t feel the need.
After a few repetitions, your partner starts to punch slightly to one side
because your blocking technique is going to push the arm over there anyway, right?
And your technique gets sloppy without you noticing because, well, it seems to
work, doesn&#39;t it?
You are standing at the wrong angle but it doesn&#39;t matter because your partner
is focusing on the (slightly off) punch and doesn&#39;t realize you are in reach of
their other arm now. And neither do you.
Let&#39;s call this way of training “sloppy mode”.&lt;/p&gt;
&lt;p&gt;This is all well and good as long as you keep training in this way.
But what happens when we add pressure, e.g., with a belt examination?
I have trained with a lot of youths who have confidently told me, “In the
examination, I&#39;ll try harder/step farther/block better!”&lt;/p&gt;
&lt;p&gt;Let me tell you what usually happens.
When the pressure is on, people do what they have always done, except sloppier
and with more force.
Will they hit &lt;em&gt;harder&lt;/em&gt;? Yes, they probably will.
Will they block with &lt;em&gt;more force&lt;/em&gt;? Yes, they probably will.
Will they find the &lt;em&gt;correct distance&lt;/em&gt; if they didn&#39;t before? No, they won&#39;t.
Will they block &lt;em&gt;more accurately&lt;/em&gt;? No way!
Will they &lt;em&gt;position&lt;/em&gt; themselves better? Forget it!
Will they &lt;em&gt;fumble&lt;/em&gt;, &lt;em&gt;forget steps&lt;/em&gt;, &lt;em&gt;miss their aim&lt;/em&gt;? Depends on how much they
have practiced.
I repeat: Under pressure, people do what they have always done, except sloppier
and with more force.&lt;/p&gt;
&lt;p&gt;This is because under pressure, there is no time to think, so you fall back to
habits.
Your mind goes blank, and your body takes over and performs the movements it
remembers.
You don&#39;t think, “Oh, wait! This time, I wanted to do it differently!”
Instead, you think, “Oh, shit!” Or, “What am I doing?” Or you don&#39;t think at all.
If you stop to think, you are already down.
When the pressure is on, you don&#39;t think. You rely on habit.&lt;/p&gt;
&lt;p&gt;The whole point of training is building habits that work well in the kind of
situation you are training for.
This means that you must strive to execute your defense flawlessly,
performing the correct movements while maintaining good form &lt;em&gt;each and every time&lt;/em&gt;.
Even if your partner&#39;s attacks are lackluster.
Even if you could – in the training situation – defend yourself by just waving
your arms in front of your face.
And even if you yourself are tired.
You may train at a lower energy level. Energy will increase under pressure.
But you must not train sloppily! If you build a habit of sloppiness, you will be
even more sloppy when it matters – when your are under pressure.&lt;/p&gt;
&lt;p&gt;If, however, you make a habit of moving correctly – even when it is not strictly
needed – you can rely on this habit to defend yourself when it matters.&lt;/p&gt;
&lt;p&gt;Or, in other words, you build excellence by putting in the effort when it
doesn&#39;t matter – because excellence is a habit.&lt;/p&gt;
&lt;p&gt;What does any of this have to do with software development?
Well, have you ever thought to yourself, “Do I really need a unit test here? Ah,
no, it&#39;s obviously correct.”
Or, “This method looks long. Maybe I should split it? … Naw, too much work, and
it&#39;s not too bad.”
Or, “Why is the linter complaining? Stupid distraction! I&#39;ll just silence the warning for now.”
Or maybe you didn&#39;t think too hard about corner cases because it got too
complicated and when are those going to occur anyway?&lt;/p&gt;
&lt;p&gt;Sometimes you just want to get something done and gloss over the details.
And it works. You get it done and move on.
Often, you can get away with skimping on code quality in the short term because
there are no immediate repercussions.
And implementing the happy path gets you quite far without thinking too hard.
And everyone is happy, right?
Because, when you get down to it, most code isn&#39;t that critical.
So what if it isn&#39;t as clean as it could be, not as robust as it could be, not
as [insert your favourite quality here] as it could be?&lt;/p&gt;
&lt;p&gt;To my mind, this is akin to practicing martial arts in sloppy mode.
Yes, it works … for now.
There are no immediately visible negative consequences.
And it is less work. It may even feel more efficient.
Writing one less “useless” unit test.
Not getting hung up on finding the perfect class name.
Copying and pasting instead of extracting common logic because it&#39;s faster in
the short term.
Coding the parts users want, not getting distracted by trivialities.
Getting things &lt;em&gt;done&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;But – and this is a &lt;em&gt;big&lt;/em&gt; BUT – what happens when it does matter?
When bugs creep in because of untested “obviously correct” code?
A real example: in one of my past projects, I reviewed a pull request by a
colleague and noted there was no test for a certain class he had changed.
He replied that the class was just there for uniformity with the rest of the
implementation and was only passing through calls to a delegate.
Such a class was not worth the effort of a unit test in his view.
The class did have one &lt;code&gt;if&lt;/code&gt; statement, however – and as it turned out later,
he had erroneously reversed the condition.
With a unit test, he would have noticed this himself – and earlier – saving us
time.&lt;/p&gt;
&lt;p&gt;What if many small “insignificant” slow-downs add up to a performance nightmare?
I once spent a lot of time speeding up a performance-sensitive application by
250%, by “just” eliminating unnecessary copying of data structures.&lt;/p&gt;
&lt;p&gt;What if the edge cases you didn&#39;t care enough about to handle correctly suddenly
start to occur en masse in production and lose you business?
When I was responsible for a payment service, we were seeing errors in the logs
that indicated there was a problem with customers who had not been active for a
year or longer. At first, we thought it was just an edge case that was badly handled,
but in reality, this edge case was occurring at a much higher rate than we would
have thought, causing payment problems for real customers.&lt;/p&gt;
&lt;p&gt;If you program in sloppy mode, your system may work as long as nothing untoward
happens. If you have few users and your software is uncritical, you can fix
issues as they come up with little ill effects.
But it all gets much worse when we add pressure.
Pressure on the system or pressure on you.&lt;/p&gt;
&lt;p&gt;Increasing load, increasing transaction volume, increasing numbers of users put pressure on
the system. Things are more likely to break when they are not built well.
Performance problems manifest when the load increases.
If you have a million customers, even an edge case that only occurs 0.1% of the
time will affect 1000 customers.
In software that is under active development, each over-complication, each
misnamed entity, each code duplication increases the chances of defects due to
misunderstandings or oversights, and also increases the effort required for even
minor changes.
This is when you need engineering excellence.
Or when you notice the lack of it.
And when you do, it is already too late.
You would have to have built in the quality before – while it wasn&#39;t yet needed.&lt;/p&gt;
&lt;p&gt;And then there is pressure on you: when an important deadline looms,
when you need to incorporate new features faster than your competitors,
when you need to scale up quickly,
when the excrement hits the ventilator in production and
you need a fix &lt;em&gt;now&lt;/em&gt;, this puts pressure on you.
And under pressure, you do what you always do, except sloppier and with more swearing.&lt;/p&gt;
&lt;p&gt;If you want high quality under these circumstances, you need to have built up
excellence … &lt;em&gt;before&lt;/em&gt;.
Because if you haven&#39;t developed the habit of programming with high quality while it
wasn&#39;t strictly necessary, why do you think you will be able to do so now?
You build excellence – the habit of excellence – by putting in the effort when it doesn&#39;t matter.
And you reap its benefits when it does.&lt;/p&gt;
&lt;p&gt;This should have been the end of my post, but between finishing and publishing
it, I went to my Aikido dojo, and my teacher said something that fits perfectly.
He was admonishing us to perform big movements and not leave out intermediate
steps, saying,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“If it&#39;s there, you can always leave it out later.
If nothing&#39;s there, you can&#39;t leave anything out.“&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Think about that.&lt;/p&gt;
&lt;p&gt;This post is part of my series on &lt;a href=&quot;https://sebastian-hans.de/blog/software-development-and-martial-arts/&quot;&gt;martial arts and software development&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/excellence-is-a-habit/</guid>
    </item>
    
    
    <item>
      <title>Don&#39;t argue with the compiler</title>
      <link>https://sebastian-hans.de/blog/dont-argue-with-the-compiler/</link>
      <description>&lt;p&gt;One of the lessons I learned from &lt;a href=&quot;https://sebastian-hans.de/blog/software-development-and-martial-arts/&quot;&gt;both martial arts and software development&lt;/a&gt;
is to find solutions within myself rather than wasting time blaming someone
else for my problems.&lt;/p&gt;
&lt;p&gt;When practicing a defense against a punch, if I&#39;m hit, the defense didn&#39;t work.
Simply put, that&#39;s all there is to it.
Beginners sometimes complain, “But I did everything right!”
They usually didn&#39;t; otherwise, the defense would have worked.
Sometimes, they start blaming their partner – or even the floor (“The edge of
the mat was sticking up!”).
However, this misses the point.
If I&#39;m hit, I&#39;m hit.
And that&#39;s my problem to solve.&lt;/p&gt;
&lt;p&gt;Sometimes, I practice with one partner and my take-down is working really
well, and then we switch partners, and suddenly I am hit every time.
The temptation to blame the new partner can be really great.
After all, it worked before, right?
Why did the new partner attack differently?
Shouldn&#39;t they do it like the first one?
I have learned to resist this line of thinking
and to focus on what &lt;em&gt;I&lt;/em&gt; need to change to cope with the way the new partner is
attacking.&lt;/p&gt;
&lt;p&gt;By now, I am an instructor myself, and there is a demonstration I like to give
when I detect this spirit creeping into the training.
I call out someone and tell them to punch me.
Then, while they are getting ready, I say, “Ah, no, we&#39;ll do it differently. Lie
down on the mat, please.”
Given the situation (I&#39;m the instructor; I&#39;m clearly trying to demonstrate
something; I&#39;m asking nicely), they always do.
Then I say something like, “If the attack comes like this, you don&#39;t need the
technique.”
Silence. And usually embarrassment on the part of the person lying on the ground.
“Don&#39;t get me wrong – cooperation is good! We need to cooperate when
we are training. And thank you for not hitting me! But if we want to practice a
defense against a punch to the chin, we need a &lt;em&gt;punch to the chin&lt;/em&gt;,
not a partner lying down, not a partner holding their hand in front of my face,
not a partner punching to the side.”&lt;/p&gt;
&lt;p&gt;This demonstration only works because in this situation the person doesn&#39;t really mean to attack me.
An attack is something I don&#39;t control.
If the other guy does exactly what I want, that&#39;s not an attack.
And if I can talk them into changing their punch to make it easier for me,
well, that&#39;s not an attack either.
I have to deal with the attack the way my opponent delivers it.
And if I&#39;m hit, it is I who needs to change something.&lt;/p&gt;
&lt;p&gt;In software development, there is a very similar situation.
If the compiler throws an error, my code doesn&#39;t work.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/dont-argue-with-the-compiler/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;
Beginners sometimes complain, “But I did everything right!”
They usually didn&#39;t; otherwise the code would have compiled.
Sometimes, they start blaming the compiler (“It must be right! Why does the
stupid compiler keep complaining!?”).
However, this misses the point.
If it doesn&#39;t compile, it doesn&#39;t compile.
And that&#39;s my problem to solve.&lt;/p&gt;
&lt;p&gt;Situations like this occur again and again.
I&#39;m sure everyone who has been programming for any length of time will recognize
it.
Why, just this week I had a problem compiling a Java program which used a third party
library. The compiler complained that it couldn&#39;t find the class from the
library I needed. For a second, I thought, “It must work! I did everything
right!” But only for a second. Then I looked at my own changes again,
and lo and behold! I had messed up the
dependency declaration.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/dont-argue-with-the-compiler/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; I fixed it, and it worked.&lt;/p&gt;
&lt;p&gt;Arguing with the compiler is a waste of time – as is arguing with my
opponent.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/dont-argue-with-the-compiler/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;
If I get an error, or if I get hit, I use this as feedback to fix my code or
my defense.&lt;/p&gt;
&lt;p&gt;Yes, compiler bugs do exist – but unless you are a compiler developer or using
some niche language with only a handful of users, they are rare.
In 30 years of software development, I&#39;ve had a production problem due to a
compiler bug exactly once, about 15 years ago. (It was a C program, and the
bug only appeared with compiler optimization turned on, i.e., in production
builds.)
In all other cases, the problem was in my own code so diverting my energy
to searching somewhere else would just slow me down. And even in the case
that the problem really &lt;em&gt;is&lt;/em&gt; elsewhere, rigorously validating my own code and
thinking enables me to better diagnose the issue, which helps, e.g., when
submitting bug reports.
So, even though &lt;em&gt;sometimes&lt;/em&gt;, &lt;em&gt;very rarely&lt;/em&gt;, it &lt;em&gt;is&lt;/em&gt; the compiler that&#39;s wrong,
I always look at my own code first. And second. And third.&lt;/p&gt;
&lt;p&gt;Likewise, in martial arts training, the attacker sometimes really is wrong.
Defense techniques are highly situational, and if your training partner has not
been paying attention, they may inadvertently change the situation enough for
the defense not to work as shown. A good instructor will notice this and correct
the form of the attack – and also explain the situation and show what you would
need to change to deal with the other form. This is part of the instructor&#39;s
job.
As a defender, you should not try to correct your partner but instead focus on
your own form and improve your capacity for dealing with attacks as they come.
Arguing with your partner does not lead to progress.&lt;/p&gt;
&lt;p&gt;So this is what I learned: To make progress, when I encounter a difficult situation,
I look to myself and don&#39;t blame the other party, be it a compiler or a training
partner.
I treat the difficulty as feedback and use it to improve my own game.
There is the odd exception, but looking for that shouldn&#39;t be my first reaction.
Not even the second.
By focusing on my part, I make progress much faster.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The same goes for
interpreters, of course. For simplicity&#39;s sake, I&#39;ll just use the word ‘compiler’. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/dont-argue-with-the-compiler/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The project uses Maven, and I had added the dependency
to the wrong section of &lt;code&gt;pom.xml&lt;/code&gt; – &lt;code&gt;&amp;lt;dependencyManagement&amp;gt;&lt;/code&gt;
instead of &lt;code&gt;&amp;lt;dependencies&amp;gt;&lt;/code&gt;. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/dont-argue-with-the-compiler/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;In the training setting! Of course, if you can talk a real attacker
down before you come to blows, that&#39;s much better! Because software development
is inherently a learning process, it has much more in common with martial arts
&lt;em&gt;training&lt;/em&gt; than with the application of martial arts in combat or self-defense. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/dont-argue-with-the-compiler/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Tue, 23 Sep 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/dont-argue-with-the-compiler/</guid>
    </item>
    
    
    <item>
      <title>Software development and martial arts</title>
      <link>https://sebastian-hans.de/blog/software-development-and-martial-arts/</link>
      <description>&lt;p&gt;I began my martial arts training when I was in school,
at about the same time as I got my first computer,
my father&#39;s old Schneider Euro PC.
It came with &lt;a href=&quot;https://en.wikipedia.org/wiki/GW-BASIC&quot;&gt;GW BASIC&lt;/a&gt;,
which got me into programming.
Both activities have been part of my life since then.
In martial arts, I started off training Goshin Jitsu,
a little-know style similar to Jujutsu,
then added Judo and Shotokan Karate before settling on Aikido.
In programming, I progressed to &lt;a href=&quot;https://en.wikipedia.org/wiki/QBasic&quot;&gt;QBasic&lt;/a&gt;,
dabbled a bit in assembler, then got into C and C++ and tried countless other
programming languages.
After school, I studied computer science and began my career as a professional
software developer.&lt;/p&gt;
&lt;p&gt;So I&#39;ve been practicing both martial arts and software development for about 30
years now, and I think I can say I have made a fair amount of progress since
then – in both areas.
Over the years, I have come to notice a kind of synchronicity of progress
in my martial arts and software development skills.
There were periods where progress stalled and I was frustrated because I felt
that I was stuck. There were periods where I was preparing for a martial arts
examination at the same time as I was preparing for a salary negotiation.
And the major successes (taking over a martial arts class, leading a development team) came remarkably close in time, too.&lt;/p&gt;
&lt;p&gt;On the one hand, this came unexpected because on the surface, typing code seems
to be very different from throwing people to the ground.
On the other hand, there obviously is a common element – &lt;em&gt;me&lt;/em&gt; – and my personal
development showing in both areas is not that surprising.
Or is it?
Why should progress I make in my martial arts training affect how I approach
software development and vice versa?
This only makes sense if there are other commonalities which make experience
gained in one area somewhat transferable to the other one.&lt;/p&gt;
&lt;p&gt;Thoughts about these commonalities have been bouncing around in my head for
quite a while now, and, beginning with this blog post, I am going to write them down
(in no particular order).
I don&#39;t know how this is going to turn out –
if my ramblings will be comprehensible to anyone other than me –
but hey, it&#39;s my blog, and I can post whatever I want, so here you go.&lt;/p&gt;
&lt;p&gt;I&#39;ll start off with the observation that developing software is not only about
typing code;
and martial arts is not only about throwing people to the ground, either.
These are just the externally observable behaviours of software developers and
martial artists, respectively.
In reality, both software development and martial arts are about dealing with people.
We develop software &lt;em&gt;for&lt;/em&gt; people.
To do this successfully, we need to understand their needs, not just the stated requirements.
Talking to customers is as much part of software development as writing code is.&lt;/p&gt;
&lt;p&gt;The preface to the classic computer science text
&lt;a href=&quot;https://mitpress.mit.edu/books/structure-and-interpretation-computer-programs&quot;&gt;&lt;em&gt;Structure and Interpretation of Computer Programs&lt;/em&gt;&lt;/a&gt;
says that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“programs must be written for people to read, and only incidentally for machines to execute.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Again, this highlights the importance of people to the software development
effort – this time, the developers themselves.
You cannot successfully develop software by only thinking about the program text
and the machines that will execute it.
Future developers (including your future self) are the main audience of the
code, and you need to anticipate how they are going to interpret (and
change) what you wrote.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/software-development-and-martial-arts/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;
As you get deeper into software development, you realize that dealing with the machines is
actually the easy part.
The much harder and, for better or worse, much more high-leverage part
is dealing with the humans involved:
customers, other developers, other roles in the organization.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/software-development-and-martial-arts/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Likewise, martial arts (in the sense of &lt;a href=&quot;https://en.wikipedia.org/wiki/Bud%C5%8D&quot;&gt;Budō&lt;/a&gt;)
are about dealing with people, and by “dealing”,
I don&#39;t mean the purely mechanical exercise of deflecting a blow by an opponent
or applying leverage to down someone twice your weight.
Ideally, martial arts are about ending conflict. As Ueshiba Morihei, the founder of Aikido, tells us in &lt;a href=&quot;https://archive.org/details/the-art-of-peace-eng-1/mode/1up&quot;&gt;&lt;em&gt;The Art Of Peace&lt;/em&gt;&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The real Art of Peace is not to sacrifice a single one of your warriors to
defeat an enemy. Vanquish your foes by always keeping yourself in a safe and
unassailable position; then no one will suffer any losses.
The Way of a Warrior, the Art of Politics, is to stop trouble before it
starts. It consists in defeating your adversaries spiritually by making them
realize the folly of their actions.
The Way of a Warrior is to establish harmony.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This requires seeing beyond the blow to the person dealing it, their motivation, emotional state, and capabilities.
Merely deflecting a blow rarely solves a problem, and throwing your opponent to the ground only replaces one problem with another. (What will you do next?)
Properly ending conflict is not achieved by fighting &lt;em&gt;against&lt;/em&gt; an enemy (although ensuring survival is certainly necessary) but rather by engaging &lt;em&gt;with&lt;/em&gt; the other party. Just as developing software benefits from engaging with your stakeholders rather than decoupling from them as far as possible.&lt;/p&gt;
&lt;p&gt;As a boy, I&#39;ve always been very good at the “hard” skills (logic, hitting an opponent, concise writing, hard/blocking techniques, …) and, I am afraid to say, bad at the “soft” skills (empathy, connecting with an opponent, active listening, soft/flowing techniques, …).
I learned about the importance of connecting with people slowly over time. As I practised accepting attacks more softly, I came to realize that the same softness helps when accepting code reviews. When I learned about the &lt;a href=&quot;https://business.tutsplus.com/tutorials/what-is-the-yes-and-improv-rule--cms-40670&quot;&gt;“Yes, and” rule&lt;/a&gt; in a business context, I linked this mentally to the martial arts principle of using the attacker&#39;s energy instead of stopping it, strengthening the idea in my mind &lt;em&gt;for use in both contexts&lt;/em&gt;.
Synchronicity.&lt;/p&gt;
&lt;p&gt;To be continued.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Further posts on this topic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/dont-argue-with-the-compiler/&quot;&gt;Don&#39;t argue with the compiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/excellence-is-a-habit/&quot;&gt;Excellence is a habit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Incidentally, this is why I prefer to talk
about “developing” software rather than “writing” software or programs.
In the context of programming, “writing” is often confused with “typing”,
even though pretty much nobody would equate those two words when talking about
the production of other texts like a novel. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/software-development-and-martial-arts/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I could go on,
mentioning
Peter Naur&#39;s &lt;a href=&quot;http://pages.cs.wisc.edu/~remzi/Naur.pdf&quot;&gt;Programming as Theory Building&lt;/a&gt;,
&lt;a href=&quot;https://martinfowler.com/bliki/ConwaysLaw.html&quot;&gt;Conway&#39;s Law&lt;/a&gt;,
the difficulty of making, communicating and enforcing architecture decisions, etc.,
but suffice it to say, software development is a people business. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/software-development-and-martial-arts/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Sat, 30 Aug 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/software-development-and-martial-arts/</guid>
    </item>
    
    
    <item>
      <title>Legacy software</title>
      <link>https://sebastian-hans.de/blog/legacy-software/</link>
      <description>&lt;p&gt;Why is it that no one actively writes legacy software?
I mean, the world is full of it. I&#39;m sure anyone who has been developing software
professionally for a while will agree, but I haven&#39;t met anyone who has told me the
code they had just written was legacy code.
How come there&#39;s so much of it then?
And what is legacy software, anyway?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Michael Feathers, the author of the book
&lt;em&gt;Working Effectively with Legacy Code&lt;/em&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;,
defines legacy code as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Legacy code is code without tests.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While pithy, I&#39;ve always found this definition slightly unsatisfying.
If I add a single test, is my software then non-legacy?
Or does the definition apply to lines of code so that a code base with
90% test coverage contains 10% legacy code?
Can software only ever become non-legacy with 100% test coverage?
This doesn&#39;t feel right.&lt;/p&gt;
&lt;p&gt;It would also mean that I could prevent my code from ever becoming legacy
if I “just” wrote enough tests.
I don&#39;t know about you, but I have certainly encountered code in my
career that had tests yet I would still classify as “legacy”.&lt;/p&gt;
&lt;p&gt;Steve Smith offers another definition on the
&lt;a href=&quot;https://www.youtube.com/@ModernSoftwareEngineeringYT&quot;&gt;Modern Software Engineering channel&lt;/a&gt;,
citing Olly Shaw:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#39;s any software system with a high cost of business change.
And as demand for business change increases, it can&#39;t respond quickly and safely.
Change can&#39;t be sustained, something somewhere will
break.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This definition is a lot broader because there may be any number of reasons why
code can be hard to change, with missing tests being one of them.
I mostly agree.
Being hard to change is certainly a property of legacy software.
Is every piece of software that is hard to change “legacy”, though?
Business change comes in many forms, and no piece of software can be changed
equally cheaply along all possible dimensions.
If the business pivots 180 degrees, and this requires architectural changes to
the software (which are typically hard to do),
is this enough to declare the software “legacy”?
I think not.
I think this definition is &lt;em&gt;too&lt;/em&gt; broad.&lt;/p&gt;
&lt;p&gt;It is also merely observational,
offering no guidance on what to do about it.
Is it hard to change? Then it must be “legacy”. Now what?
Feathers&#39; definition has the advantage of being actionable.
Write tests, and your system becomes less “legacy”.&lt;/p&gt;
&lt;p&gt;Neither of the definitions addresses the question of how software can &lt;em&gt;become&lt;/em&gt;
“legacy“. If someone writes a piece of software now, and someone else considers
it “legacy” later, what has happened in the meantime to make it so?&lt;/p&gt;
&lt;p&gt;Let&#39;s take a look at the &lt;a href=&quot;https://groups.google.com/g/net.jokes/c/k2JVKQzJSpY&quot;&gt;story of Mel&lt;/a&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
I encourage you to read the whole thing – it&#39;s a great story. But if you
don&#39;t, here&#39;s the relevant part:
It&#39;s about a programmer, Mel, who wrote very tricky code for a drum-memory computer.
When pressured to make a change he felt was unethical,
he inadvertently put a bug in it, but he liked the behaviour so much he refused to change the code afterwards.
His successor, tasked with fixing the bug, struggled considerably and eventually
gave up.
(It reads much better in the original, really, go &lt;a href=&quot;https://groups.google.com/g/net.jokes/c/k2JVKQzJSpY&quot;&gt;read it&lt;/a&gt;! You&#39;ll understand.)&lt;/p&gt;
&lt;p&gt;I&#39;m sure anyone reading this story would agree that the code was hard to change.
The story doesn&#39;t mention it, but I think we can safely assume Mel didn&#39;t write any unit tests,
either.
Was his program legacy software?
As long as Mel was in charge of it, I&#39;d say it wasn&#39;t.
When the need for a business change arose, he was able to make it easily
enough, and he would have been able to fix the bug equally cheaply if he had
wanted to.
It was only after Mel had left and his successor had been put in charge of the
program that difficulties appeared.
If someone asked &lt;em&gt;you&lt;/em&gt; to fix a bug in this program, I bet you&#39;d consider it
legacy software, whether there were any tests or not.&lt;/p&gt;
&lt;p&gt;But the program itself hasn&#39;t changed at all!
So its “legaciness” can&#39;t really be a property of the code, can it?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Enter Peter Naur.
In his classic essay “Programming as Theory Building”&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;,
Peter Naur presents the view that a computer program is much more than just its
code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In terms of Ryle’s notion of theory, what has to be built
by the programmer is a theory of how certain affairs of
the world will be handled by, or supported by, a computer
program. On the Theory Building View of programming
the theory built by the programmers has primacy
over such other products as program texts, user
documentation, and additional documentation such as
specifications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is this theory that enables programmers&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;to explain the operation of the program to themselves and others,&lt;/li&gt;
&lt;li&gt;to justify the structure of the program,&lt;/li&gt;
&lt;li&gt;and to make business changes in a way that maintains conceptual integrity
and thus long-term maintainability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since this theory lives primarily in the heads of the programmers,
Naur describes the life cycle of a program with the notions of life, death,
and revival, where:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The death of a program happens
when the programmer team possessing its theory is dissolved.
A dead program may continue to be used for
execution in a computer and to produce useful results.
The actual state of death becomes visible when demands
for modifications of the program cannot be intelligently
answered.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Does this sound like legacy software to you?
It certainly does to me.
If someone else later takes over responsibility for the program,
they will not be able to respond to the demand for business change quickly and
safely (as Shaw put it).
The code hasn&#39;t changed, but the theory behind the program has been lost.
It is not known to the successor, just as Mel&#39;s theory was not known to his
successor.
This is because the code (and its associated documentation) may have been handed over,
or down – the word “legacy” is really appropriate here.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I therefore propose this definition&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Legacy software is any software whose theory is not known to those
responsible for it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This definition is consistent with Shaw&#39;s in that loss of the theory makes
the software hard to change, but it also tells you something about how this
comes about and, by extension, what to do about it: keep the theory alive.
Naur describes how this can be done:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For a new programmer to come to possess an existing theory of a program it is
insufficient that he or she has the opportunity to become familiar with the
program text and other documentation. What is required is that the new
programmer has the opportunity to work in close contact with the programmers
who already possess the theory, so as to be able to become familiar with the
place of the program in the wider context of the relevant real world
situations and so as to acquire the knowledge of how the program works and how
unusual program reactions and program modifications are handled within the
program theory.
[…]
The most important educational activity is the student’s doing the relevant
things under suitable supervision and guidance. In the case of programming the
activity should include discussions of the relation between the program and
the relevant aspects and activities of the real world, and of the limits set
on the real world matters dealt with by the program.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Naur does not explicitly say how long this period of “supervision and guidance”
is supposed to take or how to recognize that the theory has been successfully
transferred. My own experience with taking over responsibiltiy for existing
software shows that about 2-3 months of active guidance seem to be enough for me
to get the gist of it, and I take about 5-6 months to reach a point where my
questions to former team members are mostly answered with something like, “Your
guess is as good as mine.” This is when I consider my “apprenticeship” over
and begin to drive implementation decisions from the theory I have built in my
own mind.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;
However, the TTT (time to theory) seems to vary wildly between developers,
and getting there at all requires a willingness to grapple with and internalize
a theory that &lt;em&gt;is not your own&lt;/em&gt;.
I have worked with colleagues who had not internalized the key concepts of the
software after 2 years on the team. Since they were otherwise quite smart guys
I have to conclude they simply didn&#39;t want to.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If&lt;/em&gt; you find people who are willing to absorb the theory &lt;em&gt;and&lt;/em&gt; give them enough
time to actually learn from the old programmers, you can keep the theory alive
and thus avoid the sofware becoming “legacy“.
If you don&#39;t, the theory will get lost. This can happen quite suddenly if the
team is replaced completely, or slowly over time if the theory erodes due to
insufficient absorption by new team members (whatever the cause).&lt;/p&gt;
&lt;p&gt;Looking back at Michael Feathers&#39; definition of legacy code as “code without
tests”, and applying these thoughts to it, we can seen how tests can help to
keep a code base alive. For one, good tests not only describe what the code does in
a technical sense (e.g., “skip credit card check if the ‘preAuth’ flag is set”)
but also the intent behind that (e.g., “no long-running checks during
pre-auth”). They encode part of the theory – in an executable, testable way.
And I bet when Michael begins implementing tests for a legacy system, he uses
them not only to ensure &lt;em&gt;technically&lt;/em&gt; that the behaviour does not change during
refactoring, but also to rebuild the theory of the system in his mind.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So why does no one actively write legacy software?
Because it&#39;s not legacy while they are writing it.
The theory of the program is alive in their minds.
It only becomes “legacy” when it becomes &lt;em&gt;a&lt;/em&gt; legacy,
i.e., when the code is passed on to other developers
without also passing on its theory.
Unfortunately, passing on the theory is not easily done and takes more time and
effort than is usually available, which explains why there is so much legacy
software around.&lt;/p&gt;
&lt;p&gt;Consider the code you are currently writing.
It isn&#39;t legacy yet.
What&#39;s your plan for keeping it that way?&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Feathers, Michael. &lt;em&gt;Working Effectively with Legacy Code&lt;/em&gt;. Pearson. 2004 &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Smith, Steve [Modern Software Engineering]. (2025, July 16). &lt;a href=&quot;https://www.youtube.com/watch?v=ADpCXly0bsM&quot;&gt;&lt;em&gt;Learn To LOVE Your Legacy Code&lt;/em&gt;&lt;/a&gt;. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Crawford, Matt. &lt;a href=&quot;https://groups.google.com/g/net.jokes/c/k2JVKQzJSpY&quot;&gt;&lt;em&gt;The realest programmer of all&lt;/em&gt;&lt;/a&gt;. Newsgroup: net.jokes. November 20, 1984. Retrieved July 19, 2025. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Naur, Peter. &lt;a href=&quot;http://pages.cs.wisc.edu/~remzi/Naur.pdf&quot;&gt;Programming
as Theory Building&lt;/a&gt;. 1985. Retrieved
July 19th, 2025. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I first came up with the definition
“Legacy software is any software the theory behind which has been lost,”
but nowadays it is possible to vibe code software without building a
theory in the first place.
Following the Theory Building View of programming, this produces instant legacy
software, so I had to change the wording a bit to account for this effect. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I see a connection there to the concept of
&lt;a href=&quot;https://en.wikipedia.org/wiki/Shuhari&quot;&gt;Shu Ha Ri&lt;/a&gt;, but this post is already
long enough. Maybe I will go into it some other time. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/legacy-software/#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Tue, 29 Jul 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/legacy-software/</guid>
    </item>
    
    
    <item>
      <title>Endless possibilities (a socio-technical API pattern)</title>
      <link>https://sebastian-hans.de/blog/endless-possibilities/</link>
      <description>&lt;p&gt;Thinking about &lt;a href=&quot;https://sebastian-hans.de/blog/the-api-leach-and-the-api-standard&quot;&gt;socio-technical API patterns&lt;/a&gt;
some more, another one occurred to me.
I&#39;m calling it &lt;em&gt;Endless Possibilities&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This one plays out over a long(ish) time frame.
It starts out as a fairly simple API designed by some producer to satisfy the
needs of a single consumer, or perhaps a small group of consumers.
Let&#39;s assume the API is well-designed, maybe even co-designed between producer
and consumer teams.
It covers a small number of use cases and is well-tested and well-documented.&lt;/p&gt;
&lt;p&gt;Then life happens.
A new consumer is onboarded.
Their needs align well with the established API, except for one small variation.
The producer team addresses this by adding an optional property to one of the requests,
with a backwards compatible default behaviour.&lt;/p&gt;
&lt;p&gt;When another consumer is onboarded, their special needs are addressed in a
similar manner. Maybe it is not a new property this time, but an additional enum
value on an existing property.
This new option conflicts with the first optional property, but this is not a
problem because each consumer will only be using one of them.
The API will return a helpful error message if a consumer tries to use both of them together.&lt;/p&gt;
&lt;p&gt;Later, one of the existing consumers wants to implement a feature that requires a slightly
different workflow, and the producer team needs to decide how to accommodate this:
add another specialized option to the existing API methods?
Special-case the API for the consumer?
Implement new API methods instead?
Or make the API more generic so it can handle both the old and the new workflow,
distinguishing them by some other criteria?
The team decides to take the latter route because this will enable not only the
new workflow, but open up some additional possibilities the team feels will be
interesting for some consumers.
They add some documentation on how to choose between the old and the new workflow.&lt;/p&gt;
&lt;p&gt;The lead developer of the provider team leaves the company (for unrelated reasons).
Unfortunately, no one else on the team possesses quite the same level of domain
expertise and historical knowledge of the technical intricacies.
When the next feature is requested by a consumer, the provider team comes up
with a solution that involves adding feature flags to 3 different API methods – to
be used in a certain combination.
One of the flags conflicts with an option added earlier – but only in certain
edge cases.
No one on the team knows with any amount of certainty if these are plausible
scenarios.
It may even be unknown whether the older option is still in use by some
consumers.
The team updates the documentation to indicate which scenarios are supported,
but it remains partly ambiguous.&lt;/p&gt;
&lt;p&gt;As time goes on, the API grows even more, gathering further options.
Exhaustive testing of all combinations of workflows and optional behaviour
becomes intractable.
Natural churn in the provider team leads to knowledge loss.
New features and changes are implemented in ways that almost, but not quite, fit
the original intention.
For consumers, the API begins to feel bloated and the documentation does not
offer strong guidance on how to choose between all the options.
Onboarding requires close collaboration with the provider team
because the documentation alone is no longer sufficient to understand supported
usage patterns and expected behaviour.&lt;/p&gt;
&lt;p&gt;And there you have it: an API of &lt;em&gt;Endless Possibilities&lt;/em&gt;.
It has so many optional features and ways of using it that not even the provider
team can answer questions about its behaviour outright.
Instead of knowing, they have to do research in the code and maybe perform
exploratory testing on their own API.
It has lost coherence and gained sharp edges.
Some combinations of features are well-tested and explicitly supported, others
are untested, some are explicitly forbidden, some do work but have non-intuitive
consequences.
If bugs crop up, they are difficult to diagnose because it is unclear what the
intended behaviour even &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The evolution from a clean, well-designed API to an API of &lt;em&gt;Endless Possibilities&lt;/em&gt;
is a slow one. There is no clear inflection point, no moment in time you could
point to and say, “This is where it went wrong.”
Because of this, this pattern is difficult to avoid, but you can
watch for warning signs such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The API has multiple options that interact in non-obvious ways.&lt;/li&gt;
&lt;li&gt;The API has options that are incompatible for non-obvious reasons.&lt;/li&gt;
&lt;li&gt;Options that make sense together from a business perspective are only
partially implemented (some combinations are missing).&lt;/li&gt;
&lt;li&gt;The API has multiple functions (methods, endpoints, …) that need to be invoked
in a certain order – and the order depends on some option(s).&lt;/li&gt;
&lt;li&gt;When adding a new option, it is difficult to decide how it should interact with existing options.&lt;/li&gt;
&lt;li&gt;Certain aspects of the behaviour depend on both options sent by the consumer
&lt;em&gt;and&lt;/em&gt; configuration on the provider side.&lt;/li&gt;
&lt;li&gt;The team cannot answer a question about API behaviour without research.&lt;/li&gt;
&lt;li&gt;The team cannot answer a question about API behaviour without knowing which
customer the question is about.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some suggestions how the provider team can slow – or maybe even avoid –
the slide into &lt;em&gt;Endless Possibilities&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep the API focused on a single purpose. Ensure that everyone on the team
knows this purpose and has a sense of what fits this purpose and what does
not. This helps to avoid feature creep.&lt;/li&gt;
&lt;li&gt;Resist the temptation to use options to implement features that alter the
structure of the interaction between consumer and provider; provide a new API
for the new interaction pattern instead. A clear separation avoids nasty edge
cases that may manifest when mixing interaction patterns.&lt;/li&gt;
&lt;li&gt;If you do add features/options, make them orthogonal to existing
features/options. This makes them easier to reason about.&lt;/li&gt;
&lt;li&gt;If this is not possible, at least clearly document the supported combinations
and their semantics – and emit helpful error messages if a consumer tries to
use an unsupported combination rather than allowing undefined behaviour
(because of &lt;a href=&quot;https://www.hyrumslaw.com/&quot;&gt;Hyrum&#39;s Law&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If a consumer does have special needs that cannot be addressed in a
straightforward way, it &lt;em&gt;may&lt;/em&gt; be better to just hardcode the desired
behaviour for this consumer instead of trying for a generic implementation
that adds unnecessary complexity for everyone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, this is easier said than done, and it becomes more and more difficult
the further an API grows and the more consumers with diverse use cases it
collects.
Keeping such an API from devolving into &lt;em&gt;Endless Possibilities&lt;/em&gt; requires
strong governance with a focus on long-term maintainability.
If the provider team fails to establish this, it will reach a point of no
return, and it becomes practically impossible to get out of this pattern in an
evolutionary way.
Then, the only remaining option is revolutionary,
i.e., start with a new API design from scratch – and establish stronger
governance this time.&lt;/p&gt;
</description>
      <pubDate>Thu, 26 Jun 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/endless-possibilities/</guid>
    </item>
    
    
    <item>
      <title>The API Leach and the API Standard</title>
      <link>https://sebastian-hans.de/blog/the-api-leach-and-the-api-standard/</link>
      <description>&lt;p&gt;A few days ago, Einar W. Høst published a blog post,
&lt;a href=&quot;https://einarwh.no/blog/2025/04/28/sociotechnical-api-patterns/&quot;&gt;Socio-technical API patterns&lt;/a&gt;,
in which he examines some socio-technical questions about APIs
and describes a handful of recurring patterns in the interaction of APIs and
the people designing, providing, and using these APIs.
He calls these socio-technical API patterns.
I quite liked the post. If you haven&#39;t done so, go read it!
(I am also going to refer to some of his patterns later on,
so I suggest reading his post before mine.)&lt;/p&gt;
&lt;p&gt;In this post, I would like to add two patterns I have personally observed:
the API Leach and the API Standard.&lt;/p&gt;
&lt;h2 id=&quot;the-api-leach&quot; tabindex=&quot;-1&quot;&gt;The API Leach&lt;/h2&gt;
&lt;p&gt;The API Leach is more of an anti-pattern.
It comes about when a consumer is unable or unwilling to talk to the
provider, but still needs the provided service. The consumer notices an
interface exposed (but not advertised) by the provider and starts programming
against it without the provider being aware of this. Maybe the interface is an
internal API, or maybe it is not intended as an API at all, but as a UI for
human users. Scraping web pages for data is one instance of this pattern.&lt;/p&gt;
&lt;p&gt;Since the interface is not intended for consumption by external programs, the
unwitting provider does not provide documentation, service levels, or support
channels to the consumer team. The interface behavior is reverse engineered and
may change at any time as the provider makes changes to the system. The
interface may disappear without notice, too.&lt;/p&gt;
&lt;p&gt;For the consumer team, this is a hard spot to be in. Reverse engineering an
undocumented interface is inherently hard and error-prone, and once they are
done, the team must be ready to deal with unexpected behavior (and failure) at
any time – be it due to misunderstanding during the reverse engineering phase or
to technical or functional changes. In all probability, they won&#39;t have a test
environment, either, and need to test against the production interface with
production data, which can severely limit the range of tests that are viable.&lt;/p&gt;
&lt;p&gt;The provider team is mostly unaffected, but may notice some oddities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;unexpected usage patterns (because the consumer&#39;s needs probably differ from
those of the provider team, or from regular human users),&lt;/li&gt;
&lt;li&gt;an increased error rate (the “error” part in reverse engineering by trial and
error),&lt;/li&gt;
&lt;li&gt;increased load (which can, in extreme cases, even dominate the total load),&lt;/li&gt;
&lt;li&gt;strange support requests (by the consumer trying to get information from the provider without revealing what they are doing).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These may cause the provider team to notice that something is up.
And, of course, the producer may get yelled at after performing breaking changes
if the consumer feels entitled enough.&lt;/p&gt;
&lt;p&gt;Once the producer learns of the existence of the consumer, what happens next
depends mostly on the power dynamics between the two parties. The interface
may become an official API; the consumer may be shut out; both parties may
negotiate a different API.&lt;/p&gt;
&lt;p&gt;Another factor that determines the consequences is the intention of the
consumer. The API Leach pattern is often used because the consumer is trying to
accomplish something the producer would not be inclined to agree with –
or at least has no incentive to support.
In these situations, there may not be a good alternative,
and if the leach is discovered, the provider will probably attempt to shut down the
“integration”.&lt;/p&gt;
&lt;p&gt;The pattern can also be established inadvertently, however,
e.g., if the consumer fails to contact the producer due to lack of time or
knowledge.
In these cases, the situation can be resolved by establishing communication and
explicitly agreeing on a different integration pattern.&lt;/p&gt;
&lt;p&gt;It can also be the result of a team trying to put a
&lt;a href=&quot;https://einarwh.no/blog/2025/04/28/sociotechnical-api-patterns/#millstone&quot;&gt;Millstone&lt;/a&gt;
on another team, but failing because they don&#39;t have the necessary clout.
Sometimes, downstream failures due to changes by the unwilling provider
can gather enough management attention to rectify the situation –
or at least to make the Millstone possible after a period of leaching,
which might be an improvement for the consumer, if not for the provider.&lt;/p&gt;
&lt;h2 id=&quot;the-api-standard&quot; tabindex=&quot;-1&quot;&gt;The API Standard&lt;/h2&gt;
&lt;p&gt;Using a documented API Standard is another pattern that extends beyond the
merely technical considerations into the socio-technical domain.
Using a standard decouples provider and consumer. The standard was designed for
a particular purpose and is adopted by both provider and consumer, presumably
because their needs align with this purpose. Collaboration on the API design is
minimal because most of it is defined by the standard.&lt;/p&gt;
&lt;p&gt;Most standards say little about operational aspects, however,
so the service level offered by the provider most likely depends on other factors.
Therefore, this pattern is usually combined with other patterns.&lt;/p&gt;
&lt;p&gt;In conjunction with the &lt;a href=&quot;https://einarwh.no/blog/2025/04/28/sociotechnical-api-patterns/#millstone&quot;&gt;Millstone&lt;/a&gt;,
where a software team is forced to provide an API to the consumer without
having an intrinsic motivation to do so,
the API Standard alleviates the problem of coupling between
the provider&#39;s internal data model and the API because the provider will typically have to
implement some kind of mapping anyway. It is very unlikely that the internal
model corresponds to the standard already.
Thus, the Standard establishes a stable API contract that would be difficult to enforce otherwise.
It does not solve the problem of &amp;quot;best effort&amp;quot; SLAs, though.&lt;/p&gt;
&lt;p&gt;When combined with the &lt;a href=&quot;https://einarwh.no/blog/2025/04/28/sociotechnical-api-patterns/#mountain&quot;&gt;Mountain&lt;/a&gt;,
where the consumer is dependent on a provider with little to no influence on the
API design and the API lifecycle,
the Standard provides a measure of protection from the effects of the Volcano
(the provider unilaterally deciding to discontinue – or massively alter – the API).
If this happens, there will often be other providers using the same standard
that can be moved to with little changes to the application code.
If a data migration is necessary, the Standard may or may not be of help,
depending on whether it specifies export/import functionality.&lt;/p&gt;
&lt;p&gt;The Standard does probably not help with the &lt;a href=&quot;https://einarwh.no/blog/2025/04/28/sociotechnical-api-patterns/#rapids&quot;&gt;Rapids&lt;/a&gt;,
where an API is located in the middle of a value stream,
but it may help with the &lt;a href=&quot;https://einarwh.no/blog/2025/04/28/sociotechnical-api-patterns/#sock-puppet&quot;&gt;Sock Puppet&lt;/a&gt;,
where a team is talking to itself through an API.
When a team manages multiple services,
probably the requirements are not equally unique for all of them.
There may well be services that perform standardized functions for which usage of a standardized
API can reduce the cognitive load on the team and simplify onboarding for new team members.&lt;/p&gt;
&lt;p&gt;A disadvantage of using an API Standard manifests if the needs of the consumer
diverge from what the standard provides over time. In this case, you have the
choice of forcing your needs into the corset of the standard (often suboptimal),
augmenting the API with non-standard extensions (may work, but limits the
usefulness of the standard), or ditching the standard altogether.&lt;/p&gt;
&lt;p&gt;The API Leach may benefit from a provider that adheres to an API Standard
(or maybe human interface guidelines), too,
because this reduces the chances of breaking changes –
although there is no guarantee that the provider will keep to the standard
indefinitely – or even that the provider implements the standard correctly.
Thus, the Standard does not solve the Leach&#39;s fundamental problem,
but it may reduce the pain somewhat.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, I wrote down two additional socio-technical API patterns.
The API Leach describes the situation where a consumer taps into an “unofficial”
interface of an unwitting provider.
The API Standard describes how usage of an … API standard affects various other
patterns.&lt;/p&gt;
&lt;p&gt;If you have observed other patterns, I would appreciate it if you shared them.&lt;/p&gt;
</description>
      <pubDate>Fri, 02 May 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/the-api-leach-and-the-api-standard/</guid>
    </item>
    
    
    <item>
      <title>Incremental danger</title>
      <link>https://sebastian-hans.de/blog/incremental-danger/</link>
      <description>&lt;p&gt;I&#39;m all for incremental development.
Incremental development is great.
It reduces risk by dividing complex changes into small – less complex – parts.
It allows for early delivery of value.
It enables learning and discovery.
It simplifies development by allowing you to focus on one part of the problem in
turn.
That&#39;s good, right?&lt;/p&gt;
&lt;p&gt;Yes, it is, but there is a very important caveat:
you have to make sure the parts fit together – and they actually solve the
problem in the context where it needs to be solved!&lt;/p&gt;
&lt;p&gt;Let&#39;s look at a simple example that was developed incrementally.&lt;/p&gt;
&lt;h2 id=&quot;a-simple-problem&quot; tabindex=&quot;-1&quot;&gt;A simple problem&lt;/h2&gt;
&lt;p&gt;A team was in the process of containerizing their service.
As a result, they had been generating container images at a relatively high rate.
This was noticed by the administrator of the central image repository,
and the team was asked to delete images they no longer needed
and to institute some kind of policy to limit the number of images.
The team decided to implement an automated clean-up job to delete obsolete
images.
Due to the team&#39;s (mostly) roll-forward strategy in case of problems,
most old images would never be needed again and could be deleted.
It was deemed enough to be able to roll back for 7 days or 10 images
(a somewhat arbitrary number).
These were the images they wanted to keep.
Everything else could be deleted.&lt;/p&gt;
&lt;p&gt;At the time, the rate of deployment varied between a few times a week and once
every few weeks, tending towards the longer time scale&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/incremental-danger/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
Revisions (images) could spend a few days or in some cases weeks (for complex features)
in the test environment before being deployed to production.&lt;/p&gt;
&lt;p&gt;Let&#39;s follow the developer – call him George – tasked with implementing the clean-up job.&lt;/p&gt;
&lt;h2 id=&quot;increment-1&quot; tabindex=&quot;-1&quot;&gt;Increment 1&lt;/h2&gt;
&lt;p&gt;George implements the first version of the clean-up job this way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;enumerate all images,&lt;/li&gt;
&lt;li&gt;filter out those younger than 7 days (the ‘keep list’),&lt;/li&gt;
&lt;li&gt;delete the rest.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This works fine, and the job is scheduled to run nightly.&lt;/p&gt;
&lt;p&gt;Before reading further, please take a moment to think about this implementation.
What do you think will happen given the workflow described above?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Time to think …&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Do you see the problem?&lt;/p&gt;
&lt;p&gt;George doesn&#39;t. He thinks everything is fine.&lt;/p&gt;
&lt;p&gt;But then, after a few nights, the job deletes the image that is currently running in
production because it is older than 7 days.
Fortunately, the team notices this before it turns into a serious problem.
If the runtime platform had decided it needed to pull the image again due to a restart,
this could have resulted in an outage!&lt;/p&gt;
&lt;h2 id=&quot;increment-1.1&quot; tabindex=&quot;-1&quot;&gt;Increment 1.1&lt;/h2&gt;
&lt;p&gt;Regenerating the deleted image is easy – just run the CI pipeline again.&lt;/p&gt;
&lt;p&gt;George then fixes the clean-up job by looking up which image is running in production,
taking its timestamp, and calculating 7 days backwards from there
instead of from the time the job is run.&lt;/p&gt;
&lt;p&gt;This is a bit awkward to test because there no longer are old images,
but the code looks OK, so the fix is deployed, and production images are safe
again.&lt;/p&gt;
&lt;h2 id=&quot;increment-2&quot; tabindex=&quot;-1&quot;&gt;Increment 2&lt;/h2&gt;
&lt;p&gt;George then implements keeping at least 10 images by skipping deletion
if the ‘keep list’ from above has less than 10 entries.&lt;/p&gt;
&lt;p&gt;Again, before reading further, take a moment to think about this solution.
What do you think will happen?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Time to think …&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;At first glance, this implementation does satisfy all constraints.
The image running in production is preserved,
images younger than 7 days are preserved,
and at least 10 images are preserved.
So it&#39;s good, then?&lt;/p&gt;
&lt;p&gt;Except preserving images is not the point of the exercise.
The team had this before the clean-up job was introduced and they never deleted
anything.
The main goal is to &lt;em&gt;delete&lt;/em&gt; stuff.&lt;/p&gt;
&lt;p&gt;How does this solution delete images?
It will delete images older than 7 days &lt;em&gt;if&lt;/em&gt; there are at least 10 images
younger than 7 days&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/incremental-danger/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;.
This means the job will only delete anything if the team has deployed to
production at least 10 images over the past 7 days, i.e.,
on average at least 2 deployments per day&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/incremental-danger/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
Given the actual deployment frequency of the team,
the job will &lt;em&gt;never actually delete anything&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;increment-2.1&quot; tabindex=&quot;-1&quot;&gt;Increment 2.1&lt;/h2&gt;
&lt;p&gt;This fact is discovered during a code review,
and George fixes this by changing the logic.
The job now looks at each image, going backwards in time from the production image,
and adds images to the ‘keep list’ until a time difference of 7 days &lt;em&gt;or&lt;/em&gt;
the number of 10 images have been reached.
All images older than that are deleted.&lt;/p&gt;
&lt;p&gt;Now the job actually deletes old images while preserving those it is supposed to
preserve.&lt;/p&gt;
&lt;h2 id=&quot;retrospective&quot; tabindex=&quot;-1&quot;&gt;Retrospective&lt;/h2&gt;
&lt;p&gt;To recap, the team started off with a simple requirement: don&#39;t keep too much
old stuff, but keep enough to be able to roll back in case of trouble.
They formulated this in terms of two rough rules, and George went off
to implement them incrementally.&lt;/p&gt;
&lt;p&gt;The first increment deleted too much.
The reason was a misunderstanding of the requirement: being able to roll
back for 7 days &lt;em&gt;after a production deployment&lt;/em&gt; versus keeping images younger
than 7 days from the perspective of &lt;em&gt;now&lt;/em&gt;.
Realizing that there is a significant difference hinges on the understanding of
the context the solution has to work in (the deployment frequency and the
duration between image generation and deployment in production).
Focusing solely on the task of delivering the first increment, George forgot to
consider this context – and with a little bit of bad luck could have caused a production outage.&lt;/p&gt;
&lt;p&gt;When taking on the 10 day ‘keep’ rule, George again focused solely on the
current task, and did not consider how his implementation would interact with
the previous increment and the context (the team&#39;s deployment frequency).
This time, his ‘solution’ failed to do anything at all.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This example shows how easy it is to lose your place if you consider only the
step right in front of you.
While incremental development allows you to focus on one small change at a time
(and this is one of its best features), this does not mean you should run around
with blinders. You still need to be aware of the bigger picture to ensure your
one small change actually does what it is supposed to do – and doesn&#39;t do what
it is not supposed to do.&lt;/p&gt;
&lt;p&gt;This bigger picture includes&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;what is already there&lt;/strong&gt; (for the second increment, this is the first increment),&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;the context&lt;/strong&gt; in which the solution will operate (in George&#39;s example, the
team&#39;s deployment process and cadence),&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;the intent&lt;/strong&gt; behind it all (here, to delete unnecessary stuff).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I develop incrementally, I ask myself this question for each increment:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When this is integrated and running in the target context,
will the solution as a whole support the original intent?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hopefully, the answer is “yes”.
But if it isn&#39;t, I can still celebrate,
because I have caught a flaw before it could cause damage.
And I now have the opportunity to rethink my approach and do better.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;It had gone down
somewhat from the peak during initial experimentation &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/incremental-danger/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Always counting backwards from the image currently in production. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/incremental-danger/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;No deployments on weekends. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/incremental-danger/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Wed, 23 Apr 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/incremental-danger/</guid>
    </item>
    
    
    <item>
      <title>On planning, part 5: exploration</title>
      <link>https://sebastian-hans.de/blog/on-planning-how-4/</link>
      <description>&lt;p&gt;This is the fifth and last post in my series about planning in the context of
agile teams.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-why/&quot;&gt;Part 1&lt;/a&gt; is concerned with why you should plan at all.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-1/&quot;&gt;Part 2&lt;/a&gt; describes my iterative approach to end-to-end planning.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/&quot;&gt;Part 3&lt;/a&gt; shows how I do forwards and backwards planning.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/&quot;&gt;Part 4&lt;/a&gt; demonstrates how I use the critical path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post, I will explore … exploration.&lt;/p&gt;
&lt;h2 id=&quot;happy-path-planning&quot; tabindex=&quot;-1&quot;&gt;Happy path planning&lt;/h2&gt;
&lt;p&gt;We&#39;ll start with a very simple plan.
The following picture shows how to get from Krottenkopfstraße in Munich to the
hospital Klinikum Großhadern.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;A sequence of lines annotated with street names&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/route-300w.jpeg?v=c8030f623c03&quot; width=&quot;900&quot; height=&quot;179&quot; srcset=&quot;https://sebastian-hans.de/img/route-300w.jpeg?v=c8030f623c03 300w, https://sebastian-hans.de/img/route-600w.jpeg?v=090d7d6ccd3f 600w, https://sebastian-hans.de/img/route-900w.jpeg?v=8d6ef2270581 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
A very simple plan showing a route through Munich.
&lt;br /&gt;
Drawn using map data by &lt;a href=&quot;https://www.openstreetmap.org/copyright&quot;&gt;OpenStreetMap contributors&lt;/a&gt;.
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;You start off going north, turn 5 times, and you are there.
The street names are on the plan.
What more do you need?&lt;/p&gt;
&lt;p&gt;If everything goes well – nothing.
If everything goes well, you will get from here to there using only this plan.
If everything goes well.
How likely is that?
In this particular example, and if you are walking, it is pretty likely.
If you are going by car, it&#39;s a little less likely.
There may be construction work blocking a street, traffic jams, or you might not
find a parking space once you are there.
What are you going to do then?&lt;/p&gt;
&lt;p&gt;This plan is fragile in the sense that it works while you can follow it
strictly, but if you deviate from it even slightly, it can no longer tell you
what to do.
And this is a very simple plan for a very simple ‘project’.
If your undertaking is more complex – say, like a software development project –, something is bound to go wrong,
where ‘wrong’ means deviating from the happy path.&lt;/p&gt;
&lt;p&gt;Now, you might say, this is a stupid example.
Obviously, I got the route from &lt;a href=&quot;https://www.openstreetmap.org/&quot;&gt;OpenStreetMap&lt;/a&gt;, and why would I blank out
everything but the chosen route?
Why not show the whole map so I can immediately see alternative routes if I need
them?
And you&#39;d be right.
Here is the map, including the route.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;The same route as above, but drawn on a detailed map of Munich.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/map-with-route-300w.jpeg?v=5467e1143336&quot; width=&quot;900&quot; height=&quot;449&quot; srcset=&quot;https://sebastian-hans.de/img/map-with-route-300w.jpeg?v=5467e1143336 300w, https://sebastian-hans.de/img/map-with-route-600w.jpeg?v=40857d27ba3b 600w, https://sebastian-hans.de/img/map-with-route-900w.jpeg?v=a625eefd7563 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
A much more useful and resilient plan.
&lt;br /&gt;
Map data by &lt;a href=&quot;https://www.openstreetmap.org/copyright&quot;&gt;OpenStreetMap contributors&lt;/a&gt;.
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;With this map, my plan becomes much more resilient: if I deviate from the route,
the map allows me to find back to it or to quickly choose a new route to the
target if this seems more appropriate.
If getting from here to there is in any way important, I&#39;d be stupid not to use
the map if I have it available.&lt;/p&gt;
&lt;p&gt;But how often do we have such a map when embarking on a software development
project?
The plans I have seen often resembled the first picture much more than the second.
This is what I call ‘happy path planning’.
It presupposes that all will go well and makes little provision for unexpected
obstacles.&lt;/p&gt;
&lt;h2 id=&quot;ask%2C-%E2%80%98what-can-go-wrong%3F%E2%80%99&quot; tabindex=&quot;-1&quot;&gt;Ask, ‘What can go wrong?’&lt;/h2&gt;
&lt;p&gt;If and when I make plans, I always think about things that could go wrong.
I explore what could go wrong and think about appropriate responses and alternative ways forward.&lt;/p&gt;
&lt;p&gt;I make a point of not being content after devising the first possible plan
that &lt;em&gt;could&lt;/em&gt; work out (if everything goes well), but to think about
other options we have and risks associated with each of them.
For example, I can do even better in the routing example above by including the
timetable of the subway line U6 in my plan because it could take me most of the
way if I wanted to.
It adds another option.&lt;/p&gt;
&lt;p&gt;I examine the dependencies among tasks and possible orderings.
If doing the tasks in one order results in a bottleneck (especially one at a
later stage of the project), and doing them in another order increases
flexibility, I prefer the latter.&lt;/p&gt;
&lt;p&gt;In effect, I am building a &lt;em&gt;map&lt;/em&gt; that not only shows one single path but a web
of paths, so if some obstacle blocks our way, a solution becomes apparent
quickly by looking at the alternatives I have considered.
Having this map enables me to &lt;em&gt;respond to change&lt;/em&gt; better.
(Remember this? It is one of the values from the
&lt;a href=&quot;https://agilemanifesto.org/&quot;&gt;Manifesto for Agile Software Development&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;This doesn&#39;t mean that I don&#39;t have a preferred way forward.
I do plan a route – a ‘happy path’ – on this map.
But – and this is a big ‘But’ – I am prepared to respond to changing
circumstances much better than if I had &lt;em&gt;only&lt;/em&gt; considered the happy path.&lt;/p&gt;
&lt;p&gt;Also, simply &lt;em&gt;knowing&lt;/em&gt; that we have fallback options provides
much-needed ease of mind in times of high pressure.
And this ease of mind sometimes allows us to perform feats that are impossible
when we are stressed out.
‘We would like to get this subtask done before the deadline, but we could deliver it later if necessary,’
can take out so much pressure.
So maybe having this plan is what allowed us to succeed, even though – on the
surface – we did not actually ‘need’ any of those alternatives.&lt;/p&gt;
&lt;p&gt;Here we are with general Dwight Eisenhower again:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Plans are useless but planning is indispensable.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Having planned this way (and having built a map around our plan),
we are prepared for whatever life will throw at us,
regardless of how much of the initial plan actually works.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That&#39;s it for my ‘planning’ series.
It has gotten much longer than I had planned (hah!).&lt;/p&gt;
&lt;p&gt;In conclusion, yes, I think planning is &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-why/&quot;&gt;important&lt;/a&gt;,
even for agile teams,
especially if they take on larger pieces of work, have to deal with deadlines,
or depend on external resources which may not be easily available.
I use &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/&quot;&gt;forwards and backwards planning&lt;/a&gt; to
come up with an &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-1/&quot;&gt;end-to-end plan&lt;/a&gt;
that I refine continuously.
When dealing with deadlines, I use the concept of the
&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/&quot;&gt;critical path&lt;/a&gt; to ensure my plan fits the
available time.
And I draw up not only the happy path where nothing must go wrong,
but I also explore risks and alternatives to be prepared for change because
&lt;a href=&quot;https://en.wikipedia.org/wiki/Heraclitus#Panta_rhei&quot;&gt;everything flows&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Mon, 17 Mar 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/on-planning-how-4/</guid>
    </item>
    
    
    <item>
      <title>Color scheme switching for WezTerm</title>
      <link>https://sebastian-hans.de/blog/color-scheme-switching-for-wezterm/</link>
      <description>&lt;p&gt;I really like &lt;a href=&quot;https://wezterm.org/&quot;&gt;WezTerm&lt;/a&gt;, a cross-platform terminal emulator and multiplexer
I have been using for some time now.
What particularly drew me to it was the combination of&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cross-platform support,&lt;/li&gt;
&lt;li&gt;tabbing,&lt;/li&gt;
&lt;li&gt;support for panes,&lt;/li&gt;
&lt;li&gt;ligatures, and&lt;/li&gt;
&lt;li&gt;powerful customization with Lua.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WezTerm also supports color schemes, and I use one based on the beautiful
&lt;a href=&quot;https://github.com/jan-warchol/selenized&quot;&gt;Selenized&lt;/a&gt; color palette, in
particular Selenized black &amp;amp; white.
While WezTerm comes with &lt;a href=&quot;https://wezterm.org/colorschemes/index.html&quot;&gt;&lt;em&gt;a lot of&lt;/em&gt; color schemes&lt;/a&gt;,
at the time of writing, support for Selenized Black and Selenized White is only
present out of the box in the nightly builds (via &lt;a href=&quot;https://github.com/Gogh-Co/Gogh&quot;&gt;Gogh&lt;/a&gt;).
This is why I wrote my own color scheme definitions and put them in
&lt;code&gt;~/config/wezterm-color-schemes&lt;/code&gt; so they can be picked up by WezTerm.
Here they are.&lt;/p&gt;
&lt;h2 id=&quot;selenized-black-%26-white&quot; tabindex=&quot;-1&quot;&gt;Selenized black &amp;amp; white&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Selenized Black.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;colors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;ansi&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#252525&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#ed4a46&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#70b433&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#dbb32d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#368aeb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#eb6eb7&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#3fc5b7&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#777777&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;brights&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#3b3b3b&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#ff5e56&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#83c746&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#efc541&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#4f9cfe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#ff81ca&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#56d8c9&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token string&quot;&gt;&quot;#dedede&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;background&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#181818&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;foreground&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#b9b9b9&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;cursor_bg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#83c746&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;cursor_border&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#83c746&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;cursor_fg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#181818&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;selection_bg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#b9b9b9&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;selection_fg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#181818&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;colors.indexed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;aliases&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Selenized Black&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Selenized White.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;colors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;ansi&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#ebebeb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#d6000c&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#1d9700&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#c49700&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#0064e4&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#dd0f9d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#00ad9c&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#878787&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;brights&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#cdcdcd&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#bf0000&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#008400&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#af8500&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#0054cf&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#c7008b&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#009a8a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;#282828&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;background&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#ffffff&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;foreground&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#474747&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;cursor_bg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#008400&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;cursor_border&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#008400&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;cursor_fg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#ffffff&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;selection_bg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#474747&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;selection_fg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#ffffff&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;colors.indexed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;aliases&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Selenized White&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;switching-between-color-schemes&quot; tabindex=&quot;-1&quot;&gt;Switching between color schemes&lt;/h2&gt;
&lt;p&gt;This is all well and good.
However, another thing WezTerm is missing out of the box is an easy way to switch color schemes.
I mostly use dark colors, but sometimes I want to put a screenshot of my
terminal in a document and want it to look bright, or I am sharing my screen
with someone who can read better when the text is dark on light, so having a convenient
way to switch betwwen dark and light modes is useful to me.&lt;/p&gt;
&lt;p&gt;Fortunately, WezTerm is highly configurable, so I was able to implement the
toggle myself. Here is a minimal, but heavily commented, WezTerm configuration
that shows my solution.
I have included links to the relevant WezTerm documentation so you can read up
on what each of the parts does if you wish.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wezterm.org/config/files.html&quot;&gt;Standard preamble&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; wezterm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; require &lt;span class=&quot;token string&quot;&gt;&#39;wezterm&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;config_builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look up home directory to construct some paths
(this is &lt;a href=&quot;https://www.lua.org/manual/5.3/manual.html#pdf-os.getenv&quot;&gt;standard Lua&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; home &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;HOME&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WezTerm will look for color scheme files in the directories in
&lt;a href=&quot;https://wezterm.org/config/appearance.html#defining-a-color-scheme-in-a-separate-file&quot;&gt;&lt;code&gt;color_scheme_dirs&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;color_scheme_dirs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; home &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/config/wezterm-color-schemes&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We will persist the name of the current color scheme in &lt;code&gt;~/.color-scheme&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; color_scheme_file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; home &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/.color-scheme&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We will toggle between these two color schemes:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; default_color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Selenized Black&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; alternate_color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Selenized White&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, these are the custom schemes described above, but they don&#39;t have to
be. You can name any color scheme WezTerm is able to find, custom or built in.&lt;/p&gt;
&lt;p&gt;Function to &lt;a href=&quot;https://www.lua.org/manual/5.3/manual.html#pdf-io.open&quot;&gt;load&lt;/a&gt; the name of the current color scheme,
falls back to &lt;code&gt;default_color_scheme&lt;/code&gt; if the file is not present:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;load_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; default_color_scheme
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color_scheme_file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;r&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; file &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;*a&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; color_scheme
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function to &lt;a href=&quot;https://www.lua.org/manual/5.3/manual.html#pdf-io.open&quot;&gt;save&lt;/a&gt; the name of the current color scheme:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;save_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color_scheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color_scheme_file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;w+&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; file &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color_scheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; color_scheme
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make WezTerm use the saved color scheme by assigning it to
&lt;a href=&quot;https://wezterm.org/config/appearance.html#color-scheme&quot;&gt;&lt;code&gt;color_scheme&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;load_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an event handler for the &lt;a href=&quot;https://wezterm.org/config/lua/wezterm/on.html#custom-events&quot;&gt;custom event&lt;/a&gt;
&lt;code&gt;toggle-color-scheme&lt;/code&gt;.
It determines which scheme to switch to and saves it.&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;toggle-color-scheme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_window&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _pane&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;load_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; new_color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; default_color_scheme
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; color_scheme &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; default_color_scheme &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    new_color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; alternate_color_scheme
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;save_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;new_color_scheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use &lt;code&gt;Ctrl-b&lt;/code&gt; as the &lt;a href=&quot;https://wezterm.org/config/keys.html#leader-key&quot;&gt;leader key&lt;/a&gt;,
i. e., as a prefix for my custom keyboard shortcuts:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;leader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;b&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CTRL&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout_milliseconds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we &lt;a href=&quot;https://wezterm.org/config/keys.html#configuring-key-assignments&quot;&gt;configure the key binding&lt;/a&gt;
to toggle the color scheme.
Specifying both &lt;code&gt;LEADER&lt;/code&gt; and &lt;code&gt;CTRL&lt;/code&gt; makes it so I have to press the
leader (&lt;code&gt;Ctrl-b&lt;/code&gt;), followed by &lt;code&gt;Ctrl-c&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;keys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;c&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    mods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LEADER|CTRL&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;a href=&quot;https://wezterm.org/config/lua/keyassignment/Multiple.html&quot;&gt;&lt;code&gt;Multiple&lt;/code&gt;&lt;/a&gt;
to trigger two actions when the key combination is pressed:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;    action &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Multiple&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First action: &lt;a href=&quot;https://wezterm.org/config/lua/keyassignment/EmitEvent.html&quot;&gt;fire the event&lt;/a&gt;
that will save the new color scheme:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;      wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EmitEvent &lt;span class=&quot;token string&quot;&gt;&#39;toggle-color-scheme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second action: make WezTerm
&lt;a href=&quot;https://wezterm.org/config/lua/keyassignment/ReloadConfiguration.html&quot;&gt;reload its configuration&lt;/a&gt;,
thus activating the new color scheme:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;      wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ReloadConfiguration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, return the configuration to WezTerm:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; config&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is the entire thing:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; wezterm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; require &lt;span class=&quot;token string&quot;&gt;&#39;wezterm&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;config_builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; home &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;HOME&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;color_scheme_dirs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; home &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/config/wezterm-color-schemes&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; color_scheme_file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; home &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/.color-scheme&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; default_color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Selenized Black&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; alternate_color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Selenized White&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;load_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; default_color_scheme
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color_scheme_file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;r&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; file &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;*a&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; color_scheme
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;save_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color_scheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color_scheme_file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;w+&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; file &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;color_scheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    file&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; color_scheme
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;load_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;toggle-color-scheme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_window&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _pane&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;load_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;local&lt;/span&gt; new_color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; default_color_scheme
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; color_scheme &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; default_color_scheme &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    new_color_scheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; alternate_color_scheme
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;save_color_scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;new_color_scheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;leader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;b&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CTRL&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout_milliseconds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;keys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;c&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    mods &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LEADER|CTRL&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    action &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Multiple&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EmitEvent &lt;span class=&quot;token string&quot;&gt;&#39;toggle-color-scheme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      wezterm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ReloadConfiguration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; config&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Saving this to your home directory as &lt;code&gt;.wezterm.lua&lt;/code&gt; sets you up to toggle between
light and dark mode by pressing &lt;code&gt;Ctrl-b Ctrl-c&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using this combination of persisting the color scheme and config reload means
that the change applies to all open WezTerm panes and windows (because they all
belong to the same WezTerm instance).&lt;/p&gt;
&lt;h2 id=&quot;switching-the-current-pane-only&quot; tabindex=&quot;-1&quot;&gt;Switching the current pane only&lt;/h2&gt;
&lt;p&gt;I also wrote a script to change the color
scheme for the current pane, but this one only works for my custom color schemes
because it reads the TOML files and outputs the escape sequences to change the
terminal color palette. The script expects to live in the same directory as the
TOML files and requires &lt;a href=&quot;https://github.com/gnprice/toml-cli&quot;&gt;toml-cli&lt;/a&gt; to work.
It does not toggle between two color schemes, but expects a color scheme name as
an argument.
Here it is:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/zsh&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$2&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Usage: &lt;span class=&quot;token variable&quot;&gt;$0&lt;/span&gt; COLOR_SCHEME_NAME&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;SCHEME&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;HERE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dirname&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$0&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;TOML&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$HERE&lt;/span&gt;/&lt;span class=&quot;token variable&quot;&gt;$SCHEME&lt;/span&gt;.toml&quot;&lt;/span&gt; 
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$TOML&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Color scheme not found at &lt;span class=&quot;token variable&quot;&gt;$TOML&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; toml get &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$TOML&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colors.ansi[&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;]&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; toml get &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$TOML&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colors.brights[&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$((&lt;/span&gt;$&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;))&lt;/span&gt;&lt;/span&gt;]&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    *&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; toml get &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$TOML&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colors.&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;esac&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-en&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;&#92;&quot;&gt;&#92;&#92;&lt;/span&gt;e]4;0;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;1;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;2;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;3;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;4;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;5;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;6;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;7;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;8;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;9;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;10;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;11;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;12;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;13;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;14;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;15;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token entity&quot; title=&quot;&#92;&#92;&quot;&gt;&#92;&#92;&lt;/span&gt;a&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-en&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;&#92;&quot;&gt;&#92;&#92;&lt;/span&gt;e]10;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color foreground&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color background&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color cursor_bg&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token entity&quot; title=&quot;&#92;&#92;&quot;&gt;&#92;&#92;&lt;/span&gt;a&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-en&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;&#92;&quot;&gt;&#92;&#92;&lt;/span&gt;e]17;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color selection_bg&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token entity&quot; title=&quot;&#92;&#92;&quot;&gt;&#92;&#92;&lt;/span&gt;a&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-en&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token entity&quot; title=&quot;&#92;&#92;&quot;&gt;&#92;&#92;&lt;/span&gt;e]19;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;color selection_fg&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token entity&quot; title=&quot;&#92;&#92;&quot;&gt;&#92;&#92;&lt;/span&gt;a&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It have not yet created a key binding to invoke this script, but it should be
possible with a bit of Lua code (emit a custom event and handle it by executing the
script in a subprocess, making sure the output goes to the correct pane).&lt;/p&gt;
&lt;h2 id=&quot;where-to-now%3F&quot; tabindex=&quot;-1&quot;&gt;Where to now?&lt;/h2&gt;
&lt;p&gt;For now, I am happy with my setup.
I can toggle between dark and light modes with a keybinding.
Possible extensions I have thought about, but not yet implemented are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;toggling the current pane via a key binding,&lt;/li&gt;
&lt;li&gt;cover more color options with the color schemes (e. g., &lt;a href=&quot;https://wezterm.org/config/appearance.html#tab-bar-appearance-colors&quot;&gt;tab bar colors&lt;/a&gt;),&lt;/li&gt;
&lt;li&gt;integrate color schemes into the &lt;a href=&quot;https://wezterm.org/config/launch.html#the-launcher-menu&quot;&gt;launcher menu&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sat, 15 Feb 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/color-scheme-switching-for-wezterm/</guid>
    </item>
    
    
    <item>
      <title>Mouse follows window GNOME extension</title>
      <link>https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/</link>
      <description>&lt;h2 id=&quot;tl%3Bdr&quot; tabindex=&quot;-1&quot;&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;I wrote a GNOME extension to make &lt;em&gt;focus follows mouse&lt;/em&gt; play nicely with
keyboard window manipulation shortcuts.
the
&lt;a href=&quot;https://github.com/sebhans/mfw-gnome-extension&quot;&gt;Mouse follows window GNOME extension&lt;/a&gt;.
This post gives a bit of background and walks you through the implementation.&lt;/p&gt;
&lt;p&gt;Building stuff to make my life easier is fun!
You can do it, too!&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot; tabindex=&quot;-1&quot;&gt;The problem&lt;/h2&gt;
&lt;p&gt;When I changed my employer at the beginning of the year,
I was happy that they let me have a laptop with Linux on it.
It came with &lt;a href=&quot;https://ubuntu.com/download/desktop&quot;&gt;Ubuntu&lt;/a&gt; 22.04 preinstalled,
which is not the most recent version, but since this is the only Linux
distribution and version supported by the IT department, I&#39;m living with it.
Ubuntu 22.04 comes with &lt;a href=&quot;https://www.gnome.org/&quot;&gt;GNOME&lt;/a&gt; 42.9 by default.
At first, I thought I might install &lt;a href=&quot;https://i3wm.org/&quot;&gt;i3wm&lt;/a&gt;,
which is what I use on my personal machine, but actually, GNOME isn&#39;t too bad,
and some things just work out of the box, so I decided to stick with it.&lt;/p&gt;
&lt;p&gt;One thing I really like about i3wm, a tiling window manager, is the ability to
shuffle windows around with keyboard shortcuts.
And &lt;em&gt;focus follows mouse&lt;/em&gt;.
I really can&#39;t use a desktop environment where I have to click on windows to
focus them. I want to point my mouse at a window and start typing into it.
When using the laptop keyboard, this is just a quick movement with my thumb over
the touchpad.&lt;/p&gt;
&lt;p&gt;Luckily, GNOME supports focus follows mouse and has shortcuts for what
they call ‘split left’ and ‘split right’, which means make a window cover the
left (or right) half of the screen, as well as maximizing and minimizing
windows and moving them between monitors.
So far, so good.&lt;/p&gt;
&lt;p&gt;Unfortunately, these two features do not play well together.
When I am working in a window on the left half of the screen
and split it to the right (e.g., because I want to look at something in a window
underneath the active window), the mouse pointer stays on the left half of the
screen, and the focus switches to whatever is underneath it now.
This is typically not what I want.
What I really want is for the mouse pointer to move with the window –
and the focus to stay in the window.
This is the behaviour of i3wm, and I want to replicate it in GNOME.&lt;/p&gt;
&lt;p&gt;Also, sometimes splitting a window to a half of the screen that is already
occupied by another window results in the moved window being hidden &lt;em&gt;behind&lt;/em&gt; the
other window.
I guess the compositor doesn&#39;t change the z index when moving a window this way.
This annoys me, and I want to fix this, too.&lt;/p&gt;
&lt;h2 id=&quot;the-solution&quot; tabindex=&quot;-1&quot;&gt;The solution&lt;/h2&gt;
&lt;p&gt;After some searching around, I decided that what I needed was a GNOME extension
that watched for these window ‘split’ operations and would then warp the mouse
pointer so it stayed in the window.&lt;/p&gt;
&lt;p&gt;Another option would have been to implement the ‘split’ operation myself and
move the window and the mouse pointer together.
I may still do that because it would allow me to add custom window positions,
but for the moment, left and right half are enough for me, and I want to keep
things simple.&lt;/p&gt;
&lt;p&gt;Before I come to the implementation, there is one important detail to consider:
the system is running Wayland.&lt;/p&gt;
&lt;h2 id=&quot;interlude%3A-mouse-pointer-warping-in-wayland&quot; tabindex=&quot;-1&quot;&gt;Interlude: Mouse pointer warping in Wayland&lt;/h2&gt;
&lt;p&gt;Wayland is &lt;a href=&quot;https://www.linuxmo.com/wayland-vs-x11-the-battle-of-display-protocols/&quot;&gt;more secure than X11&lt;/a&gt;,
and one consequence of this is that moving the mouse pointer around programmatically is
&lt;a href=&quot;https://www.reddit.com/r/gnome/comments/epzq6f/how_can_i_move_pointer_programmatically_in_wayland/&quot;&gt;not that&lt;/a&gt;
&lt;a href=&quot;https://stackoverflow.com/questions/75440313/automate-mouse-pointer-motion-in-wayland-on-debian-not-x11-x-window-system&quot;&gt;simple&lt;/a&gt;.
Basically, you have to emulate an input device, which is a bit more work than I
wanted to take on for such a small feature.&lt;/p&gt;
&lt;p&gt;Fortunately, others have already done the work.
There are tools that do exactly this.
They emulate input devices and generate input events.
One such tool is &lt;a href=&quot;https://github.com/ReimuNotMoe/ydotool&quot;&gt;&lt;code&gt;ydotool&lt;/code&gt;&lt;/a&gt;.
Another is &lt;a href=&quot;https://github.com/tio/input-emulator&quot;&gt;&lt;code&gt;input-emulator&lt;/code&gt;&lt;/a&gt;.
Both offer a CLI that accepts coordinates and moves the mouse pointer.&lt;/p&gt;
&lt;p&gt;For my use case, &lt;code&gt;ydotool&lt;/code&gt; would have been better because it supports absolute
positioning: give it an x/y coordinate, and it will warp the mouse pointer to
this position.
When I tried &lt;code&gt;ydotool&lt;/code&gt;, however, it always moved the mouse pointer to the left
margin of the screen. The vertical position (y) was correct, but x was always 0,
no matter how I called it.
When I tried &lt;code&gt;ydotoold&lt;/code&gt;, a daemon belonging to &lt;code&gt;ydotool&lt;/code&gt; that provides a persistent
virtual input device in the background, it segfaulted on me.
Rather than trying to analyze this, I decided to go with &lt;code&gt;input-emulator&lt;/code&gt; instead,
which did work.
The only downside is that &lt;code&gt;input-emulator&lt;/code&gt; only supports relative positioning,
i.e., I cannot simply say, ‘go there’. I have to calculate the target position relative
to the current position of the mouse pointer.&lt;/p&gt;
&lt;p&gt;Now that I know how to control the mouse pointer, I can implement the extension.&lt;/p&gt;
&lt;h2 id=&quot;the-implementation&quot; tabindex=&quot;-1&quot;&gt;The implementation&lt;/h2&gt;
&lt;p&gt;You can find the implementation at
&lt;a href=&quot;https://github.com/sebhans/mfw-gnome-extension&quot;&gt;github.com/sebhans/mfw-gnome-extension&lt;/a&gt;.
It is JavaScript and basically consists of a little boilerplate and a single class with about 100 SLOC.&lt;/p&gt;
&lt;p&gt;The idea is to track all window geometries (positions and sizes) and which window is focused.
When the geometry of the focused window changes &lt;em&gt;and&lt;/em&gt; the change
is larger than 50 pixels, we assume the window has been moved by a keyboard
shortcut&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;
and check the position of the mouse pointer.
If the mouse pointer is still inside the window, we do nothing
(since the window will still have the focus, there is no need to make the
pointer jump around).
If the mouse pointer is outside the window, however, we move it to the center of
the window.
And we also ensure that the window is in the foreground to get rid of the ‘hidden
window’ phenomenon mentioned above.&lt;/p&gt;
&lt;p&gt;Watching for position/size jumps instead of just checking whether the window
covers half the screen lets the extension handle other window movements that
would result in focus loss, like unmaximizing a window and moving a window to
another monitor, without extra effort.&lt;/p&gt;
&lt;p&gt;Here is a walkthrough of its methods
(leaving out some debug statements).&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowCreatedSignal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowSignals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowGeometries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;focusedWindowId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The constructor initializes the data structures we are going to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;windowCreatedSignal&lt;/code&gt; and &lt;code&gt;windowSignals&lt;/code&gt; will keep information about the
signals we use to track windows. More on this later.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;windowGeometries&lt;/code&gt; will keep track of the geometries (positions and sizes) of
all windows.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;focusedWindowId&lt;/code&gt; will track the ID of the currently focused window.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;enable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  Util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;spawn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/usr/local/bin/input-emulator&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&#39;start&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;mouse&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&#39;--x-max&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;5000&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&#39;--y-max&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;5000&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowCreatedSignal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    global&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&#39;window-created&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_onWindowCreated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_connectAllWindows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method is called when the extension is enabled.
It sets up &lt;code&gt;input-emulator&lt;/code&gt; to emulate a mouse.
This starts a background process that handles the virtual device and allows us
to perform pointer movements later on.
The parameters &lt;code&gt;--x-max&lt;/code&gt; and &lt;code&gt;--y-max&lt;/code&gt; specify the maximum coordinates to use.
The default is too small for my monitors. 5000 for each is somewhat arbitrary,
but I use the laptop in various setups with external monitors with different
resolutions, and 5000 works for me.&lt;/p&gt;
&lt;p&gt;The method also registers &lt;code&gt;_onWindowCreated()&lt;/code&gt; as a signal handler that will be called
whenever a new window is created (so we can track it)
and immediately begins to track all already existing windows with the helper
method &lt;code&gt;_connectAllWindows()&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;_connectAllWindows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  global&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_window_actors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;actor&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_connectWindowSignals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;actor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;meta_window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;_connectWindowSignals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowSignals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; signals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&#39;focus&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_onFocusWindowChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&#39;position-changed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_onWindowChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&#39;size-changed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_onWindowChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowSignals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; signals&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Together, these two methods set up signal handlers for every existing window.
As a result, the handler &lt;code&gt;_onFocusWindowChanged()&lt;/code&gt; will be called whenever
another window receives the focus,
and the handler &lt;code&gt;_onWindowChanged()&lt;/code&gt; will be called whenever a window changes
its position or size.
The size is relevant because the window could already be at the correct
position, but still lose focus, e.g., because it changes from maximized
(at position 0/0) to unmaximized, still at position 0/0, but much smaller, so
that the mouse pointer is now outside the window.&lt;/p&gt;
&lt;p&gt;The signals returned by the &lt;code&gt;connect()&lt;/code&gt; method are saved in the map &lt;code&gt;windowSignals&lt;/code&gt;
so we can clean them up later (as is the global &lt;code&gt;&#39;window-created&#39;&lt;/code&gt; signal in the
&lt;code&gt;enable()&lt;/code&gt; method above).&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;disable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowCreatedSignal&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    global&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowCreatedSignal&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowCreatedSignal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_disconnectAllWindows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowGeometries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  Util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;spawn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/usr/local/bin/input-emulator&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&#39;stop&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;mouse&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;_disconnectAllWindows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowSignals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;signals&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      signals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;signalId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;signalId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowSignals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The method &lt;code&gt;disable()&lt;/code&gt; is called when the extension is disabled.
It is supposed to free all resources used by the extension,
which in our case means disconnecting all signal handlers we set up
in &lt;code&gt;enable()&lt;/code&gt; and &lt;code&gt;_connectWindowSignals()&lt;/code&gt;
and stopping the &lt;code&gt;input-emulator&lt;/code&gt; background process responsible for our virtual
mouse.&lt;/p&gt;
&lt;p&gt;So far, so good.
The extension will now come up, register all necessary handlers to be notified
of window changes, and shut down gracefully.
But up to now, it can only handle windows that existed when the extension was
enabled (which will probably be none if the extension is loaded at the start of
the GNOME session).
We need to change that.
This is what the &lt;code&gt;_onWindowCreated()&lt;/code&gt; handler is for.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;_onWindowCreated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;display&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_window_type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; Meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WindowType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;MENU&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; Meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WindowType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DROPDOWN_MENU&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; Meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WindowType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;POPUP_MENU&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; Meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WindowType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TOOLTIP&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; Meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WindowType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NOTIFICATION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; Meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WindowType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;COMBO&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_connectWindowSignals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowGeometries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_frame_rect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This handler is called whenever a new window is created.
We want to set up our window handler for this new window, too
(and remember its geometry while we are at it).
There&#39;s just one minor complication: there are several types of windows,
and not all of them are relevant for us.
In my first experiments, I didn&#39;t pay attention to the window type
and was surprised when the mouse pointer jumped around while I tried to delete a
row in a table in LibreOffice Writer via the context menu.
I don&#39;t know exactly why, but menus, tooltips and the like tend to trigger our
‘jumping window’ reaction, and since shooting those kinds of windows around the
screen via keyboard doesn&#39;t make sense anyway, we may as well filter them out
right from the start.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;_onFocusWindowChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;focusedWindowId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This handler is called whenever a window receives the focus and just remembers
its ID for later.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;_onWindowChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; frame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_frame_rect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;focusedWindowId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowGeometries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      window&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; oldFrame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowGeometries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oldFrame &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_hasWarped&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; oldFrame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_ensureMouseIsIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowGeometries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This handler is called whenever a window changes its position or size.
We only need to &lt;em&gt;do&lt;/em&gt; anything interesting if it is the focused window
(this check is what we kept track of this for).
If it is not, we just remember its new geometry and return immediately.&lt;/p&gt;
&lt;p&gt;If it is, we check to see whether the window has jumped (‘warped’ I called it in
the code) and if so, we call a helper method to move the mouse pointer if
necessary.
And we also remember its new geometry.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WARP_DISTANCE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;_hasWarped&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; oldFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; oldFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WARP_DISTANCE&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; oldFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WARP_DISTANCE&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; oldFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WARP_DISTANCE&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; oldFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;WARP_DISTANCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_hasWarped()&lt;/code&gt; implements our warp detection.
We consider the window to have warped if any of its coordinates or dimensions
has changed by at least our 50 pixel threshold.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;_ensureMouseIsIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; frame &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_frame_rect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;mouse_x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mouse_y&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; global&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_pointer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mouse_x &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      mouse_x &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      mouse_y &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      mouse_y &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;activate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;global&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_current_time&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; target_x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; target_y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; target_x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; mouse_x&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; target_y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; mouse_y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  Util&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;spawn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/usr/local/bin/input-emulator&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token string&quot;&gt;&#39;mouse&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;move&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;dx&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;dy&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the real meat of the extension.
This function ensures that the mouse pointer is in the newly warped window.
First, we check whether the pointer is still inside by comparing its coordinates
with the area covered by the window&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;.
If so, there is nothing to do and we return immediately.&lt;/p&gt;
&lt;p&gt;If the coordinates of the mouse pointer are outside the window,
we first activate it, which includes bringing it to the foreground
to that it cannot be hidden behind another window.
Then we calculate the target coordinates (where we want the mouse pointer to be)
at the center of the window
and the difference of this position to the current position of the mouse pointer
(how far we have to move it).
And lastly, we call &lt;code&gt;input-emulator&lt;/code&gt; to perform the actual movement.&lt;/p&gt;
&lt;p&gt;That&#39;s it for the code.
I noticed some improvements I could have made while writing this post&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;,
but I&#39;m letting it stand for now.
I&#39;ve been using the extension for a whole workday now, and I&#39;m pretty pleased
with it.&lt;/p&gt;
&lt;h2 id=&quot;limitations&quot; tabindex=&quot;-1&quot;&gt;Limitations&lt;/h2&gt;
&lt;p&gt;I wrote this extension to scratch an itch with minimal effort.
As a result, it is narrowly tailored to my use case.
It is also the first GNOME extension I have ever written,
which is certainly not without consequence.
These are the limitations I am currently aware of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GNOME 42.9&lt;/strong&gt;&lt;br /&gt;
I wrote this for GNOME 42.9 because that is what I have on my work laptop.
Extensions work differently in GNOME &amp;gt;= 45,
so the extension probably requires changes to be useable with recent GNOME
versions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard-coded path to &lt;code&gt;input-emulator&lt;/code&gt;&lt;/strong&gt;&lt;br /&gt;
I know how I set up &lt;code&gt;input-emulator&lt;/code&gt; on my laptop,
so this is not a concern for me.
Making the path configurable
(or even bundling the tool with the extension)
would make reuse simpler&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interference&lt;/strong&gt;&lt;br /&gt;
The extension&#39;s use of &lt;code&gt;input-emulator&lt;/code&gt; might interfere with other uses of
this tool.
I don&#39;t use it for anything else, so this doesn&#39;t bother me.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory leak&lt;/strong&gt;&lt;br /&gt;
As of now, I haven&#39;t been able to find a signal that tells me when a window has been
destroyed, so windows are never removed from the maps &lt;code&gt;windowSignals&lt;/code&gt; and
&lt;code&gt;windowGeometries&lt;/code&gt;.
This means that these maps will accumulate obsolete entries, resulting in a
memory leak.
I have not yet managed to create enough windows for this to be a noticeable problem,
but I still don&#39;t like it.
I&#39;m sure there is a way to be notified on window destruction, I just haven&#39;t
found it yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pointer bumping into screen edges&lt;/strong&gt;&lt;br /&gt;
Sometimes when moving a window to another monitor, the mouse pointer bumps into
the edge between the monitors and does not reach the target coordinate.
I assume this is because my laptop screen and the external monitor have
different resolutions and thus the combined desktop is not a perfect
rectangle, which means that there are pairs of coordinates that cannot be
connected by a straight line without leaving the desktop.
I further assume that the mouse emulation performs pointer movements by
simulating mouse movements in straight lines, which does not work in these –
hah – edge cases.
I have some ideas for how to address this,
but this happens rarely enough that I&#39;m not yet sure whether it is worth the
bother.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot; tabindex=&quot;-1&quot;&gt;Resources&lt;/h2&gt;
&lt;p&gt;Here is a collection of links I found useful while I tried to get the extension
working (in addition to those mentioned above):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gjs.guide/extensions/upgrading/legacy-documentation.html&quot;&gt;GNOME extension documentation (legacy version for GNOME &amp;lt;= 44)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gjs-docs.gnome.org/&quot;&gt;API documentation for GNOME applications in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Especially the &lt;a href=&quot;https://gjs-docs.gnome.org/meta10~10/&quot;&gt;&lt;code&gt;Meta.*&lt;/code&gt; classes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/do-sch/gnome-shell-touchpad-window-move&quot;&gt;gnome-shell-touchpad-window-move&lt;/a&gt;
– as a working example of a GNOME extension; I didn&#39;t use any code from there,
but I got a feel for how extensions work&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While the extension is very basic and limited in several ways,
it has improved my daily life a lot – and I write this on the first day of using
it.
I&#39;m glad I went to the trouble.
Now that I know how GNOME extensions work,
I&#39;m probably going to tweak my desktop even more.&lt;/p&gt;
&lt;p&gt;I firmly believe that computers are there to support people and not the other
way around.
If my interaction with the computer is not satisfactory,
it is the computer that has to change, not me.
And the best thing about being a developer in an open source environment is that
I &lt;em&gt;can&lt;/em&gt; change it.&lt;/p&gt;
&lt;p&gt;And so can you.
What&#39;s your itch, and will you scratch it?&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Experimentation has shown that the changes when dragging or resizing a
window with the mouse are reported at a pretty fine granularity, even when
I am moving the mouse as fast as I can. In practice, 50 pixels work very well as
a threshold. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;For the window frame, &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are the
coordinates of the top left corner, so &lt;code&gt;x+width&lt;/code&gt; and &lt;code&gt;y+height&lt;/code&gt; are the
coordinates of the bottom right corner. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Use
&lt;code&gt;const&lt;/code&gt; more, for example, and apply the window type filter to the initial
connection, too. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;But not trivial.
&lt;code&gt;input-emulator&lt;/code&gt; requires access to &lt;code&gt;/dev/uinput&lt;/code&gt;,
which is restriced to &lt;code&gt;root&lt;/code&gt; by default.
See the &lt;a href=&quot;https://github.com/tio/input-emulator#42-set-up-permissions-for-devuinput&quot;&gt;instructions coming with &lt;code&gt;input-emulator&lt;/code&gt;&lt;/a&gt;
for how to set this up. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Sat, 25 Jan 2025 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/mouse-follows-window-gnome-extension/</guid>
    </item>
    
    
    <item>
      <title>The Euro pallet</title>
      <link>https://sebastian-hans.de/blog/the-euro-pallet/</link>
      <description>&lt;p&gt;My wife brought home a Euro pallet from work&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/the-euro-pallet/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
She thought we could use it as an elevated platform for stuff in our cellar that
shouldn&#39;t get wet in case our cellar got flooded.
It was a good idea because there actually was a large number of flooded cellars in
our neighbourhood this year.
(Not ours, though.)&lt;/p&gt;
&lt;p&gt;Anyway, we didn&#39;t immediately have something to stack on it,
so we put in in the garage.
Here it is.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Photo of a Euro pallet in my garage. It is leaning against the side, narrowing the passageway beside my car.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/the-euro-pallet-300w.jpeg?v=b9575c595c0d&quot; width=&quot;900&quot; height=&quot;506&quot; srcset=&quot;https://sebastian-hans.de/img/the-euro-pallet-300w.jpeg?v=b9575c595c0d 300w, https://sebastian-hans.de/img/the-euro-pallet-600w.jpeg?v=34367f6866ca 600w, https://sebastian-hans.de/img/the-euro-pallet-900w.jpeg?v=c6eefce8a6a0 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
The Euro pallet in my garage.
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;It has been leaning there ever since.&lt;/p&gt;
&lt;h2 id=&quot;so-what%3F&quot; tabindex=&quot;-1&quot;&gt;So what?&lt;/h2&gt;
&lt;p&gt;Today, while carrying some boxes into the cellar&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/the-euro-pallet/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;,
I was slightly annoyed by the pallet because
it was partially blocking the passageway beside the car.
This got me thinking about the pallet:
maybe I should put it to use? Find space for it in the cellar and stack some boxes on it?
Not today.
I had other things to do, and the boxes I was carrying went onto the shelves
anyway.&lt;/p&gt;
&lt;p&gt;The pallet remains in the garage, and I will probably get annoyed at it a few
more times.
Will we ever put anything on it?
Maybe, maybe not.
Doing so would require us to reorganize the space in our cellar
(and, yes, tidy it up a little).
So it is likely that we won&#39;t get around to it for some time.
Maybe never.
Or at least not before something happens that causes us to throw it out,
which amounts to the same thing.&lt;/p&gt;
&lt;p&gt;But – and this is a big but – if we don&#39;t actually put something on it,
the pallet does not have any value
since its intended purpose was to protect our stuff from flooding.
If our cellar were to be flooded now, the pallet would protect exactly …
nothing.&lt;/p&gt;
&lt;p&gt;Worse, it even has &lt;em&gt;negative&lt;/em&gt; value because it gets in my way when I&#39;m doing
things like carrying boxes around.
This was certainly not what we thought when my wife brought the pallet home.
We thought it was a good idea.
And it would have been if we had followed through.
But we didn&#39;t.
And right now I am busy writing a blog post when I could be in the
cellar instead, realizing the value of the pallet.&lt;/p&gt;
&lt;p&gt;On the other hand, I don&#39;t really know what &lt;em&gt;specifically&lt;/em&gt; I should put on the
pallet.
So maybe a better action would be to throw it away.
This at least would deliver value immediately by reducing my annoyance.&lt;/p&gt;
&lt;p&gt;As it is, we put work into a good idea (bringing the pallet home)
but failed to get to the point where we would have gotten value out of it
(by having stuff protected), resulting in low-grade annoyance and extra work
instead (having to lift boxes &lt;em&gt;over&lt;/em&gt; the leaning pallet and taking special care
lest it trip me up).&lt;/p&gt;
&lt;h2 id=&quot;software-euro-pallets&quot; tabindex=&quot;-1&quot;&gt;Software Euro pallets&lt;/h2&gt;
&lt;p&gt;Stuff like this happens in software, too.
When I joined the team responsible for an internal multi-client capable payment API,
the component had a database table that assigned clients to payment service providers.
The intention was to simplify changes should they become necessary.
Reassignment could be done at runtime by simply changing the rows in the table.
In practice, all clients were assigned to the same payment service providers.
There was only one exception, and it required extra implementation, so it was
&lt;em&gt;not&lt;/em&gt; a simple runtime reconfiguration.&lt;/p&gt;
&lt;p&gt;Testing the code responsible for the client/provider assignment required us to
implement fake payment service provider adapters because we didn&#39;t have
any alternate payment service providers that could be reasonably substituted,
even for test purposes.
Implementation, test – and maintenance – of this feature was not zero-cost.&lt;/p&gt;
&lt;p&gt;The first situation where we actually could have used it was the
replacement of our payment service provider for SEPA direct debit&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/the-euro-pallet/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
We could have implemented a new adapter for the new provider and switched our
clients over one by one by changing entries in the database table.
However, by this time, we had almost half a million customers using SEPA and wanted more
control over the migration process.
So we implemented a switch per user instead.
And removed the client/provider assignment for good.&lt;/p&gt;
&lt;p&gt;This feature was the software dev counterpart to the Euro pallet:
it was a good idea, but we didn&#39;t get any real value out of it and still had
to deal with the overhead it introduced, causing low-grade annoyance and extra work
(testing, maintenance, additional conceptual work for every other feature to
ensure it played well with this one).
The best course of action was to remove it.&lt;/p&gt;
&lt;h2 id=&quot;how-about-you%3F&quot; tabindex=&quot;-1&quot;&gt;How about you?&lt;/h2&gt;
&lt;p&gt;Do you recognize this situation?
Did you ever implement something that seemed a good idea
but remained unused and thus generated zero or even negative value?&lt;/p&gt;
&lt;p&gt;What would you have to do to get the value you originally thought you would?
Are you actually going to do it?&lt;/p&gt;
&lt;p&gt;Which features are in your software &lt;em&gt;now&lt;/em&gt; that would increase the software&#39;s
value if they were removed?&lt;/p&gt;
&lt;p&gt;What is your personal Euro pallet?&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;It would have been thrown away
otherwise, so she was allowed to ‘rescue’ it. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/the-euro-pallet/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;You can see part of the door
to the cellar in the background of the photo. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/the-euro-pallet/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I spoke
about this on last year&#39;s WeAreDevelopers DevOps Day in my talk
&lt;a href=&quot;https://www.wearedevelopers.com/en/videos/730/migrating-half-a-million-users-to-a-new-payment-service-provider&quot;&gt;Migrating half a million users to a new payment service provider&lt;/a&gt; (free account required to watch). &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/the-euro-pallet/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Sun, 29 Dec 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/the-euro-pallet/</guid>
    </item>
    
    
    <item>
      <title>On planning, part 4: the critical path</title>
      <link>https://sebastian-hans.de/blog/on-planning-how-3/</link>
      <description>&lt;p&gt;This is the fourth post in my series about planning in the context of agile teams.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-why/&quot;&gt;Part 1&lt;/a&gt; is concerned with why you should plan at all.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-1/&quot;&gt;Part 2&lt;/a&gt; describes my iterative approach to end-to-end planning.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/&quot;&gt;Part 3&lt;/a&gt; shows how I do forwards and backwards planning.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-4/&quot;&gt;Part 5&lt;/a&gt; argues for more exploration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post, I will explore the concept of the critical path.&lt;/p&gt;
&lt;h2 id=&quot;it-doesn&#39;t-fit!&quot; tabindex=&quot;-1&quot;&gt;It doesn&#39;t fit!&lt;/h2&gt;
&lt;p&gt;So I have come up with a rough plan using forwards and backwards planning,
and I have tried to map it onto a timeline. Surprise, surprise!
It turns out that the work does not fit neatly between the fixed points of my
plan. What now?
Obviously, we can play the game of ‘does this here task really take two days,
can&#39;t we get it done in one?’, but this is not really interesting, so let me
jump straight to the point where we realize that we simply cannot make it fit by arguing
about our estimates.
Enter the critical path.&lt;/p&gt;
&lt;h2 id=&quot;the-critical-path&quot; tabindex=&quot;-1&quot;&gt;The critical path&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Critical_path_method&quot;&gt;critical path&lt;/a&gt; is a
project management method that helps you deal with time pressure by
focusing on the tasks that are ‘most blocking’ project completion.
After the first plausibility check, I already have estimates for the duration of each
step, and I also know the dependencies between steps.
This allows me to identify the longest sequence of steps between
the start and the goal&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
This sequence is called the critical path because any delay here will directly
affect our chances of reaching our goal before the deadline.
Tasks that are not on the critical path have some ‘wiggle room’ because delays
will not immediately threaten the deadline.
After all, the tasks on the critical path will take longer anyway.&lt;/p&gt;
&lt;p&gt;For the migration of the payment component to a new payment service provider (PSP) we did last year,
the critical path identified by forwards and backwards planning looked roughly like this.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/critical-path-psp-1.svg&quot; alt=&quot;A diagram showing the following steps
resulting from forwards planning, arranged from left to right, beginning at ‘now’: ‘clarify technical details’, ‘wait
for access to test env’, ‘explore example scenarios manually’, ‘implement
registration, auth, capture, refund’, ‘test’, where the latter two overlap; and
the following steps resulting from backwards planning, arranged from right to left, beginning at ‘contract with old
PSP ends’: ‘remove implementation fo old PSP’, ‘disable old PSP’, ‘migrate/monitor/fix’, ‘deploy to prod’, ‘let services test’. The two series of steps are contained in a box labelled ‘Available time’ between ‘now’ and ‘contract with old PSP ends’ and have a large overlap.&quot; width=&quot;100%&quot; /&gt;
&lt;figcaption&gt;
    This is a simplified view of our migration steps between now and the time where our contract with the old payment service provider will end.
    Obviously, ‘let services test’ should come after ‘test’
    because we need to have a stable (well-tested) implementation first,
    before we ask all services who depend on the payment component to begin their integration tests.
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;As you can see, our critical path is much longer than the time between now and our deadline,
so we have a problem.
Fortunately, there are some tactics that can help.
I&#39;m going to show three of them:
moving tasks beyond the critical path,
risk mitigation by kicking off external tasks early,
and decoupling work.&lt;/p&gt;
&lt;h3 id=&quot;moving-tasks-beyond-the-critical-path-(the-easy-part)&quot; tabindex=&quot;-1&quot;&gt;Moving tasks beyond the critical path (the easy part)&lt;/h3&gt;
&lt;p&gt;Often, when there is a deadline, not &lt;em&gt;everything&lt;/em&gt; needs to be finished &lt;em&gt;before&lt;/em&gt;
the deadline.
In our PSP example, the deadline stems from the contract with the old PSP
running out. By the time the contract ends, all payments must be routed to the
new PSP, but the clean-up work can happen after the deadline.
So we can move this part off the critical path simply by refining our
understanding of which tasks
absolutely need to be done before the deadline to ensure success and which tasks
can be postponed if necessary.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/critical-path-psp-2.svg&quot; alt=&quot;The same diagram as above, but with ‘remove implementation for old PSP’ moved out of the box beyond ‘contract with old PSP ends’, thus reducing, but not eliminating, the overlap.&quot; width=&quot;100%&quot; /&gt;
&lt;figcaption&gt;
    We were able to move ‘remove implementation for old PSP’ beyond the deadline
    because it is not &lt;em&gt;really&lt;/em&gt; critical.
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This maneuver reduces the time pressure, but as you can see in the diagram, the
project still does not fit into the available time frame.&lt;/p&gt;
&lt;h3 id=&quot;mitigate-risk-by-kicking-off-external-tasks-early&quot; tabindex=&quot;-1&quot;&gt;Mitigate risk by kicking off external tasks early&lt;/h3&gt;
&lt;p&gt;If the project depends on external contributions, especially ones where we
cannot be too sure of timely delivery, we can mitigate risk by ensuring that
preparatory tasks are done early and the external tasks are not blocked.
Then, the team can continue with work that does not depend on the external
contribution. When the team is at the point where they need those results,
chances are much higher that they will be ready.&lt;/p&gt;
&lt;p&gt;In our example, there were two tasks which depend on 3&lt;sup&gt;rd&lt;/sup&gt; parties to
collaborate with us. The first was providing access to the new PSP&#39;s test
environment. When we first engaged with the PSP, they offered to conduct a few
workshops to clarify the technical details &lt;em&gt;and then&lt;/em&gt; give us access to their
test environment.
Recognizing that this would introduce an unwelcome delay before we could get
our hands dirty&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;,
we asked them to provide API access right away.
They complied, speeding up onboarding significantly.&lt;/p&gt;
&lt;p&gt;The second task not directly under our control was the testing the services
built on top of the payment component would need to do.
We worked hard to keep the change as low-profile as possible,
but nonetheless there were some integration tests that had to be done
and time had to be allocated for them.
To minimize the risk of delay associated with this test phase, we started to
engage with the service teams long before the tests were to take place.
This did not shorten the test phase or allow us to bring it forward, but it
ensured that the teams were ready to test when we needed them to.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;
This engagement was done in parallel to the development work. Thus,
even though it added work to the plan, it did not make the critical path longer.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/critical-path-psp-3.svg&quot; alt=&quot;In this version of the diagram, the first task, ‘clarify technical details’, has been split into ‘organize test env’, starting immediately, and ‘clarify API usage’, which is depicted in parallel to ‘organize test env’ and ‘wait for access to test env’. This shortens the critical path, but not enough to make it fit.
A new step ‘communicate changes and timeline to services’ has been added in front of ‘let services test’.&quot; width=&quot;100%&quot; /&gt;
&lt;figcaption&gt;
    Organizing access to the test environment early allowed us to bring the
    whole plan forward.
    Communicating the change and our timeline to the services ensured that their
    test activities would start on time, mitigating the risk of delays at this
    late stage.
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;decoupling-work&quot; tabindex=&quot;-1&quot;&gt;Decoupling work&lt;/h3&gt;
&lt;p&gt;Sometimes, tasks depend on parts of other tasks.
Example: During our PSP migration, we needed to let our API consumers test their
systems against our new implementation.
This integration test depended on our new implementation being available in our
integration test environment.&lt;/p&gt;
&lt;p&gt;Looking at the plan as visualized above, it seems as if we had to have our implementation
finished before our API consumers could begin with their tests.
These two tasks are serialized on the critical path.&lt;/p&gt;
&lt;p&gt;However, in reality our service apps only had a dependency on certain parts of
the new implementation, namely registration of new payment methods and payment
authorization.
Background processes including deferred captures and refunds were decoupled from the
direct customer experience.
By splitting our implementation task cleverly,
we had the relevant parts in our integration test environment earlier,
so the rest of the implementation could proceed in parallel with the integration
tests, saving us precious time.
In fact, the integration tests were off the critical path entirely.
At no point did we have to stop progress while waiting for the test results.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/critical-path-psp-4.svg&quot; alt=&quot;The long implementation and test
tasks have been split into 4 separate tasks each. ‘let services test’ now follows implementation and test of registration and authorization, while capture and refund are implemented and tested in parallel. The latter tasks still overlap with ‘deploy to prod’ and ‘migrate/monitor/fix’.&quot; width=&quot;100%&quot; /&gt;
&lt;figcaption&gt;
By splitting the implementation and test tasks, we decoupled the integration tests of our
services from implementation tasks that did not impact them, allowing
implementation of those parts and integration tests to run in parallel.
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;moving-tasks-beyond-the-critical-path-(the-hard-part)&quot; tabindex=&quot;-1&quot;&gt;Moving tasks beyond the critical path (the hard part)&lt;/h3&gt;
&lt;p&gt;In a similar vein, we can sometimes move tasks beyond the critical path
by investing a litte extra effort to carve out and postpone uncritical parts.&lt;/p&gt;
&lt;p&gt;In our PSP example, we would migrate users to the new PSP in batches,
beginning some time before the deadline
At first glance, it seems we needed to have the complete set of functionality (registration, authorization, deferred payment (capture), and refund) implemented for the new PSP by this point in time.
But actually, what really mattered were registration and authorization because these were part of
the checkout flow in our apps and thus a direct part of the user experience.
Payment capture, on the other hand, was a deferred batch process.
If the capture functionality had not been ready at the start of the migration,
most users would not even have noticed because all that would have happened was
that their bank accounts would have been debited a few days later.
So we split our implementation into critical and uncritical&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;
tasks and
prioritized them in such a way that we could have gone live
with a partial implementation.&lt;/p&gt;
&lt;p&gt;Refund was less critical still.
Firstly, we didn&#39;t need to refund before we could capture.
Secondly, refunds are rare when compared to captures, so far fewer users would
be impacted if refunds were delayed.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/critical-path-psp-5.svg&quot; alt=&quot;In this version of the plan,
implementation and test of the capture function has been moved a little to the right and now proceeds in parallel with the integration tests, deployment do production, and the migration phase.
A new step ‘begin capturing’ has been added right before the old PSP is disabled.
Implementation and test of the refund has been moved beyond the deadline ‘contract with old PSP ends’, and is now followed by another new step ‘begin refunding’.
There are no steps left which overlap, but shouldn&#39;t.&quot; width=&quot;100%&quot; /&gt;
&lt;figcaption&gt;
Relaxing the requirement that capture and refund functionality must be finished before
starting the migration finally made the plan seem plausible and even introduced
some slack which would allow us to deal with unforeseen problems.
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;In the end, we did have capture and refund finished before we started the
migration. Sometimes, not every risk you planned for actually materializes.
But there were some minor things (nice-to-have features in the user
interface and some hairy edge cases) that we did implement only after the migration had already started.
So while the diagram above does not accurately depict our whole plan,
it does show the kind of thinking that went into it.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thinking about the &lt;em&gt;critical path&lt;/em&gt; – the longest sequence of steps necessary to
reach a goal – is a valuable exercise when dealing with deadlines because it
highlights potential blockers and enables you to plan ahead and address them
before they go ‘boom’.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Moving tasks beyond the critical path&lt;/em&gt; means realizing that not everything that
needs to be done necessarily needs to be done before the deadline.
In some cases, this may be obvious, in others less so.
Sometimes, steps we initially think of as atomic are really composites of
critical and less critical tasks, and splitting the latter out can make the
difference between a doable project and a death march.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mitigating risk by kicking off external tasks early&lt;/em&gt; entails identifying tasks
that are not under our control but have the potential to derail our efforts when
not done in time.
Then we can arrange our plan so these tasks can start as early as possible and
anything under our control that could possible block them is out of the way.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Decoupling work&lt;/em&gt; means thinking hard about the real dependencies between tasks
and, if necessary, splitting them so other tasks are not blocked by – for them –
unnecessary activities.
If well done, this enables parallelization, which can speed up a project
dramatically.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;My &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-4/&quot;&gt;next (and final) post&lt;/a&gt;
of the series on planning addresses exploration of risks and alternative paths.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I&#39;m going to use the start and the goal in the rest of my
post for simplicity&#39;s sake, but a critical path can be identified between any two fixed
points like intermediate milestones or multiple deadlines, and the following
tactics will work just as well. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There is no substitute for interacting directly with a new API. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;We also
tested our changes rigorously ourselves before deploying them in the
test environment accessible to the services. I mention this because, sadly,
this does not seem to be the norm. Apparently, the service testers were used to
broken releases and were thus understandably nervous about the short period
of time we had given them to perform their tests. Our good preparation payed
off, and we had positive test results in record time. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Not that our
background payment processing wasn&#39;t critical from a business perspective.
It absolutely was.
But it was not critical in the sense of the critical path.
I.e., while we had to have it implemented before the project was done,
and we certainly would have liked to have it finished in time for the migration
start,
this wasn&#39;t &lt;em&gt;strictly&lt;/em&gt; necessary for the project to be successful. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Note, however, that
there is also a big difference between taking on technical debt to make a
&lt;em&gt;meaningful&lt;/em&gt; deadline and paying it off after the deadline has been successfully met on the
one hand, and firefighting mode where we focus only on critical issues and
never get to really finish anything because there is constant pressure with
artificial deadlines on the other hand.
The first is what I have in mind here.
The latter leads to burn-out pretty quickly. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Sometimes, splitting tasks takes extra effort,
and we need to examine whether the benefits outweigh the costs.
This is the case if (as in our example) the parallelization potential is
large enough &lt;em&gt;and&lt;/em&gt; if there are people available to take advantage of
it. If nobody is actually taking on the tasks thus unblocked – congratulations!
you have just made the project take longer. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Tue, 12 Nov 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/on-planning-how-3/</guid>
    </item>
    
    
    <item>
      <title>On planning, part 3: forwards and backwards planning</title>
      <link>https://sebastian-hans.de/blog/on-planning-how-2/</link>
      <description>&lt;p&gt;This is the third post in my series about planning in the context of agile
teams.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-why/&quot;&gt;Part 1&lt;/a&gt; is concerned with why you should plan at all.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-1/&quot;&gt;Part 2&lt;/a&gt; describes my iterative approach to end-to-end planning.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/&quot;&gt;Part 4&lt;/a&gt; explores the concept of the critical path.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-4/&quot;&gt;Part 5&lt;/a&gt; argues for more exploration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post, I will explore what I call forwards and backwards planning.&lt;/p&gt;
&lt;p&gt;As I wrote &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-1/&quot;&gt;previously&lt;/a&gt;, before I can begin
planning in earnest, I need to
determine my current position and my goal (what ‘done’ looks like), and it is helpful
to reflect on the means I have at my disposal to reach my goal.
For the purposes of this blog post, I will assume I have done this groundwork
already.&lt;/p&gt;
&lt;h2 id=&quot;planning-forwards-and-backwards&quot; tabindex=&quot;-1&quot;&gt;Planning forwards and backwards&lt;/h2&gt;
&lt;p&gt;I typically use this kind of planning for projects with deadlines because it
helps me think about the dependencies between tasks and ensures that there are
no gaps at the beginning and end of my plan.
(See my previous post on why this is important.)&lt;/p&gt;
&lt;p&gt;Planning forwards and backwards consists of three steps: planning forwards,
planning backwards, and matching up both ends of the plan.
Whether I start with planning forwards or planning backwards does not really
matter, but matching comes last, obviously.&lt;/p&gt;
&lt;h2 id=&quot;planning-forwards&quot; tabindex=&quot;-1&quot;&gt;Planning forwards&lt;/h2&gt;
&lt;p&gt;Planning forwards, I think about what I can do &lt;em&gt;right now&lt;/em&gt; that will bring me
closer to my goal.
I list possible steps to take immediately, then steps I can take afterwards, and
so on.
I write the steps down as a simple list, in the order in which they need to be
done, taking care not to leave any gaps.
Each step must depend only on steps appearing before it in the list.
If there are any intermediate tasks&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; to be done before the next step can be
taken, these also go onto the list.&lt;/p&gt;
&lt;p&gt;If steps can be done in parallel because they are independent of one another, I
make a note of this to be considered later on, but I don&#39;t usually draw up a
dependency graph or a Gantt chart or anything remotely complicated at this
stage.&lt;/p&gt;
&lt;p&gt;If progress depends on a 3rd party &lt;em&gt;X&lt;/em&gt; to do or deliver something, this is also a step.
The only difference is that I won&#39;t be the one doing it,
and thus I will treat it as a black box and not iteratively refine it as I will do with other steps.
However, I still need to think about the necessary preconditions for &lt;em&gt;X&lt;/em&gt; to actually do their thing!
Waiting for &lt;em&gt;X&lt;/em&gt; while being unaware that &lt;em&gt;X&lt;/em&gt; needs something from us before they can start is a failure mode I have seen time and time again.
Because of this, whenever I add such a ‘3rd party step’ to my list, I try to put
myself into their shoes and think about what I would need to get started in
their place.
If there is anything they might need from me (some artifact, information, or
maybe just a trigger), I make sure to add it to my plan before the ‘3rd party
step’.&lt;/p&gt;
&lt;p&gt;If something needs to be done at a certain point in time, or cannot be done
before a certain point in time, I note this when adding the step to my list.
This is critical information because it constrains how I can move
steps around later when trying to fit the plan to the timeline.
All preceding steps must be finished before this step is due,
and none of the following steps can start before this point in time.&lt;/p&gt;
&lt;h2 id=&quot;planning-backwards&quot; tabindex=&quot;-1&quot;&gt;Planning backwards&lt;/h2&gt;
&lt;p&gt;Planning backwards, I start with the desired end state and ask myself what needs to have happened so that we can say, ‘Yep, we are truly done now.’
This is the last step.
And then I ask myself what needs to have happened so that we can take this last
step.
This becomes the next to last step.
In this fashion I work backwards from the goal, always thinking about the
preconditions that need to be true for the current step to be taken and the
steps necessary to establish those preconditions, kind of like traveling
backwards in time.&lt;/p&gt;
&lt;p&gt;Often, there is more than one precondition or thing that has to be done first.
This is OK.
I write all of them down and pick one to continue the time travel, usually the
one that seems most important to me.
But I make sure to come back to the others later and deal with each of them.
After all, they all are necessary to reach my goal.&lt;/p&gt;
&lt;p&gt;After planning forwards and backwards for a while, possibly alternating between
the two perspectives, each of the two plans will probably have developed
several branches like this.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/planning-forwards-and-backwards.svg&quot; alt=&quot;A diagram showing two directed acyclic graphs, one beginning at a node labeled ‘start’ and branching out via several hops to three leaf nodes at the right, one beginning with two nodes and ending with a single leaf node labeled ‘goal’ at the right. The graphs are not connected.&quot; width=&quot;80%&quot; /&gt;
&lt;figcaption&gt;Planning forwards and backwards, two plans emerge, each with possibly multiple branches of linked action steps.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;If the project is complex enough for the dependencies to be non-obvious, I might actually draw a diagram like this,
otherwise, I&#39;ll make do with a linear list and keep the dependencies as notes
(or sometimes in my head).&lt;/p&gt;
&lt;h2 id=&quot;matching-up&quot; tabindex=&quot;-1&quot;&gt;Matching up&lt;/h2&gt;
&lt;p&gt;At some point, the forwards plan and the backwards plan will begin to overlap,
and it is time to match up the plans by connecting the branches of the
forwards plan to the branches of the backwards plan.
While doing this, several scenarios can appear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A step appears in both the forwards plan and the backwards plan. This is the
simplest scenario, where I can just merge them.&lt;/li&gt;
&lt;li&gt;A step in my forwards plan is a direct prerequisite for a step in my backwards plan.
Then I can connect the steps directly.
I still need to check whether the step in my backwards plan has other
prerequisites I need to connect.&lt;/li&gt;
&lt;li&gt;A step in my forwards plan is a prerequisite for a step in my backwards plan,
but I cannot go directly from here to there. In this case, I need to amend the
plan with the necessary intermediate tasks. If they are not obvious, I can
reenter forwards or backwards planning mode for a while until I have figured
it out.&lt;/li&gt;
&lt;li&gt;If a step in my backwards plan has prerequisites that are not fulfilled by my
forwards plan, these can either be filled in (see previous point), or the
forward plan is missing an important aspect entirely, in which case I will
need to fill it in, either by planning forwards, or by continuing backwards
planning until I eventually reach the area charted out by my forwards
plan&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;If a step in my forwards plan seems to lead nowhere, i.e., it cannot be
connected to my backwards plan, I need to ask myself whether it is really
necessary to reach my goal. Maybe it is a side track I can eliminate or pursue
elsewhere, or maybe I forgot something in my backwards plan, in which case I
need to reexamine it critically.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/planning-forwards-and-backwards-merged.svg&quot; alt=&quot;A diagram showing the same graphs as above, and the merge actions performed on them. One leaf node of the left graph has been cut off; the task it represents has been moved out of the project because it does not contribute directly to the goal. The leaf nodes labelled ‘implement API’ (present in both graphs) have been merged. One non-leaf node on the left has been connected directly to a leaf node on the right because it is a direct prerequisite. One intermediate step has been drawn in between another leaf node on the left and a node on the right so they are now connected. All nodes are connected now.&quot; width=&quot;80%&quot; /&gt;
&lt;figcaption&gt;The forwards plan and the backwards plan are now linked to form one plan. It contains all necessary steps and no unnecessary steps.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;When I have matched up my forwards and backwards plans, I can be reasonably sure
I have not forgotten anything important.
At this point, I have a complete end to end plan, but I have not put it onto a timeline yet.&lt;/p&gt;
&lt;h2 id=&quot;timing-it&quot; tabindex=&quot;-1&quot;&gt;Timing it&lt;/h2&gt;
&lt;p&gt;For projects with deadlines, I put those down first, along with any other known
time constraints such as the earliest possible start date or availability
periods of critical resources.
Then I try to distribute the steps of my plan around those ‘fixed points’ in a way that fits
these and any other constraints (like ordering) I have identified during
planning.&lt;/p&gt;
&lt;p&gt;Once the initial timeline is complete, I do a plausibility check:
is it realistic that we can do all this between now and the deadline?
To do this, I need to estimate the duration of each task&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
If at all possible, I do this together with the people who are going to do the
work.&lt;/p&gt;
&lt;h2 id=&quot;and-now%3F&quot; tabindex=&quot;-1&quot;&gt;And now?&lt;/h2&gt;
&lt;p&gt;Unless our schedule is very relaxed, the first naïve attempt at laying out my
plan on the timeline will probably not look so good.
If it does, all the better.
If it doesn&#39;t fit, I continue by examining the &lt;em&gt;critical path&lt;/em&gt; … critically.
More about this in my &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/&quot;&gt;next post&lt;/a&gt;.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I&#39;m going to use ‘step’ and ‘task’
interchangeably. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Which might be right at the start &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Note that estimating
the duration is different from estimating effort.
In practice, I have often found it easier to answer a question like, ‘Can we do
this in a week?’, than a question like, ‘How many hours is this going to take?’
However, you have to take wait times into consideration. For example, if one
of the steps is a review of a design document, you have to account for
roundtrip times and the possibility of multiple rounds of review. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/on-planning-how-2/</guid>
    </item>
    
    
    <item>
      <title>It depends … too much</title>
      <link>https://sebastian-hans.de/blog/it-depends-too-much/</link>
      <description>&lt;h2 id=&quot;tl%3Bdr&quot; tabindex=&quot;-1&quot;&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;This is a rant about indecisiveness.&lt;/p&gt;
&lt;h2 id=&quot;let&#39;s-go!&quot; tabindex=&quot;-1&quot;&gt;Let&#39;s go!&lt;/h2&gt;
&lt;p&gt;We all know the software architect&#39;s (or engineer&#39;s) favorite answer is, ‘It depends.’
And there are &lt;a href=&quot;https://medium.com/swlh/the-golden-rule-of-software-engineering-9faaaab85e78&quot;&gt;good reasons&lt;/a&gt; for this.
It &lt;em&gt;really&lt;/em&gt; all depends – on so many things.
When coming up with solutions to problems, there are many factors to consider,
so any blanket statement, given without knowing the context, is likely to be wrong.
Sometimes horribly so.
Every software engineer (and architect) should periodically remind themselves
that the key to good decisions is considering the context, dealing with conflicting forces, and carefully weighing benefits and risks.
The best answer always depends.
It is so.
Really.
And yet…
I sometimes feel we have taken it too far.&lt;/p&gt;
&lt;p&gt;While hammering this lesson in (and it is an important lesson!), we have somehow
elevated ‘It depends’ to dogma, repeat it like a mantra, have come to the
unconditional belief that everything always depends, and, crucially, that
knowing the answer is impossible.
Because how could it be possible?
If we accept that there are unknowns – known unknowns and even
&lt;a href=&quot;https://de.wikipedia.org/wiki/There_are_known_knowns&quot;&gt;unknown unknowns&lt;/a&gt; –
and that ‘it depends’&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/it-depends-too-much/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, how can we be certain that any solution will be right?
We can&#39;t. Not really.
So we hedge. ‘It depends’, we cry, and somewhat smugly retreat to the position
of ‘not knowing.’
This is a safe position because we can delay by asking for more context.
Which is fine, I guess, if time is not an issue.&lt;/p&gt;
&lt;p&gt;But … wait a moment!
All of &lt;em&gt;my&lt;/em&gt; architectural work is done in the context of a particular
organization, most of it in the context of a particular team.
I – and the collegues I work with – already &lt;em&gt;know the context&lt;/em&gt;.
An external consultant might get away with ‘it depends’ as an answer to an
architecture question, but we won&#39;t. I won&#39;t let us.
Here are some real conversations I have had with team mates in the last year:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;‘It depends on how many API consumers we have.’ – ‘We have 5.’&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;‘We don&#39;t know if team A is using this function.’ – ‘They are. We had a meeting about it,&lt;/em&gt; and &lt;em&gt;we can see it in the logs.’&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;‘But if team B wants to change this in the future!’ – ‘They didn&#39;t manage to&lt;/em&gt;
&lt;em&gt;change the last 3 things they wanted to change in their legacy system.&lt;/em&gt;
&lt;em&gt;They could&#39;t if they wanted to. So they won&#39;t.’&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, in general, it might depend, but in our particular case, this uncertainty
does not exist because &lt;em&gt;we already have the answers&lt;/em&gt;.
Sometimes, it seems to me we have cultivated the attitude of ‘not knowing’
to a point where anyone purporting to have an answer is immediately suspect.&lt;/p&gt;
&lt;p&gt;And another thing: dev teams don&#39;t have a monopoly on uncertainty.
Everything is uncertain.
But we usually don&#39;t qualify each and every answer we give.
Here are some conversations I &lt;em&gt;didn&#39;t&lt;/em&gt; have in the last year:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;‘Will you be home for dinner?’ – ‘It depends. If I am run over by a bus on my way home, I might not be.’&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;‘Can we watch the show later?’ – ‘It depends. It might be difficult if the monitor blows up.’&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;‘Can you make it to the meeting?’ – ‘It depends. If the S-Bahn is punctual – no problem.’&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OK, I&#39;ll grant you the last one.
You get my point, however.
At some point, we should be certain enough that we no longer need to qualify our
statements.
Yes, team B might still want – and manage – to change something, just as I might
be run over by a bus.
And if they do, we will have to change our plans, too.
But this is true in any case.
We have to work with what we know.
This is not to say we shouldn&#39;t make an effort to find out things we don&#39;t know
or to question our assumptions.
We should.
But at a certain point there are diminishing returns to further inquiry and
questioning our knowledge.
A point, at which ‘it depends’ stops being useful.
A point where we need to &lt;em&gt;decide&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Then we must do so, being fully aware that later on it may turn out we were wrong after all. But we cannot know that because … it depends.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;It doesn&#39;t even matter what ‘it’ is because everything
always depends. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/it-depends-too-much/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Fri, 20 Sep 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/it-depends-too-much/</guid>
    </item>
    
    
    <item>
      <title>Engineering, scientifically</title>
      <link>https://sebastian-hans.de/blog/engineering-scientifically/</link>
      <description>&lt;p&gt;This is an answer to Colin Breck&#39;s post
&lt;a href=&quot;https://blog.colinbreck.com/is-the-scientific-method-valuable-in-engineering/&quot;&gt;Is the Scientific Method Valuable in Engineering?&lt;/a&gt;,
so you might want to read this first.
I liked this post very much because it set me thinking, even though I do not
agree entirely with its conclusions.
In it, Breck argues that the scientific method is not useful in software
engineering because the goal of engineering is different from the goal of
science.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In science, we form a&lt;/em&gt; hypothesis &lt;em&gt;and design experiments to falsify that hypothesis. In engineering, we establish a&lt;/em&gt; process, &lt;em&gt;and apply methods rooted in science, mathematics, and statistics, to ask questions of the system, run experiments, and improve the process.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He concludes that the scientific method is not applicable to software
engineering because of those differences,
and draws on examples from Bryan Cantrill&#39;s talk &lt;a href=&quot;https://www.youtube.com/watch?v=bJ0y7Oqr4Zo&amp;t=1759s&quot;&gt;Things I Learned The Hard Way&lt;/a&gt;
and on Dave Farley&#39;s book &lt;em&gt;Modern Software Engineering&lt;/em&gt; to support his arguments.&lt;/p&gt;
&lt;p&gt;I will use the same sources and offer a different lens through which to view the interplay between software
engineering and the scientific method.
Why interplay?
Because I think the scientific method is a necessary, but insufficient
ingredient to effective engineering.
You cannot say, ‘I use the scientific method, therefore I am doing software
engineering’, but you also – in my view – cannot claim to do software
engineering without using the scientific method in places.
You may, however, be using it implicitly much of the time, which can make it a
little harder to see.
It mainly shows up when someone is &lt;em&gt;not&lt;/em&gt; doing it.
If this sounds a little cryptic, bear with me; I will explain.&lt;/p&gt;
&lt;p&gt;First, however, let&#39;s recap what the scientific method is.
I will take the easy way and just use the same quote from
Robert M. Pirsig&#39;s summary of the scientific method from
&lt;em&gt;Zen and the Art of Motorcycle Maintenance&lt;/em&gt;
that Breck used in his post:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;(1) statement of the problem, (2) hypotheses as to the cause of the problem, (3) experiments designed to test each hypothesis, (4) predicted results of the experiments, (5) observed results of the experiments, and (6) conclusions from the results of the experiments.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note that this is a purely declarative definition.
It presupposes the existence of a problem without caring where it came from.
It assumes that there are hypotheses without saying anything about how they come
to be.
It demands experiments without giving guidance on how to design them.
And so on.
&lt;a href=&quot;https://blog.colinbreck.com/is-the-scientific-method-valuable-in-engineering/#fn3&quot;&gt;Breck&#39;s 3&lt;sup&gt;rd&lt;/sup&gt; footnote&lt;/a&gt;
contains some quotes to this effect.
His – and Bryan Cantrill&#39;s – conclusion seems to be that the scientific method requires you to guess
(because each hypothesis is nothing other than a guess),
and guessing is bad because there are other methods out there that better
support goal-oriented problem solving.&lt;/p&gt;
&lt;p&gt;So let&#39;s take the examples in his post and see if we can discover the scientific
method in there!&lt;/p&gt;
&lt;h2 id=&quot;debugging%2C-scientifically&quot; tabindex=&quot;-1&quot;&gt;Debugging, scientifically&lt;/h2&gt;
&lt;p&gt;The first example is Cantrill talking about his approach to debugging, and how he
hates working with ‘hypothesis-centric engineers’:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I know this! It’s this! Oh, it wasn’t that, it’s this!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I can relate to that. Working with someone like that drives me crazy, too.&lt;/p&gt;
&lt;p&gt;Cantrill suggests asking questions instead of guessing, and to make an effort to answer them by
examining the state of the system.
The answers to those questions allow you to narrow your bug search until you
have found the bug.
This is great advice, in my opinion, and a very good talk overall; I encourage you to watch the whole thing.&lt;/p&gt;
&lt;p&gt;Anyway, if you do it his way, there&#39;s no guessing, no hypotheses, no scientific method, right?&lt;/p&gt;
&lt;p&gt;Wrong!
The way I see it, what he is doing is giving advice on how to do step 2 of the
scientific method, coming up with a good hypothesis.
Because at this point, it is still a hypothesis.
He is going ‘I know it&#39;s this!’, but after a lot more analysis than the
engineers he is complaining about, so the likelihood of his being right is much
higher than a random guess.
But there is no fix yet, so the problem is not solved, is it?
What&#39;s next?
Fixing the problem.
Assuming it is a simple bug, identifying the bug roughly equals fixing
it&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
At the end of this stands the hypothesis, ‘This was the bug, and what I just did
fixes it.’&lt;/p&gt;
&lt;p&gt;Step 3 of the scientific method says you should design experiments to test the
hypothesis. In my book, this means you should have at least one test that fails
before the fix and succeeds after the fix.
For reproducible bugs, this should be easy, for others it can be hard,
but the scientific method does not require that it be easy.&lt;/p&gt;
&lt;p&gt;The prediction of the result (step 4) is included in each test case in the form
of assertions. If the prediction turns out to be incorrect, the test fails.
Running the test, and observing the result, is step 5.&lt;/p&gt;
&lt;p&gt;Step 6: draw your conclusions.
If the test succeeds, you will probably conclude that you were right and have
fixed the bug.
Breck mentions that in science, ‘a lot of effort often goes into explaining the errors in measurements.’
This is what you do if the test fails, too.
You examine the test – maybe it contains an error?
If not, you have invalidated your hypothesis.
That is, you go, ‘Oh, it wasn&#39;t that!’
And you are back to step 2, coming up with a new hypothesis.&lt;/p&gt;
&lt;p&gt;In conclusion, Cantrill&#39;s critique of using the scientific method for debugging
seems to me to be really a critique of engineers who churn out ‘fixes’
without bothering to come up with a good hypothesis first.&lt;/p&gt;
&lt;p&gt;I use the scientific method for debugging, and many good software engineers do,
even if they do not think about it as such.&lt;/p&gt;
&lt;h2 id=&quot;improving-performance%2C-scientifically&quot; tabindex=&quot;-1&quot;&gt;Improving performance, scientifically&lt;/h2&gt;
&lt;p&gt;The second example in Breck&#39;s post is performance optimization.
Here, too, the critique seems to be mostly directed at people who jump to
conclusions instead of following a systematic approach of coming up with
well-grounded hypotheses, and my answer is the same.&lt;/p&gt;
&lt;p&gt;Let&#39;s reduce it to bullet points this time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Statement of the problem:
Where exactly is the performance
insufficient? If you skip this step, Donald Knuth might want a word with
you&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;Hypotheses as to the cause of the problem:
I agree full-heartedly that it is a bad idea to guess randomly.
Use the profiler, measure where CPU time is spent, identify areas for
improvement, come up with hypotheses of the form, ‘Unnecessary string
copies in the flubber function cause 50% of the CPU load.’&lt;/li&gt;
&lt;li&gt;Experiments designed to test each hypothesis:
Implement the fix, and carefully design performance tests (this is a rabbit hole in and of itself).&lt;/li&gt;
&lt;li&gt;Predicted results of the experiments:
‘The test will run twice as fast after the fix as before.’&lt;/li&gt;
&lt;li&gt;Observed results of the experiments:
Run the test. Uh, oh, the test shows CPU load was reduced only by 10%.&lt;/li&gt;
&lt;li&gt;Conclusions from the results of the experiments:
Examine the test (maybe we measured it wrong?), keep the
fix (10% is better than nothing), put some more work in?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Again, I see the scientific method at work when I expand my view beyond the
problem of coming up with a hypothesis.&lt;/p&gt;
&lt;h2 id=&quot;improving-continuously%2C-scientifically%3F&quot; tabindex=&quot;-1&quot;&gt;Improving continuously, scientifically?&lt;/h2&gt;
&lt;p&gt;Breck uses manipulation of process variables in a chemical reactor as an
example.
I&#39;m not sure about this one.
If the goal is simply to observe the system behavior under different conditions,
this sounds more like fiddling with the knobs and seeing what happens.
No scientific method, there; I&#39;ll grant you this.&lt;/p&gt;
&lt;p&gt;However, I think that as soon as you try to formulate a theory of how the system works and improve it on this basis,
this sounds suspiciously like scientific territory again, and you might benefit from a more rigorous application of the scientific method.
After all, is, ‘If we lower the inlet temperature by 1 degree, this will
lower our energy costs by 2% and not affect the output quality’ not a testable hypothesis?&lt;/p&gt;
&lt;h2 id=&quot;changing-software%2C-scientifically&quot; tabindex=&quot;-1&quot;&gt;Changing software, scientifically&lt;/h2&gt;
&lt;p&gt;Colin Breck cites Dave Farley and comes to the conclusion that, while his book
&lt;em&gt;Modern Software Engineering&lt;/em&gt; promotes an ‘empirical, scientific approach’, it
does not actually advocate for strict application of the scientific method.
I have not read Dave Farley&#39;s book (sorry), but have heard him talk live and on
&lt;a href=&quot;https://www.youtube.com/@ContinuousDelivery&quot;&gt;YouTube&lt;/a&gt;
and thus gotten a slightly different picture.&lt;/p&gt;
&lt;p&gt;About the tests in a continuous delivery pipeline, Breck writes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;But these tests are not being used to disprove a hypothesis or discover a fundamental truth of the system, they are being used to test the&lt;/em&gt; invariants &lt;em&gt;of the system and provide the guardrails for continued experimentation, discovery, refinement, and iteration.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And in a footnote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;It could be argued that the fundamental truth is evaluating if the software is always in a releasable state. But tests never perfectly replicate production environments and we need to continue to use empirical engineering techniques to evaluate software once deployed to production systems.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On the first point, I remember Dave Farley saying that each commit codifies the hypotheses that&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the commit does what it is supposed to do, and&lt;/li&gt;
&lt;li&gt;the commit does not break any part of the system.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And, yes, the tests are used to disprove these hypotheses.
Regression tests try to disprove the hypothesis that we did not break anything
(another way of saying the invariants of the system are still intact),
and newly added tests try to disprove that the commit does what it is supposed
to do.&lt;/p&gt;
&lt;p&gt;The part about the tests not replicating production exactly is a digression.
Other scientific experiments are not perfect either, see the &lt;a href=&quot;https://en.wikipedia.org/wiki/Observer_effect_(physics)&quot;&gt;observer effect&lt;/a&gt;.
This is one reason why experiments/tests can only ever invalidate the hypothesis, not prove its correctness.
‘Tests are not perfect‘ does not imply the scientific method is not in action.&lt;/p&gt;
&lt;p&gt;Breck describes the difference between science and engineering as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Engineering is different in that we are not working to prove or disprove a hypothesis, but rather iteratively and empirically improve a process by applying proven methods.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I agree with this, and also with science and engineering having different goals.
We can still apply the scientific method in engineering, where appropriate.
I see it not as an either/or, but rather as the scientific method supporting the
engineering processes.&lt;/p&gt;
&lt;h2 id=&quot;necessary%2C-but-not-sufficient&quot; tabindex=&quot;-1&quot;&gt;Necessary, but not sufficient&lt;/h2&gt;
&lt;p&gt;The scientific method is not all there is to software engineering.
We need to do a lot more to deliver good software, as Colin Breck, Bryan
Cantrill, and Dave Farley have pointed out.&lt;/p&gt;
&lt;p&gt;Some of this plays out &lt;em&gt;within&lt;/em&gt; certain steps of the scientific method&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;,
some of it is &lt;em&gt;outside&lt;/em&gt; the scope of the scientific method&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;.
But there are places where the scientific method is applicable, and using it
is indispensable.&lt;/p&gt;
&lt;p&gt;Consider these examples of leaving out some steps of the scientific method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Committing a change that does not address a stated problem?&lt;br /&gt;
&lt;em&gt;Why are you committing the change at all, then? Where is its value?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Committing a change without having at least regression tests (experiments)?&lt;br /&gt;
&lt;em&gt;You cannot tell whether the change was successful – very risky.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Not including assertions in your tests (= not having predicted the result of
the experiment)?&lt;br /&gt;
&lt;em&gt;Such tests are useless.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Not running your tests, or not looking at the results?&lt;br /&gt;
&lt;em&gt;You may just as well not have tests.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Fixing a bug without testing the fix (includes steps 3-5)?&lt;br /&gt;
&lt;em&gt;How do you know that you fixed the bug?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Having a test fail and not act on the result?&lt;br /&gt;
&lt;em&gt;Why bother writing and running it then?&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do not see which part you can leave out and still claim to be doing software
engineering.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot; tabindex=&quot;-1&quot;&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;The scientific method is more than guessing and testing randomly.
It includes other aspects not mentioned in the simplified summary we used for
our discussion, like &lt;a href=&quot;https://en.wikipedia.org/wiki/Inductive_reasoning&quot;&gt;inductive reasoning&lt;/a&gt;
to come up with good hypotheses.
But even if reduced to guessing and testing,
it acts as an important safeguard against bad solutions and non-solutions.
After all, even the engineers Cantrill complained about noticed that, ‘Oh, it wasn&#39;t that!’
Presumably, they had run experiments to determine this.&lt;/p&gt;
&lt;p&gt;Going about debugging in this way is not efficient&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;,
but it is certainly safer than doing it completely unscientifically.
What do you think the result would have been, had those engineers
not bothered to test their ‘fixes’?&lt;/p&gt;
&lt;p&gt;At the same time, the scientific method alone is not enough to drive software
development.
We need those repeatable processes.
We need to ask good questions, use architectural principles like coupling and cohesion, automate our testing, and work in small steps.
But we need the scientific method, too.&lt;/p&gt;
&lt;p&gt;And being strict about it is not necessarily slow.
I can do the scientific method in a few minutes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Select a small ticket from the backlog (contains the statement of the
problem).&lt;/li&gt;
&lt;li&gt;Write a little code and the first test. Since my test includes an
assertion, this covers the hypothesis (the code), the experiment (the
‘given’ and ‘when‘ parts of the test), and the prediction of the result (the
‘then’ part of the test).&lt;/li&gt;
&lt;li&gt;Run the test, observe the result.&lt;/li&gt;
&lt;li&gt;On success, celebrate and commit (which triggers another round of
experiments); on failure, go back to 2.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For me, the scientific method, translated into software development terms,
means:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You should know what you want to achieve before you start changing anything!&lt;/li&gt;
&lt;li&gt;Always write tests for your changes!&lt;/li&gt;
&lt;li&gt;No tests without assertions!&lt;/li&gt;
&lt;li&gt;Actually run the tests (new and old), look at the results, and act on them!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I, for one, would not want to have it any other way.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section role=&quot;doc-endnotes&quot; class=&quot;footnotes&quot;&gt;
&lt;h3 id=&quot;footnotes-label&quot; class=&quot;footnotes-heading&quot;&gt;Footnotes&lt;/h3&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;If it is not a simple bug, the hypothesis ‘It&#39;s this!’ may expand into multiple
hypotheses on how to fix it (which makes the process more difficult, but does not change
anything substantial, so let&#39;s ignore this rabbit hole). &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;‘Premature optimization is the root of all evil.’ — Donald Knuth &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;
Like asking questions of the system for coming up with good hypotheses when debugging. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;
Like establishing a repeatable process, and the sense of quality Breck mentions
in his post – determining what is good enough. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The scientific method does not require efficiency. &lt;a aria-label=&quot;Back to reference&quot; role=&quot;doc-backlink&quot; href=&quot;https://sebastian-hans.de/blog/engineering-scientifically/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Fri, 16 Aug 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/engineering-scientifically/</guid>
    </item>
    
    
    <item>
      <title>On planning, part 2: iterative end-to-end-planning</title>
      <link>https://sebastian-hans.de/blog/on-planning-how-1/</link>
      <description>&lt;p&gt;This is the second post in my series on planning.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-why/&quot;&gt;Part 1&lt;/a&gt; is concerned with why you should plan at all.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/&quot;&gt;Part 3&lt;/a&gt; shows how I do forwards and backwards planning.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/&quot;&gt;Part 4&lt;/a&gt; explores the concept of the critical path.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-4/&quot;&gt;Part 5&lt;/a&gt; argues for more exploration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post, I show how I create an end-to-end plan using iterative refinement.&lt;/p&gt;
&lt;h2 id=&quot;ground-work&quot; tabindex=&quot;-1&quot;&gt;Ground work&lt;/h2&gt;
&lt;p&gt;The primary purpose of planning is to prepare me to reach a goal.
For this, I need to know – or figure out – three things: where I am now, what the goal is, and what means to getting closer to the goal I have at my disposal.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Knowing my current position&lt;/em&gt; is essential because only then can I put my position in relation to the goal and determine the direction in which I will need to go.
How do I get to Reykjavik? If I am in Keflavik, I will probably get a car and drive along the coast. If I am in Nuremberg, I will probably fly.
How do I replace my payment service provider (PSP) with another one? The answer depends heavily on the structures and processes that are already in place.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Knowing my goal&lt;/em&gt; is important, because, well, if I don&#39;t know where I want to end up, there is no point in preparing to get there, is there?
In practice, the goal is not always well-defined from the start.
I may, for example, know that I want to visit Iceland, but not yet have a specific town or place in mind.
Or I may know that I want to replace my PSP, but not yet have a detailed picture of the end state.
This is fine.
I can narrow down the goal during planning.
But it still needs to be specific enough so I can tell when I have reached it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Knowing the means at my disposal&lt;/em&gt; greatly aids in planning because it allows me to focus on possible paths forward as opposed to getting hung up on impossible ones.
Means include actions I can take, time, money, work capacity, skills, material, and tools, all of which are usually limited in some way.
I have observed that we (myself, but also colleagues) tend to gravitate towards our respective default methods when solving problems and forget that others also exist.
As the saying goes, if you only have a hammer, everything looks like a nail.
But even if the toolbox is full, if the hammer is lying on top because it is the most used tool, people will still squint very hard to make their problem look like a nail – rather than search through the toolbox.
A software engineer, for example, may default to adding code for any problem, forgetting that sometimes organizational changes can work better.
Explicitly considering what I have available before entering focused planning mode can open up alternative (and sometimes better) paths I would not have thought about otherwise.
It can also prevent me from pursuing impossible plans once I discover what means are &lt;em&gt;not&lt;/em&gt; available to me.
It may, for example, be impossible to get additional resources for a project because of budget constraints.&lt;/p&gt;
&lt;p&gt;Once I have a more or less complete understanding of my starting position, my goal, and available means of getting there, I can begin planning in earnest.&lt;/p&gt;
&lt;p&gt;In the following I am going to use the examples from above. For the traveling example, let&#39;s say I want to travel from an address in Nuremberg, Germany, to a hotel in Reykjavik, Iceland, on a certain date.
For the IT example, let&#39;s say I want to replace my current payment service provider A with the new PSP B, and this must be finished 6 months from now.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Side note: I have never been to Iceland, so the details I am going to add to this example may not be factually correct, but they should serve to highlight the kind of thinking that I put into planning.&lt;/em&gt;
&lt;em&gt;I have, however, done a payment service provider migration.&lt;/em&gt;
&lt;em&gt;You can watch my talk about it at the &lt;a href=&quot;https://www.wearedevelopers.com/en/videos/730/migrating-half-a-million-users-to-a-new-payment-service-provider&quot;&gt;WeAreDevelopers DevOps Day 2023&lt;/a&gt; [free registration required].)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the case of traveling to Iceland, for example, the means available do not include direct flights from Nuremberg to Iceland.
They do include various indirect means of travel.&lt;/p&gt;
&lt;p&gt;In the case of the PSP migration, the means available include a development team (partially occupied with other activities going on at the same time), our infrastructure, and the new PSP.&lt;/p&gt;
&lt;h2 id=&quot;end-to-end-planning&quot; tabindex=&quot;-1&quot;&gt;End-to-end planning&lt;/h2&gt;
&lt;p&gt;I start planning with a rough outline of the whole journey from my starting position until the project is done, i.e., I have reached my goal completely.
Then I iterate on it by refining parts of the journey, validating assumptions, identifying risks, and examining alternative paths.&lt;/p&gt;
&lt;p&gt;This is a core part of my planning strategy, and it bears repeating because I have so often seen people miss this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I always plan the &lt;em&gt;complete&lt;/em&gt; journey from start to end.&lt;/strong&gt;
There must be no gaps in the middle of the path.
Steps may be roughly specified, i.e., details may be left out;
steps may be unvalidated;
their feasibility may be unclear;
they may be fraught with risk;
but they must not be &lt;em&gt;missing&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/planning-magic-in-the-middle.svg&quot; alt=&quot;A diagram with bobbles labelled ‘Start’ and ‘Goal’ on the left and right, respectively, and two arrows between them. The arrows are not joined, and the gap is highlighted in red and labelled ‘magic happens?’&quot; width=&quot;400em&quot; /&gt;
&lt;figcaption&gt;There must be no gaps between steps in my plan.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;As long as my plan contains gaps, it is not really executable because I cannot move from one step to the next directly.
And worse, there is no way to know whether it is at all possible to make it executable.
Small gaps may contain huge amounts of hidden complexity.
Making the effort to close those gaps has the potential to expose issues that, if unaddressed, may sink my project.
By exposing them, I make them addressable.&lt;/p&gt;
&lt;p&gt;Traveling example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I&#39;m going to fly from Nuremberg to Keflavik airport via Frankfurt and drive from Keflavik to Reykjavik with a rented car.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Notice the gap in the middle? How am I going to get from Keflavik airport to the car rental?
Forgetting this step may not be a problem if the car rental I end up using is directly at the airport, but it may turn out badly if there is some distance to cover.
A more complete plan would be:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I&#39;m going to fly from Nuremberg to Keflavik airport via Frankfurt, get to a car rental somehow, rent a car, and use it to drive from Keflavik to Reykjavik.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is not yet clear which car rental or how I am going to get there, but it is clear that this step is necessary and will have to be dealt with at some point.&lt;/p&gt;
&lt;p&gt;A first attempt at planning our PSP migration could look like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;We are going to implement the API of the new PSP, migrate users from the old to the new PSP in batches, and disable the connection to the old PSP.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, an important step is missing: before we can actually migrate users, we need to change the general terms and conditions to refer to the new payment service provider.
Thinking of this early is particularly important because there are legal deadlines to consider which may have knock-on effects on the following steps.
A more complete plan could look like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;We are going to implement the API of the new PSP, update the general terms and conditions, migrate users from the old to the new PSP in batches, and disable the connection to the old PSP.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;I always plan the complete journey from &lt;em&gt;start&lt;/em&gt; to end.&lt;/strong&gt;
How to get startecd is a point that is often glossed over, but it is a critical part.
If the journey does not start where I am &lt;em&gt;now&lt;/em&gt;, if I cannot make the first step, there is not going to be a second one, not to mention the last one where I reach my goal.
Getting started can take a lot of time and effort which, when not taken into account, can nix my whole plan.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/planning-magic-at-the-start.svg&quot; alt=&quot;A diagram with a bobble labelled ‘Start’ and an arrow pointing right. The arrow does not begin at the bobble, however, and the gap is highlighted in red and labelled ‘can&#39;t start!’&quot; width=&quot;200em&quot; /&gt;
&lt;figcaption&gt;Cannot start if the first step cannot be made from where I am.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Look again at my traveling plan and try to see what is missing at the beginning!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I&#39;m going to fly from Nuremberg to Keflavik airport via Frankfurt, get to a car rental somehow, rent a car, and use it to drive from Keflavik to Reykjavik.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My journey begins at an address in Nuremberg, not at the airport.
Getting to the airport is the first step.
Going by subway, this step can take up to an hour.
I will probably have luggage to check in, which will take time, too.
So, an even more complete plan would look like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I&#39;m going to walk to the subway, take the subway to Nuremberg airport, check in, fly from Nuremberg to Keflavik airport via Frankfurt, get to a car rental somehow, rent a car, and use it to drive from Keflavik to Reykjavik.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, for the PSP example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;We are going to implement the API of the new PSP, update the general terms and conditions, migrate users from the old to the new PSP in batches, and disable the connection to the old PSP.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What is missing at the beginning?
Can we start implementing the API right away?
No, we cannot.
First, we need to clarify API usage for our use cases with our technical contacts at the new PSP, and we need access to the PSP&#39;s test environment.
Only then can we begin to implement the API.
Starting from where we are, the plan thus looks like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;We are going clarify API usage with our technical support contact, and the new PSP is going to give us access the their test environment. Then we will test our scenarios manually, before implementing the API of the new PSP. We will update the general terms and conditions, migrate users from the old to the new PSP in batches, and disable the connection to the old PSP.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;I always plan the complete journey from start to &lt;em&gt;end&lt;/em&gt;.&lt;/strong&gt;
When is a project done?
Over the years, I have seen various attempts to define done-ness, and claims that some project or other was done, and most of them excluded certain aspects – sometimes for expediency, sometimes because the person claiming done-ness had only limited responsibility, e.g., a developer who considered a task done as soon as the code was written.
I think such narrow definitions of done do not make sense in the context of teams with true end-to-end responsibility.&lt;/p&gt;
&lt;p&gt;For me, the answer is simple: a project (task, user story, whatever) is done when there is nothing left to do.
Done is ‘done’ in the perfect tense, which means the doing has taken place in the past.
If part of the doing is still in the future, it is not done.
Is it tested? If not, it is not done.
Is it deployed? If not, it is not done.
Is it released to users? If not, it is not done.
Is it documented? If not, it is not done.
And so on.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/planning-magic-at-the-end.svg&quot; alt=&quot;A diagram with a bobble labelled ‘Goal’ and an arrow pointing to it. The arrow does not reach the bobble, however, and the gap is highlighted in red and labelled ‘not done!’&quot; width=&quot;200em&quot; /&gt;
&lt;figcaption&gt;I&#39;m only done when I&#39;m &lt;em&gt;at&lt;/em&gt; the goal, not near the goal.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;In my opinion, as soon as you start to distinguish between ‘done’ and ‘done done’ and maybe other kinds of ‘done’, you enter a zone of &lt;a href=&quot;https://martinfowler.com/bliki/SemanticDiffusion.html&quot;&gt;semantic diffusion&lt;/a&gt; where it is no longer possible to talk about when something is really finished.
After all, when ‘done’ no longer means the doing lies in the past, what does ‘finished’ mean?&lt;/p&gt;
&lt;p&gt;Consequently, when planning, I take care to not leave gaps at the end, either.
It is easy to miss important bits that happen after the ‘main part’, leading to problems later on.
Be careful not to fall into the &lt;a href=&quot;https://en.wikipedia.org/wiki/Pareto_principle&quot;&gt;Pareto&lt;/a&gt; trap, where the final (unplanned for) 1% take 99% of the time!&lt;/p&gt;
&lt;p&gt;The key question I always ask is, ‘If we have done all this, have we reached our goal completely, or is there still something to do?’
If there is, I add it to the plan and repeat the question until the gap between the end of the plan and actual goal completion has been closed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I&#39;m going to walk to the subway, take the subway to Nuremberg airport, check in, fly from Nuremberg to Keflavik airport via Frankfurt, get to a car rental somehow, rent a car, and use it to drive from Keflavik to Reykjavik.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OK, but what about the hotel I actually want to get to?
Can I drive there or is it in a pedestrian area?
Does it have parking space?
What about returning the rented car?
Let&#39;s complete the plan:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I&#39;m going to walk to the subway, take the subway to Nuremberg airport, check in, fly from Nuremberg to Keflavik airport via Frankfurt, get to a car rental somehow, rent a car, and use it to drive from Keflavik to the hotel in Reykjavik.&lt;/em&gt;
&lt;em&gt;I will park the car in the hotel garage and return it to the rental company&#39;s Reykjavik branch the next day.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now for the PSP migration:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;We are going clarify API usage with our technical support contact, and the new PSP is going to give us access the their test environment. Then we will test our scenarios manually, before implementing the API of the new PSP. We will update the general terms and conditions, migrate users from the old to the new PSP in batches, and disable the connection to the old PSP.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once all users&#39; payments are being processed by the new PSP, are we not done?
From the point of view of the users, yes.
But not from the point of view of the development team.
Payment integration is not a one-shot fire-and-forget project; it is something we will continue to maintain and extend for a long time.
The integration component must be maintainable to remain economical, which, among other things, means not keeping dead code around.
So there is at least one additional step to do:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;We are going clarify API usage with our technical support contact, and the new PSP is going to give us access the their test environment. Then we will test our scenarios manually, before implementing the API of the new PSP. We will update the general terms and conditions, migrate users from the old to the new PSP in batches, and disable the connection to the old PSP. Then, we will remove all code dealing with the old PSP.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Only then are we really done.&lt;/p&gt;
&lt;p&gt;Now I have a rough, but complete plan.
It covers the whole journey between our starting point and the ultimate goal.
While it is probably not ready for execution, it will serve as the basis for refinement and further discussion.&lt;/p&gt;
&lt;h2 id=&quot;iterative-refinement&quot; tabindex=&quot;-1&quot;&gt;Iterative refinement&lt;/h2&gt;
&lt;p&gt;The first rough draft of my plan will probably be wrong, but it has an important property:
it is &lt;em&gt;complete&lt;/em&gt;.
I have found it &lt;em&gt;much&lt;/em&gt; easier to do planning with colleagues once I have this scaffolding in place
because it serves as an anchor in discussions about feasibility, details of a particular step, risks, and possible alternatives.&lt;/p&gt;
&lt;p&gt;There is a meme on the internet, that goes something like this.
‘If I don&#39;t get an answer to my question on a forum, I simply post a wrong answer with a different account, and someone will immediately jump in to correct me.’&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://xkcd.com/386/&quot;&gt;Relevant xkcd&lt;/a&gt;:&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://imgs.xkcd.com/comics/duty_calls.png&quot; alt=&quot;xkcd comic: Duty Calls&quot; width=&quot;300&quot; height=&quot;330&quot; /&gt;
&lt;figcaption&gt;What do you want me to do?  LEAVE?  Then they&#39;ll keep being wrong!&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The same goes for plans.
If I need input from stakeholders who are reluctant to provide information or just not strongly motivated to take time to support the project,
showing them a complete, but unvalidated plan more often than not prompts them to correct me.&lt;/p&gt;
&lt;p&gt;Similarly, willing contributors who have not yet been deeply involved in the project are often missing some context.
For them, too, it is easier to join in – and provide valuable input – if they can see how the issue under discussion fits into the big picture.
When discussing a particular part of our journey, I often get questions like, ‘Yes, but what happens next?’
or, ‘What do we do with this result?’
Because the plan is already complete, I always have an answer.
Sometimes, the answer is satisfactory.
Sometimes, preceding or following steps will need to be adjusted as a result of the discussion.
Sometimes, additional steps need to be introduced, or steps need to be reordered or merged.
The impact of the step under discussion on the plan becomes clearer if we discuss it in the context of the complete plan rather than looking at each step in isolation.&lt;/p&gt;
&lt;p&gt;In a way, this is like continuously integration-testing code changes.
If I make an incompatible change to the code of a component in a project with good integration tests, a test will break and thus prompt me to restore compatibility (by either making my change compatible, or propagating my change to the dependent code).
Likewise, if I change a part of my plan so it no longer fits with the rest, knowing how the part is linked to the whole enables me to make the necessary changes so the plan stays coherent.&lt;/p&gt;
&lt;p&gt;It is OK for the plan to evolve unevenly, i.e., for some parts to be pretty clear while others are still hazy.
How detailed each part needs to be depends on what I want to get out of my plan.
If I do not have a hard deadline, it&#39;s probably enough to focus on the next couple of steps and keep the rest of the journey in the back of my mind to spot any potential problems.
Using my example of dealing with a legacy system from the &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-why/&quot;&gt;previous post&lt;/a&gt;, the plan could look like this.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/planning-granularity.svg&quot; alt=&quot;A diagram with the current state on the bottom left and a slightly shifting goal on the top right, and legacy system constraints represented by a transverse barrier in between. Arrows plot the way from the current state to the goal. The first, short arrow is labelled ‘detailed’. A second, dashed arrow labelled ‘reasonable’ extends to a point next to the barrier. A third, dotted arrow labelled ‘hazy’ points from there to the goal.&quot; width=&quot;500em&quot; /&gt;
&lt;figcaption&gt;The next step is known in detail; the way to circumvent the legacy system constraints is reasonably clear;&lt;br /&gt;beyond that, it gets hazy (but still complete).&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;If I do have a hard deadline, I need to get to a granularity that allows me to roughly estimate each step&#39;s duration to ensure my plan fits plausibly into the remaining time period.
One way to do this is to plan forwards and backwards (see my next post for more details).&lt;/p&gt;
&lt;p&gt;If there is a dependency on some third party that needs to be integrated into my plan, I may need to go deeper on the affected parts of my plan than I otherwise would to ensure a smooth integration.&lt;/p&gt;
&lt;p&gt;In any case, the important point is, as long as I have an end-to-end plan, I can iteratively refine each part of the plan as much as is useful,
and having the end-to-end view enables me to spot changes that affect other parts of the plan and deal with them accordingly.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I find it important to do planning end to end, i.e., from my current position until I have reached my goal completely, and to do it iteratively because&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the resulting plan is actionable immediately (begins where I am right now);&lt;/li&gt;
&lt;li&gt;this approach enables effective collaboration;&lt;/li&gt;
&lt;li&gt;it allows me to ‘integration test’ changes to the plan, ensuring consistency;&lt;/li&gt;
&lt;li&gt;when I&#39;m done, I&#39;m done.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use this approach for major endeavors, especially risky ones, projects including deadlines, and if there are dependencies to consider.&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/&quot;&gt;next post&lt;/a&gt;, I will write about how I use forwards and backwards planning to meet deadlines.&lt;/p&gt;
</description>
      <pubDate>Wed, 31 Jul 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/on-planning-how-1/</guid>
    </item>
    
    
    <item>
      <title>On planning, part 1: why?</title>
      <link>https://sebastian-hans.de/blog/on-planning-why/</link>
      <description>&lt;h2 id=&quot;tl%3Bdr&quot; tabindex=&quot;-1&quot;&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Planning is important even for agile teams.&lt;/li&gt;
&lt;li&gt;Planning must be done by the people who are going to do the work, because the most valuable part of planning is the process.&lt;/li&gt;
&lt;li&gt;If done right, planning helps you to avoid doing stupid things, prevents blockers, makes you faster, and enables better decisions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;intro&quot; tabindex=&quot;-1&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;I am a planner.
Have been one since my childhood.
I plan everything, from my meals to software development projects.
I even plan the unplannable.
I have children – the most unpredictable things on this planet – and still make plans.
Sometimes they even work. 😃&lt;/p&gt;
&lt;p&gt;In my first job as a professional software developer I worked in product development.
The company sold market data distribution software to financial institutions.
Development was financed in part through licensing fees, but mostly by customers paying us for extensions (new functionality and new interfaces).
As these were all fixed price contracts, planning (and its ugly twin, estimation) was vital because any mistakes were going directly to our bottom line.
We became really good at it as a result.&lt;/p&gt;
&lt;p&gt;Nowadays, I work in agile teams, and there is a strong headwind against planning anything.
I get where this is coming from, and I myself plan much less now than I did when calculating fixed price quotes 15 years ago.
However, I still think planning is a key skill to be successful in the long term.&lt;/p&gt;
&lt;p&gt;In recent years, I have noticed a certain bafflement among my fellow software developers – and not just them – when the need arose to make plans that extended more than two weeks or so into the future.
I think this is a shame.
So I decided to put some of my thoughts on planning in writing in the hope that someone might find them useful.&lt;/p&gt;
&lt;p&gt;In this post, I will examine the question of why we should plan.
In a &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-1/&quot;&gt;follow-up post&lt;/a&gt;, I will write a bit about how I approach planning.&lt;/p&gt;
&lt;h2 id=&quot;why-plan%3F&quot; tabindex=&quot;-1&quot;&gt;Why plan?&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://agilemanifesto.org/&quot;&gt;Manifesto for Agile Software Development&lt;/a&gt; values&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Responding to change over following a plan&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and this is often taken as an argument against planning.
It is not, for two reasons.
The first is mentioned right in the next sentence (emphasis mine):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;That is, while &lt;strong&gt;there is value in the items on the right&lt;/strong&gt;, we value the items on the left more.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This sentence is just as much part of the agile manifesto as the proceeding ‘X over Y’ statements, but often ignored.
Yes, there is value in following a plan – and thus, in &lt;em&gt;having&lt;/em&gt; a plan –, even if responding to change is more important.&lt;/p&gt;
&lt;p&gt;For the second reason, I turn to general Dwight Eisenhower, who gave us this insight:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Plans are useless but planning is indispensable.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While one should certainly be cautious about applying military doctrine to peaceful endeavors like software development
(wait, why are you laughing?),
it is certainly true that warfare is highly dynamic and much older as a discipline, and thus might have something to teach us about dealing with volatile situations.&lt;/p&gt;
&lt;h2 id=&quot;so%2C-why-are-plans-useless%3F&quot; tabindex=&quot;-1&quot;&gt;So, why are plans useless?&lt;/h2&gt;
&lt;p&gt;Presumably because, as Helmuth Graf von Moltke has it,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;No plan survives contact with the enemy.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enemies will not adhere to your plan.
To the contrary, enemies will try their very best to destroy your plan, and battle field commanders who try to stick to their plans in the face of intransigent enemies will probably lose.&lt;/p&gt;
&lt;p&gt;As to the applicability of this adage in software development, have you ever had the opportunity to&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;… watch the first user interact with the UI you carefully designed?&lt;/li&gt;
&lt;li&gt;… support a team who is trying to use an API you specified?&lt;/li&gt;
&lt;li&gt;… undertook to finish a task today, only to be interrupted by a (real or imagined) emergency and making no progress whatsoever?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Case closed.&lt;/p&gt;
&lt;p&gt;In effect, Eisenhower, von Moltke, and the agile manifesto are all saying roughly the same thing, which is that the value of having or following plans is limited, the difference being a matter of degree, not of principle.
I assume this is due to the fact that enemies actively trying to foil your plans make them really useless, whereas the chances of success are significantly higher in software development, where it is usually ‘just’ life getting in the way.
The value of following a plan as long as it remains practicable lies in increased efficiency.
More on this below.&lt;/p&gt;
&lt;h2 id=&quot;why-is-planning-indispensable%2C-then%3F&quot; tabindex=&quot;-1&quot;&gt;Why is planning indispensable, then?&lt;/h2&gt;
&lt;p&gt;After all, if planning is the activity that generates plans and plans are of limited value, how come planning is deemed to have a higher value than the plans it generates?
The reason is simply this: the primary purpose of planning is not to generate plans.&lt;/p&gt;
&lt;p&gt;Wait, what?&lt;/p&gt;
&lt;p&gt;I repeat, the primary purpose of planning is not to generate plans.
The primary purpose of planning is to prepare you to reach a goal.
While planning will typically produce one or more plans, those are almost incidental.
The real value of planning lies in the hard thinking that goes into it and in the shared understanding developed among the planners.
To come up with a plan, the planners need to examine the current situation and clarify the goal.
They need to explore options, assess risks, and devise countermeasures.
They will come up with ideas only to find – on closer examination – that they are unworkable.
In many cases, only one path from the current situation to the goal is written down as a plan.
If everything goes well, it may work.
If anything disrupts the path that was written down, it becomes worthless.
But if planning has been done well – and the people who have done it are still available – the hard thinking that has gone into it will enable them to plot a new path from the new current situation to the goal and follow this instead.&lt;/p&gt;
&lt;p&gt;This is why the real value lies in the planning activities and not in the final artifact called ‘plan’.
This is why Eisenhower values planning so much, even though he deems plans useless.&lt;/p&gt;
&lt;p&gt;Fine, you say, but this all sounds very generic.
What does planning do for you that you don&#39;t get if you skip it?&lt;/p&gt;
&lt;h2 id=&quot;benefits-of-planning&quot; tabindex=&quot;-1&quot;&gt;Benefits of planning&lt;/h2&gt;
&lt;p&gt;First off, &lt;strong&gt;planning prevents you from doing really stupid things.&lt;/strong&gt;
This is a typical illustration of development progress for an agile team.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/agile-development-progress-standard.svg&quot; alt=&quot;A diagram with a starting point on the bottom left and a slightly shifting goal on the top right. A sequence of zigzagging short arrows progresses from the starting point about a third of the way to the goal.&quot; width=&quot;500em&quot; /&gt;
&lt;figcaption&gt;Figure 1: The team moves towards the goal in small steps.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Beginning at the bottom left, the team takes small steps in the rough direction of the goal at the top right.
After each step, the team reflects on what they have accomplished and decides on the next step.
The assumption is that the world is volatile and the goal may shift, so you need to readjust your direction regularly, which is why you probably will not move in a straight path but a kind of zigzag line.
And if you subscribe to this line of thinking fully, there is no point in looking further than the next step because every effort you make to plan ahead will be wasted if the underlying assumptions change.&lt;/p&gt;
&lt;p&gt;However, there is a problem with this picture.
It looks as if the team were able to move in any direction it wanted.
Even though it is operating in a volatile world which ostensibly influences the direction the team is going, the picture does not show this.
It does not show any constraints at all.
My experience is that some things are indeed volatile and may change at a moment&#39;s notice, but other things do not, even if you want them to.
For example, the design and feature set of the web app you are building may evolve very quickly, but the legacy system you are relying on for half of the functionality will not – because it simply cannot.
So the reality may look more like this.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/agile-development-progress-real.svg&quot; alt=&quot;A diagram with a starting point on the bottom left and a slightly shifting goal on the top right. A sequence of zigzagging short arrows progresses from the starting point about a third of the way to the goal. At about two thirds, legacy system constraints represented by a transverse barrier are blocking the way.&quot; width=&quot;500em&quot; /&gt;
&lt;figcaption&gt;Figure 2: Unbeknownst to the team, there are some serious constraints coming up.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The team is not completely free to choose its own way because the environment it is operating in imposes constraints, such as the (in-)flexibility of the legacy system.
If you approach this situation without looking ahead and continue iterating towards your goal the same way as in the picture above, you will be in for a nasty surprise.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/agile-development-progress-stuck.svg&quot; alt=&quot;A diagram with a starting point on the bottom left and a slightly shifting goal on the top right. A sequence of zigzagging short arrows progresses from the starting point about two thirds of the way to the goal. At this point, it hits legacy system constraints represented by a transverse barrier.&quot; width=&quot;500em&quot; /&gt;
&lt;figcaption&gt;Figure 3: The team comes up against the constraints – aka the ‘Oh, shit!’ moment.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;A little planning will allow you to spot such obstacles and plot a way around them.
This does not mean you should stop taking small steps and re-examining your approach regularly.
It merely means that you take into account not only the goal, which may be a long way off, but also the major obstacles.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/agile-development-progress-planned.svg&quot; alt=&quot;A diagram with a starting point on the bottom left and a slightly shifting goal on the top right. Orange dotted lines labelled ‘Plan’ plot a way from the start to the goal, swerving around legacy system constraints represented by a transverse barrier. A sequence of zigzagging short arrows roughly following the ‘Plan’ line (which is adjusted after each step) progresses from the starting point about halfway to the goal.&quot; width=&quot;500em&quot; /&gt;
&lt;figcaption&gt;Figure 4: Planning has brought up the constraints and the team has plotted a way around them.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;As you can see in the picture, the team still adjusts its approach continuously, and the plan also changes accordingly.
Even though the team has taken the same number of steps as above, it is much nearer to its goal when taking the obstacle into account.&lt;/p&gt;
&lt;p&gt;Obstacles may also include critical resources your team depends on – and which may be unavailable or only available at certain times.
&lt;strong&gt;Planning allows you to anticipate needs and arrange for them to be met when they materialize.&lt;/strong&gt;
What use is it to only consider what you are going to do for the next two weeks if your progress depends on another team&#39;s work, and this team has a lead time of a month?
If you know beforehand you are going to need an API change from another team, you can coordinate with them to get it done in time.
If you only know when the need has become immediate, you will be stuck.
Mind you, planning does not guarantee that you will have what you need when you need it, but &lt;em&gt;not&lt;/em&gt; planning guarantees that you will &lt;em&gt;not&lt;/em&gt; have what you need when you need it.
And planning pushes you to think about this dependency and may even surface some way to decouple your work from the other team&#39;s schedule so you will not be blocked even if they fail to deliver on time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Planning enables better decisions.&lt;/strong&gt;
Why?
Because good decisions take time.
There is a concept in agile software development called &lt;a href=&quot;https://blog.codinghorror.com/the-last-responsible-moment/&quot;&gt;‘the last responsible moment’&lt;/a&gt;, which says you should delay commitment to a decision until the moment when not committing becomes more costly than committing.
The reason is that deciding later allows you to make more informed – and thus better – decisions.
For this to become true, however, learning needs to take place between now and later.
Otherwise, the decision becomes just later, not better informed.
And learning does not happen by itself.
The team actively needs to create opportunities for learning along the way.
And why should the team do this if it does not know that it will need to make the decision – if it has not planned for it?
Planning in this case does not mean anticipating the result of the decision.
It means anticipating the need for the decision, identifying important decision criteria, and ensuring that the team gathers enough relevant experience to make the decision well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Planning allows you to move faster.&lt;/strong&gt;
This may sound counterintuitive if you think of planning as a large effort that happens before you get anything done.
It doesn&#39;t need to be so.
You can plan continuously as you go along.
Your plan may turn out to be wrong, so advancing in small steps and continuously reassessing your position and the best way forward is still advisable, but the thinking you have done during planning will help you get this done very quickly.&lt;/p&gt;
&lt;p&gt;In one extreme case, I have seen a team flounder after taking a &lt;em&gt;successful&lt;/em&gt; step.
&lt;em&gt;How could they not know what to do next if everything went just as they wished?&lt;/em&gt;
If they had thought a bit about the whole journey it would have been immediately clear what the next step should be.
As it stood, they had to hold an hour long meeting to determine what they should do next.&lt;/p&gt;
&lt;p&gt;Not planning is a bit like walking around with your eyes closed in broad daylight – or maybe with your eyes glued to your smartphone.
You can do that, but you have to go slowly and feel your way forward lest you run into a wall and hurt yourself.
You can go forth with much more confidence, less risk of hurting yourself – and faster – if you look ahead.
You still have to slow down when approaching a corner you cannot see around, and if it is foggy you cannot see as far ahead as in bright sunshine, but &lt;em&gt;it still helps&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Also, plans are rarely completely wrong or useless.
Remember, the adage was coined for war – an extremely adversarial situation where the other party is actively trying to sabotage your plans.
Software development is not that (at least in my experience).
In general, the world is unpredictable, yes, but small adjustments to software projects are still more likely than drastic 180° turns.
Some plans even work.
Often, some details are off.
Less often, major adjustments are necessary, and it is likely you will be able to anticipate some of them.
And events which change &lt;em&gt;everything&lt;/em&gt; are rare.
Case in point: when the COVID-19 pandemic hit, it drastically changed my personal life; it affected how I worked to a large degree (I went from 100% on site to 100% remote in a week); it changed the goals of the project I was involved in at the time – &lt;em&gt;not one bit&lt;/em&gt;.
It also did not change the EU tender process we were going through, nor did it change our technology evaluation criteria.
The details of our plans were adjusted, but the plans were not completely invalidated and helped – rather than hindered – progress.&lt;/p&gt;
&lt;h2 id=&quot;when-to-plan&quot; tabindex=&quot;-1&quot;&gt;When to plan&lt;/h2&gt;
&lt;p&gt;So should you plan everything all the time?
Probably not.
As I mentioned, I am a planner at heart, but even I admit you can overdo it.
Very detailed plans, in particular, are pretty much useless, because details are very likely to change anyway.
So when should you plan?
I think planning is most useful in the following situations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Feasibility check&lt;/strong&gt;: You want to check whether a project is possible before committing time and resources to it. Example: your product owner has an idea for a major new feature, and it is not clear whether it is even possible to build. Planning prevents you from embarking on a fool&#39;s errand and helps with the definition of the first step if the project is deemed feasible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implementation start&lt;/strong&gt;: You are about to embark on a new non-trivial project. Example: the above-mentioned feature has been added to your backlog as an epic, and you are ready to begin work on it. Planning allows you to spot potential problems and identify key decisions early on, and to provide for the necessary learning opportunities to make the decisions well-founded ones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct deadline&lt;/strong&gt;: A project absolutely must be finished at a certain point in time. Example: your contract with a service provider is expiring and you must have integrated their successor before this. Planning helps you to move faster and allows you to spot and circumvent obstacles which could jeopardize the deadline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Indirect deadline&lt;/strong&gt;: You are providing a service to a different team, and they have a direct deadline for which they need something from you. Example: a web shop needs to do a release for Black Friday, and they need a new feature in your API for it. Here, too, planning enables faster progress and helps you deal with obstacles proactively.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency on scarce resources&lt;/strong&gt;: Your team needs expertise or resources external to the team, which are hard to get on short notice. Example: you want to build an integration with a legacy system maintained by a third party and need integration support. Planning ahead enables you to anticipate this need and to coordinate with the support provider in good time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are scenarios where a little planning can make the difference between quick success and abysmal failure.
In other situations, the effect may not be as noticeable, and an argument can be made that planning is not necessary.&lt;/p&gt;
&lt;h2 id=&quot;conclusion-(and-the-single-most-important-success-factor)&quot; tabindex=&quot;-1&quot;&gt;Conclusion (and the single most important success factor)&lt;/h2&gt;
&lt;p&gt;To make this clear, I am not in favor of overly detailed planning and strict adherence to plans.
This – shall we say – legacy practice goes hand in hand with rejecting – or even denying – change and results in software that is delivered late, if at all, and often fails to meet customer needs.
However, the current predisposition against planning seems to me to be an overreaction to this kind of overplanning.
The solution to bad planning is not no planning at all.
It is to learn good planning, and to apply it in the right way.&lt;/p&gt;
&lt;p&gt;To me, this means recognizing – like Eisenhower – that the value of planning lies not in the plans produced (which can become useless very quickly), but in the process itself and particularly in the learning that takes place during planning.
This learning enables you to execute faster while avoiding stupid mistakes.
It also enables you to manage dependencies and make better decisions by planning for learning along the way.
And it enables you to be – yes! – &lt;em&gt;more&lt;/em&gt; agile.&lt;/p&gt;
&lt;p&gt;Given that the value of planning lies in the process, the most important factor is the people involved.
If the plan is made by group A and the execution of the plan is assigned to group B, you &lt;em&gt;lose all the learning&lt;/em&gt; and usually hand over only the plan – the least useful result of planning!
To reap the benefits of planning, you therefore &lt;em&gt;must&lt;/em&gt; involve the people who are going to be responsible for execution.&lt;/p&gt;
&lt;p&gt;If you only remember two things from this post, please let it be these two:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Planning as an activity is valuable for agile teams, even if the plan is not.&lt;/li&gt;
&lt;li&gt;But only if planning is done by the people who are going to do the work.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thank you for reading, and happy planning!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt;
Originally, I thought I&#39;d write two posts, one on the benefits of planning, and a second one on how I do it. Turns out there is more to it than will fit in a single follow-up post, so I turned the &#39;how&#39; part into a series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-1/&quot;&gt;end-to-end planning and iterative refinement&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-2/&quot;&gt;forwards and backwards planning&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-3/&quot;&gt;the critical path&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-4/&quot;&gt;exploration&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Continue with &lt;a href=&quot;https://sebastian-hans.de/blog/on-planning-how-1/&quot;&gt;part 2 – iterative end-to-end-planning&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sun, 30 Jun 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/on-planning-why/</guid>
    </item>
    
    
    <item>
      <title>What is good software architecture?</title>
      <link>https://sebastian-hans.de/blog/what-is-good-software-architecture/</link>
      <description>&lt;h2 id=&quot;tl%3Bdr&quot; tabindex=&quot;-1&quot;&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;A good software architecture does not just solve a problem. It also&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;states the problem and its design goals (including any assumptions),&lt;/li&gt;
&lt;li&gt;imposes only necessary constraints on the implementation (is not too fine-grained),&lt;/li&gt;
&lt;li&gt;is visible and understandable, and&lt;/li&gt;
&lt;li&gt;shows its work (decisions are traceable to design goals).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;so%2C-your-software-has-an-architecture.&quot; tabindex=&quot;-1&quot;&gt;So, your software has an architecture.&lt;/h2&gt;
&lt;p&gt;This is a sequel to my first post, &lt;a href=&quot;https://sebastian-hans.de/blog/what-is-software-architecture/&quot;&gt;‘What is software architecture?’&lt;/a&gt;,
in which I defined a software architecture as a model, resulting from intentional, goal-oriented decisions, about how to solve a problem with software.&lt;/p&gt;
&lt;p&gt;However, just because you have an architecture, this does not necessarily mean that it is a good one.
What makes a software architecture good?&lt;/p&gt;
&lt;h2 id=&quot;isn&#39;t-it-enough-that-it-is-working%3F&quot; tabindex=&quot;-1&quot;&gt;Isn&#39;t it enough that it is working?&lt;/h2&gt;
&lt;p&gt;If you implement a system based on your architecture, and it works, why bother with the question?
First off, this approach requires you to build the software before you can evaluate your architecture.
However, discovering architectural flaws after the implementation phase is typically expensive.
It is much cheaper to eliminate flaws if they are found earlier in the software delivery lifecycle.&lt;/p&gt;
&lt;p&gt;And then, there are the ‘-ilities’.
Software architecture is often concerned with quality attribute requirements in areas such as security, maintainability, adaptability, and others (collectively referred to as ‘-ilities’), which deal with risk.
Much of software architecture is risk management.
If there is no risk you may miss your performance target, there is no need for the architecture to address performance.
If there is no risk your system may be insecure, the architecture does not have to deal with security concerns.
The crux of risks is that they may &lt;em&gt;not materialize&lt;/em&gt;.
Generally, this is a good thing.
You don&#39;t &lt;em&gt;want&lt;/em&gt; risks to materialize.
But how can you tell then whether they have been addressed?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;‘What&#39;s this?’ – ‘This is my charm against tigers.’ – ‘But there are no tigers around here!’ – ‘Yes! See how well it is working?’&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How can you be sure that your anti-tiger charm is working (or that your architecture is maintainable) if there are no tigers in the country anyway (or if you have not yet had to perform maintenance on the finished system)?
If you want your home to be secure, would you buy a front door lock that claims to prevent theft, but only as long as there are no thieves around?
I wouldn&#39;t.
I would want some assurance that the lock – or the charm – or the architecture – is actually capable of eliminating or at least mitigating the risks it is supposed to address.&lt;/p&gt;
&lt;p&gt;So how do you do that?
How do you provide this assurance?&lt;/p&gt;
&lt;h2 id=&quot;what-does-%E2%80%98good%E2%80%99-mean%3F&quot; tabindex=&quot;-1&quot;&gt;What does ‘good’ mean?&lt;/h2&gt;
&lt;p&gt;As we have seen above, ‘good’ for an architecture does not just mean that the finished system is functional.
And if you look at my definition again, you will notice that it says the architecture is a model anyway.
The architecture is not the implementation.
So, what does ‘good’ mean for this model?
I propose four criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It states the problem.&lt;/li&gt;
&lt;li&gt;It imposes only necessary constraints.&lt;/li&gt;
&lt;li&gt;It is visible and understandable.&lt;/li&gt;
&lt;li&gt;It shows its work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are my ‘conditions for software architecture goodness’.
In the following sections I will elaborate on them in turn and show why I think each of them is necessary.&lt;/p&gt;
&lt;h2 id=&quot;a-good-software-architecture-states-the-problem.&quot; tabindex=&quot;-1&quot;&gt;A good software architecture states the problem.&lt;/h2&gt;
&lt;p&gt;By definition, a software architecture is supposed to describe how to solve a problem with software.
To be considered a good software architecture, it needs to actually do this.
While this may sound obvious, there is one small hurdle to clear:
solving a problem requires the problem to be known.
In practice, it can be surprisingly hard to get a clear and complete problem statement.
While it may be easy to summarize the problem in a single sentence, this is rarely enough to give a complete picture of what is really needed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;‘I want a self-driving car.’ – ‘OK, we can do that. We&#39;ll put a little controller on an R/C car, and …’ – ‘Oh, and it must be able to drive my family to any address in the USA without getting involved in an accident.’ – ‘Ooof!’&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The finished system must fulfill the needs of its stakeholders, and it must typically do so in a specific environment while being subject to a particular set of constraints.
These can be formulated as a set of design goals which will drive the architecture.
If it is not possible to get a definitive answer to a question relevant to the architecture (because the business requirements are not yet clear or depend on circumstances outside your control, e.g., future growth),
you will have to make assumptions, which then become part of your design goals.&lt;/p&gt;
&lt;p&gt;If the architecture of a software system does not state the problem it is set to solve (including the breakdown into design goals), the software implemented based on this architecture may still fulfill its stakeholders&#39; needs, and it may still be able to operate more or less smoothly in the target environment, but it will only do so by accident.
Since the whole point of the software architecture is to &lt;em&gt;ensure&lt;/em&gt; that result, if it does not do so, it is a bad architecture, even though the system itself may be fit for purpose.&lt;/p&gt;
&lt;p&gt;A further advantage of explicitly listing your design goals is that it becomes easier to see when things change.
If, for example, the number of users to support is given as ‘up to 10,000’ and your business is so successful that the growth projection for the next year exceeds this number, it is a no-brainer to say, ‘Maybe we should review how our architecture is going to cope with this.’
If the number of users the architecture was designed for was not specified explicitly, you may only notice that you are operating outside of the system&#39;s specifications when the first problems appear in production.&lt;/p&gt;
&lt;p&gt;Thus, a &lt;em&gt;good&lt;/em&gt; software architecture contains a clear and complete problem statement as well as a set of design goals derived from the context and from the stakeholders&#39; needs (including any assumptions you need to make).
This is why the &lt;a href=&quot;https://docs.arc42.org/section-1/&quot;&gt;first chapter of the arc42 architecture documentation template&lt;/a&gt; contains an overview of the major business goals, functional requirements, and quality goals, as well as pointers to the complete documentation of each.&lt;/p&gt;
&lt;h2 id=&quot;a-good-software-architecture-imposes-only-necessary-constraints.&quot; tabindex=&quot;-1&quot;&gt;A good software architecture imposes only necessary constraints.&lt;/h2&gt;
&lt;p&gt;Once the problem is clearly stated, you need to make decisions about how to solve it.
How many decisions?
Paraphrasing &lt;a href=&quot;https://en.wikipedia.org/wiki/Occam&#39;s_razor&quot;&gt;Occam&#39;s razor&lt;/a&gt;, decisions are not to be multiplied without necessity.&lt;/p&gt;
&lt;p&gt;Because every decision puts constraints on the solution space (and if it doesn&#39;t, it is unnecessary).
This is a good thing because the whole point of software architecture is to constrain the solution space in such a way as to exclude bad implementations, i.e., implementations that fail to reach the design goals.
But too many constraints are bad, too.&lt;/p&gt;
&lt;p&gt;If there are two architectures that solve the same problem, but one of them puts more constraints on the solution than the other, the additional constraints are not necessary.
Imposing them excludes valid implementations and thus limits the flexibility of the solution design.
If (when) requirements change, but the context from which the design goals were derived does not, the smaller, coarser grained architecture will not need to change.
The larger, finer grained architecture may need to be adapted, however, because the additional constraints may stand in the way of the necessary implementation changes.&lt;/p&gt;
&lt;p&gt;Therefore, while there is no hard limit on how fine-grained a software architecture can be (at the limit, it could describe a single implementation exactly), I posit that a software architecture that imposes only constraints necessary to reach its design goals is strictly better than one that imposes additional constraints.&lt;/p&gt;
&lt;p&gt;(And if you can think of a case where an additional constraint would make the architecture better, ‘because it would enable X’, then maybe you forgot to name X as a design goal.)&lt;/p&gt;
&lt;h2 id=&quot;a-good-software-architecture-is-visible-and-understandable.&quot; tabindex=&quot;-1&quot;&gt;A good software architecture is visible and understandable.&lt;/h2&gt;
&lt;p&gt;A software architecture is a model of how to solve a problem with software.
To actually solve the problem, the software system needs to be implemented according to the architecture, which means that the people implementing it need to know about the architecture.
If there is only one developer, and it is the same person as the software architect, and the implementation is completed so quickly that there is no chance of forgetting something important,
it may suffice if the architecture lives only in their head.
In all other cases (more than one person working on the implementation, or one person working on it over a longer period of time), if the architecture is to be preserved over the course of time,
it must be made visible and must be represented in a way that will be understandable to developers (either the architects themselves or others) in the future.&lt;/p&gt;
&lt;p&gt;Sometimes, software architecture is described as a ‘shared understanding’ of the system structures by the relevant stakeholders (for references, see &lt;a href=&quot;https://sebastian-hans.de/blog/what-is-software-architecture/&quot;&gt;my previous post&lt;/a&gt;).
Building a shared understanding is much easier if there is something visible to point to when doing the sharing.&lt;/p&gt;
&lt;p&gt;Making a software architecture visible can be done in various ways.
The obvious one is having separate architecture documentation.
For guidelines on what to document and how to do it, I found the &lt;a href=&quot;https://arc42.org/overview&quot;&gt;arc42 template&lt;/a&gt; useful.&lt;/p&gt;
&lt;p&gt;The internal architecture of an application can be made visible in the code by making judicious use of your programming language&#39;s modularization features and appropriate naming of modules and other elements (packages, classes, functions, …).
For example, in the Java world, if you want to decouple an application from the implementation of a certain piece of logic,
you can put the interface definitions and implementation classes into separate JAR files and declare only the interface JAR as a compile-time dependency.
The implementation JAR is added as a runtime dependency only, which prevents the application from accessing the implementation classes directly.
An example of this is &lt;a href=&quot;https://slf4j.org/&quot;&gt;SLF4J&lt;/a&gt;, which provides an interface to logging, with several implementations backed by different logging libraries.&lt;/p&gt;
&lt;p&gt;There are also ways to test conformance to an architecture.
For Java-based software, one option is &lt;a href=&quot;https://www.archunit.org/&quot;&gt;ArchUnit&lt;/a&gt;, which allows you to write unit tests that check dependencies between packages and classes.
Some static code analysis tools offer similar features.
Tools like these make the architecture visible by raising flags on architecture violations.&lt;/p&gt;
&lt;p&gt;While none of these techniques can guarantee that the architecture will be followed when changes are made in the future,
projects using them have much better chances of keeping their architecture alive.&lt;/p&gt;
&lt;h2 id=&quot;a-good-software-architecture-shows-its-work.&quot; tabindex=&quot;-1&quot;&gt;A good software architecture shows its work.&lt;/h2&gt;
&lt;p&gt;The paper &lt;a href=&quot;https://insights.sei.cmu.edu/documents/2544/2010_010_001_513810.pdf&quot;&gt;What is your definition of software architecture?&lt;/a&gt; cites Barry Boehm and his students at the USC Center for Software Engineering like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A software system architecture comprises&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;[…]&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;A rationale which demonstrates that the components, connections, and constraints define a system that, if implemented, would satisfy the collection of system stakeholders’ need statements.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;While I think a software architecture that is missing this rationale is still a software architecture, i.e., the rationale is not the defining aspect, I agree that a &lt;em&gt;good&lt;/em&gt; software architecture does show its work in this way.&lt;/p&gt;
&lt;p&gt;I define a software architecture as a model, resulting from intentional, goal-oriented decisions.
This means each decision is made to support one or more design goals, and this in turn means someone has hopefully thought about &lt;em&gt;how&lt;/em&gt; the decision does this and &lt;em&gt;why&lt;/em&gt; the option chosen does this better than other options that were also considered.
Documenting these thoughts (possibly in &lt;a href=&quot;https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions&quot;&gt;Architecture Decision Records&lt;/a&gt;) makes the architecture better understandable for everyone who was not there when it was created (or cannot remember everything perfectly).
And it makes it easier to work with when something changes.
If a change is made to the business requirements that has an impact on the system&#39;s design goals, you can examine the rationale to determine which decisions may need to be revisited.
If a change to the architecture is proposed, you can look up from which design goals the current state was derived, and determine the impact of the change on these goals.&lt;/p&gt;
&lt;p&gt;Documenting these links is especially important for design goals that are not directly observable because they do not concern behavior but deal with risk reduction (like many of the ‘-ilities’ do).
Sometimes, the rationale connecting a design goal with architectural derisking measures is the only visible artefact that shows the design goal has been addressed.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;A software architecture that results in a working system when implemented is … not bad, but to be really good, it should also …&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;… state the problem it is solving and break it down into design goals.&lt;/li&gt;
&lt;li&gt;… refrain from imposing constraints on the implementation that are not necessary to reach its design goals.&lt;/li&gt;
&lt;li&gt;… be visible and presented in ways understandable by fellow architects and developers (which may include the future you).&lt;/li&gt;
&lt;li&gt;… provide a rationale that links design goals and architecture decisions and explains how the latter result in an architecture that meets the former.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your software architecture does these things, it will be easier to implement and easier to work with in the long term.
And, of course, it should also work.&lt;/p&gt;
</description>
      <pubDate>Mon, 27 May 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/what-is-good-software-architecture/</guid>
    </item>
    
    
    <item>
      <title>What is software architecture?</title>
      <link>https://sebastian-hans.de/blog/what-is-software-architecture/</link>
      <description>&lt;h2 id=&quot;tl%3Bdr&quot; tabindex=&quot;-1&quot;&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;A &lt;em&gt;software architecture&lt;/em&gt; is a &lt;em&gt;model&lt;/em&gt;, resulting from &lt;em&gt;intentional&lt;/em&gt;, &lt;em&gt;goal-oriented&lt;/em&gt; decisions, about how to &lt;em&gt;solve a problem&lt;/em&gt; with software.&lt;/p&gt;
&lt;p&gt;The discipline of &lt;em&gt;Software Architecture&lt;/em&gt; comprises methods to identify, make, and communicate the necessary decisions and the resulting model.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;Software Architect&lt;/em&gt; is someone who applies these methods to a specific problem to reach a concrete set of goals.&lt;/p&gt;
&lt;h2 id=&quot;why%3F&quot; tabindex=&quot;-1&quot;&gt;Why?&lt;/h2&gt;
&lt;p&gt;Why yet another definition of software architecture?
Don&#39;t we have enough definitions already?
No, of course not. I am just following &lt;a href=&quot;https://xkcd.com/927/&quot;&gt;standard procedure&lt;/a&gt; here. 😉&lt;/p&gt;
&lt;p&gt;On a more serious note, I think each new attempt to define software architecture brings a new perspective to what I see as an evolving field.
Maybe some day we will have a common definition of software architecture everyone can agree on similar to other disciplines.
But how will we get there if not by iteratively refining our understanding, discussing various aspects, coming up with new descriptions, and trying them on to see how they fit?&lt;/p&gt;
&lt;p&gt;It is in this spirit that I present my attempt at a definition, not to replace others, but to add something I have been thinking about a lot, in the hope that someone may find it useful.
I am happy to enter into a discussion on &lt;a href=&quot;https://hachyderm.io/@sebhans&quot;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-software-architecture%3F&quot; tabindex=&quot;-1&quot;&gt;What is software architecture?&lt;/h2&gt;
&lt;p&gt;I humbly propose the following definition of software architecture.
(This definition applies to systems – which may contain non-software parts, such as hardware and people – just as well. I will be using the terms &lt;em&gt;software architecture&lt;/em&gt;, &lt;em&gt;systems architecture&lt;/em&gt;, and &lt;em&gt;architecture&lt;/em&gt; more or less interchangeably.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;em&gt;software architecture&lt;/em&gt; is a model, resulting from intentional, goal-oriented decisions, about how to solve a problem with software.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This definition consists of several parts, each of which addresses an important aspect.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A software architecture is a &lt;em&gt;model&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A software architecture is &lt;em&gt;intentional&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A software architecture is &lt;em&gt;goal-oriented&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A software architecture describes how to &lt;em&gt;solve a problem&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I will discuss each of them briefly in the following sections, in reverse order.&lt;/p&gt;
&lt;p&gt;Building on this definition of &lt;em&gt;a&lt;/em&gt; software architecture,
the discipline of &lt;em&gt;Software Architecture&lt;/em&gt; comprises methods to identify, make, and communicate the necessary decisions and the resulting model,
and a &lt;em&gt;Software Architect&lt;/em&gt; is someone who applies these methods to a specific problem to reach a concrete set of goals.&lt;/p&gt;
&lt;h3 id=&quot;a-software-architecture-solves-a-problem.&quot; tabindex=&quot;-1&quot;&gt;A software architecture solves a problem.&lt;/h3&gt;
&lt;p&gt;Just as the architecture of a building is aimed at a specific purpose – the architect should know if the building is supposed to contain a factory or a sports arena or house a family – the architecture of a piece of software is intended to solve a specific problem.
If there is no problem to solve, there is no need for an architecture (and no need for the software).&lt;/p&gt;
&lt;p&gt;This is in contrast to &lt;em&gt;architecture styles&lt;/em&gt; and &lt;em&gt;architecture patterns&lt;/em&gt;, which describe generic approaches or solutions to classes of problems.
These are useful because they codify architecture experience and provide guidance to architects, but to become &lt;em&gt;an architecture&lt;/em&gt; they have to be applied to a concrete problem.&lt;/p&gt;
&lt;p&gt;While solving a problem, there are many different aspects to consider, each of which may have an impact on the final architecture.
To be able to address them effectively, they need to be broken down into design goals, which brings us to the next point.&lt;/p&gt;
&lt;h3 id=&quot;a-software-architecture-is-goal-oriented.&quot; tabindex=&quot;-1&quot;&gt;A software architecture is goal-oriented.&lt;/h3&gt;
&lt;p&gt;Every software system must operate in a certain environment, is subject to a unique set of constraints, and must satisfy its stakeholders&#39; needs, often expressed in the form of functional requirements and quality attribute requirements.
The goal of a software system&#39;s architecture is to ensure that the implementation will be fit for purpose in the sense that it &lt;em&gt;can&lt;/em&gt; operate in the target environment, that it &lt;em&gt;does&lt;/em&gt; conform to those constraints, and that it &lt;em&gt;can&lt;/em&gt; fulfill the requirements.
Thus, environment, constraints, and requirements form a set of design goals that inform the architecture, which in turn means that the resulting architecture is inextricably linked to these design goals.
If the goals are chosen differently, the architecture must be designed differently.
If the goals change, chances are good that the architecture will also need to change.
This is what I mean when I say designing a software architecture is an inherently goal-oriented process.&lt;/p&gt;
&lt;p&gt;As a consequence, if you do not have any design goals, you do not have a basis to build an architecture on. Architecture cannot ‘happen’ by chance (also see my notes on intentionality below).&lt;/p&gt;
&lt;h4 id=&quot;but-what-if-there-are-no-goals%3F&quot; tabindex=&quot;-1&quot;&gt;But what if there are no goals?&lt;/h4&gt;
&lt;p&gt;Sometimes, specific design goals are hard to come by, either because our stakeholders cannot be bothered to put them in concrete terms or because the product we are trying to build is still in an early phase and we only have a vague vision, but few hard requirements.
This makes designing an appropriate architecture hard because the whole point of architecture is to meet design goals.
In this case, you will have to work with assumptions and formulate goals from them.
Only then will you be able to design an architecture that will meet those goals.&lt;/p&gt;
&lt;h4 id=&quot;what-if-the-goals-are-wrong%3F&quot; tabindex=&quot;-1&quot;&gt;What if the goals are wrong?&lt;/h4&gt;
&lt;p&gt;If you base your design goals on assumptions, you run the risk of being wrong about them.
Of course, this risk is always there, but the more assumptions you need to make, the greater the risk becomes.&lt;/p&gt;
&lt;p&gt;If the goals are wrong, i.e., they do not represent the actual needs of the stakeholders,
you will get an architecture that looks good on paper (because it ostensibly reaches its goals), but will perform badly in practice because it fails to address the real needs.&lt;/p&gt;
&lt;h4 id=&quot;what-if-the-goals-are-lost%3F&quot; tabindex=&quot;-1&quot;&gt;What if the goals are lost?&lt;/h4&gt;
&lt;p&gt;While you must have design goals for your architecture work, sometimes these design goals are not written down explicitly and live only in the heads of the architects.
The resulting model will still be an architecture, possibly even a good one, but it will be hard to evaluate because no one who was not there during the architecture design can determine how well the architecture meets its unknown goals.&lt;/p&gt;
&lt;p&gt;It is also impossible to tell whether there really &lt;em&gt;is&lt;/em&gt; an architecture or what you are seeing is just structures that accreted over time.&lt;/p&gt;
&lt;h3 id=&quot;a-software-architecture-is-intentional.&quot; tabindex=&quot;-1&quot;&gt;A software architecture is intentional.&lt;/h3&gt;
&lt;p&gt;Problems can be solved in many ways, including throwing solutions at them and seeing what sticks, or going from one tiny subproblem to the next and fiddling around until it works.
I have seen software systems built this way.
I know it can work.
I don&#39;t think this is architecture, however.&lt;/p&gt;
&lt;h4 id=&quot;architecture-results-from-deliberate-decisions.&quot; tabindex=&quot;-1&quot;&gt;Architecture results from deliberate decisions.&lt;/h4&gt;
&lt;p&gt;To me, architecture implies a deliberate decision-making and modelling process.
There are those who say, every system has an architecture.
I disagree.
If you have not deliberately formed a model of how to solve a problem, your solution does not have an architecture.
It may have a &lt;em&gt;structure&lt;/em&gt; (it must have, to be executed by a computer), but it does not have an &lt;em&gt;architecture&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A tree trunk that has been blown over by the wind and fallen over a river may serve as a bridge, but since there is no underlying model, it does not have an architecture.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Photo of a tree trunk lying across a wild river, supported by rocks on either end.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/tree-trunk-across-river-300w.jpeg?v=01f2f4ce9012&quot; width=&quot;900&quot; height=&quot;350&quot; srcset=&quot;https://sebastian-hans.de/img/tree-trunk-across-river-300w.jpeg?v=01f2f4ce9012 300w, https://sebastian-hans.de/img/tree-trunk-across-river-600w.jpeg?v=88f21ace5c24 600w, https://sebastian-hans.de/img/tree-trunk-across-river-900w.jpeg?v=8cc7dacd3a41 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Figure 1: This tree trunk fallen across the river has no architecture.
&lt;br /&gt;
Image credit: &lt;a href=&quot;https://www.flickr.com/photos/fototaube/14934678555&quot;&gt;J. M.&lt;/a&gt; &lt;a href=&quot;https://creativecommons.org/licenses/by-sa/2.0/&quot;&gt;CC BY-SA 2.0&lt;/a&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The same tree trunk, put there deliberately as a solution to river-crossing, does have an architecture, albeit a simple one.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img src=&quot;https://sebastian-hans.de/img/river-crossing-architecture.svg&quot; alt=&quot;An architecture diagram showing a tree trunk laid across rocks on either side of a river.&quot; width=&quot;300em&quot; /&gt;
&lt;figcaption&gt;Figure 2: This architecture diagram of the above tree trunk shows how it is supposed to span the river.&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h4 id=&quot;architecture-lies-in-the-heads-of-the-designers.&quot; tabindex=&quot;-1&quot;&gt;Architecture lies in the heads of the designers.&lt;/h4&gt;
&lt;p&gt;You could also say, a system does not ‘have’ an architecture at all.
Because the architecture is not a property of the system, but of the system&#39;s designers.
It is they who form the model of how to solve the problem.
Given just the tree trunk mentioned above, there is no simple way to tell whether there exists an underlying architecture.
The same is true for software systems, but there it is much worse due to their greater complexity.&lt;/p&gt;
&lt;h4 id=&quot;architecture-comes-before-implementation.&quot; tabindex=&quot;-1&quot;&gt;Architecture comes before implementation.&lt;/h4&gt;
&lt;p&gt;If software architecture is intentional (and geared towards reaching certain goals), it must necessarily be done before the software is written.
Writing software and then claiming, ‘This is the architecture!’, is like shooting at a blank wall and drawing the target where you hit it instead of first drawing the target and then taking a shot.
The result may look similar to someone who has not witnessed the process, but the difference could not be greater.&lt;/p&gt;
&lt;p&gt;This also means that an architecture cannot be reliably reverse-engineered.
By performing &lt;a href=&quot;https://en.wikipedia.org/wiki/Software_archaeology&quot;&gt;software archaeology&lt;/a&gt;, the attempt to discover the underlying structures and design of an existing piece of software, you may indeed discover structures,
but without supporting documentation, you will not be able to tell whether these structures were intentionally created to conform to an architecture or whether they have been formed by a different process.
Or, in other words, looking at the wall with the hole in it, but no target painted on, how can you tell where the shooter &lt;em&gt;wanted&lt;/em&gt; to hit? And how can you even tell whether the shooter took aim at all or wether it was just a random shot?&lt;/p&gt;
&lt;h3 id=&quot;a-software-architecture-is-a-model.&quot; tabindex=&quot;-1&quot;&gt;A software architecture is a model.&lt;/h3&gt;
&lt;p&gt;As a model, the software architecture is an abstraction, not the software itself.
This has a couple of implications.&lt;/p&gt;
&lt;h4 id=&quot;a-model-is-not-the-thing-itself.&quot; tabindex=&quot;-1&quot;&gt;A model is not the thing itself.&lt;/h4&gt;
&lt;p&gt;As with the architecture of buildings, a software architecture consists of plans, typically including diagrams that show important components and how they relate to each other.
It does not consist of the components themselves.
The architecture is separate from the structures (buildings, software) implementing it and typically exists before the structures are built.&lt;/p&gt;
&lt;p&gt;This is not an architecture.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Photo of the Spittelhof Estate apartment building in Biel-Benken, Basel, Switzerland. The building has a long, straight front and is surrounded by green grass and woods.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/building-biel-300w.jpeg?v=34e8e7567803&quot; width=&quot;900&quot; height=&quot;675&quot; srcset=&quot;https://sebastian-hans.de/img/building-biel-300w.jpeg?v=34e8e7567803 300w, https://sebastian-hans.de/img/building-biel-600w.jpeg?v=290eba6d0f09 600w, https://sebastian-hans.de/img/building-biel-900w.jpeg?v=720c818b8ba3 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Figure 3: A building itself is not its architecture. It is just a building.
&lt;br /&gt;
Image credit: &lt;a href=&quot;https://www.flickr.com/photos/leonl/6136411699/&quot;&gt;Leon&lt;/a&gt; &lt;a href=&quot;https://creativecommons.org/licenses/by/2.0/&quot;&gt;CC BY 2.0&lt;/a&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This is.&lt;/p&gt;
&lt;figure&gt;
&lt;div&gt;
&lt;img alt=&quot;Rough drawing of a government building in Biel, showing its structure with just a few lines.&quot; loding=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://sebastian-hans.de/img/architecture-drawing-biel-300w.jpeg?v=261ac5de0513&quot; width=&quot;900&quot; height=&quot;542&quot; srcset=&quot;https://sebastian-hans.de/img/architecture-drawing-biel-300w.jpeg?v=261ac5de0513 300w, https://sebastian-hans.de/img/architecture-drawing-biel-600w.jpeg?v=4adb667987a1 600w, https://sebastian-hans.de/img/architecture-drawing-biel-900w.jpeg?v=c5cc0ba1c71a 900w&quot; sizes=&quot;(max-width: 300px) 300px, (max-width: 600px) 600px, 900px&quot; /&gt;
&lt;figcaption&gt;
Figure 4: A (building) architecture is the &lt;em&gt;model&lt;/em&gt; of a building.
&lt;br /&gt;
Image credit: &lt;a href=&quot;https://en.wikipedia.org/wiki/File:Architekturskizze_Verwaltungsgeb%C3%A4ude_Biel.jpg&quot;&gt;gottm2&lt;/a&gt; &lt;a href=&quot;https://creativecommons.org/licenses/by/2.5/deed.en&quot;&gt;CC BY 2.5&lt;/a&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;If a visitor to this building exclaims, ‘What a great architecture!’,
this does not mean that the building itself &lt;em&gt;is&lt;/em&gt; the architecture, but that the architecture is immediately apparent to the casual viewer
because it has been followed strictly and the builders have refrained from adding elements which would obscure it.
However, if they had (like Christo and Jeanne-Claude did when they &lt;a href=&quot;https://www.stiftung-doku-verhuellter-reichstag.de/das-kunstwerk/&quot;&gt;wrapped the German Reichstag building &lt;/a&gt; (website in German)), the architecture would still be there.&lt;/p&gt;
&lt;h4 id=&quot;a-model-leaves-things-out.&quot; tabindex=&quot;-1&quot;&gt;A model leaves things out.&lt;/h4&gt;
&lt;p&gt;As an abstraction, the architecture must necessarily leave many things out.
So, what should be part of the model and what should be left out?
Since the point of the architecture is to solve a problem, it should &lt;em&gt;only&lt;/em&gt; include things relevant to the solution of this problem.
This may seem trivial at first glance, but when you consider that the problem statement itself may be unclear, you come to realize that this is not as simple as it sounds.&lt;/p&gt;
&lt;p&gt;On the other hand, the model should address &lt;em&gt;all&lt;/em&gt; aspects that are important for the solution of the problem.
However, since designing and maintaining a software architecture is a deliberate process (as discussed above), it comes at a cost (in terms of effort and time).
If we had infinite resources and time, we could make our architecture model so detailed that it would effectively describe an implementation.
In practice, we are always working with limited resources and time, so we should constrain our architecture model to those aspects with the largest impact on the shape of the solution and its fit to the problem and goals we are trying to achieve.&lt;/p&gt;
&lt;p&gt;Moreover, if we plan to continue developing our solution, we need to consider the impact of ongoing development on the software architecture.
If our architecture is too detailed, it will need to be adapted very often as our solution evolves, leading to unnecessarily high costs of change.
If our architeture is too coarse, it may not be able to ensure that the solution continues to meet its design goals as changes are made.
These two concerns need to be carefully balanced to arrive at a model which contains just enough guard rails to keep development on track with respect to the design goals, but at the same time enables (instead of inhibiting) continuous change.&lt;/p&gt;
&lt;h4 id=&quot;an-undocumeted-model-is-still-a-model.&quot; tabindex=&quot;-1&quot;&gt;An undocumeted model is still a model.&lt;/h4&gt;
&lt;p&gt;A software architecture can exist in the head of a person and still be an architecture.
In my view, it does not have to be written down to ‘count’ as an architecture.
However, it can be hard to distinguish an architecture that lives only in someone&#39;s head from no architecture at all;
so, in a team context, documentation is still important.&lt;/p&gt;
&lt;h4 id=&quot;decisions-shape-a-model%2C-but-they-are-not-the-model.&quot; tabindex=&quot;-1&quot;&gt;Decisions shape a model, but they are not the model.&lt;/h4&gt;
&lt;p&gt;Even though a software architecture is the result of a series of decisions, the decisions alone do not an architecture make.
In other words, the architecture does not consist of a series of decisions, but is the model that emerges when all those decisions are applied on top of each other and any conflicts that may arise are resolved.&lt;/p&gt;
&lt;p&gt;It is perfectly possible to write down (or read about) any number of decisions and not understand what the result looks like.
As long as you do not have this understanding – the model that is the outcome of all these decisions – I argue that you do not have an architecture.&lt;/p&gt;
&lt;p&gt;Incidentally, this is why I think keeping only &lt;a href=&quot;https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions&quot;&gt;Architecture Decision Records&lt;/a&gt; is not enough.
You also need an evolving view of the &lt;em&gt;result&lt;/em&gt; of those decisions.&lt;/p&gt;
&lt;h2 id=&quot;how-does-this-relate-to-other-definitions-of-software-architecture%3F&quot; tabindex=&quot;-1&quot;&gt;How does this relate to other definitions of software architecture?&lt;/h2&gt;
&lt;p&gt;In this section I want to compare my definition of software architecture to several more or less well-known definitions and examine the differences and similarities.&lt;/p&gt;
&lt;h3 id=&quot;ansi%2Fieee-std-1471-2000&quot; tabindex=&quot;-1&quot;&gt;ANSI/IEEE Std 1471-2000&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Software Architecture: the fundamental organization of a system embodied in its components, their relationships to each other and to the environment and the principles guiding its design and evolution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a classic definition of software architecture.
Many similar definitions refer to components and their relationships, and this one includes a reference to more general ‘principles’.&lt;/p&gt;
&lt;p&gt;In my definition, the term ‘model’ subsumes these three elements, and I certainly expect the model to include them all.
Instead of further detailing the content of the model, I chose to focus on its intentionality and the purpose of the architecture.
This part is completely missing from the ANSI definition.
While the word ‘principles’ provides a clue that an architecture must be designed intentionally (since principles, unlike structures, do not simply appear unintentionally), the definition refers to a ‘system’ without saying anything about its purpose.
According to this definition, a software architecture does not need to have any goals.
However (as I wrote above), to me, if you do not have any goals, architecture becomes meaningless.&lt;/p&gt;
&lt;p&gt;Also, I find it important to note that a software architecture is the result of a decision process.
If a software architecture is a model arrived at after &lt;code&gt;N&lt;/code&gt; decisions, this leaves the possibility open to add another decision, resulting in a different (evolved) architecture,
whereas the wording ‘guiding its design and evolution’ in the ANSI definition seems to imply that while the system may evolve (within the frame of its architecture), the architecture itself remains static.&lt;/p&gt;
&lt;h3 id=&quot;iso%2Fiec%2Fieee-42010%3A2011&quot; tabindex=&quot;-1&quot;&gt;ISO/IEC/IEEE 42010:2011&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Architecture: (system) fundamental concepts or properties of a system in its environment embodied in its elements, relationships, and in the principles of its design and evolution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This one is similar to the ANSI definition, and the changes in the wording do not change my assessment.&lt;/p&gt;
&lt;h3 id=&quot;wikipedia&quot; tabindex=&quot;-1&quot;&gt;Wikipedia&lt;/h3&gt;
&lt;p&gt;From the article on &lt;a href=&quot;https://en.wikipedia.org/wiki/Software_architecture&quot;&gt;Software architecture&lt;/a&gt; on Wikipedia (accessed on April 19th, 2024):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Software architecture&lt;/strong&gt; is the set of structures needed to reason about a software system and the discipline of creating such structures and systems.
Each structure comprises software elements, relations among them, and properties of both elements and relations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This definition is completely focused on the structural part of software architecture and ignores intentionality and goal-orientation completely.
It allows for purely descriptive (e.g., reverse-engineered) ‘architectures’.
To reuse my non-software examples from above, this definition would classify descriptions of both the tree trunk fallen over the river and the hole shot randomly into a wall as architectures if they help you ‘reason about’ the system, irrespective of how they came to be.&lt;/p&gt;
&lt;p&gt;To my mind, a set of structures, even if it is helpful, is just a set of structures.
To count as an architecture, it needs to be intentionally created to meet a set of design goals.&lt;/p&gt;
&lt;h3 id=&quot;ralph-johnson-(quoted-via-martin-fowler)&quot; tabindex=&quot;-1&quot;&gt;Ralph Johnson (quoted via &lt;a href=&quot;https://martinfowler.com/architecture&quot;&gt;Martin Fowler&lt;/a&gt;)&lt;/h3&gt;
&lt;p&gt;In his essay &lt;a href=&quot;https://martinfowler.com/ieeeSoftware/whoNeedsArchitect.pdf&quot;&gt;Who Needs an Architect?&lt;/a&gt;, Martin Fowler quotes Ralph Johnson like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In most successful software projects, the expert developers working on that project have a shared understanding of the system design.
This shared understanding is called ‘architecture.’ This understanding includes how the system is divided into components and how the components interact through interfaces.
These components are usually composed of smaller components, but the architecture only includes the components and interfaces that are understood by all the developers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This definition is similar to mine in that it places the architecture in the heads of the designers.
If the architecture is the shared understanding of the system design of the developers, it depends more on the people than on the system itself.
However, it differs in how it views intentionality.
My definition requires the architecture to result from intentional decisions, whereas a shared understanding can be developed after the fact, for example, by a team which has inherited an undocumented system and is building a shared understanding of its structure.&lt;/p&gt;
&lt;p&gt;While I agree that this shared understanding is important, in my view it is not sufficient to constitute an architecture.
However, it can be transformed into one.
If the team discovers a certain structure in the software it has inherited and then &lt;em&gt;intentionally decides&lt;/em&gt; to adopt this structure as the basis for further development in order to &lt;em&gt;reach some goal&lt;/em&gt; (they are bound to have some goals; why would they bother to deal with this system otherwise?), then it will become part of the architecture.&lt;/p&gt;
&lt;p&gt;If the team discovers another structure in the same piece of software and decides to ignore it because it does not help them, it will not become part of the architecture, even though the structure exists (and may continue to exist) and they have developed a shared understanding of it.&lt;/p&gt;
&lt;p&gt;Further on in the quote, it says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;architecture is the decisions that you wish you could get right early in a project&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here we have the decisions, which covers the intentionality, and the word ‘right’ implies some sort of goal; if there was no goal, there would be no way to tell if a decision was right.
What is missing is that the decisions need to come together in a coherent model to be actually useful.&lt;/p&gt;
&lt;p&gt;The quote ends with:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Architecture is about the important stuff. Whatever that is.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While this was probably not meant to constitute a definition of architecture all by itself (it is way too broad for that; is it important to keep your developers fed?) it begs an interesting question:
what is important?
In my view, this depends on your design goals, which in turn depend on the problem your architecture is supposed to solve.
This is why my definition includes both the problem to solve and goal-oriented decisions.&lt;/p&gt;
&lt;h3 id=&quot;software-engineering-institute%2C-carnegie-mellon-university&quot; tabindex=&quot;-1&quot;&gt;Software Engineering Institute, Carnegie Mellon University&lt;/h3&gt;
&lt;p&gt;From &lt;a href=&quot;https://www.sei.cmu.edu/our-work/software-architecture/&quot;&gt;Software Architecture&lt;/a&gt; (accessed on April 19th, 2024):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The software architecture of a system represents the design decisions related to overall system structure and behavior.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This definition seems to imply that a software architecture emerges from a series of decisions, the only requirement being that those decisions relate to the system structure and behavior.
While I agree in principle, I think this definition is too lax.
As a thought experiment, imagine a series of &lt;em&gt;random&lt;/em&gt; ‘design decisions related to overall system structure and behavior’.
These decisions will certainly result in &lt;em&gt;something&lt;/em&gt;, but would you call it a software architecture?
I would not.
I think that in order to get to a software architecture, the design decisions need to be oriented towards a common set of goals.
Only then will you get a coherent model that can be called an architecture.&lt;/p&gt;
&lt;p&gt;Also, the definition seems to equate software architecture with the series of decisions, a kind of &lt;a href=&quot;https://martinfowler.com/eaaDev/EventSourcing.html&quot;&gt;event sourcing&lt;/a&gt; approach to software architecture.
I don&#39;t know about you, but I would not be able to actually work with an architecture description consisting only of decision records.
I would need to go through them and form a model of the resulting architecture before I could actually do anything with it.
So, while it may be possible to reconstruct the architecture given just a series of decision records, this is just the point.
The decisions &lt;em&gt;are&lt;/em&gt; not the architecture.
The resulting model is.
In my definition, this distinction is made explicit.&lt;/p&gt;
&lt;h3 id=&quot;barry-boehm-et-al.-(1995%2C-via-what-is-your-definition-of-software-architecture%3F)&quot; tabindex=&quot;-1&quot;&gt;Barry Boehm et al. (1995, via &lt;a href=&quot;https://insights.sei.cmu.edu/documents/2544/2010_010_001_513810.pdf&quot;&gt;What is your definition of software architecture?&lt;/a&gt;)&lt;/h3&gt;
&lt;p&gt;Barry Boehm and his students at the USC Center for Software Engineering write that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A software system architecture comprises&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A collection of software and system components, connections, and constraints.&lt;/li&gt;
&lt;li&gt;A collection of system stakeholders’ need statements.&lt;/li&gt;
&lt;li&gt;A rationale which demonstrates that the components, connections, and constraints define a system that, if implemented, would satisfy the collection of system stakeholders’ need statements.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I really like that this definition includes the needs of the system&#39;s stakeholders &lt;em&gt;and&lt;/em&gt; the connection between the system&#39;s structural properties and these needs explicitly.
In contrast, my definition does not mention the stakeholders explicitly, but it requires the architecture to solve a problem and aligns architectural work (in the form of decisions) to (design) goals.&lt;/p&gt;
&lt;p&gt;If the problem an architecture solves is a problem the system&#39;s stakeholders actually want to have solved and the design goals align with the stakeholders&#39; needs, I would regard the architecture as good.
However, if an architecture solves a problem no one has or its design goals do not align with anyone&#39;s needs, I would still regard it as an architecture, albeit a useless one.
Strictly speaking, the above definition would not really count the latter example as an architecture because it requires it to address actual stakeholder needs.&lt;/p&gt;
&lt;p&gt;On the other hand, even more strictly speaking, it only requires it to address ‘need statements’, which is not the same.
The stated needs may not be the real needs, i.e., the statements may be wrong.
An architecture based on wrong need statements would still fulfil the definition, so the latter example might actually fulfil the definition after all if the problem (which no one needs solved) and design goals (which do not align with anyone&#39;s needs) are written down as stakeholders&#39; need statements, even if they are wrong.
Which, for practical purposes, means there is not much of a difference between the USC group&#39;s ‘stakeholders&#39; need statements’ and my ‘problem’ and ‘goals’.&lt;/p&gt;
&lt;p&gt;This definition also requires there to be a model (not just decisions) and addresses goal-orientation by requiring a rationale that connects the model with the needs/goals.
The only part of my definition the USC group&#39;s definition does not address at all is the deliberate decision process.
As far as this definition is concerned, the architecture could appear out of thin air as long as the rationale can be constructed afterwards.&lt;/p&gt;
&lt;h3 id=&quot;github&quot; tabindex=&quot;-1&quot;&gt;GitHub&lt;/h3&gt;
&lt;p&gt;From &lt;a href=&quot;https://resources.github.com/software-development/what-is-software-architecture/&quot;&gt;What is software architecture?&lt;/a&gt; (accessed on April 19th, 2024):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Software architecture&lt;/strong&gt; refers to a set of fundamental structures that developers use as an overarching visual guide when planning for and building out software solutions. These frameworks also ensure that a project meets its technical and business needs by planning for important functionality such as scalability, reusability, and security. These plans are then broken down into individual components and code during the design phase.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This definition includes some elements that do not appear in any of the other definitions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It seems to require a visual representation of the structures.
While visual representations can certainly aid understanding, I would not claim non-visual structural representations do not constitute software architecture.&lt;/li&gt;
&lt;li&gt;It mentions some important quality attributes, but does so by way of example and not as a mandatory list, so the selection does not affect the overall meaning.&lt;/li&gt;
&lt;li&gt;It draws a boundary between software architecture and software design.
However, it is unclear to me whether this is supposed to be part of the definition or not.
I wrote a bit about the granularity of software architecture above.
In my opinion, where software architecture ends and lower-level design activities begin depends on the forces pushing the boundary up (avoiding architecture overhead) and down (ensuring design goals are met).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What about the other elements?
The definition limits the scope of software architecture to ‘fundamental structures’, which seems too narrow to me.
My idea of the architecture model is more general and includes concepts and principles as mentioned in some other definitions.&lt;/p&gt;
&lt;p&gt;The GitHub definition explicitly calls out the use of software architecture for ‘planning and building software solutions’, which aligns with my idea of an intentional and prescriptive (rather than purely descriptive) approach.&lt;/p&gt;
&lt;p&gt;It also mentions ‘technical and business needs’, which hints at the problem-solving and goal-orientation aspects, but leaves out the decision-making process.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As the comparison of my definition of software architecture with several other definitions shows, most of them cover one or more of the important aspects of software architecture I discussed in my article, but none of them covers the whole set in quite the same way.&lt;/p&gt;
&lt;p&gt;The characterization as a &lt;em&gt;model&lt;/em&gt; as opposed to a set of decisions is important because the interplay between the decisions can be non-trivial and you have to know (and understand) the result in order to work with it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Intentionality&lt;/em&gt; is important because an architecture is something you create deliberately.&lt;/p&gt;
&lt;p&gt;Designing a software architecture is a &lt;em&gt;goal-orientated&lt;/em&gt; process because the whole point of an architecture is to ensure the software meets certain design goals.&lt;/p&gt;
&lt;p&gt;An architecture is a means to an end, and the end is to &lt;em&gt;solve a problem&lt;/em&gt;. This should always be kept in mind, lest the architecture become an end in itself.&lt;/p&gt;
&lt;p&gt;This is why, to me, a software architecture is a model, resulting from intentional, goal-oriented decisions, about how to solve a problem with software.&lt;/p&gt;
&lt;p&gt;I hope someone finds these thoughts useful.&lt;/p&gt;
</description>
      <pubDate>Tue, 30 Apr 2024 00:00:00 +0000</pubDate>
      <dc:creator>Sebastian Hans</dc:creator>
      <guid>https://sebastian-hans.de/blog/what-is-software-architecture/</guid>
    </item>
    
    
  </channel>
</rss>
