高度な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="%" />
2
3failif value > 30%
4
5warnif value > 20%
6
7let timeToDev = codeBase.EffortToDevelop()
8
9let debt = Issues.Sum(i => i.Debt)
10
11select 100d * debt.ToManDay() / timeToDev.ToManDay()

違反されたクリティカル ルールの数をカウントするトレンド メトリックは、次のようになります:

1// <TrendMetric Name="# Critical Rules Violated" Unit="rules"/>
2
3from rule in Rules
4
5where rule.IsViolated() && rule.IsCritical
6
7select new {
8
9 rule,
10
11 issues = rule.Issues(),
12
13 debt = rule.Debt(),
14
15 annualInterest = rule.AnnualInterest(),
16
17 maxSeverity = rule.Issues().Max(i => i.Severity)
18
19}

このトレンド メトリックはトレンドの追跡に役立つだけでなく、その結果を参照して詳細に調査することもできます:

CppDependのトレンドメトリクス

ベースライン以降の差分をクエリ

ベースラインが利用可能な場合、ルールは実際のコードベーススナップショットに対して実行されることに加えて、ベースラインに対しても実行されます。その結果、CppDepend は両方の問題セットを比較できます。ベースラインに対してルールを実行して得られた問題セットと、実際のコードベーススナップショットに対してルールを実行して得られた問題セットです。

PRQL を使用して、Debt、Issues、Rules、Quality Gates の差分をクエリできます。たとえば、ベースライン以降の新しい問題をカウントするトレンドメトリクスは次のようになります。

1// <TrendMetric Name="# New Issues since Baseline" Unit="issues"/>
2
3from issue in Issues
4
5where issue.WasAdded()
6
7select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }

ベースライン以降の技術的負債が 2 人日を超えることを禁止する Quality Gate は、次のようになります:

1// <QualityGate Name="New Debt since Baseline" Unit="man-days" />
2
3failif value > 2 man-days
4
5warnif value > 0 man-days
6
7let debt = Issues.Sum(i => i.Debt)
8
9let debtInBaseline = IssuesInBaseline.Sum(i => i.Debt)
10
11select (debt - debtInBaseline).ToManDay()

12 個の Quality Gate が既定で定義されており、それらをカスタマイズしたり新しいものを作成したりするのは簡単です。下のスクリーンショットでは、このクエリ(ダッシュボード上のワンクリックで生成)は、Quality Gates の現在のステータスだけでなく、ベースラインでの Quality Gates のステータスも示しています。差分に依存する Quality Gates はベースラインに対して実行できないため、それらには 利用不可 (N/A) 値。

CppDependクオリティゲート

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

CppDependトレンド監視

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

CppDependダッシュボード

仕組み

特殊な型は、負債モデルを指定するために 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.AllRulescontext.IssuesSet.AllQualityGates.

これらのドメインは、次の型の範囲変数と見なせます: IEnumerable ; IEnumerable ; IEnumerable.

これらのドメインを使用すると、次のような簡単なクエリを記述できます…

1from i in Issues select i

...または単に:

問題

同様に、たとえば次のようなデータを取得するために issues-set や issues-set-diff を常に参照する代わりに…

1from codeElement in CodeElements
2
3where context.IssuesSet.HasIssue(codeElement)
4
5select new {
6
7 codeElement,
8
9 issues = context.IssuesSet.Issues(codeElement),
10
11 newIssues = context.IssuesSet.Issues(codeElement)
12
13 .Where(i => context.IssuesSetDiff.WasAdded(i))
14
15}

...型 IssuesSetIssuesSetDiff は、PRQL コンパイラによって context.IssuesSet および context.IssuesSetDiff の呼び出しに自動的に変換される便利な拡張メソッドを提供します。

例えば上記のクエリは書き換え可能:

1from codeElement in CodeElements
2
3where codeElement.HasIssue()
4
5select new {
6
7 codeElement,
8
9 issues = codeElement.Issues(),
10
11 newIssues = codeElement.NewerIssues()
12
13}

提案されている拡張メソッドの完全なリストは次のとおりです:

  • 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.TechnicalDebt
2
3let annualInterest = debt.AnnualInterest

