C# Style Best Practice Software Development

To Helper or not to Helper

This is another RAMP51 best practices document.

In programming we use class names for expressing a purpose or a role that the class plays in the system. When the purpose of our code is clear the name also tends to be clear.

It’s common to hear advice that you should never name classes Helper or Utility. I think most of us agree.

In the .NET Framework Reference source for .NET 4.7.2 there are 20,176 files, among those files I found roughly 340 files named *Helper.cs

Microsoft is definitely a leader among leaders in terms of experience developing frameworks and SDKs for others to use and consume. So the collective intelligence of their developers and those that worked on the .NET Framework would seem to indicate that there were times where they wanted something named Helper.

I personally try to shy away from anything Helper because it’s easiest for its creator to understand what belongs in the Helper and what doesn’t, but less intuitive for your teammates.

Some times it’s definitely hard to find a name for one off utility methods. I guess we could glean from the .NET framework code base a ratio of Helper files to Non-Helper files, which would come out to roughly 1 helper file to 58 files that aren’t named helper. This obviously assumes code files are spread evenly across all assemblies and namespaces, which we know is not the case.

How many “Helper” or “Utility” files do you expect to see in your solutions? In the classic ASP days I remember having one “junk drawer” include that just was a grab bag of utility functions, and it worked ok. Often code would be duplicated though. With today’s SAST tools we’d find these duplications fairly quickly. Does it make sense to have junk drawer now? Or is nuget simply the junk drawer? I’m still trying to name everything with a specific purpose in it’s context to inform future developers as to the intent of how functionality should be maintained and enhanced. So for now, I’m still keeping the junk drawer at home.

C# Style Best Practice Software Development

Method Design – Communicating Failures

How does a method name communicate failure expectations? When you call a method what do you expect to happen if the method can’t accomplish what it’s meant to?

Will it throw an exception? Will it return some data that allows you (the caller) to be aware of a problem?

It’s a tough situation as a developer, your boss, your teammates, the internet all have their opinions and best practices to bestow upon you, like the following:

  • Exceptions are heavy don’t overuse them
  • Handle exceptions gracefully
  • Expect failures and handle them

None of this advice is wrong. It will however lead you down a frustrating path if they are not followed when and where appropriate. Above and beyond all of that; the most important thing you can do to prevent failures is:

  • Effectively communicate expectations to the caller of the method you design
  • Effectively communicate expectations of the method to the future developers that need to maintain, use, or extend the functionality of this method

Without effective communication about the expectations of a method, code quickly turns into spaghetti code trying to handle a bunch of oddball scenarios that cause failures and bugs. Which in turn will complicate the ability of the GUI to articulate these exceptions and errors to its end users. The problems surfaced to the end users may have easy work arounds, or problems that are just total failures, and the user should go away and come back later. Effectively communicating failure, possible remediation, and total failure to your end user can have a huge impact on how positive or negative the user experience is. Which is the ONLY REASON we write code to begin with: to positively impact an end user or stakeholder.

In C# when we call a method we usually want to understand the input and outputs as quickly as possible. This way we understand what we need to pass in and what we can expect to happen from calling a method with a given set of parameters.

When I’m choosing the name of my methods I usually name them in two ways:

  1. A name that communicates the expectation that the method will succeed when called, or an exception will be thrown.
  2. A name that communicates the expectation that the method will attempt an operation and the caller will need to perform some evaluation to determine if the call was a success. These methods will not throw exceptions, and usually would have a very tight focus on a single operation. An example would be int.TryParse the goal is to parse a string to an int, if it fails to do so it returns a false value indicating the string was not successfully parsed, and you now have to evaluate that boolean value of false to determine what you want to do next.

int.TryParse is an example of a method in .NET that communicates to the caller that it will try an operation and return with a result that indicates if it was successful. When you call methods in the .NET framework you expect them to either throw an exception or complete successfully, such as int.Parse. These two methods illustrate both categories above.

When a developer needs to maintain these functions it’s clear that one throws exceptions, and one returns an indicator of failure (the returned boolean). So the developer should know that he needs to maintain those expectations.

Where we get into trouble is when we do something like this:

Person p = datalayer.AddPerson(firstName, lastName);

Do you expect that line of code to throw an exception if it fails to add the person to the database?

Do you expect p == null if that fails?

Do you expect p != null && p.Id == 0 ?

This is the type of ambiguity we find ourselves in when we deviate from the pattern of throwing or bubbling up exceptions to whatever frame of the call stack has the best chance to remedy the error. In some cases you can’t remedy the problem and you just have to tell the user to try again later.

However when the problem can be remedied, we need a way to know it failed. Exceptions are a consistent contract for understanding WHAT failed. If we go back to the TryParse example, we find that when try parse fails we know it fails but we don’t know why. Which may be ok because parsing an integer is so trivial that we don’t care why, we just know we need to ask the caller for a better value to parse. In our business logic these scenarios are usually more complex with more dependencies and distributed infrastructure involved.

If you call AddPerson and you get a Connection exception because you can’t connect to the database, then that should not be caught in AddPerson but let it bubble up to whatever layer should handle it. In the case of a Connection exception, let’s assume the configuration file is just configured with the wrong connection. If this is executing inside a rest API the expectation of that caller would be a 500, and internally in our code the exception would bubble up to the Action “essentially the application layer”, and in that layer it would be caught, logged, and return the appropriate 500 with a generic error message indicate the server has an issue and to try again later.

The consumer of this api then doesn’t have to make any guesses about the failure, and whether or not there was a problem with their request. If AddPerson returned a 200, with some sort of error message in the payload the user would need to decipher the error message. Once they sort out system errors from errors the user can affect they then need to present them back to the end user in a meaningful way to keep the user experience pleasant.

Surely there are valid use cases for such a contract but it creates complexity because there is more opportunity for you to create an inconsistent response and failure result between different types of operations.

Across boundaries like a call from one system to another system’s REST API there is always a little bit of a need to understand the various exceptions and response codes and translate those back to your use case and UX. However inside a system the various calling methods and called methods should rely on a much simpler and consistent contact, which is what I’m communicating above. Try methods, and methods that throw exceptions.

As usual I’m just trying to lay out some best practices. Everything is situational and quite frankly I’m going to probably put you to sleep if I try any harder to explain this.

C# Style Best Practice Software Development

Designing Methods for Humans

This post series will be focused on C# and techniques we try to use to maintain high quality code intended to be read by humans. It will be opinionated in favor of readability and intuitive method design. It won’t touch on concerns of extremely high memory or high performance application requirements. These best practices are just practices in general, not hard and fast rules.

A Method can be a lot of things in a software application. What it can be and what the developer writing the calling method expects can be very different. So how do we communicate the behavior of our methods in just a method name?

It’s not easy. We have to rely on some conventions. There is much to consider when calling a method:

  • Will it throw exceptions?
  • Will it block?
  • Will it cause side effects?
  • Will it retry?
  • Will it be idempotent?

Those five concerns don’t even touch on the reason WHY you want to call the method to begin with. What do you hope to gain from calling this method?

  • What will it return to you?
  • What will it do for you?
  • What does it need from you?
  • Does the method require the caller to handle the returned values with special care?

When a developer begins work on the codebase in the future are the caller’s expectations of the method clear and obvious? If they aren’t it is easy for the developer to change the method in a way that defies an expectation of the caller; potentially breaking production code. Some of which is solved by a strong unit test suite.

In the next post, I’m going to talk about how I design C# methods, to communicate how the call should deal with failures or exceptions.

Feel free to comment if you think I missed anything or there is anything in particular you would like to discuss or review.