Scala代码静态检查利器

大数据快速发展,催生以Spark等数据处理组件和技术同时,也让scala成为大数据领域炙手可热明星语言。与之热度形成反差的是代码检查和分析工具远远落后于Java, C/C++等老牌语言。

基于不同原理设计的代码检查工具有多种。Java代码检查工具有Findbugs、PMD、Checkstyle、Sonar。虽然Scala运行在JVM上,但是以上工具不能很好的兼容Scala检查。Scalastyle 是专门针对Scala代码而开发静态检查工具。本文介绍scalastyle使用,静态检查功能等。

本文内容包括一下几部分,略长,使用者可直接到第二部分:

  • 静态检查简介
  • scalastyle配置和使用
  • 源码分析

静态检查简介


在静态代码检查领域,有各种明星检查工具。代码检查的原理,参见[1],概括而言原理是:

  • 检查Java代码,缺陷模式匹配
  • 检查编译函数字节码

前者适用与Java代码分析,不适用于Scala源代码。缺点是针对代码的静态检查,运行时绑定和资源的操作无法进行检查。后者则为对编译完成的字节码进行检查。这种模式为基于JVM虚拟机运行Scala语言,提供了一种检查工具,比如Findbugs是对字节码的检查。两种检查模式优缺点并存。本文不讨论优缺点,仅关注Scalastyle使用和运行。

当前Scalastyle 1.0版本包含 69个检查项。检查规则文件可参考:scalastyle config
举例来讲静态检查如下:

//BAD
class TestClass{ // 类名后需要加空格
  def testFunc(in: Set[String]) = { // 非private接口需要 提供返回类型
    println("testFunc out")
  }
}
//GOOD
class TestClass {
  def testFunc(in: Set[String]):Unit = {
    println("testFunc out")
  }
}

可以看到针对Scala的源代码检查有助于代码规范化和可读性。对代码进行了强制性规范,保证开发任务代码格式上做到统一。

配置和使用


scalastyle config链接中配置项目很多,但非常易用,举例说明下:
<check level="warning" class="org.scalastyle.file.WhitespaceEndOfLineChecker" enabled="true"> <parameters> <parameter name="ignoreWhitespaceLines"><![CDATA[false]]></parameter> </parameters> </check> <check level="warning" class="org.scalastyle.file.FileLineLengthChecker" enabled="true">

以上设置激活了检查行结束是否有空格、检查文件长度 两项检查。如果想关闭,设置enable为false即可。根据项目需要设置检查规则。

以IDEA 开发工具为例说明:
IDEA环境本身已经支持scalastyle,不需要下载插件,仅需要配置。

若代码已经生成了IDEA项目,会存在.idea目录。从官方下载配置文件,将该配置文件复制到.idea目录下去或者放到工程项目的project目录。也可以参考:

Scalastyle examines your Scala code and indicates potential problems
with it. Place scalastyle_config.xml in the <root>/.idea or
<root>/project directory. Full documentation is available on the
Scalastyle website.

如下操作打开IDEA对scalastyle支持的开关:
selecting Settings->Editor->Inspections, then searching for Scala style inspections. 确保“scala style inspection”被勾中。

到这里完成了配置。Scalastyle会在我们编写代码时,准实时提示。我们根据提示把代码规范化。

源码分析


Scalastyle静态检查运行过程: 配置加载 -> 执行类型校验 -> 结果输出.

  • 1.配置加载

上文配置xml列举scalastyle格式检查类型,根据配置,IDEA加载xml文件检查项到内存中。实现加载类如下图ScalastyleConfiguration同时也实现了配置结构化读取、以及配置创建。

ScalastyleConfiguration类图

下面以Scalastyle配置文件,说明加载类运行过程。

XML配置示例

内容保存在case class ScalastyleConfiguration中,结构如下:

case class ScalastyleConfiguration(name: String, commentFilter: Boolean, checks: List[ConfigurationChecker])

样例类各字段和XML关系显而易见。而关键 checkS保存类型检查的入口(图中class字符串),通过Java的类加载机制加载运行。
这也就意味着所有检查类别通过单例类ScalastyleConfiguration获取。在对代码进行检查时,只需调用ScalastyleConfiguration就可以完成。在这里我们看到: 接口设计在满足功能的前提,易读最优先保证。

  • 2.执行类型校验

Scalastyle类型检查是由工具类调用各个静态检查项目。工具类作为通用类,它是所有调用所有检查项的入口。而各静态检查项实现检查逻辑不管调用流程,调用和检查逻辑分离。

工具类

