Startseite/Dokumentation/PRQL & Code-Abfrage/Erweiterte PRQL-Funktionen

Erweiterte PRQL-Funktionen

Erweiterte PRQL-Funktionen

Dieser Abschnitt beschreibt die erweiterten Möglichkeiten von PRQL (Code Query LINQ), der Abfragesprache von CppDepend zur Analyse von C/C++-Code. Vom Abfragen von Schulden und Quality Gates bis zum Erkunden von Codeabhängigkeiten und Namenskonventionen bietet PRQL leistungsstarke Möglichkeiten für tiefe Codeanalysen.

Abfragen von Schuld, Problemen, Regeln und Quality Gates

Einführung

Seit der Einführung von CppDepend v2017.1 geht es bei PRQL nicht nur um Code-Abfragen, sondern auch um das Abfragen von Schulden, Issues, Regeln und Quality Gates.

Diese Funktion ist nützlich für die tiefgreifende Erkundung der technischen Schulden. Im Dokumentation zur technischen Schuld zeigen wir, wie ein paar Klicks im Dashboard Abfragen generieren können, um Schulden und Issues zu erkunden.

Diese Funktion ist auch nützlich, um zu definieren benutzerdefinierte Trendmetriken, Quality Gates und Regeln.

Zum Beispiel könnte ein Quality Gate, das Schwellenwerte für den Prozentsatz technischer Schulden definiert, so aussehen:

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()

Eine Trend-Metrik, die die Anzahl verletzter kritischer Regeln zählt, könnte so aussehen:

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}

Diese Trend-Metrik ist nicht nur nützlich, um den Trend zu verfolgen — ihr Ergebnis lässt sich auch zur tieferen Erkundung durchsuchen:

Trendmetrik in CppDepend

Abfragen des Diffs seit der Baseline

Wenn eine Baseline verfügbar ist, werden die Regeln zusätzlich zum aktuellen Codebasis-Snapshot auch gegen die Baseline ausgeführt. Dadurch kann CppDepend beide Problemsätze vergleichen: den Problemsatz, der durch Ausführen der Regeln auf der Baseline erhalten wurde, und den Problemsatz, der durch Ausführen der Regeln auf dem aktuellen Codebasis-Snapshot erhalten wurde.

PRQL kann dann verwendet werden, um die Diffs von Debt, Issues, Rules und Quality Gates abzufragen. Eine Trendmetrik, die beispielsweise die neuen Probleme seit der Baseline zählt, könnte so aussehen:

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 }

Ein Quality Gate, das mehr als 2 Personentage technische Schulden seit der Baseline verbietet, könnte so aussehen:

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()

Ein Dutzend Quality Gates sind standardmäßig definiert, und es ist einfach, sie anzupassen und neue zu erstellen. Im Screenshot unten zeigt diese Abfrage (generiert durch einen einzigen Klick auf dem Dashboard) nicht nur den aktuellen Status der Quality Gates, sondern auch den Status der Quality Gates auf der Baseline. Quality Gates, die auf Diffs basieren, können nicht gegen die Baseline ausgeführt werden, und deshalb haben sie ein Nicht verfügbar (N/A) Wert.

CppDepend Quality Gates

Ebenso sind viele Trendmetriken zu Debt, Issues, Rules und Quality Gates standardmäßig definiert, und es ist einfach, sie anzupassen und neue zu erstellen.

CppDepend-Trendüberwachung

Das Dashboard bietet mehrere Menüs zum Generieren von Abfragen, um den Status von Debt, Issues, Rules und Quality Gates zu untersuchen. Jede Zahl ist ebenfalls anklickbar, um eine Abfrage zu generieren, die die gezählten Elemente auflistet.

CppDepend-Dashboard

So funktioniert es

Spezialisierte Typen werden von der CppDepend.API definiert, um das Schuldenmodell zu spezifizieren, darunter Schuld ; IIssue ; IRule ; IQualityGate ; QualityGateStatus.

