Scala代碼靜態(tài)檢查利器

大數(shù)據(jù)快速發(fā)展封寞,催生以Spark等數(shù)據(jù)處理組件和技術(shù)同時(shí)惫周,也讓scala成為大數(shù)據(jù)領(lǐng)域炙手可熱明星語(yǔ)言钥勋。與之熱度形成反差的是代碼檢查和分析工具遠(yuǎn)遠(yuǎn)落后于Java, C/C++等老牌語(yǔ)言科阎。

基于不同原理設(shè)計(jì)的代碼檢查工具有多種述吸。Java代碼檢查工具有Findbugs、PMD、Checkstyle蝌矛、Sonar道批。雖然Scala運(yùn)行在JVM上,但是以上工具不能很好的兼容Scala檢查入撒。Scalastyle 是專門針對(duì)Scala代碼而開發(fā)靜態(tài)檢查工具隆豹。本文介紹scalastyle使用,靜態(tài)檢查功能等茅逮。

本文內(nèi)容包括一下幾部分璃赡,略長(zhǎng),使用者可直接到第二部分:

  • 靜態(tài)檢查簡(jiǎn)介
  • scalastyle配置和使用
  • 源碼分析

靜態(tài)檢查簡(jiǎn)介


在靜態(tài)代碼檢查領(lǐng)域献雅,有各種明星檢查工具碉考。代碼檢查的原理,參見[1]惩琉,概括而言原理是:

  • 檢查Java代碼,缺陷模式匹配
  • 檢查編譯函數(shù)字節(jié)碼

前者適用與Java代碼分析豆励,不適用于Scala源代碼。缺點(diǎn)是針對(duì)代碼的靜態(tài)檢查瞒渠,運(yùn)行時(shí)綁定和資源的操作無(wú)法進(jìn)行檢查。后者則為對(duì)編譯完成的字節(jié)碼進(jìn)行檢查技扼。這種模式為基于JVM虛擬機(jī)運(yùn)行Scala語(yǔ)言伍玖,提供了一種檢查工具,比如Findbugs是對(duì)字節(jié)碼的檢查剿吻。兩種檢查模式優(yōu)缺點(diǎn)并存窍箍。本文不討論優(yōu)缺點(diǎn),僅關(guān)注Scalastyle使用和運(yùn)行丽旅。

當(dāng)前Scalastyle 1.0版本包含 69個(gè)檢查項(xiàng)椰棘。檢查規(guī)則文件可參考:scalastyle config
舉例來(lái)講靜態(tài)檢查如下:

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

可以看到針對(duì)Scala的源代碼檢查有助于代碼規(guī)范化和可讀性。對(duì)代碼進(jìn)行了強(qiáng)制性規(guī)范榄笙,保證開發(fā)任務(wù)代碼格式上做到統(tǒng)一邪狞。

配置和使用


scalastyle config鏈接中配置項(xiàng)目很多,但非常易用茅撞,舉例說(shuō)明下:
<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">

以上設(shè)置激活了檢查行結(jié)束是否有空格帆卓、檢查文件長(zhǎng)度 兩項(xiàng)檢查。如果想關(guān)閉米丘,設(shè)置enable為false即可剑令。根據(jù)項(xiàng)目需要設(shè)置檢查規(guī)則。

以IDEA 開發(fā)工具為例說(shuō)明:
IDEA環(huán)境本身已經(jīng)支持scalastyle拄查,不需要下載插件吁津,僅需要配置。

若代碼已經(jīng)生成了IDEA項(xiàng)目堕扶,會(huì)存在.idea目錄碍脏。從官方下載配置文件癣疟,將該配置文件復(fù)制到.idea目錄下去或者放到工程項(xiàng)目的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對(duì)scalastyle支持的開關(guān):
selecting Settings->Editor->Inspections, then searching for Scala style inspections. 確背本疲“scala style inspection”被勾中睛挚。

到這里完成了配置。Scalastyle會(huì)在我們編寫代碼時(shí)急黎,準(zhǔn)實(shí)時(shí)提示扎狱。我們根據(jù)提示把代碼規(guī)范化。

