The Mischievous Nerd's Guide to becoming a Master of Programming
Monday, 15 June 2026
Well Structured AI Development Practises
Wednesday, 20 May 2026
TDD, or should Claude just write everything?
Let's start with a question you've been avoiding: do you actually do TDD, or do you nod along at the conference talk, then go home and write the implementation first like everyone else? Be honest. The tests aren't listening. The uncomfortable truth is that TDD has always demanded a very particular flavour of masochism: sit down, write code that deliberately fails, resist every instinct to just make the thing work, and trust that the scaffolding of failure will eventually produce something beautiful. Most developers respect this in the same way they respect flossing. And now here comes Claude, ready to help. What could go wrong?
Quite a lot, as it happens, and in a rather specific way. Left to its own devices, Claude will skip the red phase entirely. It's too helpful. You ask it to write a test for a feature that doesn't exist yet, and, like an eager intern who's read ahead, it writes the test and the implementation in the same breath, hands them to you simultaneously, and waits for praise. The tests pass, obviously, because Claude just wrote both sides of the conversation. This is the AI equivalent of marking your own homework, then putting a gold star on it. SD Times put it this way: AI doesn't eliminate TDD, it exposes whether you understand it. Which is a polite way of saying it will cheerfully help you do it wrong if you let it.
The solution is that TDD and AI are actually a spectacular match, provided you hold the leash. The cognitive barrier that kills TDD in practice is that writing a test requires defining the interface, which requires understanding the architecture, which requires mentally sketching the implementation anyway; the circle eats itself before you've typed a single assertion. AI dissolves that barrier completely. Let Claude draft the test, you review it, then let Claude implement against it, one cycle at a time, not in bulk.
In other words, use this exact prompt:
"Sit. Good boy. Now, write one failing test for the login function. Just one. Don't implement anything. Don't even think about the implementation. I can see your little cursor twitching. Stop that. Write the test, run it, show me it's red, and then sit back down. If you've written only the test and nothing else, you'll get a biscuit."
Now, some of you will have noticed a tension and are feeling very pleased with yourselves about it. If we're telling Claude to stop after every single test like an overexcited Labrador, when exactly does the *plan* happen? Surely we want Claude to think before it acts? Yes, probably. The thing is, plan mode and TDD aren't quite solving the same problem (or at least, that's how it seems to me). Plan mode is about *what you're building*. TDD is about whether you actually built it. One is a map. The other is the slightly neurotic habit of checking you're still on the road every five minutes, which feels excessive right up until the moment you aren't.
The temptation is treating a good plan as a green light to let Claude implement the whole thing in one uninterrupted sprint. My suspicion is that this works right up until it doesn't, and when it doesn't, you're several pull requests deep into something that's coherent, confident, and subtly wrong in ways that are deeply tedious to unpick. The plan tells Claude where it's going. TDD, if I'm being honest, is just the thing that keeps it from quietly lying to you about whether it got there. You probably want both: plan mode to decide what you're building, TDD to keep Claude honest while it builds it. But I'll admit I'm still working out exactly where the seams should be, and anyone who tells you they've fully figured this out is either very clever or writing a different blog.
Thursday, 17 February 2022
How to reduce complexity and know enough.
Quality
His code is like a Rolex.
You look at his gherkin, and smile. It says what it's testing, in plain English, and there's nothing else on the page to confuse or distract you... just English.
"Why then, did our unit tests give me a feeling of dread?" I asked myself. It was quite obvious, that even though we used a neat, little NuGet package, called BDDfy to allow us to write English gherkins, our code was littered with private methods and other necessary evils:
MyTestClass()
{
_subject = new Subject();
}
[Fact]
public void WhenNoOneReadsYourBlog_WriteABunchOfCrap()
{
this
.Given(_ => _.NoOneReadsYourBlog())
.When(_ => _.YouFeelLikeWriting())
.Then(_ => _.JustWriteABunchOfCrap())
.BDDfy()
}
private async Task IAmADumbAnnoyingPrivateMethodThatNoOneWantsToSeeAsync()
{
var IMyInterface mockableMockObj = Substitute.For<IMyInterface>();
mockableMockObj.SomeStupidMethodThatNoOneReadingTheTestCaresAbout
.ShouldReturnSomethingElseInsteadOfNotDoingSomethingElse();
for(var i in someStupidComplicatedIteratableThing)
{
await WhyAmIEvenHereAsync();
}
}
"If we could just hide all the non-gherkin stuff, that would make the code more pleasing to read, understand, and work with," I thought to myself. So I suggested to my team that we use a partial class. We put the public methods, with the English definition of the tests in one file, and everything else in another. The team loved the idea, so that is what we do.
Now, we still have many tests in a file (See how I organize unit tests here). So, the question is, are the tests still useful, to understand what the system does?
Let me put it another way... how do you know enough?
Simplification
The company I work for, IHS Markit, is merging with S&P Global. The combined company will have a hundred gazillion staff members, but only 6 divisions. How do you know what S&P do? You list the 6 divisions.
The universe consists of 200,000,000,000,000,000,000,000 stars. Perhaps it can be summarized as a bag of galaxies.
It takes many years to learn about the human body, but you can summarize it as five things: head, neck, body, a pair of arms and a pair of legs.
Simplifying complexity into a tree, where each node has about 5 children, makes everything easy to understand.
Microservices is the modern, cool way to develop distributed systems. AWS S3 consists of 300 services. I haven't seen this architecture, but I imagine if they're just a list of services that call each other higgledy piggledy, then AWS has a big problem. Our software has 30 services, and even that is a lot to try to understand.
My point is, that I think pretty much everything in software, and perhaps even life, needs to be broken down into a tree, limited to about 5 child nodes per parent to make it easy to understand. Now, don't get me wrong... 5 is more of a thumb suck than anything else, but 5 is a handful.You may be wondering, well, what if it can't be broken down?
- Maybe I have a property for each country... well, that's just a bag, collection, list, or array.
- Maybe my system just has 10 things it needs to do... categorize them... facilities, business logic, etc.
Problems with flat, rather than code organized in hierarchies, adding to complexity are:
- Private methods, private fields and private classes clutter the code, making it more difficult to understand than it needs to be.
- Long methods.
Normally we wouldn't organize classes in a flat structure. We'd use a hierarchy of folders. I think it would be interesting if a class or namespace was a folder. Perhaps an IDE plugin could be written. It might look like this:
Contemplation
Consider code within classes too.Would limiting methods to 5 per class or interface help to make it easier to understand? The interface segregation principle suggests keeping interfaces "small" to prevent having classes that have to implement methods unnecessarily. It may require a little less thought, to have alarm bells going off in your head when you reach 5 methods, to consider whether another interface is required.
Can limiting properties to 5 per class make them easier to work with? I've seen some pretty large view models. Code becomes much cleaner when they're broken down into a tree structure. It can be tricky to change it later, so best to keep them small from the start.
Can limiting lines to 5 per method make the logic easier to follow? Uncle Bob says, "extract, extract, extract." If you follow that, your methods will be pretty small. Using the number 5 might be too artificial, but when you have to scroll to see your whole method, you've probably gone way too far.
I've heard a suggesting that methods should only have 3 parameters. The suggestion is that if there are more, they should be in a class.
Disclaimer: As I've only thought of some of these ideas recently, I haven't experimented with these ideas very much. So, I'm going to try them out, and see how it goes.
Thoughts?
Final Thoughts
Image credits:
https://www.freeimages.com/photo/skeleton-pocket-watch-back-1637098
https://www.freeimages.com/photo/nebula-space-astro-photo-astronomy-sky-1420873
https://www.freeimages.com/photo/burning-tree-1377053
https://www.freeimages.com/photo/dave-in-window-1553933
Sunday, 17 October 2021
Picking a Better Data Store
I like Entity Framework. It's like arriving at Hogwarts and trying out your wand for the first time . Sometimes you wave it, and it transforms your rat into a perfect water goblet. Other times it just blows up in your face, and you have to spend the rest of your high school years trying to master the correct incantation.
Tuesday, 27 July 2021
SOLID
![]() |
| Nerdy Monk with Kittens |
SOLID, YAGNI and DRY are acronyms commonly used to describe good programming practices.
![]() |
| DRY is not WET |
DRY is my favourite: Don't Repeat Yourself... instead of writing the same code twice, wrap it up in a function and put it somewhere where it can be easily found and re-used. This also means if you have to fix a problem, or improve your code, you'll only have to make your change once.
I tend to take DRY to the extreme, by writing little tools, either in PowerShell, or Python, or a little desktop app that takes care of any monotonous task that I might have. The tasks are usually things to do with my dev environment, like automatically powering up useful AWS resources when I login to Windows, and adjusting settings in JetBrains' Rider, so that it only has to compile the bare minimum it needs in order for me to debug the code I'm working on. Other tools I've written include swapping in and out fake and real libraries that my code connects to, so that I can debug under a variety of conditions. These tools not only save time, but allow me to focus on what I enjoy, and what really matters.
![]() |
| DROP is WET |
I'd love to invent my own acronym here, DROP: Don't Repeat Other People.
Whenever writing your own generic code, there should be a few things that come to mind... Why has no-one else written this before? Oh, maybe they have. Is there a NuGet package I can reuse? Because, really, if it's generic, and your code is better than any other open source library out there, perhaps you should be either creating, or contributing to open source.
![]() |
| YAGNI... unless it's a kitten! |
![]() |
| Chaos from multiple responsibilities |
1. Single Responsibility:
A class should only have one responsibility.
Example: A Person class should not contain code that ensures the email address is in a correct, standard email address format. Email validation is generic and should go elsewhere.
2. Open / Closed Principle:
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
To quote Robert C. Martin (Uncle Bob):
When a single change to a program results in a cascade of changes to dependent modules, that program exhibits the undesirable attributes that we have come to associate with “bad” design. The program becomes fragile, rigid, unpredictable and unreusable. The open-closed principle attacks this in a very straightforward way. It says that you should design modules that never change. When requirements change, you extend the behavior of such modules by adding new code, not by changing old code that already works.[1]
This means, if you have created a class that is being used, don't make changes unless you're fixing bugs. Inherit from it instead, and make the changes there. Of course all the cool kids are preferring composition over inheritance these days, so adding a property that's an object is fine, but work with abstractions. When I do this, I might use an interface like IContainX, and then implement a new interface IContainY. That way, your object can be extended with any existing code being referred to (X) remaining unchanged.
If you used TDD properly to build the class, and you're not actually changing the behaviour... You can make performance improvements as long as your original tests pass.
3. Liskov Substitution Principle:
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
So, if the Tesla class inherits from the Car class, you might not want to have a method Car.FillUpWithPetrol(), or you should simply not create a Tesla class inheriting from the Car class.
4. Interface Segregation Principle:
Many client-specific interfaces are better than one general-purpose interface.
- Because any implementation should ideally implement every member. When testing, and creating fakes, mocks or stubs, we don't want to have to care about whether or not a member is implemented if it's not related to the test.
- Unnecessary complexity (See my blog post on reducing complexity). Not everyone who flies in a plane wants to see all the controls in the cockpit of a Boeing 747. All they may need is a button to call the flight attendant.
5. Dependency inversion principle:
Depend upon abstractions, rather than concretions.
This is often done using dependency injection. It's a way of loosely coupling objects. You don't solder wires from a lamp into a wall socket... you create interfaces, which both the lamp and source of electricity can use.
The way SOLID's used is a bit funny. I say it's funny, because when I interview someone they usually remember SINGLE RESPONSIBILITY and waffle through the rest. Yup, we're only human. I've heard the word SOLID used a few times, when the speaker's intention was to refer to single responsibility. I don't think it really helps to ask about it at interviews, directly. It's possible that a good answer just shows that the person's attended many interviews. Perhaps a better test would be to offer some code, and ask what the interviewee would change about it. Although, when I've done that in the past, I tend to get a lot of silence... so who knows?
Saturday, 23 March 2019
Microservices in a nutshell
There are drawbacks to creating Microservices. The problem with popular patterns, is it's easy to consider them to be the right way to do things, but often the best approach is determined through experience, and considering what is really important for the specific context. In many scenarios you will achieve better performance running code on a single computer, and you'll be able to develop it faster. Nevertheless, for when it is a good approach, here are some advantages of splitting code into Microservices:
- All words in a model have a single meaning. This avoids confusion and complexity which could come about from a large model.
- Less chance of code conflict: Development teams can focus on single services, without writing code that conflicts with teams working on other services.
- Services fail independently, rather than the entire system going down.
- Services can be written in different languages.
- A single service can be updated without affecting anything else.
- Enables continous delivery: services can be released independently when a feature is done, without waiting for a feature in another service.
- Easy to understand, as a small piece of functionality. New developers do not need to understand the entire application.
- Scalability - easy to scale and integrate with 3rd parties. Components can be spread across multiple servers.
- It provides a 100% reliable audit log of the changes made to a business entity.
- One can get the state of an entity at any point in time by replaying events.
- Communication between services without too much concern about a service being unavailable.
- One can create a view database (readonly) combining everything for a particular UI view.
- Supports multiple denormalized views that are scalable and performant.
- Platform independence: Build once, run anywhere.
- Much smaller than VMs.
- Effective isolation and resource sharing.
- Speed: Start, create, replicate or destroy containers in seconds.
- Smart scaling: where you only run the containers needed in real time.
- Simpler to update OS for all containers than multiple VMs.
- Free from issues associated to environmental inconsistencies.
- Roll-out & roll-back with zero downtime.
Thursday, 14 March 2019
High Precision Timing in SQL Server
DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter int = 0;
DECLARE @iterations int = 1000;
SET @startTime = SYSDATETIME();
WHILE @counter < @iterations
BEGIN
-- code to time here
SET @counter = @counter + 1;
END;
SET @endTime = SYSDATETIME();
SELECT DATEDIFF(MICROSECOND,@startTime,@endTime) / @iterations
Friday, 12 August 2016
Well Organized Unit Tests
Friday, 24 June 2016
C# In Memory Search Performance Comparison
Warming up...
Single threaded search...
Testing HashSet...
Create (and sort) took 209ms
Search took 208ms
Total time 417ms
Testing Dictionary...
Create (and sort) took 375ms
Search took 117ms
Total time 492ms
Testing BinarySearch...
Create (and sort) took 187ms
Search took 554ms
Total time 741ms
Testing SortedList...
Create (and sort) took 419ms
Search took 543ms
Total time 962ms
Testing HashTable...
Create (and sort) took 1180ms
Search took 530ms
Total time 1710ms
Multithreaded search...
Testing HashSet...
Create (and sort) took 114ms
Search took 65ms
Total time 179ms
Testing Dictionary...
Create (and sort) took 218ms
Search took 55ms
Total time 273ms
Testing BinarySearch...
Create (and sort) took 186ms
Search took 168ms
Total time 354ms
Testing SortedList...
Create (and sort) took 431ms
Search took 171ms
Total time 602ms
Testing HashTable...
Create (and sort) took 972ms
Search took 343ms
Total time 1315ms
And here's the source code I used to get these stats:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections;
namespace PerformanceTesting
{
class Program
{
static void Main(string[] args)
{
Random rnd = new Random();
var warmUp = Enumerable.Range(0, 1000000)
.Select(i => rnd.Next())
.Distinct()
.Take(100000)
.ToArray();
var randomIntegersToStore = Enumerable
.Range(0, 10000000)
.Select(i => rnd.Next())
.Distinct()
.Take(1000000)
.ToArray();
if (randomIntegersToStore.Count() != 1000000)
throw new NotSupportedException("Incorrect number of integers");
var randomIntegersToSearchFor = Enumerable.Range(0, 1000000)
.Select(i => rnd.Next())
.ToArray();
var tests = new List<ITest>
{
new HashSetTest(),
new DictionaryTest(),
new BinarySearchTest(),
new SortedListTest(),
new HashTableTest()
};
Console.WriteLine("Warming up...");
Console.WriteLine();
foreach(var test in tests)
{
test.Initialize(warmUp);
test.Search(warmUp, false);
}
Console.WriteLine("Single threaded search...");
Console.WriteLine();
RunTests(tests, warmUp, randomIntegersToStore, randomIntegersToSearchFor, false);
Console.WriteLine("Multithreaded search...");
Console.WriteLine();
RunTests(tests, warmUp, randomIntegersToStore, randomIntegersToSearchFor, true); Console.ReadLine();
}
private static void RunTests(IEnumerable<ITest> tests,
int[] warmUp, int[] randomIntegersToStore, int[] randomIntegersToSearchFor,
bool multithreadedSearch)
{
foreach (var test in tests)
{
test.Initialize(warmUp);
Console.WriteLine("Testing " + test.GetType().Name.Replace("Test", "") + "...");
var stopwatch = Stopwatch.StartNew();
test.Initialize(randomIntegersToStore);
stopwatch.Stop();
var createTime = stopwatch.ElapsedMilliseconds;
Console.WriteLine("Create (and sort) took " + createTime + "ms");
test.Search(warmUp, multithreadedSearch);
stopwatch.Restart();
test.Search(randomIntegersToSearchFor, multithreadedSearch);
stopwatch.Stop();
var searchTime = stopwatch.ElapsedMilliseconds;
Console.WriteLine("Search took " + searchTime + "ms");
Console.WriteLine("Total time " + (createTime + searchTime).ToString() + "ms");
Console.WriteLine();
}
}
interface ITest
{
void Initialize(int[] randomIntegers);
void Search(int[] randomIntegers, bool multiThreaded);
}
class HashSetTest : ITest
{
private HashSet<int> hashSet;
public void Initialize(int[] randomIntegers)
{
hashSet = new HashSet<int>(randomIntegers);
}
public void Search(int[] randomIntegers, bool multiThreaded)
{
if (multiThreaded)
Parallel.ForEach(randomIntegers, i => hashSet.Contains(i));
else
foreach (var i in randomIntegers)
{
hashSet.Contains(i);
}
}
}
class BinarySearchTest : ITest
{
private List<int> list;
public void Initialize(int[] randomIntegers)
{
list = new List<int>(randomIntegers);
list.Sort();
}
public void Search(int[] randomIntegers, bool multiThreaded)
{
if (multiThreaded)
Parallel.ForEach(randomIntegers, i => list.BinarySearch(i));
else
foreach (var i in randomIntegers)
{
list.BinarySearch(i);
}
}
}
class SortedListTest : ITest
{
private SortedList<int, int> list;
public void Initialize(int[] randomIntegers)
{
list = new SortedList<int, int>(randomIntegers.ToDictionary(i => i));
}
public void Search(int[] randomIntegers, bool multiThreaded)
{
if (multiThreaded)
Parallel.ForEach(randomIntegers, i => list.ContainsKey(i));
else
foreach (var i in randomIntegers)
{
list.ContainsKey(i);
}
}
}
class DictionaryTest : ITest
{
private Dictionary<int, int> dictionary;
public void Initialize(int[] randomIntegers)
{
dictionary = randomIntegers.ToDictionary(i => i);
}
public void Search(int[] randomIntegers, bool multiThreaded)
{
if (multiThreaded)
Parallel.ForEach(randomIntegers, i => dictionary.ContainsKey(i));
else
foreach (var i in randomIntegers)
{
dictionary.ContainsKey(i);
}
}
}
class HashTableTest : ITest
{
private Hashtable hashTable;
public void Initialize(int[] randomIntegers)
{
hashTable = new Hashtable(randomIntegers.ToDictionary(i => i));
}
public void Search(int[] randomIntegers, bool multiThreaded)
{
if (multiThreaded)
Parallel.ForEach(randomIntegers, i => hashTable.ContainsKey(i));
else
foreach (var i in randomIntegers)
{
hashTable.ContainsKey(i);
}
}
}
}
}
Well Structured AI Development Practises
How's AI working out for your team? Not "what's the vision." Actually. In practice. Are you getting consistent, trustworth...
-
Some people write unit tests by copying all of the arrange code to every unit test, so they will end up with code like this: [TestMethod...
-
Quality We have a great QA. His code is like a Rolex. You look at his gherkin, and smile. It says what it's testing, in plain English, ...









