DEV Community

loading...

Guard Clauses

entomy profile image Patrick Kelly ・8 min read

Programming defensively is generally a good thing. I'm not going to get into the cases where you shouldn't, mostly because I don't want to deal with internet warriors, and I'm probably going to get at least one just from not saying that as a universal. But everyone can agree that, when you can, you absolutely should program defensively. First let's talk about what I mean by defensive programing, then what guard clauses are specifically, why I think we aren't using them often, and how to use them more easily and therefore use more of them.

Firstly, defensive programming is the practice of treating all code like it's under attack. This isn't just against hackers, but also against downstream developers who might misuse your API. Unsurprisingly then, defensive programming is more applicable to library authors than application authors; although it has its uses there as well.

Secondly, guard clauses are a specific pattern in defensive programming which resemble contracts, and are used like contracts in languages that don't support them. C# is one of these languages. Common guards are things like "is this argument not null" or "is this argument a natural integer", although there's certainly opportunities for more advanced guards like "is this fat pointer valid" or "is this stream readable".

Thirdly, I think we aren't using them often because writing them out often gets in the way, and can generally require looking for a "best fit" exception, since a perfect fit probably isn't there in the standard library, and then thinking of a message for it, or just leaving the message out entirely. And, of course, if you rely on ArgumentException instead of one of its derived exceptions, the parameter order is actually opposite of the derived ones, so... yeah, have fun with that. All of these compound into make your job more complicated than it needs to be. There's a phrase I'm very fond of:

Making code safe shouldn't get in the way of making code work.

Fourthly, people out there recognize this fact. That's actually why design-by-contract became a thing, where this functionality was introduced into various languages like Eiffel and Ada. But what do we do about languages that don't support this? C# has System.Diagnostics.Contracts, but without analyzers for it, those contracts just exist; they don't do anything. Someone should really address that, but that's a whole other topic. In the mean time, what can be done? Enter, guard clause libraries. There's numerous different approaches for this, and therefore numerous different libraries. But the general idea is the same. Provide individual functions for every guard clause. Accept the necessary parameters, and do the check.

Necessary disclosure here, I've got a guard clause library and I'm obviously biased in favor of it: Defender. I'm using it extensively in my own projects, so there's a hefty amount of support for it, in addition to extensive dog fooding to make sure it works well. But I will mention other guard clause libraries, how they differ, and why I think mine is superior. If you disagree, well, you just got shown a library you'd rather use, and can go right ahead.

Let's consider a simple null guard:

void Example(String argument) {
    if (argument is null) {
        throw new ArgumentNullException(nameof(argument),
            "Some message that explains the issue");
    }
    // Do some actual work
}
Enter fullscreen mode Exit fullscreen mode

I think you can see why, especially when many guards are required, this would result in many developers just not adding the guards at all.

How does the same look with Defender?

void Example(String argument) {
    Guard.NotNull(argument, nameof(argument));
    // Do some actual work
}
Enter fullscreen mode Exit fullscreen mode

That's considerably less typing! But there's an additional advantage that's only obvious when you're actually using this. Intellisense! Guard is a static class containing all the guards, so, you type that, and boom! All the guards I've ever written that could be made into reusable subroutines have been. This includes convention conveniences, such as the following:

unsafe void Example(Char* text, Int32 length) {
    Guard.Pointer(text, nameof(text), length, nameof(length));
    // Do some unsafe work
}
Enter fullscreen mode Exit fullscreen mode

Pointer() is just two guards, NotNull() and GreaterThanOrEqualTo(), used together. But because I've got enough API's that either directly use, or provide overloads for, fat pointers, it made sense to provide that convenience. After all, I just cut the typing down even more.

I also try to be very comprehensive, so there's guards for IEnumerable, IEnumerable<T>, Memory<T>, Span<T>, Stream, and all sorts of other stuff. Remember, I use this directly, extensively. I also plan to, or already do, include non-guard clauses in this library, since it's meant for general defensive programming, so there's more to it than just that.

So what other options are out there?

Ardalis/GuardClauses is something that on paper looks like a really good option. Until you drill down into performance, that is. The syntax is fantastic. Ardalis is also someone who knows his shit. However, not enough of it. The major problem seems to stem from use of interfaces where there shouldn't be any, forcing boxing to occur, which incurs overhead. I actually strongly considered contributing to this project and using it instead of rolling my own, until I ran some benchmarks.

