简体   繁体   中英

Coding pattern c#

I came across some code that looked like this in a code review the other day.

public void DoSomeTasks()
{
  if (CheckSomeState()==true) return;
    DoTaskOne();
  if (CheckSomeState()==true) return;
    DoTaskTwo();
  if (CheckSomeState()==true) return;
    DoTaskThree();
  if (CheckSomeState()==true) return;
    DoTaskFour(); 
}

As the number of tasks increases the code ends up with an ever higher cyclomatic complexity and it also just doesn't feel right to me.

A solution that I have come up with to resolve this is.

private void DoTasksWhile(Func<bool> condition, Action[] tasks)
{
   foreach (var task in tasks)
   {
      if (condition.Invoke()==false) break;
        task.Invoke();
   }
}

Used like this

public void DoSomeTasks()
{
 var tasks = new Action[] { 
  {()=DoTaskOne()},
  {()=DoTaskTwo()},
  {()=DoTaskThree()},
  {()=DoTaskFour()}

  }

  DoTasksWhile(()=>CheckSomeState(), tasks);
}

Anyone got any suggestions to make the code more readable?

I made a little refactoring of your implementation

private void DoTasksWhile(Func<bool> predicate, IEnumerable<Action> tasks)
{
    foreach (var task in tasks)
    {
        if (!predicate())
            return;
        task();
    }
}
  • you don't need to Invoke delegates. Just execute them
  • do not compare boolean values with true/false (it's useless and you can assign boolean value by mistake)
  • thus you only enumerating tasks, IEnumerable is good for parameter

Also you can create extension method

public static void DoWhile(this IEnumerable<Action> actions,Func<bool> predicate)
{
    foreach (var action in actions)
    {
        if (!predicate())
            return;
        actions();
    }
}

Usage will be very simple:

tasks.DoWhile(() => CheckSomeState());

If your orchestration code is going to be very complex, look into using a framework such as WF (workflow foundation).

Otherwise, your code is fine, but I wouldn't change the initial one as it is more readable. It is also more flexible as in the future you may modify those IFs, ie the conditions might not remain identical.

I don't see how the cyclomatic complexity is lowered or can be lowered.

Your solution is perfectly adequate as it stands (although may be overkill in the case of just four steps), but whether it is the best approach in this case depends on what the purpose of CheckSomeState actually is in the context of your program:

  • Is it important that CheckSomeState is called exactly once between every Task?
  • Might it sometimes also need to be called midway through a task?

So for example, if CheckSomeState is actually checking a cancellation flag, then it might need additional checks while inside a long-running task.

Another option available to you is throwing exceptions (eg an OperationCancelledException). This keeps your cyclomatic complexity very low as you can now simply do:

CheckForCancel();
DoTaskOne();
CheckForCancel();
DoTaskTwo();

(the downside is that some would consider this to be using exceptions for control flow which is generally frowned upon).

I should also add that the goal should not be to reduce cyclomatic complexity, but to have code that is easy to understand and maintain. Cyclomatic complexity is a useful metric but it sometimes unnecessarily penalises code that is quite straightforward.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM