<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Pete Beech's blog]]></title><description><![CDATA[Pete Beech's blog]]></description><link>https://petebeech.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 10 Apr 2026 19:55:58 GMT</lastBuildDate><atom:link href="https://petebeech.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Assert Builder]]></title><description><![CDATA[Introduction
Unit-tests will often contain a series of Asserts, any or all of which could fail.
When running such tests, the first assert failure encountered is shown and the test stops. Often, after fixing and rerunning the test, you find that furth...]]></description><link>https://petebeech.com/assert-builder</link><guid isPermaLink="true">https://petebeech.com/assert-builder</guid><category><![CDATA[unit testing]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Software Testing]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Pete Beech]]></dc:creator><pubDate>Sun, 04 Dec 2022 14:57:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670164393967/lXZYn1iiX.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Unit-tests will often contain a series of Asserts, any or all of which could fail.</p>
<p>When running such tests, the first assert failure encountered is shown and the test stops. Often, after fixing and rerunning the test, you find that further asserts in the test also fail. You have to fix each one in turn, re-running each time. This can be frustrating and inefficient.</p>
<p>Any simple test which has multiple asserts has this potential problem, but some situations are worse than others.</p>
<p>For example, I've recently been working on some tests which test the contents of a large complex data-structure which is generated by some Generator class.</p>
<p>This testing is done by setting up the expected structure using some test specific DTO classes. There's one DTO for each type of object which can be present in the data-structure. Each of these DTOs has an AssertEquals method, which basically goes through all the properties one by one, asserting that they are equal to the corresponding property in the real generated data structure.</p>
<p>Each assert will stop the test, which means you will only gradually see the whole picture as you repeatedly fix and retest.</p>
<h2 id="heading-the-assertbuilder-approach">The AssertBuilder approach</h2>
<p>This is where an AssertBuilder comes in.</p>
<p>An Assert Builder is a class that allows developers to assert multiple times, but instead of asserting each time it stores any assert failure messages and allows the test to proceed.</p>
<p>It is somewhat analogous to a StringBuilder.</p>
<p>At some suitable point, after building it up from multiple asserts, you can decide to assert on the whole assertBuilder instance. If there have been any failures the aggregated assert fails, stopping the test. All the accumulated messages will be outputted together. This allows developers to see all of the issues at once, rather than having to fix and re-test each one individually.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AssertBuilder</span>
{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">List</span>&lt;<span class="hljs-title">string</span>&gt; _listAssertFailures</span> = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt;();

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AreEqual</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">T expected, T actual</span>)</span>
    {
        AreEqual(expected, actual, <span class="hljs-keyword">string</span>.Empty);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AreEqual</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">T expected, T actual, <span class="hljs-keyword">string</span> message</span>)</span>
    {
        <span class="hljs-keyword">try</span>
        {
            Assert.AreEqual(expected, actual, message, <span class="hljs-literal">null</span>);
        }
        <span class="hljs-keyword">catch</span> (AssertFailedException ex)
        {
            _listAssertFailures.Add(ex.Message);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CompleteAssert</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (!AllAssertsSucceeded)
        {
            <span class="hljs-keyword">string</span> assertMessage = BuildAssertMessage();

            Assert.Fail(assertMessage);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> <span class="hljs-title">BuildAssertMessage</span>(<span class="hljs-params"></span>)</span>
    {
        StringBuilder sb = <span class="hljs-keyword">new</span> StringBuilder();

        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> _listAssertFailures)
        {
            sb.AppendLine(item.ToString());
        }

        <span class="hljs-keyword">return</span> sb.ToString();
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> AllAssertsSucceeded =&gt; _listAssertFailures.Count == <span class="hljs-number">0</span>;
}
</code></pre>
<p>The example as shown only allows AreEqual calls, but can easily be extended to support whatever is required.</p>
<p>An example of usage is as follows:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">TestMethod</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TestUsingAssertBuilder</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">int</span> expectedInt = <span class="hljs-number">3</span>;
    <span class="hljs-keyword">string</span> expectedString = <span class="hljs-string">"test string"</span>;
    DateTime expectedDate = <span class="hljs-keyword">new</span> DateTime(<span class="hljs-number">2019</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>);

    AssertBuilder assertBuilder = <span class="hljs-keyword">new</span> AssertBuilder();

    assertBuilder.AreEqual(expectedInt, <span class="hljs-number">3</span>);
    assertBuilder.AreEqual(expectedString, <span class="hljs-string">"y"</span>);
    assertBuilder.AreEqual(expectedDate, <span class="hljs-keyword">new</span> DateTime(<span class="hljs-number">2020</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>), <span class="hljs-string">"Dates should be equal"</span>);

    assertBuilder.CompleteAssert();
}
</code></pre>
<h2 id="heading-alternative-approach">Alternative approach</h2>
<p>Another approach, which could be more flexible and may allow better stack trace possibilities to show the individual assert failures, is to build up arbitrary assert actions and run them all at once, throwing an AggregateException containing each individual assert exception.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AssertBuilder2</span>
{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">List</span>&lt;<span class="hljs-title">AssertFailedException</span>&gt; _assertFailures</span> = <span class="hljs-keyword">new</span> List&lt;AssertFailedException&gt;();

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AddAssert</span>(<span class="hljs-params">Action assert</span>)</span>
    {
        <span class="hljs-keyword">try</span>
        {
            assert();
        }
        <span class="hljs-keyword">catch</span> (AssertFailedException ex)
        {
            _assertFailures.Add(ex);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">RunAsserts</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (_assertFailures.Count &gt; <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> AggregateException(<span class="hljs-string">"Multiple assert failures occurred:"</span>, _assertFailures);
        }
    }
}
</code></pre>
<h2 id="heading-fluent-assertions-assertion-scopes">Fluent Assertion's "Assertion Scopes"</h2>
<p>Alternatively, if you wanted to use the Fluent Assertions library, this has the concept of an Assertion Scope. Fluent assertions, as the name suggests, allows you to write assertions with a fluent API which can help improve readability.</p>
<p>The <a target="_blank" href="https://fluentassertions.com/introduction#assertion-scopes">Assertion Scopes</a> feature additionally allows asserts to be grouped together, and ensures that all the asserts are run within that group in much the same way as the AssertBuilder ideas above.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As I've shown, whether using Fluent Assertions and AssertionScopes, or a basic AssertBuilder approach, it is possible to avoid the assert fail/fix/retest cycle and be more efficient when testing.</p>
<p>Let's face it, fast and efficient feedback is what we're looking for from tests, and every little helps!</p>
]]></content:encoded></item><item><title><![CDATA[Is Technical Debt misunderstood?]]></title><description><![CDATA[As Dijkstra once said in his famous "radical novelty" talk, analogies and metaphors don't always seem to work for software. (Actually, it's fair to say his opinion was somewhat stronger than that, but we'll leave it there.) Although very useful, anal...]]></description><link>https://petebeech.com/is-technical-debt-misunderstood</link><guid isPermaLink="true">https://petebeech.com/is-technical-debt-misunderstood</guid><category><![CDATA[technical-debt]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[maintenance]]></category><dc:creator><![CDATA[Pete Beech]]></dc:creator><pubDate>Sat, 19 Nov 2022 14:40:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/AUwH_abodWU/upload/v1668868626657/wzokaJJ5G.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As Dijkstra once said in his famous <a target="_blank" href="https://www.cs.utexas.edu/users/EWD/transcriptions/EWD10xx/EWD1036.html">"radical novelty" talk</a>, analogies and metaphors don't always seem to work for software. (Actually, it's fair to say his opinion was somewhat stronger than that, but we'll leave it there.) Although very useful, analogies and metaphors can break down. This is generally true but seems to be especially true with software.</p>
<p>Is creating software like building bridges? Is it like designing a city? Is it like running a restaurant? Maybe it is like all of these things in some respects. However, there's something very unique about software which is hard to pin down - and which makes it hard to pin metaphors to.</p>
<p>I love analogies and metaphors and use them all the time. They are a great way to reason about fundamental aspects of software systems, and the creation of those systems. I also appreciate it when people who write and talk about software use them. Anything that helps humans grasp the difficult concepts and intricacies inherent in software can surely only be a good thing.</p>
<p>But they can also be dangerous. This is at least partly what Dijkstra was getting at.</p>
<p>Take the widely used metaphor of "technical debt". Great metaphor, easily understood, and really conveys the issue. Or does it? Can it be possible that this is misleading, or misunderstood, or perhaps both?</p>
<p>This metaphor, in what seems to be the most prevalent understanding of it, says that the kind of "cruft" (as Martin Fowler <a target="_blank" href="https://www.martinfowler.com/bliki/TechnicalDebt.html">calls it</a>) which builds up in a software system can be thought of as debt.</p>
<p>Martin Fowler defines cruft as "deficiencies in internal quality that make it harder than it would ideally be to modify and extend the system further".</p>
<p>The crux of the metaphor is that this cruft is debt that must at some point be repaid if we want to keep the cost of adding new features or maintaining the code low. If the debt isn't repaid, the cost (meaning the effort) remains high - and the difference is essentially the "interest" on this debt.</p>
<p>At least, this is the crux of it according to what seems to be the common understanding of "technical debt".</p>
<p>Does this metaphor make sense? Is this even how it was originally intended?</p>
<h2 id="heading-messy-code">Messy Code</h2>
<p>Many people would think of cruft as bad code. Messy code. Perhaps code that is hacked in, or sloppy. Never mind that the definition above doesn't say that exactly - this is what generally springs to mind. </p>
<p>Organisations that suffer from code mess will sometimes comfort themselves by calling it "technical debt". By thinking of it as a debt, it seems like something that can just be paid off at some point in the future, and it doesn't really affect the day to day. After all, in real life a debt is often due to an investment which makes your life better. For example, a new house. And even while you have the debt, your environment can be absolutely fine. The conditions in which you have to exist are totally OK. Yes, you have a debt. But you also have a nice house, and you're paying it off. Meanwhile everything is rosy.</p>
<p>So, with this kind of thinking, you might say "well, we have some technical debt now because we really rushed that feature. We’ll have to pay it off some day". And the danger is that it just gets deferred and deferred. The point that is often missed is that, if the "technical debt" is actually just messy code, then the interest element is essentially due to the deteriorating environment (the codebase) in which the software engineers need to work. It's a codebase starting to disintegrate. Features will take longer to put in. When they are in, they invariably have bugs - more bugs than there would have been if the codebase was clean and well written. Regressions in other parts of the system are more common, because it is so entangled. Further tickets need to be put into sprints to address these issues, which are wasted on fixing the inevitable problems rather than adding more features and more value. Releases can even end up going out with problems which aren't picked up.</p>
<p>Software engineers suffer when having to deal with this sort of situation. It's not really like a debt. It's certainly not like living with the sort of debt where your immediate environment is totally unaffected, and where the only way you really know you have a debt is some uncomfortably high numbers in red on your bank statements.</p>
<p>This kind of situation, I would argue, is more like an unsafe structure. A hazardous work environment. It's like a building site littered with broken tools, broken scaffolding, and maybe water logged for good measure. In short, it's not good to work in, and it's not good for productivity and profit.</p>
<h2 id="heading-the-original-technical-debt-metaphor">The original "Technical Debt" metaphor</h2>
<p>The thing is, this simple idea of "messy code which we know needs cleaning up" is actually not what was meant by "technical debt" in the first place.</p>
<p>The metaphor was first <a target="_blank" href="http://c2.com/doc/oopsla92.html">coined</a> by Ward Cunningham in 1992.</p>
<p>As far as I can tell, messy code was far from his mind when he came up with this idea. As I interpret it, it is a way to describe or justify why, when designing and delivering a first incremental release of the system, certain design decisions were known to not really be sustainable. Rather than delaying release, reworking the design and fixing these flaws was deferred to later.</p>
<p>This, to me, is the essence of true "technical debt". It's a deliberate decision to invest by getting something released while knowing full well that the design is not quite right. It's the same kind of advantage as when you take on a loan to buy a house. In that case you get to live it in sooner than you would otherwise be able to. And in the software case, you get to release it sooner because you incur the debt of needing to sort it out afterwards. It is not about sloppy code, but about issues with the current iteration of the design. Issues which you're fully aware of.</p>
<p>As Cunningham himself said "I’m never in favor of writing code poorly, but I am in favor of writing code to reflect your current understanding of a problem even if that understanding is partial."</p>
<p>After delivering that first (or second, or third) incremental release the team knew that, at the very least refactoring would be necessary, and almost certainly some redesign. If this was not done then the cost of further work would stay high. This, in the analogy, is the paying down of the debt.</p>
<p>This seems to be to be a totally different ball game. It's deliberate, and thoughtful. It's a tactic by which software can be put into the hands of users earlier, perhaps to get valuable feedback or to gain an advantage on competition. It's not accidental complexity and mess due to continually rushing work on an ongoing basis.</p>
<p>It's an acceptance that your first stab at a solution will be imperfect, and is a considered and thoughtful way of getting a system out of the door in a reasonable timeframe with the caveat that some subsequent rework or refactoring will need to be applied to keep it all going.</p>
<p>And in contrast to the popular idea of "technical debt" the code is still good, and well written. Concerns are separated, tests are in place. The code itself is a "habitable place" as Dave Farley likes to say. The only problem is that the fundamentals of the design don't quite sit right. That's the debt.</p>
<h2 id="heading-technical-debt-from-another-angle">"Technical Debt" from another angle</h2>
<p>To visualise this idea, here's a simple graph.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668867691609/f-eyGqXUk.png" alt="TechDebt.png" /></p>
<p>The benefit is that productivity should increase whenever you pay down the debt. The costs of putting new features in decreases, because the new insights gained from the first iteration have been applied to the design.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668867728286/KwBy1KvRo.png" alt="TD_Productivity.png" /></p>
<p>I think another way of looking at this is to think of it as regular investments.</p>
<p>The investment in applying design changes and refactoring with the benefit of the knowledge gained helps increase work throughput and the overall quality. Regular investments maintain this quality and keep the design aligned with the actual problem being solved. These investments cost time and effort, but pay dividends because of the increase in productivity, quality and throughput.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668867746410/2gQKttY7j.png" alt="TD_Investments.png" /></p>
<p>The basic principle doesn't change, but the analogy is different. Thinking of it as an "investment" emphasizes the benefits and is, I believe, a much more positive way of looking at it.</p>
<p>Whichever way you look at this though, it certainly isn't allowing the creation of an unsafe or hazardous environment, i.e., a total codebase mess, which can make a software engineers life very unpleasant and be very bad for business.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The trouble with the "technical debt" metaphor is that it seems to have taken on a life of its own. It can be used to justify living with code mess, and big balls of mud, because in the real world we can live with debt. As I've tried to argue, this isn't "technical debt". It's more like a "technical wasteland". The misappropriation of a metaphor leads to consequences. You can live with debt for some time, but who would want to live in a wasteland? And how can you make progress in it?</p>
<p>The original "technical debt" idea is good, but I do think it's worth considering flipping things around and seeing it instead as "technical investment". Either way it is potentially a very valid strategy, but under the name "technical debt" it's so easily misconstrued.</p>
<p>I like to think that this is the kind of problem Dijkstra had in mind when he gave that talk all those years ago.</p>
]]></content:encoded></item><item><title><![CDATA[The first post]]></title><description><![CDATA[I've been writing code for a long time. I started on my friends TRS-80 when I was a small kid. I wrote code on the ZX-81, ZX-Spectrum, and the obscure Memotech MTX-512. I wrote on the school computer for my computer studies GCSE. At university I wrot...]]></description><link>https://petebeech.com/the-first-post</link><guid isPermaLink="true">https://petebeech.com/the-first-post</guid><category><![CDATA[General Programming]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[maintenance]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Pete Beech]]></dc:creator><pubDate>Wed, 09 Nov 2022 18:41:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668104340088/c9MfsNNje.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been writing code for a long time. I started on my friends TRS-80 when I was a small kid. I wrote code on the ZX-81, ZX-Spectrum, and the obscure Memotech MTX-512. I wrote on the school computer for my computer studies GCSE. At university I wrote stuff in APL and C on a mainframe. Then I moved on to PCs, using a wide variety of languages: assembler, Modula 2, Pascal, VB, C, c++, Java, javascript, and C#. </p>
<p>But the fundamentals always seem to stay the same. Software, and the development of software, is endlessly fascinating. It's rewarding and frustrating in equal measure. Not a day goes past without finding myself gaining some new insight into software engineering, or an interesting coding technique. Or perhaps discovering a new design pattern, or maybe a fresh angle on an old one. </p>
<p>It never really seems to get dull, and no matter how hard you try you can never say that you've truly mastered it. You keep climbing higher and higher, but the peak is always above you shrouded in mist. </p>
<p>I'm interested in many, many aspects of software development, but a major interest is the value of maintenance: refactoring, finding seams, getting software under test. Preventing software rot, and trying to keep code "habitable", seems to be an endless battle. In our profession we spend so much longer maintaining systems than we do creating them from scratch. When these systems have a poor codebase, working on them can be so demoralizing. This means that, quite apart from all the other benefits it brings, striving to keep that codebase good and healthy can help protect the mental health of ourselves and our colleagues. Software is hard, but there's no need to make it even harder than it needs to be! </p>
<p>In this blog I hope to cover all sorts of topics to do with software development and modern software engineering. I may post about some low-level code technique in C# or .NET, or about the details of a design pattern; or it might be about something at a higher level, perhaps some more fundamental and timeless aspects of software engineering. Maybe, though, there will be a slant towards the topic of maintenance and keeping code healthy. </p>
<p>All of us who are passionate about software development, and the processes, craft and engineering which go into it, have a voice. We all have some unique insights from our experiences. Whether it's wrestling with architecture or design, or the naming of methods or variables, or a neat technique to split up of some entangled classes, or how to get part of a gnarly system under test - all of this experience builds up into a big bag of techniques and ideas.</p>
<p>Many times I've watched a software talk or read something in a coding blog and felt that I could have said or written it. Equally though I have encountered many ideas and insights which might never have occurred to me, due to the unique experiences of the blogger or speaker.</p>
<p>And so, after gaining tremendous insights over the years from all these thoughtful and inspiring blog posts, books and talks, it's time to try to give something back. It's time to see if any insights from my own experiences - <em>however small</em> - can be useful to someone.</p>
]]></content:encoded></item></channel></rss>