Write immortal code of maintainable software

Posted by morphy on Thu, 03 Feb 2022 03:40:15 +0100

Write simple code units

There are many smaller problems inside each problem.

principle:

Limit the number of branch points per code unit to no more than 4

A common way to measure complexity is to calculate the number of possible paths in a piece of code, that is, the number of branches. In C #, specifically, if and switch statements.

Branch coverage: the number of branch points in a code unit is the minimum number of branch paths generated by covering all branch points.

Execution path: the combination of all branches is the execution path of the code unit, also known as the maximum number of unit paths.

The number of branch points is the minimum value of the path, and the execution path is the maximum value of all paths. The standard to measure complexity is the number of branch points + 1 and cycle complexity.

Code units without any branch points can only have one execution path, so there will only be one test case.

Statements and operators considered as branch points in C #: if case?,??   &&,||  while  for,foreach  catch

How does high complexity occur?

Complex code units contain many coupled code blocks, and their complexity is the sum of the complexity of each code block. The complexity will increase with the nesting of if then else. The more nested, the more difficult the code is to understand.

Another reason is to use a very long chain if then else or switch. For nested, the extraction method mentioned in the previous chapter can be used to improve.  

Take chestnuts for example:

Method to return flag color

public IList<Color> GetFlagColors(Nationality nationality)
{
  List<Color> result;
  switch(nationality)
  {
    case Nationality.DUTCH:
      result = new List<Color> { Color.Red, Color.White, Color.Blue };
      break;
    case Nationality.GERMAN:
      result = new List<Color> { Color.Black, Color.Red, Color.Yellow };
      break;
    case Nationality.BELGIAN:
      result = new List<Color> { Color.Black, Color.Yellow, Color.Red };
      break;
    case Nationality.FRENCH:
      result = new List<Color> { Color.Blue, Color.White, Color.Red };
      break;
    case Nationality.ITALIAN:
      result = new List<Color> { Color.Green, Color.White, Color.Red };
      break;
    case Nationality.UNCLASSIFIED:
    default:
      result = new List<Color> { Color.Gray };
    break;
  }
  return result;
}
One way to reduce its complexity is to introduce a Map Data structure that maps countries to specified Flag Object.

private static Dictionary<Nationality, IList<Color>> FLAGS = new Dictionary<Nationality, IList<Color>>();
static FlagFactoryWithMap()
{
  FLAGS[Nationality.DUTCH] = new List<Color> { Color.Red, Color.White, Color.Blue };
  FLAGS[Nationality.GERMAN] = new List<Color> { Color.Black, Color.Red, Color.Yellow };
  FLAGS[Nationality.BELGIAN] = new List<Color> { Color.Black, Color.Yellow, Color.Red };
  ...
}

public IList<Color> GetFlagColors(Nationality nationality)
{
  IList<Color> colors = FLAGS[nationality];
  return colors ?? new List<Color> { Color.Gray };
}

The second method is to split the functions of different flags into different flag types, and use polymorphism instead of conditional judgment, so that each flag has its own type and implements the same interface.

Same interface

public interface IFlag
{
IList<Color> Colors { get; }
}

Dutch flag
public class DutchFlag : IFlag
{
public IList<Color> Colors { get { new List<Color> { Color.Red, Color.White, Color.Blue }; } }
}

Italian flag
public class ItalianFlag : IFlag
{
public IList<Color> Colors { get { new List<Color> { Color.Green, Color.White, Color.Red }; } }
}
//Other countries


private static readonly Dictionary<Nationality, IFlag> FLAGS = new Dictionary<Nationality, IFlag>();
static FlagFactory()
{
  FLAGS[Nationality.DUTCH] = new DutchFlag();
  FLAGS[Nationality.ITALIAN] = new ItalianFlag();
  //Flags of other countries
}
public IList<Color> GetFlagColors(Nationality nationality)
{
  IFlag flag = FLAGS[nationality];
  flag = flag ?? new DefaultFlag();
  return flag.Colors;
}

Handling nested conditional statements

The root node of a binary search tree and an integer are given below to calculate the position of the integer in the tree. If found, it returns the depth of the integer in the tree, otherwise an exception is thrown.

public static int CalculateDepth(BinaryTreeNode<int> t, int n)
{
  int depth = 0;
  if (t.value == n)
  {
    return depth;
  }
  else
  {
    if (n < t.value)
    {
      BinaryTreeNode<int> left = t.left;
      if (left == null)
      {
        throw new TreeException("value not found in tree");
      }
      else
      {
        return 1 + CalculateDepth(left, n);
      }
    }
    else
    {
      BinaryTreeNode<int> right = t.right;
      if (right == null)
      {
        throw new TreeException("value not found in tree");
      }
      else
      {
        return 1 + CalculateDepth(right, n);
      }
    }
  }
}

To improve readability, you can identify various independent situations and insert return statements instead of nested conditional statements. This practice is called {using guard statements instead of nested conditional statements

If the conditional statements are extremely complex, they should be disassembled, checked one by one, and returned from the function immediately when the condition is true. Such a separate check is usually called "guard statements"

The effect of Wei sentence is to sort out the condition judgment that needs to read the code carefully and sort out the logic carefully into a logical relationship that can be seen through at a glance. The effect is like the following:

if(it == (Live){
    if(it == (person){

        if(it != (women){
            return dislike;

        } else {
            return Like;

        }

    } else {
        return dislike;

    }

} else {
    return dislike;

}

The above code is nothing more than to express that I only like living women, but the code layer by layer obviously hinders my efficiency of obtaining the most core information at the first time. The use of guard sentences is:

if (it! = alive) {return don't like}

if (it! = people) {return don't like}

if (it! = woman) {return don't like}

if = & return = &}

if (anything else) {return doesn't like}

It's probably such a thing. Of course, these codes have repeated logical judgments in them.

public static int CalculateDepth(BinaryTreeNode<int> t, int n)
{
  int depth = 0;
  if (t.value == n)
  {
    return depth;
  }
  if ((n < t.value) && (t.left != null))
  {
    return 1 + CalculateDepth(left, n);
  }
  if ((n > t.value) && (t.right != null))
  {
    return 1 + CalculateDepth(right, n);
  }
  throw new TreeException("value not found in tree");
}

In order to reduce complexity, nested conditional statements need to be extracted into other methods:

public static int CalculateDepth(BinaryTreeNode<int> t, int n)
{
  int depth = 0;
  if (t.value == n)
  {
    return depth;
  }
  else
  {
    return TraverseByValue(t, n);
  }
}

private static int TraverseByValue(BinaryTreeNode<int> t, int n)
{
  BinaryTreeNode<int> childNode = GetChildNode(t, n);
  if(childNode==null)
  {
    throw new TreeException("value not found in tree");
  }
  else
  {
    return 1 + CalculateDepth(childNode, n);
  }
}
private static BinaryTreeNode<int> GetChildNode(BinaryTreeNode<int> t, int n)
{
  if (n < t.value)
  {
    return t.left;
  }
  else
  {
    return t.right;
  }
}

Objection:

High complexity is inevitable. Complex fields do not necessarily require complex technical implementation. The responsibility of developers is to simplify problems and write simple code. However, the more complex the field, the more developers spend more energy on building simple technical solutions, which can be done!

The splitting method will not reduce the complexity. For example, replacing the method with complexity 15 with three methods with complexity 5, the total is still 15, which doesn't feel good. In terms of maintainability, it will become easier to test and understand.

 

SIG evaluates the complexity of code units, with a 4-star rating of more than 25, more than 10, more than 5 and less than 5