Skip to content

Instantly share code, notes, and snippets.

@ig-sinicyn
Last active March 31, 2016 21:07
Show Gist options
  • Save ig-sinicyn/864bc39ac9fc4f884264768f6871ff10 to your computer and use it in GitHub Desktop.
Save ig-sinicyn/864bc39ac9fc4f884264768f6871ff10 to your computer and use it in GitHub Desktop.

CodeAssertions

Intro

What it is?

Code assertions is a set of helper methods aimed to proof your assumptions at runtime.

When should I use it?

Every time you want to be sure your code is working right.
Argument validation, checking the state of the object, health checks on debug builds? Here we go:)

How it works

Perfectly. No trace under normal conditions and you will be notified if things go wrong.

To be more precise: for each assertion failure you will be awarded with an exception but wait, there's more! If you attach the debugger the execution will break exactly on the line with the failed assertion, not inside the assertion method itself. And it does not matter if the exception is swallowed somewhere down the call stack.

I hope that way you will be annoyed enough to fix the error instead of just ignoring it. Don't thank for this.

What about performance?

We fight for it.
First, we're running perftests to proof that all assertions are running not slower than usual if-then-throw checks.
Second, you can mark assertions as debug-time only and they will be stripped out from release builds. More on that later.

What about extensibility?

Both yes and no.

No, there's no extensibility points. All in all, it's simple assertions class, no magic attached (almost, more on that later).
And yes, you can easily create your own sets of assertions and have the same experience the out-of-the box assertions provide.

All you have to do is read the instruction. In fact, you are.

Shut up and show me the code!

You will be disappointed, I promise!

	Code.NotNull(argument, nameof(argument));

I'm afraid that's all. Seriously, if you want to use the assertions - what are you doing here?:) Go away and write the code and use the autocomplete to learn. This is how it is assumed to work.

What it is: developer's point of view

There are three main parts of the code assertions:

  • Exception factory methods. You need them to throw out in case nothing else helps.
  • Implementations of common assertions.
  • And, finally, helper methods to write your own assertions inline with a user code.

Let's take it one-by-one:

Exception factory methods

These are meant to be used in cases you want to throw the assertion exception explicitly, say, from default switch clause:

	switch (someEnum)
	{
		...
		case default:
			throw CodeExceptions.UnexpectedValue(someEnum); // InvalidOperationException
	}

Assertion implementation methods

Here they are:

	public static SomeData CountStatistics(string content, int start, int count)
	{
		Code.NotNull(content, nameof(content));                          // ArgumentNullException
		Code.IsBetween(start, nameof(start), 0, content.Length - 1);     // ArgumentOutOfRangeException 
		Code.IsBetween(count, nameof(count), 0, content.Length - start); // ArgumentOutOfRangeException 
		...
		// here be boring code
	}

Pretty simple, right? Actually, it's even simpler than this:

	public static SomeData CountStatistics(string content, int start, int count)
	{
		Code.NotNull(content, nameof(content));                     // ArgumentNullException
		Code.ValidIndex(start, nameof(start), 0, content.Length);   // IndexOutOfRangeException 
		Code.VaildCount(count, nameof(count), start, start.Length); // ArgumentOutOfRangeException 

		...
		// here be boring code
	}

And so on.
I'm not going to document all of them as their usage should be obvious. And if it is not, the mission is failed already, so there's no point in topping the bad code with bad documentation. Thank you for caring.

Assertion helper methods

These should be used in case you did not find proper assertion method to call but do not want to copy-paste boilerplate like this:

		if (!IsGood(arg)) // inverted assertion
		{
			throw CodeExceptions.Argument(
				nameof(arg),
				"You're bad one, {0}.",
				argument);
		}

Instead, use the

		Code.AssertArgument(        // ArgumentException
			IsGood(arg),            // assertion
			nameof(arg),            // name of argument
			"You're bad one, {0}.", // message format
			arg);                   // format args