Die beiden Schlüsseltypen sind jedoch: IIssuesSet ; IIssuesSetDiff.

  • Zuerst führt CppDepend die aktivierten Regeln sowohl auf dem aktuellen Snapshot als auch auf der Baseline aus.
  • Es berechnet Probleme, Schuldzahlen und Diffs.
  • Dann füllt es diese issues-set- und issues-set-diff-Objekte.
  • Abfragen, die auf issues-set und issues-set-diff basieren, werden erst ausgeführt, sobald diese Mengen gefüllt sind. Folglich kann eine Regel nicht auf diesen Mengen basieren, aber ein Quality Gate schon.

Anstatt eine Abfrage zu schreiben wie...

1from i in context.IssuesSet.AllIssues select i

...oder wie...

1from i in context.IssuesSetDiff.OlderIssuesSet.AllIssues select i

...4 Domänen werden von PRQL vorgeschlagen: Probleme, IssuesOnBaseline, Regeln und QualityGates.

Diese Domänen sind Abkürzungen für context.IssuesSet.AllIssues, context.IssuesSetDiff.OlderIssuesSet.AllIssues, context.IssuesSet.AllRules und context.IssuesSet.AllQualityGates.

Diese Domänen können als Bereichsvariablen vom Typ betrachtet werden: IEnumerable ; IEnumerable ; IEnumerable.

Mit diesen Domänen lassen sich dann einfache Abfragen schreiben wie...

1from i in Issues select i

...oder auch nur:

Probleme

Auf die gleiche Weise, statt ständig auf issues-set und issues-set-diff zu verweisen, um Daten zu erhalten wie zum Beispiel...

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}

...die Typen IssuesSet und IssuesSetDiff bietet praktische Erweiterungsmethoden, die vom PRQL-Compiler automatisch in Aufrufe von context.IssuesSet und context.IssuesSetDiff übersetzt werden.

Die obige Abfrage kann zum Beispiel umgeschrieben werden:

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}

Die vollständige Liste der vorgeschlagenen Erweiterungsmethoden:

  • HasIssue() / HasNewIssue() / HasIssueOnBaseline()
  • Debt() / NewDebt() / DebtOnBaseline()
  • AnnualInterest() / NewAnnualInterest() / AnnualInterestOnBaseline()
  • Issues() / NewerIssues() / IssuesOnBaseline()
  • RulesViolated() / RulesViolatedOnBaseline()
  • QualityGatesStatus() / QualityGatesStatusOnBaseline()

Beachten Sie, dass beim Arbeiten mit einer Eigenschaft wie AnnualInterest, der Typ Schuld zurückgegebene definiert eine implizite Konvertierung in TimeSpan (denn eine Schuld ist eine bestimmte Menge an Personenzeit zur Behebung). Daher kann die Unterabfrage so geschrieben werden:

1let annualInterest = Issues.Sum(i => i.AnnualInterest)

Hier annualInterest kann vom Typ sein Schuld oder TimeSpan Dies gilt auch für einen Teilausdruck wie:

1let debt = codeBase.TechnicalDebt
2
3let annualInterest = debt.AnnualInterest

...was umgeschrieben werden kann:

1let annualInterest = codeBase.TechnicalDebt.AnnualInterest

Abfragen des Code-Objektmodells

Jedes Mal, wenn Sie eine Codeabfrage schreiben, schreiben Sie eine Abfrage gegen die Menge der Typen, Methoden und Felder Ihres Codebasis-Modells. Daher sind die Codeelemente zentrale Objekte.

Hier ist eine einfache Abfrage, die zeigt, wie man die Basisklassen einer Klasse aufzählt:

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

Und hier ist eine Abfrage, die zeigt, wie man Methoden aufzählt, die von einer Methode überschrieben werden, und Methoden, die eine Methode überschreiben:

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 }

Nach oben

Abfragen der Codeabhängigkeiten und des Designs

Das Abhängigkeitsmodell ist allgemein in dem Sinne, dass es nicht von der Art der Nutzung abhängt (Feldzuweisung, Methodenaufruf...). Ein und B sind zwei Codeelemente, sagen wir, dass Ein hängt ab von B wenn, falls B ist nicht verfügbar, Ein kann nicht kompiliert werden.

Hier ist ein Beispiel für eine Abhängigkeitsabfrage:

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

Beachten Sie, wie solche Erweiterungsmethoden von Regeln verwendet werden, die aus Abhängigkeitsgraph oder Abhängigkeitsmatrix, um eine bestimmte Abhängigkeit zu verbieten:

