用CppDepend掌握PRQL语法
用CppDepend掌握PRQL语法
本文档假定您熟悉 LINQ语法,并展示PRQL语法的特点。
PRQL(Code Query LINQ)是 CppDepend 工具自版本 3 起提供的功能,用于通过 LINQ 查询查询 C/C++ 代码。
入门视频
CppDepend 文档网站上有一个介绍 PRQL 语法的 1 分 50 秒短视频。
PRQL查询编辑
PRQL 查询可以在 CppDepend UI 中实时编辑(独立版或 Visual Studio 中)。

查询编译成功后立即自动执行。
请注意上面截图中的 13毫秒 位于右上方,指示查询的执行时长。
PRQL 速度很快,专为针对大型真实代码库每秒运行数百个查询而设计。
PRQL版还附带 代码补全/IntelliSense,以及 工具提示文档 鼠标悬停在查询体上时。
预定义域
PRQL 定义了一些可查询的预定义域,包括: 类型 ; 方法 ; 字段 ; 命名空间 ; 项目
这些域不仅枚举所查询代码库的所有代码元素,还枚举所有 第三方 代码库使用的代码元素(例如类型 string 以及该类型的所有方法和字段 string 被代码库使用的)。
语法如下简单:
1from m in Methods where m.NbLinesOfCode > 30 select m
PRQL 查询可以依赖一个或多个域。请注意上面查询中域词如何 方法 以不同方式高亮。
有两个常用且方便的预定义域: 应用程序 和 ThirdParty。顾名思义,这些域可用于枚举仅在应用程序项目中定义的代码元素,或仅在第三方项目中定义的代码元素(如 STL, MFC 或 Boost) 和 被应用程序代码使用。这两个域分别代表整个代码库的部分视图。
1from m in Application.Methods where m.NbLinesOfCode > 30 select m
细化这些预定义域很容易。
例如,下面的查询匹配定义的大型方法 仅 在命名空间中 ProductName.FeatureA 及其子命名空间:
1from m in Application.Namespaces.WithNameLike("ProductName.FeatureA").ChildMethods()2where m.CyclomaticComplexity > 10 select m
使用 notmycode 前缀定义代码库视图 JustMyCode
还有另一个方便的预定义域,名为 JustMyCode.
域 JustMyCode 代表 PRQL 的一项功能,用于从 PRQL 查询结果中排除生成的代码元素。
例如,以下查询只匹配不是由工具(如 UI 设计器)生成的大型方法:
1from m in JustMyCode.Methods where m.NbLinesOfCode > 30 select m
生成的代码元素集合由以 PRQL 关键字为前缀的 PRQL 查询定义 notmycode.
例如,下面的查询匹配在名称包含特定单词的源文件中定义的方法。
1notmycode from m in Methods where23 m.SourceFileDeclAvailable &&45 m.SourceDecls.First().SourceFile.FileName.ToLower().Contains("generated.cpp")67select m
PRQL查询运行器执行所有 notmycode 依赖于的查询之前的查询 JustMyCode,因此该域 JustMyCode 只定义一次。显然,如果出现以下情况,PRQL 编译器会报错 notmycode 查询依赖于 JustMyCode 域。
PRQL代码规则
通过在 PRQL 查询前添加使用两个 PRQL 关键字定义的条件,可以轻松将其转换为规则 warnif计数.
关键字 count 是一个无符号整数,等于查询匹配的代码元素数量。
例如,如果代码库的应用程序方法中匹配到大型方法,以下查询会发出警告:
1// <Name>Avoid too large methods</Name>23warnif count > 045from m in Application.Methods67where m.NbLinesOfCode > 3089select m