for assertions on argument
and the

		Code.AssertState(                    // InvalidOperationException
			SomeCheck(this.Location),        // assertion
			"We're not in Kansas anymore!"); // guess what

for state-checking assertions.

As you may have noticed, the assertion helpers differs from assertion implementation methods.
First, they allow you to specify detailed message (or message format string).
Second, they are named after exception being thrown, not after assertion they're implementing (they are not). As a rule of thumb, assertion helpers' names are prefixed with Assert but there're exceptions. To name one,

		Code.DisposedIf(disposedFlag, this, "Some message");

These are not meant to be used directly. Instead, wrap the helper into a new method alike

	protected void AssertNotDisposed() =>
		Code.DisposedIf(_disposed, this, "Some message");

and copy-paste the AssertNotDisposed(); line across the code.

Performance

Caveats with assertion helper methods

Assertion helper methods are a simple and easy to use.
However, there's a performance hit if you're using the overload with string messageFormat, params object[] args.
Each time these overloads are called a new array will be created and filled with the args passed into a method call.

If the assertion assumed to be called hundred times per second or more, consider to use assertion implementation method or to write your own assertion method, that's easy:

	private static void AssertIsGood(int arg, string argName)
	{
		if (!IsGood(arg)) // inverted assertion
		{
			throw CodeExceptions.Argument(
				nameof(arg),
				"You're bad one, {0}.",
				argument);
		}
	}

Things are getting much worse if you're using assertion helper methods together with the c# 6 string interpolation feature, like this:

		Code.AssertArgument(
			IsGood(arg), nameof(arg),
			$"You're bad one, {arg}.");

Now the entire string has to be formatted on each method call as the compiler converts the code into

		Code.AssertArgument(
			IsGood(arg), nameof(arg),
			string.Format("You're bad one, {0}.", arg));

Note that if assertion is not fired, the formatted string would not be used at all. In fact, all it does is adding more pressure for the GC. Not a good thing to happen.
This issue could not be solved with the addition of overload with FormattableString parameter, the compiler will ignore it.

Excluding heavy assertions

You can't have enough performance. So, here goes my favorite trick for code speedup: there is no code faster that the one that does not execute.
If you have tight loop with a lot of assertions or the assertion itself is very complex and heavy (e.g. checks that file exists or something like this) it may be a good idea to add these assertions only into debug builds.

To achieve this, use methods from DebugCode class instead of the ones from Code. As an example:

	private void DoSomething()
	{
		// Debug-only assertions;
		DebugCode.AssertState(File.Exists("someFile"), "File is gone.");
		DebugCode.AssertState(ConnectionOk(connectionString), "We're doomed!");

		// This one will be checked in release builds too;
		Code.AssertState(IsInitialized, "Setup me!");

		...
	}

The DebugCode class contains counterparts for almost all methods from the Code class. The ones that missing are assertions meant to be thrown always or not thrown at all. Code.DisposedIf, for example.

IMPORTANT Note that the check is removed only if it is written inline, as an argument of the assertion. For example, here

		var allOK = File.Exists("someFile") && ConnectionOk(connectionString);
		DebugCode.AssertState(allOK, "You lose.");

the allOK line will be executed in release builds too, only line with the assertion will be stripped out

If you do not want to bother with all this stuff, just wrap the assertions into a new method and mark it with a [Conditional(DebugCode.DebugCondition)] attribute. Here's an example:

	[Conditional(DebugCode.DebugCondition)]
	private void ValidateMe()
	{
		// The method calls will performed only in debug builds;
		Code.AssertState(File.Exists("someFile"), "File is gone.");
		Code.AssertState(ConnectionOk(connectionString), "We're doomed!");
		Code.AssertState(IsInitialized, "Setup me!");
	}
	
	private void DoSomething()
	{
		// Debug-only assertions;
		ValidateMe();
		...
	}

The debug-only assertion method can be static or instance one, usual restrictions from ConditionalAttribute apply.

Usage guidelines

TBD

Creating custom code assertions

TBD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment