A Story Of Reliable Code

How compile-time checks assist .NET devs in delivering software

Imagine you are a C# programmer designing API of a tiny search engine. The engine is really simple — it searches for goods in a database using a query string provided by a user. You sit down, decide not to focus on implementation details yet, and write a simple C# interface:

Fine. You still don’t want to focus on implementation details, you’d like to ask someone to implement that interface for you. But before doing that, you ask yourself — what will happen, if your mate writes buggy code and such code passes code review? You decide to analyze potential issues and try to eliminate them. So, what’s wrong with the contract defined above?

  1. The Search method accepts a string. C# doesn’t prevent you from passing null instead of a string to that method, but will the search engine implementation handle the null value correctly? Will it fail fast or return some search results? No answer.
  2. The Search method returns a Task<T> , which can also be null. Yeah, it is a really bad practice to return null from methods returning tasks, but bad things happen.
  3. The Search method can return a Task containing a null value. Is this meant to be correct behavior or not, what that null value means — again, no answer.
  4. The search method executes correctly, consumes a query string, and returns a sequence of search results. Assuming SearchResult is a reference type, is there any guarantee, that our program will never fail with NullReferenceException when we try to access a SearchResult instance property in our code? No, we have to trust the implementation details.
  5. Exceptions. Who knows, what exceptions a custom search engine might throw. The exceptions apparently are not documented anywhere yet.

If you use JavaScript or Python without type annotations, the issues amount is actually a lot bigger than those 5 mentioned applicable to C#. Assuming all the above, the safe way of calling the ISearchEngine.Search method is something like this:

Highlighted by carbon.now.sh

No one writes such code! Most time we don’t handle all these cases separately and simply let the program fail at run time if anything goes wrong. This means if we use a bad interface implementation, our software will likely crash.

Make Contracts More Reliable

Willing to make the contract more reliable, you, as a C# developer, download JetBrains.Annotations NuGet package, and annotate the code snippet above.

This is definitely better. You mark your input and output parameters with the NotNull and ItemNotNull attributes — so the project won’t compile if someone tries to use null s anywhere. Additionally, you document all possible exceptions that a search engine implementation might throw — so one can omit the global try catch block when working with that interface. Now, one can call the ISearchEngine.Search method safely!

This is the best way of resolving such issues with C#, but it isn’t a silver bullet. The person who implements the ISearchEngine interface can easily ignore recommendations defined in the signature, or turn ReSharper off. So, what do we have to do to absolutely trust our code base?

Write Unit Tests For Interface Implementations

This is quite obvious, and prevents our software from unexpected errors in most cases, but sometimes requires doing a ton of work each time the API changes. That’s why we move forward and try to learn new tools.

Use F#, Functional Programming and Types

F# is a strongly typed, multi-paradigm programming language that encompasses functional, imperative, and object-oriented programming methods. F# runs everywhere C# does.

Considering our search engine implementation, the first benefit we get when using F# is null safety. This means a reference type can never implicitly be null in F#. For example, a string variable can never be null, if we need to store a null value inside it, we use Nullable<string> type.

Yeah, no more null reference exceptions. Hopefully, null safety is going to be introduced in C#8. But now, if we rewrite our C# interface declared above in F#, we’ll eliminate four of five issues from our list! The only issue left is related to exceptions that are not documented.

Functional programmers don’t like exceptions. Although one can write a pure function that uses raise statements under the hood, exceptions violate referential transparency. Instead of exceptions, it’s better to use explicitly typed output and discriminated unions.

Assuming that, let’s refactor the C# interface declared above into F# types.

Here we describe all possible values that a function may produce, including errors, using a SearchResponse discriminated union. Instead of using implicit exceptions and XML documentation, we use types, and our code becomes self-documenting. Instead of using an interface here, we use a functional type. The search engine becomes a function that takes a query string and returns a response asynchronously. “Implementing” and using such function is simple.

We can finally trust the search engine implementation. Our app will never fail with null reference exceptions, and compiler forces us to handle all errors that a function may return, using pattern matching over a discriminated union. If we don’t handle all cases, we’ll receive a warning each time we are attempting to build the project. Far better!

Yeah, sounds tricky at first glance. If you find yourself interested and would like to learn more, visit F# For Fun and Profit website. Additionally, it’s worth reading “Domain Modelling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#” book by Scott Wlaschin for more real-world code examples and best practices. Code snippets in this article are highlighted using carbon.now.sh online service.

Full-stack developer, masters student, digital design enthusiast twitter.com/worldbeaterdev

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store