Dawn.Guard is for if you fangirl over fluent syntax. Look, I love fluent syntax where it's appropriate, but guards aren't a workflow, they're a "let's get this out of the way" thing. The less they are in your face, and the less performance hit you get, the better. He did get one thing right though: There's a common type all guards are fluent off of. I'll let the benchmarks speak for themselves here. After this little snippet from his repo:

A high-performance, extensible argument validation library.

LiteGuard is... Am I missing something? It seems like this library only includes null guards. The code itself seems fine. The library is set up reasonably. It just... Lacks.

Light.GuardClauses is best avoided no matter how much you like fluent syntax. Look, I can at least provide justifications for some of the aforementioned. But not here. Light.GuardClauses does the mind boggling thing of fluent syntax, but introduced as extension methods for the type that they would be guarding. Sure, it removes the need for a guard singleton, but it poses a huge problem. You want guard clauses to stay out of your way. To be easily discoverable when you need them, and totally out of your face otherwise. By extending the types themselves, they now show up in intellisense always. Prepare to have a harder time finding literally everything.

Perf Showdown

Now, these all fundamentally do the same thing, just in different ways. And you certainly don't want to slow your code down just to do a few safety checks. So how do these stack up?

|      Method |           Job |       Runtime | Argument |           Mean |       Error |      StdDev |         Median | Ratio | RatioSD |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------ |-------------- |-------------- |--------- |---------------:|------------:|------------:|---------------:|------:|--------:|-------:|------:|------:|----------:|
|     NoGuard |      .NET 4.8 |      .NET 4.8 |    hello |      0.2862 ns |   0.0108 ns |   0.0096 ns |      0.2844 ns |  0.47 |    0.02 |      - |     - |     - |         - |
| ManualGuard |      .NET 4.8 |      .NET 4.8 |    hello |      0.6160 ns |   0.0088 ns |   0.0083 ns |      0.6136 ns |  1.00 |    0.00 |      - |     - |     - |         - |
|     Ardalis |      .NET 4.8 |      .NET 4.8 |    hello |      1.7282 ns |   0.0132 ns |   0.0123 ns |      1.7251 ns |  2.81 |    0.04 |      - |     - |     - |         - |
|    Defender |      .NET 4.8 |      .NET 4.8 |    hello |      0.3033 ns |   0.0080 ns |   0.0074 ns |      0.3030 ns |  0.49 |    0.02 |      - |     - |     - |         - |
|   DawnGuard |      .NET 4.8 |      .NET 4.8 |    hello |     12.7173 ns |   0.0647 ns |   0.0574 ns |     12.7035 ns | 20.67 |    0.25 |      - |     - |     - |         - |
|   LiteGuard |      .NET 4.8 |      .NET 4.8 |    hello |      1.8685 ns |   0.0127 ns |   0.0119 ns |      1.8724 ns |  3.03 |    0.05 |      - |     - |     - |         - |
|  LightGuard |      .NET 4.8 |      .NET 4.8 |    hello |      0.3955 ns |   0.0076 ns |   0.0071 ns |      0.3946 ns |  0.64 |    0.01 |      - |     - |     - |         - |
|             |               |               |          |                |             |             |                |       |         |        |       |       |           |
|     NoGuard | .NET Core 3.1 | .NET Core 3.1 |    hello |      0.0061 ns |   0.0044 ns |   0.0039 ns |      0.0066 ns |  0.01 |    0.01 |      - |     - |     - |         - |
| ManualGuard | .NET Core 3.1 | .NET Core 3.1 |    hello |      0.4095 ns |   0.0083 ns |   0.0077 ns |      0.4122 ns |  1.00 |    0.00 |      - |     - |     - |         - |
|     Ardalis | .NET Core 3.1 | .NET Core 3.1 |    hello |      1.8142 ns |   0.0126 ns |   0.0118 ns |      1.8145 ns |  4.43 |    0.10 |      - |     - |     - |         - |
|    Defender | .NET Core 3.1 | .NET Core 3.1 |    hello |      0.2773 ns |   0.0097 ns |   0.0091 ns |      0.2729 ns |  0.68 |    0.02 |      - |     - |     - |         - |
|   DawnGuard | .NET Core 3.1 | .NET Core 3.1 |    hello |      4.4950 ns |   0.0456 ns |   0.0404 ns |      4.4785 ns | 10.99 |    0.20 |      - |     - |     - |         - |
|   LiteGuard | .NET Core 3.1 | .NET Core 3.1 |    hello |      1.9336 ns |   0.0146 ns |   0.0130 ns |      1.9367 ns |  4.73 |    0.10 |      - |     - |     - |         - |
|  LightGuard | .NET Core 3.1 | .NET Core 3.1 |    hello |      0.2940 ns |   0.0126 ns |   0.0118 ns |      0.2905 ns |  0.72 |    0.03 |      - |     - |     - |         - |
|             |               |               |          |                |             |             |                |       |         |        |       |       |           |
|     NoGuard |    CoreRt 3.1 |    CoreRt 3.1 |    hello |      0.0067 ns |   0.0078 ns |   0.0069 ns |      0.0050 ns |  0.01 |    0.01 |      - |     - |     - |         - |
| ManualGuard |    CoreRt 3.1 |    CoreRt 3.1 |    hello |      0.6482 ns |   0.0216 ns |   0.0202 ns |      0.6546 ns |  1.00 |    0.00 |      - |     - |     - |         - |
|     Ardalis |    CoreRt 3.1 |    CoreRt 3.1 |    hello |      2.3960 ns |   0.0322 ns |   0.0301 ns |      2.3871 ns |  3.70 |    0.12 |      - |     - |     - |         - |
|    Defender |    CoreRt 3.1 |    CoreRt 3.1 |    hello |      0.7384 ns |   0.0154 ns |   0.0136 ns |      0.7395 ns |  1.14 |    0.05 |      - |     - |     - |         - |
|   DawnGuard |    CoreRt 3.1 |    CoreRt 3.1 |    hello |      3.6147 ns |   0.0347 ns |   0.0325 ns |      3.6071 ns |  5.58 |    0.18 |      - |     - |     - |         - |
|   LiteGuard |    CoreRt 3.1 |    CoreRt 3.1 |    hello |      1.5235 ns |   0.0243 ns |   0.0216 ns |      1.5167 ns |  2.35 |    0.08 |      - |     - |     - |         - |
|  LightGuard |    CoreRt 3.1 |    CoreRt 3.1 |    hello |      0.6079 ns |   0.0189 ns |   0.0158 ns |      0.6103 ns |  0.93 |    0.04 |      - |     - |     - |         - |
|             |               |               |          |                |             |             |                |       |         |        |       |       |           |
|     NoGuard |          Mono |          Mono |    hello |      0.6488 ns |   0.0135 ns |   0.0126 ns |      0.6515 ns |  0.42 |    0.01 |      - |     - |     - |         - |
| ManualGuard |          Mono |          Mono |    hello |      1.5529 ns |   0.0273 ns |   0.0255 ns |      1.5530 ns |  1.00 |    0.00 |      - |     - |     - |         - |
|     Ardalis |          Mono |          Mono |    hello |      2.8846 ns |   0.0341 ns |   0.0319 ns |      2.8860 ns |  1.86 |    0.03 |      - |     - |     - |         - |
|    Defender |          Mono |          Mono |    hello |      1.5580 ns |   0.0140 ns |   0.0124 ns |      1.5585 ns |  1.00 |    0.01 |      - |     - |     - |         - |
|   DawnGuard |          Mono |          Mono |    hello |     23.0727 ns |   0.0950 ns |   0.0888 ns |     23.0643 ns | 14.86 |    0.24 |      - |     - |     - |         - |
|   LiteGuard |          Mono |          Mono |    hello |      3.1757 ns |   0.0188 ns |   0.0176 ns |      3.1720 ns |  2.05 |    0.04 |      - |     - |     - |         - |
|  LightGuard |          Mono |          Mono |    hello |      0.9608 ns |   0.0087 ns |   0.0082 ns |      0.9613 ns |  0.62 |    0.01 |      - |     - |     - |         - |
Enter fullscreen mode Exit fullscreen mode

If you're interested in running these benchmarks yourself, adding more to them, or whatever, they are part of Defender, and I definitely encourage open and transparent data collection.

Regardless of what approach you go with:

Keep your code safe

And yourself. You should probably stay safe as well. 🤷🏻‍♂️

Discussion (1)

pic
Editor guide
Collapse
entomy profile image
Patrick Kelly Author

Something I didn't mention, because to the best of my knowledge no guard clause library implements it yet, is that you can actually optimize the throwing of exceptions, and the mechanism for doing so isn't super obvious. It's only by a single instruction, and it's not worth doing in basically any scenario. However, a library who's job it is to literally do this kind of stuff, could be doing it. That's technically an advantage over manually written guards, and something I plan to implement eventually. Right now my hands are elsewhere.