Showing posts with label BestPracticesCSharp. Show all posts
Showing posts with label BestPracticesCSharp. Show all posts

Best practices while refactoring legacy code

2022-04-17

Separate the new feature changes and code refactoring. Don't mix up both.

If you try to take on all this refactoring at the same time as the change you were originally planning, that’s a lot of stuff to fit in your head. Maybe you’re a refactoring wizard, in which case go for it, but I wouldn’t trust myself to handle that kind of cognitive load without making any mistakes. It’s much safer to split the work into stages: either make the change, then refactor, or vice versa. Don’t try to do both at the same time. Splitting refactoring work and other changes into separate commits in your version control system also makes it easier for you and other developers both to review the changes and to make sense of what you were doing when you revisit the code later.

The Mikado Method

Every software developer with a few years of experience has endured this situation: You want to make a small change that should take you a few minutes. However, when you start you see one problem after the other you need to fix first, then you need to refactor here a bit and something there, and before you know it, a day has gone by and you are no step closer to implement your change.

That extra work is not complicated, but you try to solve a problem on top of an endless list of other problems. Your tests started to fail right after the second refactoring and after hours of work you would be happy if your code just would compile. The longer this takes, the more frustrating it gets.

What I like so much on the Mikado Method is that you need only a slight little change in your routine to prevent those problems. It is so easy that you will not believe that this makes any difference. But trust me, it works. The high-level view on the Mikado Method looks like this:

Set a goal
Experiment
Visualize
Undo

Best Practices In Handling Exceptions

2022-04-05

 A well-designed app handles exceptions and errors to prevent app crashes. Below are some of the best practices:

1. Use try/catch/finally blocks to recover from errors or release resources

Use try/catch blocks around code that can potentially generate an exception and your code can recover from that exception.

2. Throw specific exceptions to make it easy for handling.

In catch blocks, always order exceptions from the most derived to the least derived.

3. Avoid return statement inside Finally block as it suppress the exception being thrown out from the method.

4. Avoid exceptions, Avoid returning Null, Handle common conditions

Handle common conditions without throwing exceptions. Design classes so that exceptions can be avoided. A class can provide methods or properties that enable you to avoid making a call that would trigger an exception. For example, a FileStream class provides methods that help determine whether the end of the file has been reached. These can be used to avoid the exception that is thrown if you read past the end of the file.

It may sound obvious to avoid exceptions. But many methods that throw an exception can be avoided by defensive programming.

One of the most common exceptions is NullReferenceException. When we return null, we are essentially creating work for ourselves and foisting problems upon our callers. All it takes is one missing null check to send an application spinning out of control. In some cases, you may want to allow null but forget to check for null. Here is an example that throws a NullReferenceException:

Address a = null;
var city = a.City;

Accessing a throws an exception but play along and imagine that a is provided as a parameter. In case you want to allow a city with a null value, you can avoid the exception by using the null-conditional operator:

Address a = null;
var city = a?.City;

By appending ? when accessing a, C# automatically handles the scenario where the address is null. In this case, the city variable will get the value null.

Do not use exceptions for the normal flow of control, if possible.  Except for system failures and operations with potential race conditions, framework designers should design APIs so users can write code that does not throw exceptions. For example, you can provide a way to check preconditions before calling a member so users can write code that does not throw exceptions.

5. Throw exceptions instead of returning an error code. Exceptions ensure that failures do not go unnoticed because calling code didn't check a return code.

6. Consider the performance implications of throwing exceptions. Throw rates above 100 per second are likely to noticeably impact the performance of most applications.

7. Do document all exceptions thrown by publicly callable members because of a violation of the member contract (rather than a system failure) and treat them as part of your contract.

  Exceptions that are a part of the contract should not change from one version to the next (i.e. exception type should not change, and new exceptions should not be added).

8. Place throw statements so that the stack trace will be helpful.

 The stack trace begins at the statement where the exception is thrown and ends at the catch statement that catches the exception.

Catch (SpecificException specificException)
{
    // .....
    throw specificException;
}
Catch (SpecificException specificException)
{
    // .....
    throw;
}
The main difference here is that the first example re-throw the SpecificException which causes the stack trace of original exception to reset while the second example simply retain all of the details of the original exception. You almost always want to use the 2nd example.

9. Consider using exception builder methods.
It is common to throw the same exception from different places. To avoid code bloat, use helper methods that create exceptions and initialize their properties.
Also, members that throw exceptions are not getting inlined. Moving the throw statement inside the builder might allow the member to be inlined.

class FileReader
{
    private string fileName;
	
    public FileReader(string path)
    {
        fileName = path;
    }
	
    public byte[] Read(int bytes)
    {
        byte[] results = FileUtils.ReadFromFile(fileName, bytes);
        if (results == null)
        {
            throw NewFileIOException();
        }
        return results;
    }

