Categories
Software Development

The hovering laptop

“I need my laptop to hover in the air. Who can build this, or can you recommend someone that is selling this feature?”

So, you work in technology, in some role trying to deliver solutions to stakeholders. When you receive the request “I need my laptop to hover in the air,” what do you do next?

It’s natural to want to be the first one to display your technical prowess balanced by your profound sense of how to deliver ROI (a problem I clearly struggle with, lol); listing the reasons why you can’t solve this, that the existing tooling won’t support it, or that the risk/reward is just too unbalanced to even try.

After your response, the requestor assumes it can’t be done or isn’t worth doing, and everyone moves on. Do we really know what we left to die on the table?

The example of a hovering laptop is absurd enough that most of us probably think, “I would definitely ask WHY before responding?” While that may be true, we often get requests where our assumptions about the request defeat our best intention of asking WHY?

More than ever, the stakeholders we work with tend to come to us with solutions rather than problems and are more technically inclined than their counterparts from 20 years ago. Naturally as people become more tech savvy and more aware of the techniques used to deliver solutions they become more solution focused.

We need to ask questions we didn’t have to ask 20 years ago. When people were more likely to come to you and say, “I’m not a techie! Here is what I do today; my boss told me to loop your team in to see how we can improve the process.” At this point, you’d start learning about the process they want to be improved, what data is involved, how frequently it changes, and the people involved, along with other factors, and formulate a proposed new process.

Today many stakeholders are “Solution Proposers” and technologists as “Solution Providers” we need to be experts at peeling back the layers of the proposed solution to identify how and why the requestor came to the conclusion that they need “A hovering laptop.” Maybe they are right, maybe they do need a hovering laptop.

Despite whether they are right or wrong, as a solution provider, I still want to ensure that any solution I put in place will address the underlying needs driving the request. So we start asking questions.

  • While your laptop is hovering, will you be actively using it? Is it a touch screen, will you use an attached mouse and keyboard, or the keyboard and touchpad that are built in?
  • Are you mobile while using this hovering laptop? Are you sitting, standing, or riding something?

Your approach may vary depending on the people you are working with. For some, asking questions about the solution requested can be a more straightforward approach aligned with how the stakeholder already views the outcome. Remind them that you just want to talk through the proposed solution and that you are there not just to deliver a solution but to help them make an appropriate decision on what solution best meets all of the goals and constraints they may not have shared yet.

From your initial questions the requestor may start simplifying their request:

Hrm. I guess if I’m just sitting at my desk, I could use something to lift the laptop up. It doesn’t really need to hover, per se.

Maybe the requestor has never seen a riser that sits on a desk, or perhaps the requestor is just assuming the company wouldn’t build a shelf above her desk or that the cubicle wall won’t support attaching a shelf.

Clearly, today, if we really wanted, we could make a laptop hover. It would likely come with a lot of unintended user experience problems.

We can probably find a simpler, cheaper, faster to deploy, and less likely to fail solution. We just need to get to the WHY. We can only do that through curiosity and understanding of what brought the requestor to suggest the specific solution they did.

This post is getting pretty long, so I’ll just end it by saying: Don’t lead the conversation by enumerating the reasons you can’t deliver what is being asked. First seek to understand why you are being asked for a particular solutions.

Categories
Software Development Visual Basic.NET

Is IIF part of the VB.NET language?

The answer is NO. Does it feel like it is part of the language, YES.

When coding in Visual Basic.NET you’ll come across three variations of an “IF”.

  • IIF Function
  • IF() Operator
  • IF…Then…Else Statement

The IF() operator and the IF…Then…Else Statement are part of the VB.NET Language.

The IIF Function is not, even though you can use it. These three features operate differently and have different purposes, this post isn’t meant to get into those differences, but will discuss how IIF() and IF() are defined and used by the compiler. Most likely when you are using the IIF Function, you probably want to be using the IF() Operator.

The IIF function is imported by default due to the VB Compiler Response file that is included when the compiler is installed. The VB Compiler Response file includes a line which makes this possible:

/imports:Microsoft.VisualBasic

The Microsoft.VisualBasic.dll is always referenced by the compiler regardless of what command line options you specify.

Which is why compiling this code will work, even though IIF is NOT part of the Visual Basic .NET language.

Module Module1

    Sub Main()

        Console.WriteLine(IIf(True, "Hi Mark", "Hi Tom"))


    End Sub

End Module

Which is successfully compiled with this command

vbc ExampleModule.vb

However, if you were to enter this command

vbc /noconfig ExampleModule.vb

You would be presented with the following errors:

error BC30451: 'Console' is not declared. It may be inaccessible due to its protection level.

        Console.WriteLine(IIf(True, "Hi Mark", "Hi Tom"))
        ~~~~~~~

error BC30451: 'IIf' is not declared. It may be inaccessible due to its protection level.

        Console.WriteLine(IIf(True, "Hi Mark", "Hi Tom"))
                          ~~~

