Posts

  • Integration Testing with a Real Database

    In software systems that use a database as their primary data store, it’s important to include the database in integration tests. This provides the most accurate representation of how the system behaves in production. You should never mock the database. But this raises an important question: How do you use a real database in an integration test?

  • Complex Test Data Builders

    The Test Data Builder Pattern works incredibly well for constructing test data in a consistent and readable way. It's easy to set up for simple domain objects without many constraints. However, it becomes trickier when dealing with domain objects that enforce strict rules or state transitions. This post explores how to build a more complex Test Data Builder that respects domain rules and disallows invalid configurations.

  • Test Data Builders with Related Data

    Integration tests often require a lot of related data to satisfy foreign key constraints. If your database enforces these constraints (as it should), setting up test data becomes verbose and repetitive. This typically results in large amounts of duplicated setup code scattered across your test suite—code that's hard to maintain and distracts from the actual purpose of the test.

  • Domain-Validating Deletion

    Deleting entries in a database is quite straightforward. In SQL, you might write something like:

  • Modelling Draft Objects

    In applications with complex forms, users often need to save their progress to return later or hand the work over to someone else. These drafts are therefore incomplete and contain empty fields or partially filled objects. Representing this in a domain model is challenging, as the domain typically requires valid, complete data for the process that follows submission. This post explores different approaches to modeling such drafts.

  • Never Mock the Database

    When writing automated tests, it's common to mock or simulate the database. The motivation is clear: mocks are fast, easy to set up, and avoid the overhead of managing a real database. Frameworks like Entity Framework Core offer in-memory implementations that make this approach even more convenient.

  • Test Data Builder

    In unit and integration tests, the construction of test data often becomes bloated and duplicated across projects. This makes it harder to clearly express what a test is verifying and increases the maintenance burden when changes occur over time. The idea behind the Test Data Builder pattern is to introduce a common utility for constructing test data that includes only the data relevant to the specific test. The goal is to make tests as readable as possible while keeping domain objects easy to extend and change.

  • Exceptions are for Exceptional Behavior

    Exceptions are used in many object-oriented languages to handle failures. They provide an alternative control flow that allows you to skip normal method execution. This can be a powerful way to signal errors, but it also introduces invisible state that can be hard to reason about. This post argues that exceptions should only be used for exceptional or unexpected situations, not for regular control flow.

  • Don't rely on the File System

    File systems are ubiquitous in development environments, but this availability often leads developers to assume file access will always be present. This assumption can cause issues in production, especially in containerized or cloud setups. There are two important questions that are often overlooked:

    • Does the program have access to a file system?
    • Is the expected output truly a file?
  • Teams should prioritize live code reviews

    Pull requests were originally created by open-source communities to enable asynchronous collaboration on shared codebases. In these environments, contributions are often reviewed over several days or even weeks, since many contributors participate part-time. This asynchronous approach works well in such settings, allowing maintainers to review code when it's convenient for them.

  • Complexity vs. collaboration

    The ability to unit test code is highly correlated with the modularization of said code. The more modular code is, the easier it is to write accurate tests for it. When code is tightly coupled, writing good unit tests becomes nearly impossible. Therefore, unit testing has introduced new ways of thinking about code. This post is about one such important concept: separating code into complexity and collaboration. This not only helps with unit testing but also improves modularization and long-term maintainability.

  • Code smell: Public setters

    Public setters have two huge problems: they expose the internal state of objects and therefore increase the coupling of the system over time. This might not be obvious to new developers or to experienced developers who haven’t worked in systems with strong encapsulation practices. Therefore it is a widely used practice that hurts software quality in the industry. Sometimes setters are useful, but there are major downsides to long term maintainability. Overall code becomes clearer and easier to work with over time if public setters are omitted.

  • Don't name it Service!

    Developers often fall into the trap of naming classes XService — like FoodService for food-related logic. Over time, FoodService accumulates methods like add, save, get, build, getMore, and even getXWithYWhileZ. This pattern leads to bloated, unmaintainable code that violates core design principles and creates tightly coupled systems.

  • Intentional Validation: Ditching Frameworks for Clarity and Control

    In most applications, validation is typically done using a validation framework that helps setup validation faster. This article argues that using these validation frameworks is an antipattern to clean code. It introduces complexities that makes the code harder to debug, is hard/impossible to properly test and is not extendable enough. While validation frameworks offer quick setup and standardization, they can introduce hidden complexity and become limiting over time. Instead it is suggested that each project should do its validation manually, because it in the end is more maintainable and stable than the common approach.

  • Eliminating NullPointerExceptions in Java

    In C#, nullable reference types must be explicitly declared. For example, a string is not nullable, but a string? is. By introducing this concept, the developer is forced to consider null for any nullable reference. This concept also exists in languages like Kotlin, a modern JVM language that is fully interoperable with Java. Unfortunately, in Java, all reference types are nullable by default. This post is meant to be practical advice for getting rid of NullPointerException in existing codebases.

  • Feature-oriented folder structure

    In many applications the source code is split into the different layers that the application consists of from a technical architecture perspective. For example with a feature1 and feature2, the following could be the folder structure in a simple onion architecture:

  • Current date is an infrastructure concern

    Getting the current date is an easy task in most languages. In Java you can call LocalDate.now(), in C# DateTime.Now or Rust chrono::offset::Local::now(). This seems quite easy to do, so most developers will do it close to where the date is needed. The argument this article presents is that you want to push this code into the infrastructure layer of your code. This applies both to getting the date and time, but for simplicify, the date is the focus.

  • Code smell: Don't talk to strangers

    “Don’t talk to strangers” can be a useful rule of thumb for identifying code smells when writing code.

  • Code smell: Arrowhead code

    A tip for recognising a common code smell is to look for arrowheads. Consider the following python code:

  • The diminishing returns of rewriting a system without the original authors

    Choosing to rewrite an existing system is a big decision. It usually requires a project to reach a point where the technical debt is too much to deal with, or the technology is so ancient that the compentencies no longer exists. However, it can also come from an engineering team that have gone the wrong way, or chosen to rely on a technology that has become deprecated. This particular story is about the later case. It aims to illustrate the two most important takeaways: domain independence and tribal knowledge.

  • Benchmarking EFCore 3.1 Cartesian explosion problem

    In Entity Framework Core 3.1 any LinQ expression is translated into a single SQL statement. This will not be a problem for most queries, but it can cause performance problems if developers are not cautious of this when crafting queries. This article will introduce a simplified model of a performance problem that was discovered and solved in a production environment.

subscribe via RSS