    FileReaderException NewFileIOException()
    {
        string description = "My NewFileIOException Description";

        return new FileReaderException(description);
    }
}
10. Restore state when methods don't complete due to exceptions
Callers should be able to assume that there are no side effects when an exception is thrown from a method. For example, if you have code that transfers money by withdrawing from one account and depositing in another account, and an exception is thrown while executing the deposit, you don't want the withdrawal to remain in effect.

public void TransferFunds(Account from, Account to, decimal amount)
{
    from.Withdrawal(amount);
    // If the deposit fails, the withdrawal shouldn't remain in effect.
    to.Deposit(amount);
}
The method above does not directly throw any exceptions, but must be written defensively so that if the deposit operation fails, the withdrawal is reversed.
One way to handle this situation is to catch any exceptions thrown by the deposit transaction and roll back the withdrawal.
private static void TransferFunds(Account from, Account to, decimal amount)
{
    string withdrawalTrxID = from.Withdrawal(amount);
    try
    {
        to.Deposit(amount);
    }
    catch
    {
        from.RollbackTransaction(withdrawalTrxID);
        throw;
    }
}
This example illustrates the use of throw to re-throw the original exception, which can make it easier for callers to see the real cause of the problem without having to examine the InnerException property. An alternative is to throw a new exception and include the original exception as the inner exception

11. Log exceptions

This seem so obvious. But we can see too much code failing in the subsequent lines when using this pattern:

try
{
    service.SomeCall();
}
catch
{
    // Ignored
}
Logging both uncaught and caught exceptions is the least you can do for your users. Nothing is worse than users contacting your support, and you had no idea that errors had been introduced and what happened. Logging will help you with that.


Avoid Asynchronous Calls That Do Not Add Parallelism

2022-03-13

Avoid asynchronous calls that will block multiple threads for the same operation. The following code shows an asynchronous call to a Web service. The calling code blocks while waiting for the Web service call to complete. Notice that the calling code performs no additional work while the asynchronous call is executing.

// get a proxy to the Web service
  customerService serviceProxy = new customerService (); 
  //start async call to CustomerUpdate 
  IAsyncResult result = serviceProxy.BeginCustomerUpdate(null,null); 
  
// Useful work that can be done in parallel should appear here
// but is absent here
//wait for the asynchronous operation to complete
// Client is blocked until call is done
  result.AsyncWaitHandle.WaitOne(); 
  serviceProxy.EndCustomerUpdate(result); 
  
When code like this is executed in a server application such as an ASP.NET application or Web service, it uses two threads to do one task and offers no benefit; in fact, it delays other requests being processed. This practice should be avoided.
Ref: https://msdn.microsoft.com/en-us/library/ff647790.aspx

Give a thought on this before doing: Parallel vs. Synchronous Tasks

Before implementing asynchronous code, carefully consider the need for performing multiple tasks in parallel. Increasing parallelism can have a significant effect on your performance metrics. Additional threads consume resources such as memory, disk I/O, network bandwidth, and database connections. Also, additional threads may cause significant overhead from contention, or context switching. In all cases, it is important to verify that adding threads is helping you to meet your objectives rather than hindering your progress.

The following are examples where performing multiple tasks in parallel might be appropriate:
Where one task is not dependent on the results of another, such that it can run without waiting on the other.
If work is I/O bound. Any task involving I/O benefits from having its own thread, because the thread sleeps during the I/O operation which allows other threads to execute. However, if the work is CPU bound, parallel execution is likely to have a negative impact on performance.

Best Practices

  • Passing large structures by reference can be significantly faster than passing them by value, as the latter requires copying the whole structure. Likewise, returning a large structure by reference can be faster.
Ref: https://www.infoq.com/news/2016/04/CSharp-7?utm_source=infoq&utm_medium=popular_widget&utm_campaign=popular_content_list&utm_content=homepage
  • Throw specific exceptions to make it easy for handling.
  • Avoid return statement inside Finally block as it suppress the exception being thrown out from the method.
  • Avoid returning Null. 
 When we return null, we are essentially creating work for ourselves and foisting problems upon our callers. All it takes is one missing null check to send an application spinning out of control.

Handling Null Value in Your Code

2010-05-23

Is null value should be handled in your code? I found a good article on this topic.

In short here is the answer:

No, if you are wrting the code make sure that objects are always initialized and thus avoid creating null refernces to the objects.

Yes, there are times when you need to check for null. If you are writing a library that external developers will use, then you will probably want to check for null in all of your methods that are exposed as part of the API. (On your internal methods, you don’t need to since you have already cleansed the input.)

Also, if your code is being used as a callback to some API and you don’t know if it can ever pass null into your code, you probably should check for null.