There are two errors, because /noconfig caused the vb compiler to not load the compiler responses file, which means neither System or Microsoft.VisualBasic were imported by default.

If we modify our code above, to include these imports, it will now compile with /noconfig

Imports System
Imports Microsoft.VisualBasic

Module Module1
    Sub Main()
        Console.WriteLine(IIf(True, "Hi Mark", "Hi Tom"))
    End Sub
End Module

This will compile with the /noconfig flag. Since there isn’t any reason to make our lives harder we typically wouldn’t run VBC with the /noconfig (although Visual Studio does, but at least there it deals with specificying all the required parameters.)

If IIF isn’t part of the Language where does it come from?

Inside the Microsoft.VisualBasic.dll there is a Module named Interactions which defines the globally scoped function IIF

public static object IIf (bool Expression, object TruePart, object FalsePart);

Due to Microsoft.VisualBasic.dll being referenced by the compiler intrinsically and the Import of the Microsoft.VisualBasic namespace by the compiler responses file (in the case of a Visual Studio project it’s specified in the build file,) the IIF function is available for use automatically.

Why is the IIF Function there when we have the IF() Operator?

I don’t have a first hand answer from any decision makers involved in drafting the Visual Basic Language specification, so this answer is part research, intuition, and speculation.

From 2002 to 2008, the Visual Basic.NET language didn’t offer the IF() Operator. Prior to Visual Basic.NET, Basic and Visual Basic programmers had access to an IIF function in many other languages, like VBA, FoxPro, and T-SQL. I believe I remember using it in versions of Basic and Visual Basic, but can’t find any supporting documentation.

In addition to retaining the functionality programmers were used to; adding it to Visual Basic.NET with similar behavior to what existed prior likely made upgrading old basic systems to VB.NET an easier process.

In 2008, with Visual Basic .NET 9.0 the IF() operator was introduced. Also bringing forward the wave of new advice and discussions of using “IF() instead of IIF()”

If IIF is not part of the language, then what is it?

Ok, so strictly speaking, it’s not a feature of the Visual Basic.NET language. On the other hand if you create a Code.vb txt file and compile it, by default IIF is a feature available to you. Which is also true when creating a new project in Visual Studio. I’d call it a Framework feature that is available to VB code files by default.

If IIF is a framework feature, can we use it from C#?

Let’s give it a try, we’ll start with this code

namespace ExampleIfInCSharp
{
    using System;
    using Microsoft.VisualBasic;

    public class Program
    {
        public static int Main(string[] args)
        {
            // Framework Feature IIF, in Microsoft.VisualBasic
            Console.WriteLine(IIF(true, "Hi Mark", "Hi Tom"));
            Console.WriteLine(IIF(false, "Hi Mark", "Hi Tom"));

            // C# Language Feature - Ternary Operator
            Console.WriteLine(true ? "Hi Mark" : "Hi Tom");

            // VB.NET Language Feature - IF() Operator
            //    this won't compile with C# 
            Console.WriteLine(IF(true, "Hi Mark", "Hi Tom"));

            return 0;
        }
    }
}

Now let’s try to compile that

csc iif-in-csharp.cs
Microsoft (R) Visual C# Compiler version 3.10.0-4.21318.11 (7ceb6331)
Copyright (C) Microsoft Corporation. All rights reserved.

iif-in-csharp.cs(11,31): error CS0103: The name 'IIF' does not exist in the current context
iif-in-csharp.cs(12,31): error CS0103: The name 'IIF' does not exist in the current context
iif-in-csharp.cs(19,31): error CS0103: The name 'IF' does not exist in the current context

We see that the compile does not like IF or IIF. Above we discovered that IIF requires a reference to the Microsoft.VisualBasic.dll which happens automatically with the VB Compiler, and that the VB Compiler Responses file automatically Imports the Microsoft.VisualBasic namespace.

Let’s take this a step further, our code already has a using importing the Microsoft.VisualBasic namespace. Let’s add a reference to Microsoft.VisualBasic.dll and compile:

csc /r:Microsoft.VisualBasic.dll iif-in-csharp.cs
Microsoft (R) Visual C# Compiler version 3.10.0-4.21318.11 (7ceb6331)
Copyright (C) Microsoft Corporation. All rights reserved.

iif-in-csharp.cs(11,31): error CS0103: The name 'IIF' does not exist in the current context
iif-in-csharp.cs(12,31): error CS0103: The name 'IIF' does not exist in the current context
iif-in-csharp.cs(19,31): error CS0103: The name 'IF' does not exist in the current context

What is interesting here, is that we now have a reference to Microsoft.VisualBasic.dll and a using for Microsoft.VisualBasic in our code. The code hasn’t changed from above, all we changed was command we were using to compile our code. We still have all of the same errors though.

C# is different from VB.NET; it has different language features. A feature of C# is that it’s case sensitive, while VB.NET is case-insensitive. Also VB.NET supports global modules as a feature while C# does not support modules. I think we have to make a few more code changes:

We are going to change the casing of IIF to IIf, and prefix it with the module name Interaction.