Abhängigkeit in CppDepend verbieten

Die generierte Regel wird unten gezeigt. Sie könnte leicht angepasst werden, um beliebige Abhängigkeiten in einer Codebasis zu verbieten oder zu erzwingen.

PRQL in CppDepend

CppDepend bietet außerdem eine komfortable Möglichkeit, Abhängigkeiten einer Codebasis abzufragen (und oft ein schneller genauso):

1from t in Types.UsedByAny(Types.Where(t => t.IsStatic)) select t

Indirekte Verwendung

CppDepend stellt einige Methoden bereit, die das Wort Indirekt in ihren Namen, um mit indirekten Abhängigkeiten umzugehen, wie zum Beispiel die Methode IsIndirectlyUsing().

Wenn beispielsweise Ein verwendet B das verwendet C, Ein ist nicht direkt using C aber Ein ist indirekt using C (mit einer Tiefe von 2).

Hier ist eine Abfrage, die Methoden aufzählt, die die Methode Print direkt oder indirekt aufrufen MyClass.Print().

1from m in Methods
2
3 let depth0 = m.IsIndirectlyUsing("MyClass.Print()")
4
5select m

Die Methode DepthOfIsUsing geht weiter, da sie die Nutzungstiefe zurückgibt.

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 }

Die zurückgegebene Tiefe ist eine Nullable Wert.

  • Es ist null wenn m ruft die Zielmethode nicht indirekt auf.
  • Es ist 1 wenn m ruft direkt die Zielmethode auf.
  • Es ist größer als 1 wenn m ruft indirekt die Zielmethode auf.

Diese indirekte Nutzungsmöglichkeit ist besonders nützlich, um Aufrufgraphen oder Klassenvererbungsgraphen.

Einige der Methoden enthalten auch das Wort Indirekt oder das Wort Tiefe um mit indirekter Nutzung von einer Sequenz zu einem Code-Element oder sogar beliebig Codeelemente einer Zielsequenz (Suffix Beliebig).

Zum Beispiel matcht die folgende Abfrage alle Methoden, die aufrufen, direkt oder indirekt die statischen Methoden, mit dem Tiefe des Aufrufs:

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 }

Nach oben

Abfragen der Codequalität und Codemetriken

CppDepend berechnet mehr als 80 Codemetriken.

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 }

Beachten Sie, dass viele dieser Codemetriken einen nullbaren numerischen Wert zurückgeben, da sie nicht notwendigerweise für alle Codeelemente definiert sind. Beispielsweise die #Codezeilen wird für Drittanbieter-Codeelemente nicht berechnet.

Benutzerdefinierte Metriken

Dank der LINQ-Flexibilität lassen sich die Standard-Code-Metriken leicht zu ausgefeilteren Metriken kombinieren, wie zum Beispiel:

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 }

Ein benutzerdefiniertes Code-Metrik-Objekt kann auch über die Methode FillIterative(). Diese Möglichkeit wird beispielsweise in der Regel verwendet, um tote Typen zu ermitteln, aber auch, um Typen zu ermitteln, die nur von toten Typen verwendet werden (sowie die Nutzungstiefe):

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] }

Nach oben

Abfragen des Code-Diffs

CppDepend bietet die einzigartige Funktion, zwei verschiedene Schnappschüsse einer Codebasis vergleichen um zu sehen, was geändert/hinzugefügt/entfernt wurde.

Zum Beispiel zählt die folgende Abfrage Methoden auf, bei denen Code geändert wurde:

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

Tatsächlich können Sie einfach die kürzeste Abfrage unten schreiben, und der PRQL-Compiler wandelt sie in die obige Abfrage um:

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

Sobald eine solche Abfrage geschrieben ist, bietet die CppDepend-UI die Möglichkeit, die beiden Quelldatei-Versionen der geänderten Methode zu vergleichen.

Methoden-Diff in CppDepend

Beachten Sie die beiden Methoden OlderVersion() und NewerVersion(). Wie ihre Namen vermuten lassen, geben diese Methoden die ältere oder neuere Version eines Codeelements zurück, oder null ob das Codeelement hinzugefügt (daher keine ältere Version) oder entfernt (daher keine neuere Version) wurde. Diese Methoden können beispielsweise nützlich sein, um die untenstehende Abfrage zu schreiben, die die Entwicklung der Methodenkomplexität verfolgt:

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 }

