高度なPRQL機能
高度なPRQL機能
このセクションでは、C/C++ コードを分析するための CppDepend のクエリ言語である PRQL(Code Query LINQ)の高度な機能について詳しく説明します。負債や Quality Gate のクエリからコードの依存関係や命名規則の探索まで、PRQL は深いコード分析のための強力な機能を提供します。
負債、問題、ルール、クオリティゲートのクエリ
はじめに
CppDepend v2017.1 の導入以降、PRQL はコードのクエリだけでなく、負債、問題、ルール、および次のクエリにも対応しています クオリティゲート。
この機能は、技術的負債を深く調査するのに役立ちます。次の 技術的負債ドキュメント ダッシュボードから数回クリックするだけで、負債と問題を調査するクエリを生成できることを示します。
この機能はの定義にも便利 カスタムトレンドメトリクス, Quality Gates と ルール.
たとえば、技術的負債の割合に関するしきい値を定義する Quality Gate は、次のようになります:
1// <QualityGate Name="Percentage Debt" Unit="%" />23failif value > 30%45warnif value > 20%67let timeToDev = codeBase.EffortToDevelop()89let debt = Issues.Sum(i => i.Debt)1011select 100d * debt.ToManDay() / timeToDev.ToManDay()
違反されたクリティカル ルールの数をカウントするトレンド メトリックは、次のようになります:
1// <TrendMetric Name="# Critical Rules Violated" Unit="rules"/>23from rule in Rules45where rule.IsViolated() && rule.IsCritical67select new {89 rule,1011 issues = rule.Issues(),1213 debt = rule.Debt(),1415 annualInterest = rule.AnnualInterest(),1617 maxSeverity = rule.Issues().Max(i => i.Severity)1819}
このトレンド メトリックはトレンドの追跡に役立つだけでなく、その結果を参照して詳細に調査することもできます:

ベースライン以降の差分をクエリ
ベースラインが利用可能な場合、ルールは実際のコードベーススナップショットに対して実行されることに加えて、ベースラインに対しても実行されます。その結果、CppDepend は両方の問題セットを比較できます。ベースラインに対してルールを実行して得られた問題セットと、実際のコードベーススナップショットに対してルールを実行して得られた問題セットです。
PRQL を使用して、Debt、Issues、Rules、Quality Gates の差分をクエリできます。たとえば、ベースライン以降の新しい問題をカウントするトレンドメトリクスは次のようになります。
1// <TrendMetric Name="# New Issues since Baseline" Unit="issues"/>23from issue in Issues45where issue.WasAdded()67select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }
ベースライン以降の技術的負債が 2 人日を超えることを禁止する Quality Gate は、次のようになります:
1// <QualityGate Name="New Debt since Baseline" Unit="man-days" />23failif value > 2 man-days45warnif value > 0 man-days67let debt = Issues.Sum(i => i.Debt)89let debtInBaseline = IssuesInBaseline.Sum(i => i.Debt)1011select (debt - debtInBaseline).ToManDay()
12 個の Quality Gate が既定で定義されており、それらをカスタマイズしたり新しいものを作成したりするのは簡単です。下のスクリーンショットでは、このクエリ(ダッシュボード上のワンクリックで生成)は、Quality Gates の現在のステータスだけでなく、ベースラインでの Quality Gates のステータスも示しています。差分に依存する Quality Gates はベースラインに対して実行できないため、それらには 利用不可 (N/A) 値。

同様に、Debt、Issues、Rules、Quality Gates に関連する多くのトレンドメトリクスが既定で定義されており、それらをカスタマイズしたり新しいものを作成したりするのは簡単です。

ダッシュボードには、Debt、Issues、Rules、Quality Gates のステータスを調査するクエリを生成するためのメニューがいくつか用意されています。任意の数値をクリックして、カウントされた項目を一覧表示するクエリを生成することもできます。