PRQL 代码规则可用于定义团队希望在代码库中避免的不良实践。
团队可以在 CppDepend UI(独立版或 Visual Studio 中)中看到代码规则违规警告,或者 在报告中.
团队还可以将某些规则定义为 严重规则.
查询运算符和查询表达式语法
使用 PRQL 时,查询运算符语法和查询表达式语法都允许使用。
查询运算符语法是直接调用如下方法的语法 Where() 和 Select()...
1Methods.Where(m => m.NbLinesOfCode > 30)
查询表达式语法包含如下关键字 where 和 选择...
1from m in Methods where m.NbLinesOfCode > 30 select m
通常,在一个查询中混合使用两种语法会很方便。
- 查询运算符语法便于定义子集和子域。
- 查询表达式语法便于在这些子集和子域上定义操作。
例如,下面的查询使用查询运算符语法定义静态类型中定义的方法子集,并使用查询表达式语法从该子集中筛选和投影大型方法。
1from m in Application.Types.Where(t => t.IsNested).ChildMethods()2where m.NbLinesOfCode > 30 select m
用let定义范围变量
LINQ 语法提供了使用关键字定义范围变量的功能 让 在本节中,我们想强调这种可能性,因为使用 让 关键字是编写 PRQL 查询时的常见做法。
例如,以下默认规则借助多个范围变量定义了一个自定义代码指标:
1// <Name>C.R.A.P method code metric</Name>23// Change Risk Analyzer and Predictor (i.e. CRAP) code metric45// This code metric helps in pinpointing overly complex and untested code.67// Formula: CRAP(m) = comp(m)^2 * (1 - cov(m)/100)^3 + comp(m)89warnif count > 01011from m in JustMyCode.Methods1213// Don't match too short methods1415where m.NbLinesOfCode > 101617let CC = m.CyclomaticComplexity1819let uncov = (100 - m.PercentageCoverage) / 100f2021let CRAP = (CC * CC * uncov * uncov * uncov) + CC2223where CRAP != null && CRAP > 302425orderby CRAP descending, m.NbLinesOfCode descending2627select new { m, CRAP, CC, uncoveredPercentage = uncov*100, m.NbLinesOfCode }
请注意使用许多 让 主查询循环中的子句,可能会显著降低查询执行性能。
以let开始查询
PRQL 编译器扩展了 LINQ 的用法 让 关键字,因为使用PRQL, 让 关键字可用于在 PRQL 查询开头定义变量。
例如,下面的默认 PRQL 规则首先尝试匹配 BaseClass 类型;如果找到,则执行查询。
1warnif count > 023let base = ThirdParty.Types.WithFullName("BaseClass").FirstOrDefault()45where base != null // base can be null if the code base doesn't use at all BaseClass67from t in Application.Types where89 !t.DeriveFrom(base) select t
对于其他一些 PRQL 规则,通过多个以下方式定义多个子集可能很方便 让 关键字表达式,然后再执行查询本身。
例如,下面的默认 PRQL 规则首先定义子集 uiTypes 和 dbTypes 在查询代码中使用之前。
1// <Name>UI layer shouldn't use directly DB types</Name>23warnif count > 045// UI layer is made of types in namespaces using a UI framework67let uiTypes = Application.Namespaces.UsingAny(89 Projects.WithNameIn("PresentationFramework", "MFC")1011 ).ChildTypes()1213// You can easily customize this line to define what are DB types.1415let dbTypes = ThirdParty.Assemblies.WithNameIn("Data").ChildTypes()1617 .Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow"))1819from uiType in uiTypes.UsingAny(dbTypes)2021let dbTypesUsed = dbTypes.Intersect(uiType.TypesUsed)2223select new { uiType, dbTypesUsed }
在查询中定义过程
使用 LINQ 语法,可以在查询中创建过程。如果您想从查询中的不同位置调用该过程,这将非常有用。
下面的默认规则演示了这种可能性,其中检查某个类型是否 可被视为死类型 需要从两个不同的位置调用:
1// <Name>Potentially dead Types</Name>23warnif count > 045// Filter procedure for types that shouldn't be considered as dead67let canTypeBeConsideredAsDeadProc = new Func<IType, bool>(89 t => !t.IsPublic && // Public types might be used by client applications of your assemblies.1011 t.Name != "ClassToExclude"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(2627types => 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] }