Beachten Sie den Aufruf von IsPresentInBothBuilds um sicherzustellen, dass wir nur Methoden behandeln, die sowohl im älteren als auch im neueren Codebasis-Snapshot existieren, um NullReferenceException während die Abfrage läuft!

Wie diese letzte Abfrage zeigt, ermöglicht das Mischen von Diff Funktion mit anderen PRQL-Funktionen wie Codequalitätsmetriken oder Abhängigkeiten zu kombinieren, kann zu leistungsstarken Codeabfragen und Regeln führen, um die Entwicklung einer Codebasis zu verfolgen.

Nach oben

Abfragen der Benennung von Codeelementen

PRQL bietet mehrere Möglichkeiten, die Namen von Code-Elementen abzufragen. Dies ist besonders nützlich, um einfach Code-Benennungskonventionen (basierend auf reguläre Ausdrücke)...

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 }

...oder intelligent Code-Benennungskonventionen:

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) }

Die Benennungsfunktion für Code-Elemente wird im PRQL-Syntaxdokument zusammengefasst, im Abschnitt Abgleichen von Codeelementen nach Namenszeichenfolge.

Nach oben

Abfragen der Zustandsmutabilität

Das Konzept der Unveränderlichkeit wird immer beliebter. Unveränderlichkeit ist besonders nützlich beim Umgang mit konkurrierenden Zugriffen in einer Multithreading-Umgebung.

Ein Typ gilt als unveränderlich ob seine Instanzfelder nicht mehr geändert werden können, sobald eine Instanz durch einen Konstruktor erstellt wurde. Ein statisch Feld gilt als unveränderlich ob es privat ist und nur vom statischen Konstruktor zugewiesen wird. Ein Instanz Feld gilt als unveränderlich ob es privat ist und nur vom Konstruktor bzw. den Konstruktoren seines Typs oder dessen statischem Konstruktor zugewiesen wird. Beachten Sie, dass ein als readonly ist notwendigerweise unveränderlich, aber ein Feld kann unveränderlich sein, ohne als readonly. In diesem letzten Fall kann das Schlüsselwort readonly kann der Felddeklaration hinzugefügt werden, ohne Kompilierfehler zu verursachen.

Zur Analysezeit berechnet CppDepend die Veränderlichkeit von Typen und Feldern. Das Ergebnis ist über die Eigenschaften Type.IsImmutable und Field.IsImmutable verfügbar.

Es ist dann einfach, eine solche Regel zu schreiben, zum Beispiel:

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 }

Die beiden Eigenschaften ChangesObjectState und ChangesTypeState können verwendet werden, um zu erzwingen oder zu prüfen, dass eine Methode rein. Ein rein Methode ist eine Methode, die keine Instanz- oder statischen Felder zuweist.

Um außerdem den Schreibzugriff auf ein bestimmtes Feld zu kontrollieren, können Sie die Erweiterungsmethode AssignField() verwenden:

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

Zusätzlich bietet CppDepend für Felder die 3 Eigenschaften MethodsAssigningMe, MethodsReadingMeButNotAssigningMe und MethodsUsingMe, und für Methoden die Methode AssignField().

Nach oben

Abfragen von Quelldateipfaden

Für jedes Codeelement können Sie auf dessen SourceDecls zugreifen. Der Zugriff auf Quelldatei-Deklarationen eröffnet eine Reihe interessanter Anwendungen. Beispielsweise die Standardabfrage, die Methoden zum Aussortieren aus dem JustMyCode Codebasis-Ansicht, stützt sich auf einige Muster im Quelldateinamen und kann leicht an jede Situation angepasst werden:

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 }

Beim Erstellen eines neuen CppDepend-Projekts werden mehrere Standardregeln zur Organisation der Quelldateien vorgeschlagen.

Testen Sie CppDepend noch heute

Starten Sie Ihre 14-tägige kostenlose Testversion mit vollem Zugriff auf alle Dokumentationsfunktionen. Keine Kreditkarte erforderlich.