...これは書き換え可能:

1let annualInterest = codeBase.TechnicalDebt.AnnualInterest

コードオブジェクトモデルのクエリ

コードクエリを記述するたびに、コードベースモデルの型、メソッド、フィールドのセットに対するクエリを記述していることになります。そのため、コード要素は中心的なオブジェクトです。

クラスの基底クラスを列挙する方法を示す簡単なクエリを次に示します:

1from t in Application.Types where t.NameLike ("MyClass")
2
3select new {
4
5 t,
6
7 baseClasses = t.BaseClasses }

メソッドによってオーバーライドされるメソッドと、メソッドをオーバーライドするメソッドを列挙する方法を示すクエリを次に示します:

1from m in Application.Methods where m.NameLike ("MyMethod")
2
3select new {
4
5 m,
6
7 // Enumerates methods overridden by m
8
9 m.OverriddensBase,
10
11 // Enumerates methods that overrides m directly
12
13 m.OverridesDirectDerived,
14
15 // Enumerates methods that overrides m directly or indirectly
16
17 m.OverridesDerived }

トップへ

コードの依存関係と設計のクエリ

依存関係モデルは 一般 使用の種類(フィールド代入、メソッド呼び出しなど)に依存しないという意味で。 AB 2つのコード要素について、と言います A 依存 B 場合、いつ B 利用できません、 A コンパイルできません。

依存関係クエリの例:

1from m in Methods where
2
3 m.IsUsing("MyType") &&
4
5 m.IsUsedBy("MyProjectName".AllowNoMatch())
6
7select m

このような拡張メソッドが次から生成されるルールでどのように使用されるかに注目 依存関係グラフ または 依存マトリクス、特定の依存関係を禁止するには:

CppDependで依存関係を禁止

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

CppDependのPRQL

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 Methods
2
3 let depth0 = m.IsIndirectlyUsing("MyClass.Print()")
4
5select m

DepthOfIsUsing メソッドは使用の深さを返すため、さらに詳細です。

1from m in Methods
2
3 let depth0 = m.DepthOfIsUsing("MyClass.Print()")
4
5 where depth0 >= 0 orderby depth0 ascending
6
7select new { m, depth0 }

返される深さは Null許容 値。

  • それは null 場合 m ターゲットメソッドを間接的に呼び出しません。
  • それは 1 場合 m ターゲットメソッドを直接呼び出します。
  • より大きい 1 場合 m ターゲットメソッドを間接的に呼び出します。

この間接的な使用方法は、次の生成に特に便利です コールグラフ または クラス継承グラフ.

一部のメソッドにはという単語も含まれます 間接 または単語 深さ シーケンスからコード要素への間接的な使用、さらには 任意 ターゲットシーケンスのコード要素 (サフィックス 任意).

たとえば、次のクエリは、呼び出しを行っているすべてのメソッドに一致します。 直接 または 間接的に 静的メソッド、 深さ 呼び出しの:

1let statics = Methods.Where(m => m.IsStatic).ToHashSet()
2
3 let depthMetric = Application.Methods.DepthOfIsUsingAny(statics)
4
5 from m in depthMetric.DefinitionDomain
6
7 let depthValue = depthMetric[m]
8
9 orderby depthValue ascending
10
11select new { m, depthValue }

トップへ

コード品質とコードメトリクスのクエリ

CppDependは以上を計算 80のコードメトリクス.

1// <Name>Quick summary of methods to refactor</Name>
2
3warnif count > 0 from m in JustMyCode.Methods where
4
5 // Code Metrics' definitions
6
7 m.NbLinesOfCode > 30 || // http://www.cppdepend.com/documentation/code-metrics#NbLinesOfCode
8
9 m.CyclomaticComplexity > 20 || // https://www.cppdepend.com/documentation/code-metrics#CC
10
11 m.NestingDepth > 5 || // http://www.cppdepend.com/documentation/code-metrics#ILNestingDepth
12
13 m.NbParameters > 5 || // http://www.cppdepend.com/documentation/code-metrics#NbParameters
14
15 m.NbVariables > 8 || // http://www.cppdepend.com/documentation/code-metrics#NbVariables
16
17 m.NbOverloads > 6 // http://www.cppdepend.com/documentation/code-metrics#NbOverloads
18
19select new { m, m.NbLinesOfCode, m.CyclomaticComplexity,
20
21 m.NbParameters, m.NbVariables, m.NbOverloads }

