.NET Tools
Essential productivity kit for .NET and game developers
.NET Annotated Monthly | October 2023
The first program to replicate itself was “The Creeper”, essentially being the first computer virus. The software would seat itself in a machine and print “I’m the creeper, catch me if you can!”. But no virus is complete without its nemesis, and in this case it’s “The Reaper”. It was the first (antivirus) program built to eradicate The Creeper. From there, we all know how it plays out.
.NET news
- .NET Conf 2023 – Celebrating the Release of .NET 8!
- The .NET Conf Student Zone is Back!
- Announcing .NET 8 Preview 7
- Announcing .NET MAUI in .NET 8 Release Candidate 1: Quality
- Announcing .NET 8 RC1
- Performance Improvements in .NET 8
- What’s new in System.Text.Json in .NET 8
Featured content
We’d like to thank Paul Blasucci for curating this month’s featured content! He is passionate – yet pragmatic – about a multi-paradigm and polyglot approach to writing software. He has spent the past 25 years blending a disparate array of languages, technologies, and methodologies to develop compelling solutions to a wide range of business problems. Paul especially enjoys solving challenges in heterogeneous enterprise systems. His weblog, https://paul.blasuc.ci, is irregularly updated with various musings on software development, and he explores the fediverse as @pblasucci@hachyderm.io. When not at the keyboard, Paul may be found globetrotting with his wife and children (though his soul is still catchin’ waves at the Jersey Shore).
I’m thrilled to be given this opportunity to engage the community. Thanks JetBrains! As a long-time .NET developer, one topic about which I am passionate is Error Handling Failure Management. Thanks, readers, for indulging me…
Software will fail. It’s not a question of “if”, or even “when”. Rather, it’s about what developers can do to both minimize failure and recover gracefully (if at all possible). There are many approaches one might take. However, in the.NET ecosystem, it is not uncommon to find developers haven’t truly given the topic due consideration. In particular, there seems to either be a lot of focus on handling failures, or a lot of focus on communicating failures. However, a robust program really needs to address both.
Communicating Failure
When a piece of code wants to notify callers that something is wrong, there are three common approaches:
- Panic
- Raise an exception
- Use a purpose-build return type
ASIDE: Actors Fail Fast
Actor systems, like those found in Elixir, or Orleans, or Akka.NET, often take a strategy of “fail fast”, whereby panics are the main form of indicating something went wrong. However, such systems are beyond the scope of this article, as they redefine the notion of “executable process”. Still, they present a fascinating tool for building robust software, and are definitely worth studying.
It is vital to note that no one approach is exclusive of the other two. That is, a robust program will combine these mechanisms as needed. However, each of these modalities is best paired with a particular kind of failure. Panics are reserved for when something truly unrecoverable has gone seriously wrong. For example, a necessary piece of configuration is unavailable during program start-up.
let configureDatabase (configuration : IConfiguration) (services : IServiceCollection) : IServiceCollection = match configuration.GetConnectionString("default") with | Empty -> ``panic!`` "No database connection details found!" | Trimmed connectionString -> let dataSource = NpgsqlDataSourceBuilder(connectionString) services.AddSingleton(dataSource.Build())
Exceptions are for failures which are, well, exceptional. That is, they are not part of the ordinary program flow. Often they are very unexpected. However, a surprising number of exceptions may be anticipated as _potential_ failures. Usually, this is closely related to infrastructure concerns. For example, receiving a 503 Service Unavailable response to an HTTP request is not part of routine program flow. But code can predict it might occur, and plan some strategy for re-submitting the HTTP request.
let submitScores (client : HttpClient) (cancel : CancellationToken) (request : SubmitScoresRequest) = task { let request' = buildHttpRequest request try let! response = client.SendAsync(request', cancel) return unpackHttpResponse response with :? HttpRequestException as ex -> return! ex |> rescheduleHttpRequest client cancel request }
Finally, failures which emanate from the problem domain of the software itself
are fantastic candidates for purpose-built return types. A common example would
be validation: checking that a piece of inventory is in stock before it can
be shipped.
let checkInventory (stock : IStockService) ({ Sku = itemCode; Quantity = count } as item) : Result<LineItem, InventoryProblem> = match stock.ItemQuantity(itemCode) with | 0 -> Error(OutOfStock itemCode) | n when n < count -> let notEnough = InsufficientSupply(itemCode, count) Error notEnough | _ -> Ok item
Handling Failure
Responding to a particular failure is, by and large, determined by how said failure is communicated. If some code panics, the only possible response is to restart the process. Similarly, if one expects an exception, one traps it and then chooses the best course of action. Finally, a return type will need to have its value(s) inspected in order to select the appropriate program flow.
However, the important detail is – panics notwithstanding – the point-in-execution where an exception is trapped or a problem is revealed, is also the point-in-execution where the modality can be changed. That is, once an exception is trapped, it can be downgraded into a problem.
DbResult ArchiveReport(Report report) { try { _db.Insert(report.ToArchivalFormat(_logger, _clock)); return DbResult.Success(report.ReferenceCode); } catch(SqlException x) when (x.Number is 2601) { return DbResult.DuplicateArchivalAttempt(report.ReferenceCode); } }
Similarly, at some path in a program’s execution (usually near the top of a call chain), it maybe be reasonable to promote a problem into an exception.
webApp.MapGet("/report/{referenceCode}", (referenceCode, archive) => archive.TryGetReport(referenceCode, out var report) switch { true => report, false => throw new UnknownReportException(referenceCode) } );
Summary
Failure management in .NET is a nuanced and far-ranging topic. However, by keeping several key guidelines in mind, it is possible to build more robust software. To recap:
- Failures may be communicated by: panicking, raising an exception, or returning data.
- Panics can only be handled by relaunching the executing process.
- Exceptions should not be used for regular control flow, but for signaling abnormal program state.
- Exceptions, once trapped, may be inspected and various programmatic responses taken.
- Problems are regular types, returned from methods or functions, which indicate something went wrong.
- Problems should closely align to the business domain which the program serves.
- Problems are not abnormal or unexpected, and are a core part of program design.
- Programs may convert between exceptions and problems as the scope of failure shifts between infrastructure and domain.
Hopefully, this short article (which really could’ve been a whole book!) highlights a more robust approach to managing the failures which inevitably must occur when designing, coding, and executing software. At the very least, it perhaps gives “food for thought”.
Further Reading
If you are interested in more detailed, and better constructed, explorations of this topic, please see the following links:
- Miscomputation in software: Learning to live with errors
- The Error Model
- Railway Oriented Programming
- Against Railway Oriented Programming
- You’re Better off Using Exceptions
Programing tutorials and tips
.NET tutorials and tips
- Trying out JetBrains Rider for .NET MAUI development – Hey MAUI devs! Since Microsoft dumped Visual Studio for Mac, now is a great time to try out Rider. Daniel Hindrikes did, and here’s his review.
- Mastering Git in JetBrains Rider – Everyone loves to love the command line, but VCS integration in your IDE can give you an even better experience. Watch Dan Clark’s video to find out more.
- My IDE as .NET Developer Using a MacBook (2023) – You guessed it! It’s Rider! Gui Ferreira shows us his setup and developer experience on a MacBook.
- Exporting a WinForms GridView’s Content to Different Formats – Check out this great tutorial by Desislava Yordanova on how to export a GridView’s content to different formats.
- Beyond the Basics: Exploring Simple Navigation in .NET MAUI – If users can’t navigate well, they’ll stop using your software. In this article, Leomaris Reyes shows us how to make navigation best by sticking with the basics.
- What is with shipping in November? .NET Release Cycle Explained – I’ve been waiting for a post explaining how Microsoft shipping cycles work. Thanks Isaac Levin, so many people need this.
- System.Text.Json JsonConverter Test Helpers – Bookmark this super handy blog post by Khalid Abuhakmeh, for when you need to write a JsonConverter test helper.
- Discriminated Unions in C# – F# developers have enjoyed discriminated unions for a long time. Now, it’s available in C#. What are they? How do you use them? Maarten Balliauw explains.
- A new way of doing reflection with .NET 8 – Steven Giesel shows a way to do reflection with the `UnsafeAccessorAttribute` that is far better performing than before.
- DateTimeOffset vs DateTime in C# – Have you been using `DateTimeOffset`? Do you know the difference between it and `DateTime`? Now you can learn with this piece by Code Maze.
- Hooked on .NET newsletter – Layla Porter has launched a new .NET newsletter that you all definitely want to keep an eye on.
- Why Do Older .NET Developers Hate Modern .NET? – Nick Chapsas throws down the gauntlet on the geezers out there to explain why older .NET folks hate the new stuff.
- New FREE Ebooks: Modernize Your Apps with Blazor and .NET MAUI – Who doesn’t love a free book? Galina Jordanowa has provided links to two eBooks: Migration to .NET MAUI by Rossitza Fakalieva and Planning a Blazor Application by Ed Charbenau. Get them hot off the press!
Related programming tutorials and tips:
- Tips for onboarding to a new codebase or working with legacy code – Watch this short video by Marit van Dijk for some great tips when joining new codebases.
- Learn Prompt Engineering – Full Course – Prompt engineering is a great skill for programmers to pick up, and it’s becoming increasingly important as we incorporate AI into our workflows and code. Here’s the full training course by Anu Kubo.
- Temporal Table Data Fixes – We all need that temp table, but very often they’re misused. Learn some tips for fixing temp tables by Josephine Bush.
- The 300% Production Problem – Lee Briggs penned a post about abstractions. When an abstraction is the problem you have, it’s not enough just to know about it. You need to look a level (or more) deeper.
Interesting and cool stuff
- The Power of Thinking About “The Thing After the Thing” – Sean Killeen philosophizes about “what comes next?”. Thinking about the future and possibilities that can happen is something we need to do. Not just with error trapping, but with the focus and direction of our software as a product.
- 10 Software Development Quality Metrics (And What to Do About Them) – Got metrics? Tyler Hakes does. And he’s showing us how we might collect and act on them for our software projects.
And finally, the latest from JetBrains
Here’s a chance to catch up on JetBrains news that you might have missed:
- Recordings from JetBrains .NET Day Online ’23 are available
- ReSharper 2023.3 Early Access Program Begins!
- Rider Kicks Off the Early Access Program for the 2023.3 Release!
⚒️ Check out our .NET Guide! Videos, tips, and tricks on .NET related topics. ⚒️
More posts…
- Eager, Lazy and Explicit Loading with Entity Framework Core
- First-class Upgrades for ASP.NET Core with JetBrains Annotations
Don’t miss this fantastic offer! CODE Magazine is offering a free subscription to JetBrains customers. Get your copy today!
Sharing is caring! So share content that you find useful with other readers. Don’t keep it to yourself! Send us an email with your suggestions for publication in future newsletters!