namespace ExampleIfInCSharp
{
    using System;
    using Microsoft.VisualBasic;

    public class Program
    {
        public static int Main(string[] args)
        {
            // Framework Feature IIF, in Microsoft.VisualBasic
            Console.WriteLine(Interaction.IIf(true, "Hi Mark", "Hi Tom"));
            Console.WriteLine(Interaction.IIf(false, "Hi Mark", "Hi Tom"));

            // C# Language Feature - Ternary Operator
            Console.WriteLine(true ? "Hi Mark" : "Hi Tom");

            // VB.NET Language Feature - IF() Operator
            //    this won't compile with C# 
            Console.WriteLine(IF(true, "Hi Mark", "Hi Tom"));

            return 0;
        }
    }
}

Now we’ll compile this and see what happens:

csc /r:Microsoft.VisualBasic.dll iif-in-csharp.cs
Microsoft (R) Visual C# Compiler version 3.10.0-4.21318.11 (7ceb6331)
Copyright (C) Microsoft Corporation. All rights reserved.

iif-in-csharp.cs(19,31): error CS0103: The name 'IF' does not exist in the current context

Progress! We have IIf working in our c# code, but we still have the problem with the IF() operator. Our only choice is to remove this from our code, or create our own function named If(), but we would never do this, because the C# ternary operator (?:) does the same thing, and adding an If() function would create confusion. Additionally writing an IF() function in C# would still not behave like the IF() operator in VB, it would behave just like the IIF function.

The IF() operator can not be used directly in C# because of where it’s defined. It’s a language feature, meaning it’s specified in the “grammar” of what makes a Visual Basic.NET program. There isn’t a way to tell the CSharp compiler that it should refer to the Visual Basic.NET Grammar, which makes a lot of sense. This would be like trying to convince your swim teacher that a basketball is required for swimming. Clearly a basketball is not a tool in a swimmer’s toolbox. Just as the CSC compiler wouldn’t consider IF() as tool that could be used by a programmer in a C# program.

The IIF function is available to C# and VB.NET because it’s part of the “Framework.” It was coded in some language, which is irrelevant, and it was compiled into a .NET assembly, therefore .NET Applications can reference that assembly and use the publicly exposed types in it.

Here is our final program in C#

namespace ExampleIfInCSharp
{
    using System;
    using Microsoft.VisualBasic;

    public class Program
    {
        public static int Main(string[] args)
        {
            // Framework Feature IIF, in Microsoft.VisualBasic
            Console.WriteLine(Interaction.IIf(true, "Hi Mark", "Hi Tom"));
            Console.WriteLine(Interaction.IIf(false, "Hi Mark", "Hi Tom"));

            // C# Language Feature - Ternary Operator
            Console.WriteLine(true ? "Hi Mark" : "Hi Tom");

            // VB.NET Language Feature - IF() Operator
            //    this won't compile with C# 
            //Console.WriteLine(IF(true, "Hi Mark", "Hi Tom"));

            return 0;
        }
    }
}

And when we compile, Voila! No errors.

csc /r:Microsoft.VisualBasic.dll iif-in-csharp.cs
Microsoft (R) Visual C# Compiler version 3.10.0-4.21318.11 (7ceb6331)
Copyright (C) Microsoft Corporation. All rights reserved.

In conclusion, we can use framework features in any language, although you can see in this case how Microsoft made it difficult to use this in C# but not impossible. This is probably intentional since using IIf in C# wouldn’t add any value to a C# program. Today most would argue that IIf doesn’t add any value to a VB.NET program either, unless you had a very specific need for a confusing line of code that represented logic branching in an IF-like structure but didn’t have short-circuiting.

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
Software Development

Integer, Long or String: 10488084?

Today most enterprise software development requires integrating with systems built by other teams and other companies. Often times those integrations need to coordinate data and processes which means there is a set of shared identifiers used by those systems to facilitate the integration.

When you are building an integration with another system, that returns and identifier like 10488084, do you store it as an integer, long, or a string?

With a few exceptions that most of us developers don’t really need to deal with, I would universally say it should be a string. Why should it be a string when it’s clearly a number?

  • We aren’t performing any mathematical operations on it.
  • We don’t have any control of the identifier, or the range of values.
  • We likely don’t need to sort by ID, so whether the sort is numeric or alphabetic isn’t a concern.
  • Storage space is cheap. We don’t need to worry about the efficiency of storing the value as string vs a numeric type.
  • Performance on ID comparison is easily solved with data structures / indexes. Your database system is likely very capable at matching these strings quickly, even if it’s slightly slower then using numeric types.

Based on those reasons, any ID that is retrieved from a third party system should be stored as a string. Which protects your system from changes in the third party system. For example, if you store 10488084 as an integer in .NET, and then the third party decides all new id’s will start at 3000000000, you have a problem. The new ID’s will overflow and throw exceptions, unless you convert them to longs or strings throughout your application. So you might as well start with strings.

What are your thoughts? Any other reasons why we shouldn’t universally store third party ID’s as strings?

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.