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="%" />23failif value > 30%45warnif value > 20%67let timeToDev = codeBase.EffortToDevelop()89let debt = Issues.Sum(i => i.Debt)1011select 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"/>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}
Diese Trend-Metrik ist nicht nur nützlich, um den Trend zu verfolgen — ihr Ergebnis lässt sich auch zur tieferen Erkundung durchsuchen:

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"/>23from issue in Issues45where issue.WasAdded()67select 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" />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()
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.

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

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.

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
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 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}
...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 CodeElements23where codeElement.HasIssue()45select new {67 codeElement,89 issues = codeElement.Issues(),1011 newIssues = codeElement.NewerIssues()1213}
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.TechnicalDebt23let 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")23select new {45 t,67 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")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 }
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 where23 m.IsUsing("MyType") &&45 m.IsUsedBy("MyProjectName".AllowNoMatch())67select m
Beachten Sie, wie solche Erweiterungsmethoden von Regeln verwendet werden, die aus Abhängigkeitsgraph oder Abhängigkeitsmatrix, um eine bestimmte Abhängigkeit zu 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.

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 Methods23 let depth0 = m.IsIndirectlyUsing("MyClass.Print()")45select m
Die Methode DepthOfIsUsing geht weiter, da sie die Nutzungstiefe zurückgibt.
1from m in Methods23 let depth0 = m.DepthOfIsUsing("MyClass.Print()")45 where depth0 >= 0 orderby depth0 ascending67select new { m, depth0 }
Die zurückgegebene Tiefe ist eine Nullable
- 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()23 let depthMetric = Application.Methods.DepthOfIsUsingAny(statics)45 from m in depthMetric.DefinitionDomain67 let depthValue = depthMetric[m]89 orderby depthValue ascending1011select new { m, depthValue }
Abfragen der Codequalität und Codemetriken
CppDepend berechnet mehr als 80 Codemetriken.
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 }
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>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 }
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>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] }
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.Methods23where context.CompareContext.CodeWasChanged(m)45select 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.Methods23where m.CodeWasChanged()45select m
Sobald eine solche Abfrage geschrieben ist, bietet die CppDepend-UI die Möglichkeit, die beiden Quelldatei-Versionen der geänderten Methode zu vergleichen.

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>23from m in codeBase.OlderVersion().Methods45where m.IsPresentInBothBuilds()67let oldCC = m.CyclomaticComplexity89let newCC = m.NewerVersion().CyclomaticComplexity1011where oldCC != null && newCC > oldCC1213select 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.
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>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 }
...oder intelligent Code-Benennungskonventionen:
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) }
Die Benennungsfunktion für Code-Elemente wird im PRQL-Syntaxdokument zusammengefasst, im Abschnitt Abgleichen von Codeelementen nach Namenszeichenfolge.
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>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 }
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("MyClass.myfield")23select 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().
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>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 }
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.
