最適化コイル複雑度(Cyclomatic complexity,CC)

12785 ワード

サークル複雑度(Cyclomatic complexity,CC)
概要
コイル複雑度(Cyclomatic complexity、略記CC)は条件複雑度とも呼ばれ、コード複雑度の測定基準である.多くの社内でコードをオンにすると、この指標を測る必要があるので、ここで簡単に記録しておきます.
本文の全文は《詳しく輪の複雑さを解きます》に基づいて、原作の書くのはとても細かくてしかもはっきりしていて分かりやすくて、輪の複雑さの計算などについて原作を参照することができます.本論文では,最適化リングの複雑さの部分のみを記録する.
意味
  • は、欠陥が欠陥となる前にそれらを捕捉する.
  • 一般に、ループの複雑さが10より大きい方法は、大きなエラーリスクを有する.コイル複雑度と欠陥個数には高度な正相関がある:コイル複雑度が最も高いモジュールと方法は、その欠陥個数も最も多くなる可能性がある.
  • ループの複雑さとソフトウェア品質
    サークル複雑度
    コード状況
    そくていせい
    保守コスト
    1-10
    明確、構造化
    高い
    低い
    10-20
    複雑


    20-30
    非常に複雑
    低い
    高い
    >30
    読み取り不可
    計り知れない
    非常に高い

  • コイルの複雑さを低減する方法
    1.関数の再編成
    1.1抽出関数
    コードをまとめて独立させることができます.
          void Example(int val)
          {
          	if( val > MAX_VAL)
          	{
          		val = MAX_VAL;
          	}
    
          	for( int i = 0; i < val; i++)
          	{
          		doSomething(i);
          	}
          }
    

    このコードを独立した関数に挿入し、関数名にその関数の用途を説明します.
       int getValidVal(int val)
       {
          	if( val > MAX_VAL)
       	{
       		return MAX_VAL;
       	}
           return val;
       }
    
       void doSomethings(int val)
       {
       	for( int i = 0; i < val; i++)
       	{
       		doSomething(i);
       	}
       }
    
       void Example(int val)
       {
           doSomethings(getValidVal(val));
       }
    

    最後に,関数の内容が統一階層にあるかどうかを見直す.
    1.2置換アルゴリズム
    あるアルゴリズムを別のより明確なアルゴリズムに置き換えます.
       string foundPerson(const vector& peoples){
         for (auto& people : peoples)
         {
           if (people == "Don"){
             return "Don";
           }
           if (people == "John"){
             return "John";
           }
           if (people == "Kent"){
             return "Kent";
           }
         }
         return "";
       }
    

    関数インプリメンテーションを別のアルゴリズムに置き換えます.
       string foundPerson(const vector& people){
         std::mapcandidates{
           	{ "Don", "Don"},
           	{ "John", "John"},
           	{ "Kent", "Kent"},
              };
         for (auto& people : peoples)
         {
           auto& it = candidates.find(people);
           if(it != candidates.end())
               return it->second;
         }
       }
    

    いわゆるテーブル駆動.
    2.条件式の簡略化
    2.1逆表現
    コードに存在する可能性のある条件は、次のように表されます.
       if ((condition1() && condition2()) || !condition1())
       {
           return true;
       }
       else
       {
           return false;
       }
    

    逆表現を適用して表現順序を変更すると、次のような効果が得られます.
       if(condition1() && !condition2())
       {
           return false;
       }
    
       return true;
    

    2.2分解条件
    コードに複雑な条件表現があります.
       if(date.before (SUMMER_START) || date.after(SUMMER_END))
           charge = quantity * _winterRate + _winterServiceCharge;
       else
           charge = quantity * _summerRate;
    

    if、then、elseの3つの段落から独立関数をそれぞれ抽出します.
       if(notSummer(date))
           charge = winterCharge(quantity);
       else
           charge = summerCharge (quantity);
    

    2.3連結条件
    一連の条件判断は、同じ結果を得た.
       double disabilityAmount()
       {
           if (_seniority < 2) return 0;
           if (_monthsDisabled > 12) return 0;
           if (_isPartTime) return 0;
           // compute the disability amount
           ......
    

    これらの判断を条件式に結合し、この条件式を独立関数に抽出します.
       double disabilityAmount()
       {
           if (isNotEligableForDisability()) return 0;
           // compute the disability amount
           ......
    

    2.4制御タグの削除
    コードロジックでは、boolタイプが論理制御タグとして使用される場合があります.
       void checkSecurity(vector& peoples) {
       	bool found = false;
       	for (auto& people : peoples)
           {
       		if (! found) {
       			if (people == "Don"){
       				sendAlert();
       				found = true;
       			}
       			if (people == "John"){
       				   sendAlert();
       				   found = true;
       			}
       		}
       	}
       }
    

    コントロールタグの代わりにbreakとreturnを使用します.
       void checkSecurity(vector& peoples) {
       	for (auto& people : peoples)
       	{
       		if (people == "Don" || people == "John")
       		{
       			sendAlert();
       			break;
       		}
       	}
       }
    

    2.5多態置換条件式
    条件式は、オブジェクトのタイプによって異なる動作を選択します.
       double getSpeed()
       {
           switch (_type) {
               case EUROPEAN:
                   return getBaseSpeed();
               case AFRICAN:
                   return getBaseSpeed() - getLoadFactor() *_numberOfCoconuts;
               case NORWEGIAN_BLUE:
                   return (_isNailed) ? 0 : getBaseSpeed(_voltage);
           }
           throw new RuntimeException ("Should be unreachable");
       }
    

    条件式の各ブランチ全体をサブクラスのリロードメソッドに配置し、元の関数を抽象メソッドとして宣言します.
       class Bird
       {
       public:
           virtual double getSpeed() = 0;
    
       protected:
           double getBaseSpeed();
       }
    
       class EuropeanBird
       {
       public:
           double getSpeed()
           {
               return getBaseSpeed();
           }
       }
    
       class AfricanBird
       {
       public:
           double getSpeed()
           {
               return getBaseSpeed() - getLoadFactor() *_numberOfCoconuts;
           }
    
       private:
           double getLoadFactor();
    
           double _numberOfCoconuts;
       }
    
       class NorwegianBlueBird
       {
       public:
           double getSpeed()
           {
               return (_isNailed) ? 0 : getBaseSpeed(_voltage);
           };
    
       private:
           bool _isNailed;
       }
    

    3.関数呼び出しの簡略化
    3.1読み書き分離
    関数は、オブジェクトのステータス値を返し、オブジェクトのステータスを変更します.
       class Customer
       {
       int getTotalOutstandingAndSetReadyForSummaries(int number);
       }
    

    2つの異なる関数を作成します.1つはクエリーを担当し、もう1つは変更を担当します.
       class Customer
       {
           int getTotalOutstanding();
           void SetReadyForSummaries(int number);
       }
    

    3.2パラメータ化方法
    いくつかの関数は似たような作業をしていますが、関数本体には異なる値が含まれています.
       Dollars baseCharge()
        {
           double result = Math.min(lastUsage(),100) * 0.03;
           if (lastUsage() > 100)
           {
               result += (Math.min (lastUsage(),200) - 100) * 0.05;
           }
           if (lastUsage() > 200)
           {
               result += (lastUsage() - 200) * 0.07;
           }
           return new Dollars (result);
       }
    

    パラメータで異なる値を表す単一の関数を作成します.
       Dollars baseCharge()
       {
           double result = usageInRange(0, 100) * 0.03;
           result += usageInRange (100,200) * 0.05;
           result += usageInRange (200, Integer.MAX_VALUE) * 0.07;
           return new Dollars (result);
       }
    
       int usageInRange(int start, int end)
       {
           if (lastUsage() > start)
               return Math.min(lastUsage(),end) -start;
    
           return 0;
       }
    

    3.3パラメータを明確な関数で置換する
    関数の実装は、パラメータ値に完全に依存して異なる反応を示します.
       void setValue (string name, int value)
       {
           if (name == "height")
               _height = value;
           else if (name == "width")
               _width = value;
           Assert.shouldNeverReachHere();
       }
    

    このパラメータの可能な値ごとに、独立した関数を作成します.
       void setHeight(int arg)
       {
           _height = arg;
       }
       void setWidth (int arg)
       {
           _width = arg;
       }
    

    実戦練習
    CC値を以前に集計した例です.
      U32 find (string match){
             for(auto var : List)
             {
                 if(var == match && from != INVALID_U32) 
    	     	return INVALID_U32;
             }
             //match step1
             if(session == getName() && key == getKey())
             {
                 for (auto& kv : Map)
                 {
                     if (kv.second == last && match == kv.first)
                     {
                         return last;
                     }
                 }
    
             }
             //match step2
             auto var = Map.find(match);
             if(var != Map.end()&& (from != var->second)) return var->second;
    
             //match step3
             for(auto var: Map)
             {
                 if((var.first, match) && from != var.second)
                 {
                     return var.second;
                 }
             }
             return INVALID_U32;
         };
    

    CC値を下げるテクニックを総合的に運用した後:
    namespace
    {
        struct Matcher
        {
            Matcher(string name, string key);
            U32 find();
    
        private:
            bool except();
            U32 matchStep1();
            U32 matchStep2();
            U32 matchStep3();
    
            bool isTheSameMatch();
    
            string match;
            U32 from;
        };
    
        Matcher::Matcher(string name, string key):
            match(name + key)
        {
            from = GetFrom();
        }
    
        U32 Matcher::find()
        {
            if (except())
                return INVALID_U32;
    
            auto result = matchStep1();
            if (result != INVALID_U32)
                return result;
    
            result = matchStep2();
            if (result != INVALID_U32)
                return result;
    
            return matchStep3();
        }
    
        bool Matcher::except()
        {
            for(auto var : List)
            {
                if(var == match && from != INVALID_U32)
                    return true;
            }
    
            return false;
        }
    
        U32 Matcher::matchStep1()
        {
            if(!isTheSameMatch())
            {
                return INVALID_U32;
            }
    
            for (auto& kv : Map)
            {
                if ( last == kv.second && match == kv.first)
                {
                    return last;
                }
            }
    
            return INVALID_U32;
        }
    
        bool Matcher::isTheSameMatch()
        {
            return match == getName() + getKey();
        }
    
        U32 Matcher::matchStep2()
        {
            auto var = Map.find(match);
            if(var != Map.end()&& (from != var->second))
            {
                return var->second;
            }
    
            return INVALID_U32;
        }
    
        U32 Matcher::matchStep3()
        {
            for(auto var: Map)
            {
                if(keyMatch(var.first, match) && from != var.second)
                {
                    return var.second;
                }
            }
    
            return INVALID_U32;
        }
    }
    
    U32 find (string match)
    {
        Matcher matcher;
        
        return matcher.find(match);
    }
    

    この例では、マッチングアルゴリズムをMatcherクラスにカプセル化し、元のロジックを抽出関数(テクニック1)とマージ条件(テクニック6)によってマッチングロジックを能力クエリー、ヒステリシス、正確なマッチング、およびファジイマッチングの4つのステップに抽象化することで、ループと条件分岐を小関数にカプセル化し、インタフェース関数(findPno)のループの複雑さを低減する.関数の職責もより単一で明確です.全体的なループの複雑さは、単一の関数の14から複数の関数の最も高い5に低下する.
    圏複雑度思弁
    1.複雑度の高いコードはメンテナンス性が悪いか
    実際のプロジェクトでは、デバッグを容易にするために、メッセージ番号に対応する名前を印刷することがよくあります.
    string getMessageName(Message msg)
    {
        switch(msg)
        {
            case MSG_1:
                return "MSG_1";
            case MSG_2:
                return "MSG_2";
            case MSG_3:
                return "MSG_3";
            case MSG_4:
                return "MSG_4";
            case MSG_5:
                return "MSG_5";
            case MSG_6:
                return "MSG_6";
            case MSG_7:
                return "MSG_7";
            case MSG_8:
                return "MSG_8";
            default:
                return "MSG_UNKNOWN"
        }
    }
    

    このコードは可読性からも保守性からも受信可能である.したがって、「高い」複雑度のために再構成を行うと(例えば、テクニック2やテクニック6)、ループ複雑度を低減しながら不要な論理複雑度をもたらす.
    もちろん、次のような場合は、輪の複雑さをさらに低減する必要があります.
  • メッセージ数が多すぎます.
  • switch...case...複数箇所繰り返します.メッセージが多すぎる場合は,メッセージを分類し,テクニック1を用いて再構築することが考えられる.複数の重複が発生した場合、同じcaseのコンテンツをテクニック6で特定のクラスに集約する方法で、マルチステート方式で使用することができる.

  • 2.複雑度が同じコードが一致しているか
    例えば、次の2つのコードフラグメントのループ複雑度は6である.
  • コードフラグメント1:
  • string getWeight(int i) {
            if (i <= 0) 
            {
                    return "no weight";
            }
            if (i < 10) 
            {
                    return "light";
            }
            if (i < 20) 
            {
                    return "medium";
            }
            if (i < 30) 
            {
                    return "heavy";
            }
            if (i < 40)
            {
                return "very heavy";
            }
            
            return "super heavy"
    }
    
  • コードフラグメント2
  • int sumOfNonPrimes(int limit) {
            bool bAdd = false;
            int sum = 0;
            for (int i = 0; i < limit; ++i) {
                    if (i <= 2) 
                        continue;
                
                    for (int j = 2; j < i; ++j) 
                    {
                        if (i % j == 0) 
                        {
                                bAdd = false;
                                break;
                        }
                        bAdd = true;
                    }
                    if (bAdd)
                        sum += i;
            }
            return sum;
    }
    

    しかし、それらのコードは、可読性においても、メンテナンス性においても、コードフラグメント1がコードフラグメント2よりも優れているべきであり、コードフラグメント2の悪い味がより濃い.したがって,ループ複雑度は,再構成のメトリック指標としてのみ決定の参考となる具体的な状況を具体的に分析する必要がある.
    参考資料
    [1]. 詳細コイル複雑度