源碼分析


Scalastyle靜態(tài)檢查運(yùn)行過(guò)程: 配置加載 -> 執(zhí)行類型校驗(yàn) -> 結(jié)果輸出.

  • 1.配置加載

上文配置xml列舉scalastyle格式檢查類型勃教,根據(jù)配置淤击,IDEA加載xml文件檢查項(xiàng)到內(nèi)存中。實(shí)現(xiàn)加載類如下圖ScalastyleConfiguration同時(shí)也實(shí)現(xiàn)了配置結(jié)構(gòu)化讀取故源、以及配置創(chuàng)建污抬。

ScalastyleConfiguration類圖

下面以Scalastyle配置文件,說(shuō)明加載類運(yùn)行過(guò)程绳军。

XML配置示例

內(nèi)容保存在case class ScalastyleConfiguration中印机,結(jié)構(gòu)如下:

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

樣例類各字段和XML關(guān)系顯而易見。而關(guān)鍵 checkS保存類型檢查的入口(圖中class字符串)门驾,通過(guò)Java的類加載機(jī)制加載運(yùn)行射赛。
這也就意味著所有檢查類別通過(guò)單例類ScalastyleConfiguration獲取。在對(duì)代碼進(jìn)行檢查時(shí)奶是,只需調(diào)用ScalastyleConfiguration就可以完成楣责。在這里我們看到: 接口設(shè)計(jì)在滿足功能的前提,易讀最優(yōu)先保證聂沙。

  • 2.執(zhí)行類型校驗(yàn)

Scalastyle類型檢查是由工具類調(diào)用各個(gè)靜態(tài)檢查項(xiàng)目秆麸。工具類作為通用類,它是所有調(diào)用所有檢查項(xiàng)的入口及汉。而各靜態(tài)檢查項(xiàng)實(shí)現(xiàn)檢查邏輯不管調(diào)用流程沮趣,調(diào)用和檢查邏輯分離。

工具類

我們以ClassTypeParameterChecker為例來(lái)說(shuō)明調(diào)用和檢查過(guò)程豁生。UML中兔毒,CheckUtils入?yún)⑹?code>classLoader : Option[ClassLoader]配置XML中class的字符值,負(fù)責(zé)加載檢查項(xiàng)目類甸箱。 verifySource verifyFile分別基于scala文件和source類型檢查育叁。
verifySource根據(jù)模式匹配進(jìn)行調(diào)用。先對(duì)匹配項(xiàng)有些印象芍殖,后面說(shuō)明豪嗽。
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是檢查結(jié)果輸出系統(tǒng)待后面描述。其它實(shí)現(xiàn)和使用不贅述。

ClassTypeParameterCheckerUML涉及圖龟梦。
[圖片上傳失敗...(image-9b363-1516091666310)]
圖中頂層類是Checker[A]隐锭。它屬于所有檢查項(xiàng)目的父類。checker子類包含ScalariformChecker子類计贰。 名字有點(diǎn)熟悉吧钦睡,在工具類中verifySource匹配到的模式,會(huì)調(diào)用 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))

}
該方法調(diào)用接口函數(shù) verify(ast : A) : List[PositionError]荞怒。這個(gè)接口的實(shí)現(xiàn)在AbstractClassChecker,它的實(shí)現(xiàn)方法秧秉。

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

}
方法中調(diào)用待實(shí)現(xiàn)接口matches(t : TmplClazz) : boolean褐桌,在類型檢查ClassTypeParameterChecker類中函數(shù)被實(shí)現(xiàn),實(shí)現(xiàn)簡(jiǎn)單不贅述象迎。也就是說(shuō)只要對(duì)類類型參數(shù)檢查荧嵌,需要實(shí)現(xiàn)matcher就完成開發(fā),對(duì)開發(fā)者而言砾淌,接口越簡(jiǎn)單啦撮,擴(kuò)展性越強(qiáng)
讓我們看下另一個(gè)函數(shù)matches(t : TypeParamClause) : boolean。它是實(shí)現(xiàn)class type 參數(shù)檢查關(guān)鍵拇舀。

