Code Smells : Change Preventers

If you ever have been in a situation when you need to make change in one place, but had to make changes in many places too, then Change Preventers is a code smell you should be vary off. Change Preventers is result of poor structuring of code and can be broadly categorized into 3.

Divergent Change

Symptoms of Divergent Change are obvious when you have to make changes in several unrelated methods when you need to make a single change in a class. The solution for the code smell lies in splitting up the class. We could segregate the different behaviors of the class using Extract Class or in scenarios where, different classes have same behavior, we could employ Extract SubClass or Extract SuperClass whichever seem appropriate for the particular scenario.

Shotgun Surgery

Another closely related Change Preventer is known as Shotgun Surgery, which is almost opposite to Divergent Change. With Divergent Change, many changes were required to a Single Class, while with Shotgun Surgery, a single change is made to several classes. This is a resultant of a single responsibility split among many classes and can happen due to ‘over-refactoring’.

The resolution lies in bringing together all associated methods to form a single class that has the responsibility rather than distributing it over multiple classes. Refactoring techniques like Move Method and Move Field can be employed for the purpose.

Parrallel Inheritance Hierarchy

If you find your self in situations where you intend to create a derived class, but also end up having to create sub classes for another class, then Parallel Inheritance Hierarchy. This eventually would result in making changes harder as related changes would be spread across the codebase.

To remove such duplication, we would need to employ refactoring techniques like Move Method and Move Field after creating a reference of the dependent class in your original class. Once all the methods and fields has been moved, you can get rid of the reference and duplicate class.

Switching Context With Async

Whenever an await is encountered in the code, a context is captured, which is later used when the promise is completed and ready to continue. If the method is called by the UI thread, usually, the context could be UI context, unless the awaited method itself creates a new thread. This can be demonstrated in the following example.

 

async Task DoAsync()
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(TimeSpan.FromSeconds(3));
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);

}
The ThreadID displayed would be the same. However, there could be scenarios where you might be interested to actually switch the context. The framework provides an easy way to do same, using the ConfigureAwait method, which accepts a boolean which dictates whether to continue on captured context. Let’s change the above code and check the new output.

 

async Task DoAsync()
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);

}
The ThreadIDs would be different in this case.

Stephen Clearly mentions a deadlock situation in his famous book, where ConfigureAwait helps in breaking the deadlock. Let’s consider the deadlock situation first.

private async void button1_Click(object sender, EventArgs e)
{
Task temp = DoAsync();
Debug.WriteLine("Continue");
temp.Wait();
Debug.WriteLine("Not Deadlock");
}

async Task DoAsync()
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(TimeSpan.FromSeconds(3));
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);

}

The above code would end up in a deadlock. This is because, in the DoAsync Method, the captured context in which the execution needs to continue, has a thread which is already blocked by the Wait Method in the calling Button_Click event. Since the context only allows a single thread to continue, this would result in a deadlock situation. This could be resolved if we switch context in the await method.