我们以ClassTypeParameterChecker为例来说明调用和检查过程。UML中,CheckUtils入参是classLoader : Option[ClassLoader]配置XML中class的字符值,负责加载检查项目类。 verifySource verifyFile分别基于scala文件和source类型检查。
verifySource根据模式匹配进行调用。先对匹配项有些印象,后面说明。
c match { case c: FileChecker => { c.verify(file, c.level, lines, lines) } case c: ScalariformChecker => { c.verify(file, c.level, scalariformAst.ast, lines) } case c: CombinedChecker =>{ c.verify(file, c.level, CombinedAst(scalariformAst.ast, lines), lines) } case _ => Nil }
FileSpec是检查结果输出系统待后面描述。其它实现和使用不赘述。

ClassTypeParameterCheckerUML涉及图。
[图片上传失败...(image-9b363-1516091666310)]
图中顶层类是Checker[A]。它属于所有检查项目的父类。checker子类包含ScalariformChecker子类。 名字有点熟悉吧,在工具类中verifySource匹配到的模式,会调用 verify[T <: FileSpec](file : T, level : Level, ast : A, lines : Lines) : List[Message[T]]方法。

  def verify[T <: FileSpec](file: T, level: Level, ast: A, lines: Lines): List[Message[T]] = {
verify(ast).map(p => toStyleError(file, p, level, lines))

}
该方法调用接口函数 verify(ast : A) : List[PositionError]。这个接口的实现在AbstractClassChecker,它的实现方法。

def verify(ast: CompilationUnit): List[PositionError] = {
val it = for {
  f <- visit[TmplDef, TmplClazz](map)(ast.immediateChildren.head)
  t <- traverse(f, matches)
} yield {
  PositionError(t.t.name.offset)
}
it

}
方法中调用待实现接口matches(t : TmplClazz) : boolean,在类型检查ClassTypeParameterChecker类中函数被实现,实现简单不赘述。也就是说只要对类类型参数检查,需要实现matcher就完成开发,对开发者而言,接口越简单,扩展性越强
让我们看下另一个函数matches(t : TypeParamClause) : boolean。它是实现class type 参数检查关键。

def matches(t: TypeParamClause): Boolean = {
val regexString = getString("regex", DefaultRegex)
val regex = regexString.r //class para 检查正则表达式

t.contents.flatMap(c => innermostName(c)).exists(s => !matchesRegex(regex, s))
}

函数中flatMap循环对数据每行进行处理。innermostName则是内容提取函数,然后和正则表达式匹配返回结果。

def innermostName(ast: Any): Option[String] = {
ast match {
  case typeParam: TypeParam => {
    typeParam.contents match {
      case List(GeneralTokens(list)) => Some(list.head.text)
      case List(GeneralTokens(list), TypeParamClause(x)) => innermostName(x(1))
      case VarianceTypeElement(_) :: GeneralTokens(list) :: Nil => Some(list.head.text)
      case GeneralTokens(list) :: tail => Some(list.head.text)
      case VarianceTypeElement(_) :: GeneralTokens(list) :: tail => Some(list.head.text)
      case _ => None
    }
  }
  case _ => None
}


到这里,整个调用流程已经完成。如果我们开发新的检查项目,只需要实现matcher函数接口。对我们而言,一个的项目,提供这样接口在易读性、可测试性无疑是必须的。

  • 3.结果输出

经过代码分析,在检查校验时def verify[T <: FileSpec](file: T, level: Level, ast: A, lines: Lines): List[Message[T]]被调用,所有List[Message[T]]是我们要说明结果输出。
Message的定义sealed abstract class Message[+T <: FileSpec](),T类型是 FileSpec协变类型。那么T对应哪些类型哪?

trait FileSpec
class RealFileSpec(val name: String, val encoding: Option[String]) extends FileSpec
class SourceSpec(val name: String, val contents: String) extends FileSpec

FileSpec就是榨干的空壳,内容都RealFileSpec等子类存储。因此说输出结果就是多个class存储返回结果。

写在后面####


Scalastyle静态类型检查是开发者的福音,是一款订制化的检查工具。从另一方面说:基于开发的原理造成先天的缺陷,它又略尴尬。因为它只提供代码格式检查,类似findbugs 资源关闭,空指针检查等并不具备。

使用Scalastyle时常用关键字://scalastyle:on //scalastyle:off ,懒人的大杀器。

注:文中UML使用是基于Java的工具画的,存在不准确情况。只是描述基本的类结构。


1.https://www.ibm.com/developerworks/cn/java/j-lo-statictest-tools/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容