大數(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)建污抬。
下面以Scalastyle配置文件,說(shuō)明加載類運(yùn)行過(guò)程绳军。
內(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)和使用不贅述。
ClassTypeParameterChecker
UML涉及圖龟梦。
[圖片上傳失敗...(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/