Reduce algorithm complexity and promote modularity
Just about every developer has found himself in a situation where they had a complicated algorithm in a single, virtually unreadable method, that was entangled together with other methods in a class. For example, say you have a general-purpose class for solving equations:
public class EquationSolvers
{
public static Tuple<double, double> Quadratic(double a, double b, double c)
{
double disc = b*b - 4*a*c;
if (disc < 0)
throw new ArgumentException("Cannot solve equation with complex roots");
double sqrt = Math.Sqrt(disc);
return new Tuple<double, double>(
(-b + sqrt) / (2 * a), (-b - sqrt) / (2 * a));
}
// other solvers here
}
The above equation solver is hard-coded, meaning that to substitute a different solver, you would have to manually replace each instance. Let’s start by taking it out into a separate class. To do this, we use the Move to Another Type refactoring Ctrl0R,0O:
![Modularity with ReSharper Modularity with ReSharper](https://resources.jetbrains.com/help/img/dotnet/2024.3/cookbook_modularity1.png)
Then, we need to specify the class to move the method to. In order to separate concerns better, we pick a separate class called QuadraticEquationSolver
for this:
![Modularity with ReSharper Modularity with ReSharper](https://resources.jetbrains.com/help/img/dotnet/2024.3/cookbook_modularity2.png)
Now that the method has been moved, let’s try taking the discriminant out to a separate calculation. This is easy — we select the discriminant calculation and invoke the Extract Method refactoring Ctrl0R,0M:
![Modularity with ReSharper Modularity with ReSharper](https://resources.jetbrains.com/help/img/dotnet/2024.3/cookbook_modularity3.png)
Now, all we need to do is to give the new method a name:
![Modularity with ReSharper Modularity with ReSharper](https://resources.jetbrains.com/help/img/dotnet/2024.3/cookbook_modularity4.png)
And it’s done:
private static double CalculateDiscriminant(double a, double b, double c)
{
return b * b - 4 * a * c;
}
Now, let’s suppose that, after a while, we find a safer solver for quadratic equations. To factor it into the program, we’ll first need to create an abstract base class QuadraticEquationSolverBase
. We use the Extract Superclass refactoring refactoring available in the Refactor This menu CtrlShift0R:
![Modularity with ReSharper Modularity with ReSharper](https://resources.jetbrains.com/help/img/dotnet/2024.3/cookbook_modularity5.png)
In the dialog that shows up, we get to pick which members will be promoted upwards. We only want the CalculateDiscriminant
method:
![Modularity with ReSharper Modularity with ReSharper](https://resources.jetbrains.com/help/img/dotnet/2024.3/cookbook_modularity6.png)
We add an abstract definition of the Calculate()
method (previously called Quadratic()
) and end up with the following base class:
public abstract class QuadraticEquationSolverBase
{
protected double CalculateDiscriminant(double a, double b, double c)
{
return b*b - 4*a*c;
}
public abstract Tuple<double, double> Calculate(double a, double b, double c);
}
We also got rid of the static
keyword anywhere with the assumption that the implementations of QuadraticEquationSolverBase
will be handled by a lifetime manager within our code. Consequently, ReSharper reminds us to add the override
keyword to the renamed Calculate
method in our QuadraticEquationSolver class:
![Modularity with ReSharper Modularity with ReSharper](https://resources.jetbrains.com/help/img/dotnet/2024.3/cookbook_modularity7.png)
Now, let’s say we found a safer version of the quadratic equation solver. Let’s implement it. First, we use the Create derived type context action on our base class:
Then, we are asked to implement members on this type, which we do:
Finally, we provide an implementation, making use of the base class’ CalculateDiscriminant()
method:
class SafeQuadraticEquationSolver : QuadraticEquationSolverBase
{
public override Tuple<double, double> Calculate(double a, double b, double c)
{
double disc = CalculateDiscriminant(a, b, c);
if (disc < 0)
throw new ArgumentException("Cannot solve equation with complex roots");
double q = -0.5*(b + Math.Sign(b)*disc);
return new Tuple<double, double> (q/a, c/q);
}
}
And we’re done! Now the quadratic equation solver can be easily used, with its configuration and instantiation typically handled by an IoC container.