仕組み
特殊な型は、負債モデルを指定するために CppDepend.API によって定義されます。次を含みます 負債 ; IIssue ; IRule ; IQualityGate ; QualityGateStatus.
ただし2つのキータイプは: IIssuesSet ; IIssuesSetDiff.
- まず、CppDepend は有効化されたルールを現在のスナップショットとベースラインの両方で実行します。
- 問題、負債数、差分を計算します。
- 次に、これらの issues-set と issues-set-diff オブジェクトを設定します。
- issues-set および issues-set-diff に依存するクエリは、これらのセットが満たされて初めて実行されます。その結果、ルールはこれらのセットに依存できませんが、Quality Gate は依存できます。
次のようなクエリを書く代わりに...
1from i in context.IssuesSet.AllIssues select i
...または...
1from i in context.IssuesSetDiff.OlderIssuesSet.AllIssues select i
...PRQLでは4つのドメインが提案されます: 問題, IssuesOnBaseline, ルール と QualityGates.
これらのドメインはのショートカット context.IssuesSet.AllIssues, context.IssuesSetDiff.OlderIssuesSet.AllIssues, context.IssuesSet.AllRules と context.IssuesSet.AllQualityGates.
これらのドメインは、次の型の範囲変数と見なせます: IEnumerable
これらのドメインを使用すると、次のような簡単なクエリを記述できます…
1from i in Issues select i
...または単に:
問題
同様に、たとえば次のようなデータを取得するために issues-set や issues-set-diff を常に参照する代わりに…
1from codeElement in CodeElements23where context.IssuesSet.HasIssue(codeElement)45select new {67 codeElement,89 issues = context.IssuesSet.Issues(codeElement),1011 newIssues = context.IssuesSet.Issues(codeElement)1213 .Where(i => context.IssuesSetDiff.WasAdded(i))1415}
...型 IssuesSet と IssuesSetDiff は、PRQL コンパイラによって context.IssuesSet および context.IssuesSetDiff の呼び出しに自動的に変換される便利な拡張メソッドを提供します。
例えば上記のクエリは書き換え可能:
1from codeElement in CodeElements23where codeElement.HasIssue()45select new {67 codeElement,89 issues = codeElement.Issues(),1011 newIssues = codeElement.NewerIssues()1213}
提案されている拡張メソッドの完全なリストは次のとおりです:
- HasIssue() / HasNewIssue() / HasIssueOnBaseline()
- Debt() / NewDebt() / DebtOnBaseline()
- AnnualInterest() / NewAnnualInterest() / AnnualInterestOnBaseline()
- Issues() / NewerIssues() / IssuesOnBaseline()
- RulesViolated() / RulesViolatedOnBaseline()
- QualityGatesStatus() / QualityGatesStatusOnBaseline()
のようなプロパティを扱う場合、 AnnualInterest、型 負債 返されるはへの暗黙変換を定義 TimeSpan (負債は修正に必要な一定の工数であるため)。したがって、サブクエリは次のように記述できます:
1let annualInterest = Issues.Sum(i => i.AnnualInterest)
ここ annualInterest 型にできる 負債 または TimeSpan これは次のような部分式にも当てはまります:
1let debt = codeBase.TechnicalDebt23let annualInterest = debt.AnnualInterest
...これは書き換え可能:
1let annualInterest = codeBase.TechnicalDebt.AnnualInterest
コードオブジェクトモデルのクエリ
コードクエリを記述するたびに、コードベースモデルの型、メソッド、フィールドのセットに対するクエリを記述していることになります。そのため、コード要素は中心的なオブジェクトです。
クラスの基底クラスを列挙する方法を示す簡単なクエリを次に示します:
1from t in Application.Types where t.NameLike ("MyClass")23select new {45 t,67 baseClasses = t.BaseClasses }
メソッドによってオーバーライドされるメソッドと、メソッドをオーバーライドするメソッドを列挙する方法を示すクエリを次に示します:
1from m in Application.Methods where m.NameLike ("MyMethod")23select new {45 m,67 // Enumerates methods overridden by m89 m.OverriddensBase,1011 // Enumerates methods that overrides m directly1213 m.OverridesDirectDerived,1415 // Enumerates methods that overrides m directly or indirectly1617 m.OverridesDerived }
コードの依存関係と設計のクエリ
依存関係モデルは 一般 使用の種類(フィールド代入、メソッド呼び出しなど)に依存しないという意味で。 A と B 2つのコード要素について、と言います A 依存 B 場合、いつ B 利用できません、 A コンパイルできません。
依存関係クエリの例:
1from m in Methods where23 m.IsUsing("MyType") &&45 m.IsUsedBy("MyProjectName".AllowNoMatch())67select m
このような拡張メソッドが次から生成されるルールでどのように使用されるかに注目 依存関係グラフ または 依存マトリクス、特定の依存関係を禁止するには:

生成されたルールを以下に示します。コード ベース内の任意の依存関係を禁止または強制するように簡単に適合させることができます。

CppDepend は、コード ベースの依存関係をクエリする便利な方法も提供します(そしてしばしば 高速 同様に):
1from t in Types.UsedByAny(Types.Where(t => t.IsStatic)) select t
間接使用
CppDepend は次の単語を含むいくつかのメソッドを提供します 間接 名前に含めて、たとえば次のメソッドのような間接依存関係を扱います IsIndirectlyUsing().
例えば A 使用中 B 使用中の C, A ではない 直接 using C しかし A です 間接的に using C (深さ 2).
Print メソッドを直接または間接的に呼び出しているメソッドを列挙するクエリを次に示します MyClass.Print().
1from m in Methods23 let depth0 = m.IsIndirectlyUsing("MyClass.Print()")45select m
DepthOfIsUsing メソッドは使用の深さを返すため、さらに詳細です。
1from m in Methods23 let depth0 = m.DepthOfIsUsing("MyClass.Print()")45 where depth0 >= 0 orderby depth0 ascending67select new { m, depth0 }
返される深さは Null許容
- それは null 場合 m ターゲットメソッドを間接的に呼び出しません。
- それは 1 場合 m ターゲットメソッドを直接呼び出します。
- より大きい 1 場合 m ターゲットメソッドを間接的に呼び出します。
この間接的な使用方法は、次の生成に特に便利です コールグラフ または クラス継承グラフ.
一部のメソッドにはという単語も含まれます 間接 または単語 深さ シーケンスからコード要素への間接的な使用、さらには 任意 ターゲットシーケンスのコード要素 (サフィックス 任意).
たとえば、次のクエリは、呼び出しを行っているすべてのメソッドに一致します。 直接 または 間接的に 静的メソッド、 深さ 呼び出しの:
1let statics = Methods.Where(m => m.IsStatic).ToHashSet()23 let depthMetric = Application.Methods.DepthOfIsUsingAny(statics)45 from m in depthMetric.DefinitionDomain67 let depthValue = depthMetric[m]89 orderby depthValue ascending1011select new { m, depthValue }
コード品質とコードメトリクスのクエリ
CppDependは以上を計算 80のコードメトリクス.
1// <Name>Quick summary of methods to refactor</Name>23warnif count > 0 from m in JustMyCode.Methods where45 // Code Metrics' definitions67 m.NbLinesOfCode > 30 || // http://www.cppdepend.com/documentation/code-metrics#NbLinesOfCode89 m.CyclomaticComplexity > 20 || // https://www.cppdepend.com/documentation/code-metrics#CC1011 m.NestingDepth > 5 || // http://www.cppdepend.com/documentation/code-metrics#ILNestingDepth1213 m.NbParameters > 5 || // http://www.cppdepend.com/documentation/code-metrics#NbParameters1415 m.NbVariables > 8 || // http://www.cppdepend.com/documentation/code-metrics#NbVariables1617 m.NbOverloads > 6 // http://www.cppdepend.com/documentation/code-metrics#NbOverloads1819select new { m, m.NbLinesOfCode, m.CyclomaticComplexity,2021 m.NbParameters, m.NbVariables, m.NbOverloads }
これらのコードメトリクスの多くは、すべてのコード要素に対して必ずしも定義されているわけではないため、null 許容の数値を返すことに注意してください。たとえば、 #コード行数 サードパーティのコード要素には計算されません。
カスタムメトリクス
LINQ の柔軟性により、デフォルトのコード メトリックを組み合わせて、次のようなより高度なコード メトリックを簡単に作成できます
1// <Name>Custom metric</Name>23warnif count > 045from m in JustMyCode.Methods67// Don't match too short methods89where m.NbLinesOfCode > 101011let CC = m.CyclomaticComplexity1213let CustomMetric = (CC * CC )/1001415where Custom != null && Custom > 301617orderby Custom descending, m.NbLinesOfCode descending1819select new { m, Custom, CC, m.NbLinesOfCode }
カスタム コード メトリック オブジェクトは、メソッドからも取得できます FillIterative()。たとえば、この可能性は、デッド型を取得するルールで使用されるほか、デッド型によってのみ使用される型(および使用の深さ)を取得するためにも使用されます。
1// <Name>Potentially dead Types</Name>23warnif count > 045// Filter procedure for types that should'nt be considered as dead67let canTypeBeConsideredAsDeadProc = new Func<IType, bool>(89 t => !t.IsPublic && // Public types might be used by client applications of your projects.1011 !t.IsGeneratedByCompiler)1213// Select types unused1415let typesUnused =1617 from t in JustMyCode.Types where1819 t.NbTypesUsingMe == 0 && canTypeBeConsideredAsDeadProc(t)2021 select t2223// Dead types = types used only by unused types (recursive)2425let deadTypesMetric = typesUnused.FillIterative(2627 types => from t in codeBase.Application.Types.UsedByAny(types).Except(types)2829 where canTypeBeConsideredAsDeadProc(t) &&3031 t.TypesUsingMe.Intersect(types).Count() == t.NbTypesUsingMe3233 select t)3435from t in deadTypesMetric.DefinitionDomain3637select new { t, t.TypesUsingMe, depth = deadTypesMetric[t] }
コード差分のクエリ
CppDependにはする独自機能があります コードベースの2つの異なるスナップショットを比較 変更/追加/削除されたものを確認。
たとえば、次のクエリは、コードが変更されたメソッドを列挙します:
1from m in Application.Methods23where context.CompareContext.CodeWasChanged(m)45select m
実際には、以下の最短のクエリを記述するだけで、PRQL コンパイラがそれを上記のクエリに変換します:
1from m in Application.Methods23where m.CodeWasChanged()45select m
このようなクエリが記述されると、CppDepend UI で、変更されたメソッドの 2 つのソース ファイル バージョンを比較できます。

2 つのメソッド OlderVersion() と NewerVersion() に注目してください。名前が示すように、これらのメソッドはコード要素の古いバージョンまたは新しいバージョンを返します。 null コード要素が追加された(したがって古いバージョンがない)か、削除された(したがって新しいバージョンがない)かを示します。これらのメソッドは、たとえば、メソッドの複雑さの進化を追跡する以下のクエリを記述するのに役立ちます。
1// <Name>Methods that became more complex</Name>23from m in codeBase.OlderVersion().Methods45where m.IsPresentInBothBuilds()67let oldCC = m.CyclomaticComplexity89let newCC = m.NewerVersion().CyclomaticComplexity1011where oldCC != null && newCC > oldCC1213select new { m, oldCC, newCC }
の呼び出しに注意 IsPresentInBothBuilds 古いスナップショットと新しいスナップショットの両方に存在するメソッドのみを扱うようにして、次を防ぎます NullReferenceException クエリ実行中!
この最後のクエリが示すように、を混ぜると 差分 機能を、コード品質メトリクスや依存関係などの他の PRQL 機能と組み合わせることで、コードベースの進化を追跡する強力なコードクエリとルールにつながる可能性があります。
コード要素の命名のクエリ
PRQL は、コード要素の名前をクエリするいくつかの機能を提供します。これは次の記述に特に便利です シンプル コード命名規則 (に基づく 正規表現)...
1// <Name>Abstract base class should be suffixed with 'Base'</Name>23warnif count > 0 from t in Application.Types where45 t.IsAbstract &&67 t.IsClass &&89 t.DepthOfInheritance == 1 &&1011 ((!t.IsGeneric && !t.NameLike (@"Base$")) ||1213 ( t.IsGeneric && !t.NameLike (@"Base<")))1415select new { t, t.DepthOfInheritance }
...または スマート コード命名規則:
1// <Name>Avoid naming types and namespaces with the same identifier</Name>23// Not only this can provoke compiler resolution collision,45// but also, this makes code less maintainable because67// concepts are not concisely identified.89warnif count > 01011let hashsetShortNames = Namespaces.Where(n => n.Name.Length > 0).Select(n => n.SimpleName).ToHashSet()1213from t in JustMyCode.Types1415where hashsetShortNames.Contains(t.Name)1617select new { t, namespaces = Namespaces.Where(n => n.SimpleName == t.Name) }
コード要素の命名機能は、PRQL 構文ドキュメントの次のセクションにまとめられています 名前文字列でコード要素をマッチング.
状態の可変性のクエリ
不変性の概念はますます一般的になっています。不変性は、マルチスレッド環境での同時アクセスを扱う場合に特に便利です。
型はと見なされる 不変 コンストラクターによってインスタンスが構築された後、そのインスタンス フィールドを変更できないかどうか。1 つの 静的 フィールドはと見なされる 不変 プライベートであり、静的コンストラクターによってのみ代入されるかどうか。1 つの インスタンス フィールドはと見なされる 不変 プライベートであり、その型のコンストラクターまたは静的コンストラクターによってのみ代入されるかどうか。次のように宣言されたフィールドは 読み取り専用 は必ずイミュータブルですが、フィールドは次のように宣言されなくてもイミュータブルにできます 読み取り専用。この最後の場合、キーワード 読み取り専用 コンパイル エラーを発生させずに、フィールド宣言に追加できます。
分析時に、CppDepend は型とフィールドの変更可能性を計算します。結果は、Type.IsImmutable および Field.IsImmutable プロパティを通じて利用できます。
このようなルールを簡単に記述できます。例:
1// <Name>Structures should be immutable</Name>23warnif count > 0 from t in Application.Types where45 t.IsStructure &&67 !t.IsImmutable89let mutableFields = t.Fields.Where(f => !f.IsImmutable)1011select new { t, t.NbLinesOfCode, mutableFields }
2 つのプロパティ ChangesObjectState と ChangesTypeState を使用して、メソッドが次であることを強制または確認できます 純粋。ある 純粋 メソッドは、インスタンス フィールドや静的フィールドに代入しないメソッドです。
また、特定のフィールドへの書き込みアクセスを制御するには、拡張メソッド AssignField() を使用できます:
1from m in Methods where m.AssignField("MyClass.myfield")23select new { m, m.NbLinesOfCode }
さらに、CppDepend はフィールドに対して MethodsAssigningMe、MethodsReadingMeButNotAssigningMe、MethodsUsingMe の 3 つのプロパティを提供し、メソッドに対しては AssignField() メソッドを提供します。
ソースファイルパスのクエリ
各コード要素について、その SourceDecls にアクセスできます。ソースファイル宣言へのアクセスにより、さまざまな興味深いアプリケーションが可能になります。たとえば、次から破棄するメソッドに一致する既定のクエリ JustMyCode コード ベース ビューは、ソース ファイル名のいくつかのパターンに依存し、あらゆる状況に簡単に適合させることができます:
1// <Name>Discard generated and designer Methods from JustMyCode</Name>23// --- Make sure to make this query richer to discard generated methods from CppDepend rules results ---45notmycode67//89// First define source files paths to discard1011//1213from a in Application.Projects141516let projectSourceFilesPaths = a.SourceDecls.Select(s => s.SourceFile.FilePath)1718let sourceFilesPathsToDiscard = (1920 from filePath in projectSourceFilesPaths2122 let filePathLower= filePath.ToString().ToLower()2324 where2526 filePathLower.Contains("generated")2728 select filePath2930).ToHashSet()3132//3334// Second: discard methods in sourceFilesPathsToDiscard3536//3738from m in a.ChildMethods3940where (sourceFilesPathsToDiscard.Contains(m.SourceDecls.First().SourceFile.FilePath))4142select new { m, m.NbLinesOfCode }
新しい CppDepend プロジェクトを作成すると、ソース ファイルの構成に関するいくつかのデフォルト ルールが提案されます。
