Scala 的代码风格怎么统一?这份 scalastyle 配置你可以无脑复制
前言
本文隶属于专栏《1000个问题搞定大数据技术体系》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!
本专栏目录结构和参考文献请见1000个问题搞定大数据技术体系
正文
本文参考 Apache Spark 的 scalastyle 配置。
首先需要在 pom.xml 里面新增 scalastyle 的 plugin。
pom.xml
先定义 2 个和文件字符编码相关的全局变量。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
配置 scalastyle-maven-plugin
<plugin>
<groupId>org.scalastyle</groupId>
<artifactId>scalastyle-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<verbose>false</verbose>
<failOnViolation>true</failOnViolation>
<includeTestSourceDirectory>false</includeTestSourceDirectory>
<failOnWarning>false</failOnWarning>
<sourceDirectory>${basedir}/src/main/scala</sourceDirectory>
<testSourceDirectory>${basedir}/src/test/scala</testSourceDirectory>
<configLocation>scalastyle-config.xml</configLocation>
<outputFile>${basedir}/target/scalastyle-output.xml</outputFile>
<inputEncoding>${project.build.sourceEncoding}</inputEncoding>
<outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
在项目根目录下定义 scalastyle 的配置文件:scalastyle-config.xml
scalastyle-config.xml
可以无脑复制的部分
<!--
这里参考的 Apache Spark 的 scala 代码风格配置文件。
如果希望关闭代码段的检查,可以在源代码中添加注释
在代码段之前和之后,使用以下语法:
// scalastyle:off
... // 用来破坏风格的东西
// scalastyle:on
也可以通过指定规则 ID 来单独取消一条规则,就像这样:
http://www.scalastyle.org/rules-0.7.0.html
// scalastyle:off no.finalize
override def finalize(): Unit = ...
// scalastyle:on no.finalize
这个文件分成了 3 个部分:
(1) 我们应当遵守的规则
(2) 我们想要遵守的规则,但是实际情况不允许。
(3) 我们不想遵守的规则
-->
<scalastyle>
<name>Scalastyle standard configuration</name>
<!-- ================================================================================ -->
<!-- 我们应当遵守的规则 -->
<!-- ================================================================================ -->
<!-- 检查文件中是否有标签 -->
<check level="error" class="org.scalastyle.file.FileTabChecker" enabled="true"></check>
<!-- 检查加号后面是否有空格 -->
<check level="error" class="org.scalastyle.scalariform.SpacesAfterPlusChecker" enabled="true"></check>
<!-- 检查加号前面是否有空格 -->
<check level="error" class="org.scalastyle.scalariform.SpacesBeforePlusChecker" enabled="true"></check>
<!-- 检查行尾没有空格 -->
<check level="error" class="org.scalastyle.file.WhitespaceEndOfLineChecker" enabled="true"></check>
<!-- 检查一行中的字符数 -->
<check level="error" class="org.scalastyle.file.FileLineLengthChecker" enabled="true">
<parameters>
<parameter name="maxLineLength"><![CDATA[100]]></parameter>
<parameter name="tabSize"><![CDATA[2]]></parameter>
<parameter name="ignoreImports">true</parameter>
</parameters>
</check>
<!-- 检查类名是否与正则表达式匹配 -->
<check level="error" class="org.scalastyle.scalariform.ClassNamesChecker" enabled="true">
<parameters>
<parameter name="regex"><![CDATA[[A-Z][A-Za-z]*]]></parameter>
</parameters>
</check>
<!-- 检查对象名称是否与正则表达式匹配 -->
<check level="error" class="org.scalastyle.scalariform.ObjectNamesChecker" enabled="true">
<parameters>
<parameter name="regex"><![CDATA[(config|[A-Z][A-Za-z]*)]]></parameter>
</parameters>
</check>
<!-- 检查包对象名是否与正则表达式匹配 -->
<check level="error" class="org.scalastyle.scalariform.PackageObjectNamesChecker" enabled="true">
<parameters>
<parameter name="regex"><![CDATA[^[a-z][A-Za-z]*$]]></parameter>
</parameters>
</check>
<!-- 检查方法的参数个数是否超过设置数量 -->
<check customId="argcount" level="error" class="org.scalastyle.scalariform.ParameterNumberChecker" enabled="true">
<parameters>
<parameter name="maxParameters"><![CDATA[10]]></parameter>
</parameters>
</check>
<!-- 检查类和对象是否定义了finalize()方法 -->
<check level="error" class="org.scalastyle.scalariform.NoFinalizeChecker" enabled="true"></check>
<!-- 检查类和对象在没有覆盖的情况下定义了相等(java.lang.object) -->
<check level="error" class="org.scalastyle.scalariform.CovariantEqualsChecker" enabled="true"></check>
<!-- 检查结构类型是否未被使用 -->
<check level="error" class="org.scalastyle.scalariform.StructuralTypeChecker" enabled="true"></check>
<!-- 检查如果使用长字符串,则使用大写字母 -->
<check level="error" class="org.scalastyle.scalariform.UppercaseLChecker" enabled="true"></check>
<!-- 检查if是否使用大括号 -->
<check level="error" class="org.scalastyle.scalariform.IfBraceChecker" enabled="true">
<parameters>
<parameter name="singleLineAllowed"><![CDATA[true]]></parameter>
<parameter name="doubleLineAllowed"><![CDATA[true]]></parameter>
</parameters>
</check>
<!-- 检查方法是否具有显式返回类型 -->
<check level="error" class="org.scalastyle.scalariform.PublicMethodsHaveTypeChecker" enabled="true"></check>
<!-- 检查文件是否以换行符结尾 -->
<check level="error" class="org.scalastyle.file.NewLineAtEofChecker" enabled="true"></check>
<!-- 检查是否使用非ASCII字符(Unicode字符) -->
<check customId="nonascii" level="error" class="org.scalastyle.scalariform.NonASCIICharacterChecker"
enabled="true"></check>
<!-- 检查在注释后是否有一个空格 -->
<check level="error" class="org.scalastyle.scalariform.SpaceAfterCommentStartChecker" enabled="true"></check>
<!-- 检查某些既定标记前是否有空间 -->
<check level="error" class="org.scalastyle.scalariform.EnsureSingleSpaceBeforeTokenChecker" enabled="true">
<parameters>
<parameter name="tokens">ARROW, EQUALS, ELSE, TRY, CATCH, FINALLY, LARROW, RARROW</parameter>
</parameters>
</check>
<!-- 检查某些既定标记后是否有空间 -->
<check level="error" class="org.scalastyle.scalariform.EnsureSingleSpaceAfterTokenChecker" enabled="true">
<parameters>
<parameter name="tokens">ARROW, EQUALS, COMMA, COLON, IF, ELSE, DO, WHILE, FOR, MATCH, TRY, CATCH, FINALLY,
LARROW, RARROW
</parameter>
</parameters>
</check>
<!-- 检查代码是否有 ??? 操作符 -->
<check level="error" class="org.scalastyle.scalariform.NotImplementedErrorUsage" enabled="true"></check>
<!-- 由于 SPARK-7977, 所有的 println 都应该由 '// scalastyle:off/on println' 包裹-->
<check customId="println" level="error" class="org.scalastyle.scalariform.TokenChecker" enabled="true">
<parameters>
<parameter name="regex">^println$</parameter>
</parameters>
<customMessage><![CDATA[确定要 println 吗? 如果是的话,需要用下面的代码段包裹:
// scalastyle:off println
println(...)
// scalastyle:on println]]></customMessage>
</check>
<!-- mutable.SynchronizedBuffer -->
<check customId="mutablesynchronizedbuffer" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">mutable.SynchronizedBuffer</parameter>
</parameters>
<customMessage><![CDATA[
确定要使用 mutable.SynchronizedBuffer 吗? 大多数场景下,你应该使用
java.util.concurrent.ConcurrentLinkedQueue 来替代。
如果你必须使用 mutable.SynchronizedBuffer,使用下面的代码段来包裹:
// scalastyle:off mutablesynchronizedbuffer
mutable.SynchronizedBuffer[...]
// scalastyle:on mutablesynchronizedbuffer
]]></customMessage>
</check>
<!-- Class.forName -->
<check customId="classforname" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">Class.forName</parameter>
</parameters>
<customMessage><![CDATA[
确定要使用 Class.forName 吗? 大多数场景下,你应该使用 Utils.classForName 来替代.
如果你必须使用 Class.forName,使用下面的代码段来包裹:
// scalastyle:off classforname
Class.forName(...)
// scalastyle:on classforname
]]></customMessage>
</check>
<!-- 检查正则表达式是否匹配 -->
<check customId="caselocale" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">(.toUpperCase|.toLowerCase)(?!((|(Locale.ROOT)))</parameter>
</parameters>
<customMessage><![CDATA[
确定要使用 toUpperCase or toLowerCase without the root locale ? 大多数场景下,你应该使用
toUpperCase(Locale.ROOT) or toLowerCase(Locale.ROOT) 来替代。
如果你必须使用 toUpperCase or toLowerCase without the root locale,使用下面的代码段来包裹:
// scalastyle:off caselocale
.toUpperCase
.toLowerCase
// scalastyle:on caselocale
]]></customMessage>
</check>
<!-- throw new Error -->
<check customId="throwerror" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">throw new w+Error(</parameter>
</parameters>
<customMessage><![CDATA[
确定要这样抛出异常吗?大多数场景下,你应该使用精确的异常来替代。
如果你必须这样抛出异常,使用下面的代码段来包裹:
// scalastyle:off throwerror
throw new XXXError(...)
// scalastyle:on throwerror
]]></customMessage>
</check>
<!-- 由于 SPARK-9613, JavaConversions 应该替换成 JavaConverters -->
<check customId="javaconversions" level="error" class="org.scalastyle.scalariform.TokenChecker" enabled="true">
<parameters>
<parameter name="regex">JavaConversions</parameter>
</parameters>
<customMessage>不要去隐式导入 scala.collection.JavaConversions._ 了, 导入
scala.collection.JavaConverters._ 并且使用 .asScala / .asJava 方法
</customMessage>
</check>
<!-- extractOpt -->
<check customId="extractopt" level="error" class="org.scalastyle.scalariform.TokenChecker" enabled="true">
<parameters>
<parameter name="regex">extractOpt</parameter>
</parameters>
<customMessage>使用 jsonOption(x).map(.extract[T]) 来替代 .extractOpt[T],因为后者太低级了。
</customMessage>
</check>
<!-- 检查某些既定标记前不允许使用空格 -->
<check level="error" class="org.scalastyle.scalariform.DisallowSpaceBeforeTokenChecker" enabled="true">
<parameters>
<parameter name="tokens">COMMA</parameter>
</parameters>
</check>
<!-- SPARK-3854: 在 ')' 和 '{' 之间使用单个空格隔开 -->
<check customId="SingleSpaceBetweenRParenAndLCurlyBrace" level="error" class="org.scalastyle.file.RegexChecker"
enabled="true">
<parameters>
<parameter name="regex">){</parameter>
</parameters>
<customMessage><![CDATA[
在 ')' 和 '{' 之间使用单个空格隔开。
]]></customMessage>
</check>
<!-- 多行注释使用 Javadoc 风格的行首缩进 -->
<check customId="NoScalaDoc" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">(?m)^(s*)/[*][*].*$(r|)n^1 [*]</parameter>
</parameters>
<customMessage>多行注释使用 Javadoc 风格的行首缩进</customMessage>
</check>
<!-- case 子句省略括号 -->
<check customId="OmitBracesInCase" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">case[^n>]*=>s*{</parameter>
</parameters>
<customMessage> case 子句省略括号</customMessage>
</check>
<!-- SPARK-16877: 避免使用 Java 的 @Override 注解 -->
<check level="error" class="org.scalastyle.scalariform.OverrideJavaChecker" enabled="true"></check>
<!-- 检查应该用Scala @deprecated代替Java @Deprecated -->
<check level="error" class="org.scalastyle.scalariform.DeprecatedJavaChecker" enabled="true"></check>
<!-- 检查类没有导入某些类 -->
<check level="error" class="org.scalastyle.scalariform.IllegalImportsChecker" enabled="true">
<parameters>
<parameter name="illegalImports"><![CDATA[scala.collection.Seq,scala.collection.IndexedSeq]]></parameter>
</parameters>
<customMessage><![CDATA[
不要导入 scala.collection.Seq 和 scala.collection.IndexedSeq, 因为它会带来 Scala 2.12 和 2.13 混合编译问题。
查看下面的页面了解 Seq / IndexedSeq 变更的细节。
https://docs.scala-lang.org/overviews/core/collections-migration-213.html
如果你必须使用 scala.collection.Seq 或者 scala.collection.IndexedSeq,请使用全限定名称。
]]></customMessage>
</check>
<!-- ================================================================================ -->
<!-- 我们想要遵守的规则,但是实际情况不允许 -->
<!-- ================================================================================ -->
<!-- 检查每个文件的前几行与文本匹配-->
<check level="error" class="org.scalastyle.file.HeaderMatchesChecker" enabled="false"/>
<!-- 我们无法启用以下两个选项,因为它会使许多字符串插值用例失败。-->
<!-- 理想情况下,应配置以下两条规则以排除字符串插值。 -->
<check level="error" class="org.scalastyle.scalariform.NoWhitespaceBeforeLeftBracketChecker"
enabled="false"></check>
<check level="error" class="org.scalastyle.scalariform.NoWhitespaceAfterLeftBracketChecker" enabled="false"></check>
<!-- 检查方法名是否与正则表达式匹配 -->
<check level="error" class="org.scalastyle.scalariform.MethodNamesChecker" enabled="false">
<parameters>
<parameter name="regex"><![CDATA[^[a-z][A-Za-z0-9]*$]]></parameter>
</parameters>
</check>
<!-- 检查如果一个类实现了 equals 或者 hashCode,那它应该实现另一个-->
<check level="error" class="org.scalastyle.scalariform.EqualsHashCodeChecker" enabled="true"></check>
<!-- ================================================================================ -->
<!-- 我们不想遵守的规则 -->
<!-- ================================================================================ -->
<!-- 检查类没有导入某些类 -->
<check level="error" class="org.scalastyle.scalariform.IllegalImportsChecker" enabled="false">
<parameters>
<parameter name="illegalImports"><![CDATA[sun._,java.awt._]]></parameter>
</parameters>
</check>
<!-- 检查文件不是以换行符结尾 -->
<check level="error" class="org.scalastyle.file.NoNewLineAtEofChecker" enabled="false"></check>
<!-- 检查Boolean表达式是否可以简化 -->
<check level="error" class="org.scalastyle.scalariform.SimplifyBooleanExpressionChecker" enabled="false"></check>
<!-- 检查是否使用 return -->
<!-- 对于控制流和守卫,我们使用了相当多的 return -->
<check level="error" class="org.scalastyle.scalariform.ReturnChecker" enabled="false"></check>
<!-- 检查是否使用 null -->
<!-- 我们在低级代码中大量使用null,并通过接口调用第三方代码 -->
<check level="error" class="org.scalastyle.scalariform.NullChecker" enabled="false"></check>
<!-- 检查类和对象没有定义clone()方法 -->
<check level="error" class="org.scalastyle.scalariform.NoCloneChecker" enabled="false"></check>
<!-- 检查文件中的行数 -->
<check level="error" class="org.scalastyle.file.FileLengthChecker" enabled="false">
<parameters>
<parameter name="maxFileLength">800></parameter>
</parameters>
</check>
<!-- 检查文件中声明的类型是否过多 -->
<check level="error" class="org.scalastyle.scalariform.NumberOfTypesChecker" enabled="false">
<parameters>
<parameter name="maxTypes">30</parameter>
</parameters>
</check>
<!-- 检查方法的参数复杂度是否超过设定值 -->
<check level="error" class="org.scalastyle.scalariform.CyclomaticComplexityChecker" enabled="false">
<parameters>
<parameter name="maximum">10</parameter>
</parameters>
</check>
<!-- 检查方法不超过最大长度 -->
<check level="error" class="org.scalastyle.scalariform.MethodLengthChecker" enabled="false">
<parameters>
<parameter name="maxLength">50</parameter>
</parameters>
</check>
<!-- 检查一个类/特性/对象是否申明太多的方法 -->
<check level="error" class="org.scalastyle.scalariform.NumberOfMethodsInTypeChecker" enabled="false">
<parameters>
<parameter name="maxMethods"><![CDATA[30]]></parameter>
</parameters>
</check>
<!-- 检查魔数是否使用var定义 -->
<check level="error" class="org.scalastyle.scalariform.MagicNumberChecker" enabled="false">
<parameters>
<parameter name="ignore">-1,0,1,2,3</parameter>
</parameters>
</check>
</scalastyle>
需要自定义配置的部分
<!-- 检查是否根据样式配置对导入进行分组和排序,根据自己的工程来自定义配置 -->
<check level="error" class="org.scalastyle.scalariform.ImportOrderChecker" enabled="true">
<parameters>
<parameter name="groups">java,scala,3rdParty,shockang</parameter>
<parameter name="group.java">javax?..*</parameter>
<parameter name="group.scala">scala..*</parameter>
<parameter name="group.3rdParty">(?!com.shockang.study.spark.).*</parameter>
<parameter name="group.shockang">com.shockang.study.spark..*</parameter>
</parameters>
</check>
可选部分
<!-- hadoopConfiguration -->
<check customId="hadoopconfiguration" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">spark(.sqlContext)?.sparkContext.hadoopConfiguration</parameter>
</parameters>
<customMessage><![CDATA[
确认要使用 sparkContext.hadoopConfiguration 吗?大多数场景下,你应该使用 spark.sessionState.newHadoopConf()
来替代。这样的话,Spark session 里面的 hadoop 配置将会很快起作用。
如果你必须使用 sparkContext.hadoopConfiguration,使用下面的代码段来包裹:
// scalastyle:off hadoopconfiguration
spark.sparkContext.hadoopConfiguration...
// scalastyle:on hadoopconfiguration
]]></customMessage>
</check>
<!-- Runtime.getRuntime.addShutdownHook -->
<check customId="runtimeaddshutdownhook" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">Runtime.getRuntime.addShutdownHook</parameter>
</parameters>
<customMessage><![CDATA[
确定要使用 Runtime.getRuntime.addShutdownHook 吗? 大多数场景下,你应该使用
ShutdownHookManager.addShutdownHook 来替代。
如果你必须使用 Runtime.getRuntime.addShutdownHook,使用下面的代码段来包裹:
// scalastyle:off runtimeaddshutdownhook
Runtime.getRuntime.addShutdownHook(...)
// scalastyle:on runtimeaddshutdownhook
]]></customMessage>
</check>
<!-- Class.forName -->
<check customId="classforname" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">Class.forName</parameter>
</parameters>
<customMessage><![CDATA[
确定要使用 Class.forName 吗? 大多数场景下,你应该使用 Utils.classForName 来替代.
如果你必须使用 Class.forName,使用下面的代码段来包裹:
// scalastyle:off classforname
Class.forName(...)
// scalastyle:on classforname
]]></customMessage>
</check>
<!-- Await.result -->
<check customId="awaitresult" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">Await.result</parameter>
</parameters>
<customMessage><![CDATA[
确定要使用 Await.result? 大多数场景下,你应该使用 ThreadUtils.awaitResult 来替代。
如果你必须使用 Await.result,使用下面的代码段来包裹:
// scalastyle:off awaitresult
Await.result(...)
// scalastyle:on awaitresult
]]></customMessage>
</check>
<!-- Await.ready -->
<check customId="awaitready" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">Await.ready</parameter>
</parameters>
<customMessage><![CDATA[
确定要使用 Await.ready? 大多数场景下,你应该使用 ThreadUtils.awaitReady 来替代。
如果你必须使用 Await.ready,使用下面的代码段来包裹:
// scalastyle:off awaitready
Await.ready(...)
// scalastyle:on awaitready
]]></customMessage>
</check>
<!-- org.apache.commons.lang -->
<check customId="commonslang2" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">org.apache.commons.lang.</parameter>
</parameters>
<customMessage>使用 Commons Lang 3 的类 (包 org.apache.commons.lang3.*) ,不要去使用
Commons Lang 2 了(包 org.apache.commons.lang.*)
</customMessage>
</check>
<!-- FileSystem.get -->
<check customId="FileSystemGet" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">FileSystem.get([a-zA-Z_$][a-zA-Z_$0-9]*)</parameter>
</parameters>
<customMessage><![CDATA[
确定要使用 "FileSystem.get(Configuration conf)"?如果输入配置没有合理的被设置,将会返回一个默认的
FileSystem 实例。这会导致你在处理多文件系统的时候出现错误。
请考虑使用 "FileSystem.get(URI uri, Configuration conf)" 或者 "Path.getFileSystem(Configuration conf)" 。
如果你必须使用 "FileSystem.get(Configuration conf)",使用下面的代码段来包裹:
// scalastyle:off FileSystemGet
FileSystem.get(...)
// scalastyle:on FileSystemGet
]]></customMessage>
</check>
<!-- Objects.toStringHelper -->
<check customId="GuavaToStringHelper" level="error" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex">Objects.toStringHelper</parameter>
</parameters>
<customMessage>避免使用 Object.toStringHelper。使用 ToStringBuilder 来替代。</customMessage>
</check>
笔者后续会开源一些 scala 工程,里面使用了上面的 scalastyle 配置,敬请期待。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
二维码