これらのコードメトリクスの多くは、すべてのコード要素に対して必ずしも定義されているわけではないため、null 許容の数値を返すことに注意してください。たとえば、 #コード行数 サードパーティのコード要素には計算されません。

カスタムメトリクス

LINQ の柔軟性により、デフォルトのコード メトリックを組み合わせて、次のようなより高度なコード メトリックを簡単に作成できます

1// <Name>Custom metric</Name>
2
3warnif count > 0
4
5from m in JustMyCode.Methods
6
7// Don't match too short methods
8
9where m.NbLinesOfCode > 10
10
11let CC = m.CyclomaticComplexity
12
13let CustomMetric = (CC * CC )/100
14
15where Custom != null && Custom > 30
16
17orderby Custom descending, m.NbLinesOfCode descending
18
19select new { m, Custom, CC, m.NbLinesOfCode }

カスタム コード メトリック オブジェクトは、メソッドからも取得できます FillIterative()。たとえば、この可能性は、デッド型を取得するルールで使用されるほか、デッド型によってのみ使用される型(および使用の深さ)を取得するためにも使用されます。

1// <Name>Potentially dead Types</Name>
2
3warnif count > 0
4
5// Filter procedure for types that should'nt be considered as dead
6
7let canTypeBeConsideredAsDeadProc = new Func<IType, bool>(
8
9 t => !t.IsPublic && // Public types might be used by client applications of your projects.
10
11 !t.IsGeneratedByCompiler)
12
13// Select types unused
14
15let typesUnused =
16
17 from t in JustMyCode.Types where
18
19 t.NbTypesUsingMe == 0 && canTypeBeConsideredAsDeadProc(t)
20
21 select t
22
23// Dead types = types used only by unused types (recursive)
24
25let deadTypesMetric = typesUnused.FillIterative(
26
27 types => from t in codeBase.Application.Types.UsedByAny(types).Except(types)
28
29 where canTypeBeConsideredAsDeadProc(t) &&
30
31 t.TypesUsingMe.Intersect(types).Count() == t.NbTypesUsingMe
32
33 select t)
34
35from t in deadTypesMetric.DefinitionDomain
36
37select new { t, t.TypesUsingMe, depth = deadTypesMetric[t] }

トップへ

コード差分のクエリ

CppDependにはする独自機能があります コードベースの2つの異なるスナップショットを比較 変更/追加/削除されたものを確認。

たとえば、次のクエリは、コードが変更されたメソッドを列挙します:

1from m in Application.Methods
2
3where context.CompareContext.CodeWasChanged(m)
4
5select m

実際には、以下の最短のクエリを記述するだけで、PRQL コンパイラがそれを上記のクエリに変換します:

1from m in Application.Methods
2
3where m.CodeWasChanged()
4
5select m

このようなクエリが記述されると、CppDepend UI で、変更されたメソッドの 2 つのソース ファイル バージョンを比較できます。

CppDependのメソッド差分

2 つのメソッド OlderVersion() と NewerVersion() に注目してください。名前が示すように、これらのメソッドはコード要素の古いバージョンまたは新しいバージョンを返します。 null コード要素が追加された(したがって古いバージョンがない)か、削除された(したがって新しいバージョンがない)かを示します。これらのメソッドは、たとえば、メソッドの複雑さの進化を追跡する以下のクエリを記述するのに役立ちます。

1// <Name>Methods that became more complex</Name>
2
3from m in codeBase.OlderVersion().Methods
4
5where m.IsPresentInBothBuilds()
6
7let oldCC = m.CyclomaticComplexity
8
9let newCC = m.NewerVersion().CyclomaticComplexity
10
11where oldCC != null && newCC > oldCC
12
13select new { m, oldCC, newCC }

の呼び出しに注意 IsPresentInBothBuilds 古いスナップショットと新しいスナップショットの両方に存在するメソッドのみを扱うようにして、次を防ぎます NullReferenceException クエリ実行中!

