-
Notifications
You must be signed in to change notification settings - Fork 8
How to Structure Your Precondition Checks
The main job of Light.GuardClauses is to validate your parameters, thus the assertions that start with Must
always throw an exception that derives from ArgumentException
(directly or indirectly) by default. You can customize this behavior to your likings.
Every assertion starting with Must
usually has two overloads. The first one allows you to pass in the parameterName
and message
of the exception. This way you can customize the default exception:
public class ConsoleWriter
{
private readonly ConsoleColor _foregroundColor;
public ConsoleWriter(ConsoleColor foregroundColor = ConsoleColor.Black)
{
_foreGroundColor = foregroundColor.MustBeValidEnumValue(parameterName: nameof(foregroundColor));
}
}
public class Entity
{
public Guid Id { get; }
public Entity(Guid id) =>
Id = id.MustNotBeEmpty(message: "You cannot create an entity with an empty GUID.");
}
However, you are not obligated to use parameterName
or message
. These are optional, and in my opinion it's perfectly fine to not use these in application development where code stays within your team.
The second overload allows you to throw your custom exception. To achieve that, you pass in a factory delegate called exceptionFactory
that creates the exception instance:
public class CustomerController : Controller
{
private readonly ICustomerRepository _repo;
public CustomerController(ICustomerRepository? repo)
{
_repo = repo.MustNotBeNull(() => new StupidTeamMembersException("Who is the idiot that forgot to register ICustomerRepository with the DI container?"));
}
}
Some exceptionFactory
delegates take parameters to avoid closure, e.g. MustBeHttpsUrl
passes the specified URI that does not start with https:// to your delegate:
public void RequestCustomerInfos(Uri? uri)
{
uri.MustBeHttpsUrl(url => new Exception("Why the heck is your URL \"{url}\" not using HTTPS? What kind of developer are you?");
// Further code in your method...
}
It's important to understand that your exceptionFactory
delegate is always called, no matter which check fails inside an assertion. This is especially interesting for assertions that check several things (like MustBeHttpsUrl
above which checks for null, for the URI being absolute, and for the URI having the HTTPS scheme).
You'll probably use the default assertions most often, but Light.GuardClauses allows you to customize everything to your needs if necessary.
In all examples so far, you've seen that the assertions of Light.GuardClauses were called as extension methods directly on the corresponding parameter. However, you do not have to do that:
public class Foo
{
private readonly Bar _bar;
public Foo(Bar? bar)
{
Check.MustNotBeNull(bar, nameof(bar));
_bar = bar;
}
}
All assertions are part of the static class Check
and can be invoked without using the extension method syntax. Furthermore, all assertions that throw an exception return the subject (called parameter
) when the assertions executed successfully, thus you could also write:
public class Foo
{
private readonly Bar _bar;
public Foo(Bar? bar)
{
_bar = Check.MustNotBeNull(bar, nameof(bar));
}
}
Personally, I prefer to use the extension method style, but it's up to you how you want to structure your precondition checks. Also, there's no need to use the assertions of Light.GuardClauses everywhere in your code - if you think it's better to use an if statement and throw an exception manually, then do it. Light.GuardClauses should be used as a tool, not as a dogma.