Categories
C# Style Best Practice Software Development

Method Design – Exceptional Behaviors

When designing methods in our solutions, it’s hard to communicate all of the behaviors of the method we are writing. How does the caller of our method know what the behaviors of the method will be?

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

My team and I try to adhere to a default set of behavioral assumptions about methods. Let’s also keep in mind that there are at least two contexts under which a method is designed. The first group being methods only intended to be used by their creators (usually private or internal), but of course if you work on a team of developers you won’t be the only one using the methods you write. The other context is a method which we expect others to use and maybe even developers that we don’t know or that work for other companies. In either case we find that we need to have or communicate some basic assumptions about any given method.

This post will only cover how we communicate if a method will throw or not.

Will it throw exceptions?

Exceptions are, and should be exceptional. We believe methods should be written and used with the expectation that they’ll succeed, as a matter of convention. Which is not implying that you should not expect failure. It’s actually implying that any normal method, should succeed at it’s intended operation or throw an Exception if it can’t succeed for an exceptional reason that could be planned for or controlled by the developer. Which communicates to the developer writing the Caller method that the Callee may throw an exception and somewhere in the call chain there should be an exception handler.

Then there are methods where failure is not exceptional, but part of the normal and expected behavior. These methods are the exception to our convention of “methods are expected to succeed or throw.”

This class of methods usually needs to test or attempt something that has a high failure rate, is based on an input that hasn’t yet been sanitized, or requires special handling of failures. An examples of such methods are the TryParse methods found on many primitives, like int.TryParse. These are the methods of Exceptional Behavior, in that failure is not exceptional, and exceptions should not be thrown, because failure is an expected behavior.

Microsoft has published guidance as part of the .NET Framework guidelines regarding two patterns they call the “Tester-Doer Pattern”, and the “Try-Parse Pattern”, you can read more about these patterns here: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions-and-performance

The subject of exception handling and designing methods that communicate their behavior when it comes to throwing exception is so near and dear to me, that I have another blog entry similar to this one which can be found here: https://blog.ramp51.com/2021/12/21/method-design-communicating-failures/ which really digs in to the ideas around how you maintain consistent expectations for the consumers of the methods you design when designing both methods of conventional behavior and methods of exceptional behavior.

In a future blog posts we’ll get into another conventions and expectations around methods as they pertain to blocking, reentrancy, idempotency, and purity.

Categories
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.

Categories
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.

Categories
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.

Categories
C# Style Best Practice Software Development

if-else statement

Great developers seek out patterns that prevent errors, unintended behaviors, and promote readability. This post will discuss one of these best practices.

In the Microsoft C# documentation the “if” keyword is documented as part of an “if-else” statement (if-else reference). The else part is important enough to be included in the name of the statement.

if (object.property == SomeEnum.AnEnumValue)
{
// do something
}

When you write an if statement similar to the example above; one of two things is happening:

  1. You didn’t think about or plan for what should happen if the condition is false
  2. You did think about it; and you understand the code base well enough to know it’s not an issue

The if SHOULD be followed by an else-statement

if (object.property == SomeEnum.AnEnumValue)
{
   // do something
}
else
{
  // handle negative logic branch
}

Adding the else-statement here can force you to deal with the fact that every if statement has two outcomes despite whether or not you’ve coded the else-statement. It can also help communicate to other developers on your team that the else-statement side of the branch has no negative impact on the logic or state of the application.

Some developers will argue the code is cleaner, more concise and more readable otherwise, and in most cases I’d probably disagree, of course there are exceptions. A best practice is a guideline not a rule.

If you don’t have anything to code in the else-statement just add a comment, of // intentional empty branch

if (object.property == SomeEnum.AnEnumValue)
{
   // do something
}
else
{
  // intentional empty branch
}

The reason for this is that it is ALMOST UNIVERSALLY TRUE that if the then-statement side has a DESIRED effect on the state of the application, the else-statement side has an UNDESIRED effect on the state of the application.

if (object.property != SomeEnum.Unknown)
{
   // do something, because the property is not set to the Unknown State of the enum
}
else
{
  // can't write code for a unidentified use case
  // must throw an exception, because there isn't a definition of how to proceed
  throw new InvalidOperationException("Object.Property, was set to Unknown");
}

Which could also be written in reverse

if (object.property == SomeEnum.Unknown)
{
  // can't write code for a unidentified use case
  // must throw an exception, because there isn't a definition of how to proceed
  throw new InvalidOperationException("Object.Property, was set to Unknown");
}
else
{
   // do something, because the property is not set to the Unknown State of the enum
}

Or even better using a optimistically named and descriptive conditional variable

bool isPropertyValid = object.Property != SomeEnum.Unknown;

if (isPropertyValid)
{
   // do something, because the property is not set to the Unknown State of the enum
}
else
{
  // can't write code for a unidentified use case
  // must throw an exception, because there isn't a definition of how to proceed
  throw new InvalidOperationException("Object.Property, was set to Unknown");
}

This practice is so essential because we tend to gravitate towards testing what we expect to happen when writing if-else statements. To ensure that you don’t make costly assumptions, it’s crucial to define what might happen if the condition isn’t true.