def matches(t: TypeParamClause): Boolean = {
val regexString = getString("regex", DefaultRegex)
val regex = regexString.r //class para 檢查正則表達(dá)式

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

函數(shù)中flatMap循環(huán)對(duì)數(shù)據(jù)每行進(jìn)行處理逻族。innermostName則是內(nèi)容提取函數(shù),然后和正則表達(dá)式匹配返回結(jié)果骄崩。

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
}


到這里,整個(gè)調(diào)用流程已經(jīng)完成薄辅。如果我們開發(fā)新的檢查項(xiàng)目要拂,只需要實(shí)現(xiàn)matcher函數(shù)接口。對(duì)我們而言站楚,一個(gè)的項(xiàng)目脱惰,提供這樣接口在易讀性、可測(cè)試性無(wú)疑是必須的窿春。

  • 3.結(jié)果輸出

經(jīng)過(guò)代碼分析拉一,在檢查校驗(yàn)時(shí)def verify[T <: FileSpec](file: T, level: Level, ast: A, lines: Lines): List[Message[T]]被調(diào)用,所有List[Message[T]]是我們要說(shuō)明結(jié)果輸出旧乞。
Message的定義sealed abstract class Message[+T <: FileSpec]()蔚润,T類型是 FileSpec協(xié)變類型。那么T對(duì)應(yīng)哪些類型哪尺栖?

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

FileSpec就是榨干的空殼嫡纠,內(nèi)容都RealFileSpec等子類存儲(chǔ)。因此說(shuō)輸出結(jié)果就是多個(gè)class存儲(chǔ)返回結(jié)果。

寫在后面####


Scalastyle靜態(tài)類型檢查是開發(fā)者的福音除盏,是一款訂制化的檢查工具叉橱。從另一方面說(shuō):基于開發(fā)的原理造成先天的缺陷,它又略尷尬者蠕。因?yàn)樗惶峁┐a格式檢查窃祝,類似findbugs 資源關(guān)閉,空指針檢查等并不具備踱侣。

使用Scalastyle時(shí)常用關(guān)鍵字://scalastyle:on //scalastyle:off 粪小,懶人的大殺器。

注:文中UML使用是基于Java的工具畫的泻仙,存在不準(zhǔn)確情況糕再。只是描述基本的類結(jié)構(gòu)。


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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玉转,一起剝皮案震驚了整個(gè)濱河市突想,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌究抓,老刑警劉巖猾担,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異刺下,居然都是意外死亡绑嘹,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門橘茉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)工腋,“玉大人,你說(shuō)我怎么就攤上這事畅卓∩醚” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵翁潘,是天一觀的道長(zhǎng)趁冈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拜马,這世上最難降的妖魔是什么渗勘? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮俩莽,結(jié)果婚禮上旺坠,老公的妹妹穿的比我還像新娘。我一直安慰自己豹绪,他們只是感情好价淌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布申眼。 她就那樣靜靜地躺著,像睡著了一般蝉衣。 火紅的嫁衣襯著肌膚如雪括尸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天病毡,我揣著相機(jī)與錄音濒翻,去河邊找鬼。 笑死啦膜,一個(gè)胖子當(dāng)著我的面吹牛有送,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播僧家,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雀摘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了八拱?” 一聲冷哼從身側(cè)響起阵赠,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肌稻,沒(méi)想到半個(gè)月后清蚀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爹谭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年枷邪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诺凡。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡东揣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腹泌,到底是詐尸還是另有隱情救斑,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布真屯,位于F島的核電站,受9級(jí)特大地震影響穷娱,放射性物質(zhì)發(fā)生泄漏绑蔫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一泵额、第九天 我趴在偏房一處隱蔽的房頂上張望配深。 院中可真熱鬧,春花似錦嫁盲、人聲如沸篓叶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缸托。三九已至左敌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俐镐,已是汗流浹背矫限。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佩抹,地道東北人叼风。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像棍苹,于是被迫代替她去往敵國(guó)和親无宿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容