この最後のクエリが示すように、を混ぜると 差分 機能を、コード品質メトリクスや依存関係などの他の PRQL 機能と組み合わせることで、コードベースの進化を追跡する強力なコードクエリとルールにつながる可能性があります。

トップへ

コード要素の命名のクエリ

PRQL は、コード要素の名前をクエリするいくつかの機能を提供します。これは次の記述に特に便利です シンプル コード命名規則 (に基づく 正規表現)...

1// <Name>Abstract base class should be suffixed with 'Base'</Name>
2
3warnif count > 0 from t in Application.Types where
4
5 t.IsAbstract &&
6
7 t.IsClass &&
8
9 t.DepthOfInheritance == 1 &&
10
11 ((!t.IsGeneric && !t.NameLike (@&quot;Base$&quot;)) ||
12
13 ( t.IsGeneric && !t.NameLike (@&quot;Base&lt;&quot;)))
14
15select new { t, t.DepthOfInheritance }

...または スマート コード命名規則:

1// <Name>Avoid naming types and namespaces with the same identifier</Name>
2
3// Not only this can provoke compiler resolution collision,
4
5// but also, this makes code less maintainable because
6
7// concepts are not concisely identified.
8
9warnif count > 0
10
11let hashsetShortNames = Namespaces.Where(n => n.Name.Length > 0).Select(n => n.SimpleName).ToHashSet()
12
13from t in JustMyCode.Types
14
15where hashsetShortNames.Contains(t.Name)
16
17select 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>
2
3warnif count > 0 from t in Application.Types where
4
5 t.IsStructure &&
6
7 !t.IsImmutable
8
9let mutableFields = t.Fields.Where(f => !f.IsImmutable)
10
11select new { t, t.NbLinesOfCode, mutableFields }

2 つのプロパティ ChangesObjectState と ChangesTypeState を使用して、メソッドが次であることを強制または確認できます 純粋。ある 純粋 メソッドは、インスタンス フィールドや静的フィールドに代入しないメソッドです。

また、特定のフィールドへの書き込みアクセスを制御するには、拡張メソッド AssignField() を使用できます:

1from m in Methods where m.AssignField(&quot;MyClass.myfield&quot;)
2
3select new { m, m.NbLinesOfCode }

さらに、CppDepend はフィールドに対して MethodsAssigningMe、MethodsReadingMeButNotAssigningMe、MethodsUsingMe の 3 つのプロパティを提供し、メソッドに対しては AssignField() メソッドを提供します。

トップへ

ソースファイルパスのクエリ

各コード要素について、その SourceDecls にアクセスできます。ソースファイル宣言へのアクセスにより、さまざまな興味深いアプリケーションが可能になります。たとえば、次から破棄するメソッドに一致する既定のクエリ JustMyCode コード ベース ビューは、ソース ファイル名のいくつかのパターンに依存し、あらゆる状況に簡単に適合させることができます:

1// <Name>Discard generated and designer Methods from JustMyCode</Name>
2
3// --- Make sure to make this query richer to discard generated methods from CppDepend rules results ---
4
5notmycode
6
7//
8
9// First define source files paths to discard
10
11//
12
13from a in Application.Projects
14
15
16let projectSourceFilesPaths = a.SourceDecls.Select(s => s.SourceFile.FilePath)
17
18let sourceFilesPathsToDiscard = (
19
20 from filePath in projectSourceFilesPaths
21
22 let filePathLower= filePath.ToString().ToLower()
23
24 where
25
26 filePathLower.Contains(&quot;generated&quot;)
27
28 select filePath
29
30).ToHashSet()
31
32//
33
34// Second: discard methods in sourceFilesPathsToDiscard
35
36//
37
38from m in a.ChildMethods
39
40where (sourceFilesPathsToDiscard.Contains(m.SourceDecls.First().SourceFile.FilePath))
41
42select new { m, m.NbLinesOfCode }

新しい CppDepend プロジェクトを作成すると、ソース ファイルの構成に関するいくつかのデフォルト ルールが提案されます。

今すぐCppDependを試す

ドキュメントの全機能にアクセスできる14日間無料トライアル。クレジットカード不要。