如何写好kotlin代码

关于kotlin

kotlin作为一个JVM语言,它在高度兼容Java代码的前提之下,对Java语言进行了极大的简化和升级,在保留了面向对象的编程语言的优势前提下,拔高语言中函数的地位并拓展函数的能力,为开发者提供了函数式的编程框架,各种函数用法使kotlin代码极其简洁,甚至乍一看已经和Java完全不同了,,以上种种保证它可以不影响现有代码的前提下,提高后续的代码开发效率。因此kotlin这几年在迅速普及,据我所知,大量的前后端开发团队都已经在使用kotlin了。

既然kotlin大家都在用,那么我们如何写好kotlin代码呢?最佳选择当然是参考官方教程或者官方代码规范指导等等文档来写。比如作为Android开发的推荐编程语言,Android官方提出了一些代码规范:Kotlin代码风格指导,而kotlin自己的官方语言教程里面其实也有各种各样的代码规范。

也就是说,想要自己写出更规范和健壮(稳定)的kotlin代码,就需要熟悉所有相关的规则规范和kotlin一些语言教程的文档

这好像是一句正确的废话...

image

当然,熟读完并且记住所有的规则规范然后写代码的时候不出错,显然有些强人所难了,因此,我们需要一个插件来记住所有的代码规则规范,然后扫描我们的代码,然后帮助我们分析自己的kotlin代码。

kotlin编码规范最全的还是 ktlint,ktlint项目不仅参考了Android官方的Kotlin style guide,而且还有kotlin官方的编码规范等等。因此ktlint是一个不错的选择。

但是,一段好的代码不仅仅是代码格式的规范,代码本身应该具备稳定性,即代码运行没有性能或崩溃隐患,所以代码分析插件最好能指出哪些代码片段会影响代码稳定性,而ktlint主要能力还是检查编码规范,无法扫描kotlin代码中的性能问题和隐患代码,因此不算完美。

好了,不卖关子了,介绍下detekt。目前来讲,它相对更全面的Kotlin代码分析插件。

detekt是一款kotlin静态代码分析工具,它可以检查kotlin的编码规范,也能检查代码的性能开销和隐患,而且,你可以自定义自己的代码分析规则体系,这样,你就不必全盘接受代码所有代码规范,可以去掉那些无关紧要的规则检查,让开发相对灵活一些。

detekt使用简介

安装

参考官网,其实安装使用的方式有很多种,这里介绍一种。

  • 单独gradle文件集成

以下配置写在一个单独的gradle文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
repositories {
    // if you 'gradle install' all detekt modules
    mavenLocal()
    // or when all modules should be provided
    maven {
        url  "http://dl.bintray.com/arturbosch/code-analysis"
    }
}

configurations {
    detekt
}

task detekt(type: JavaExec) {
    main = "io.gitlab.arturbosch.detekt.cli.Main"
    classpath = configurations.detekt
    def input = "$project.projectDir.absolutePath"
    // detekt.yml就是我们可自定义的代码分析规则的配置文档,后面会讲
    def config = "$project.projectDir/detekt.yml"
    def filters = ".*test.*"
    ...
    ...
    // 输出结果的文件,可选html,xml,或txt等其他格式
    def output = "$project.projectDir/reports/detekt/detekt-report.html"
    def outputXml = "$project.projectDir/reports/detekt/detekt-report.xml"
    def params = [ '-p', input, '-c', config, '-f', filters, '-r', "html:$output",'-r', "xml:$outputXml"]
    args(params)
}

dependencies {
    detekt 'io.gitlab.arturbosch.detekt:detekt-cli:1.0.0.[CURRENT_MILESTONE]'
    detekt 'io.gitlab.arturbosch.detekt:detekt-formatting:1.0.0.[CURRENT_MILESTONE]'
}

然后命令行运行gradle detekt即可,默认输出文件在build文件夹下,当然我们也可以自己配置输出目录地址。

代码分析规则

上面提到的输入文件detekt.yml就是detekt分析kotlin代码的分析规则依据,我们可以自定义编写规则,detekt可以定义的规则类型分为9类

  • comments 注释文档规范检查
  • complexity 复杂度检查
  • empty-blocks 空block检查
  • exceptions 表达式规范检查
  • formatting 格式规范检查
  • naming 命名规范检查
  • performance 性能检查
  • potential-bugs 潜在bug检查
  • style 代码风格检查

举个例子,以下是糗百项目中使用的部分规则配置(其实主要是根据默认配置文件来选择自己需要的规则配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
test-pattern: # Configure exclusions for test sources
  active: true
  patterns: # Test file regexes
  - '.*/test/.*'
  - '.*/androidTest/.*'
  - '.*Test.kt'
  - '.*Spec.kt'
  - '.*Spek.kt'
  exclude-rule-sets:
  - 'comments'
  exclude-rules:
  - 'NamingRules'
  - 'WildcardImport'
  - 'MagicNumber'
  - 'MaxLineLength'
  - 'LateinitUsage'

build:
  maxIssues: 1  # 发现超过多少个问题时,task返回结果失败
  weights:  #比重
  # complexity: 2
  # LongParameterList: 1
  # style: 1
  # comments: 1


comments:
  active: false  // 是否激活该规则
  CommentOverPrivateFunction:
    active: false
  CommentOverPrivateProperty:
    active: false
  EndOfSentenceFormat:
    active: false
    endOfSentenceFormat: ([.?!][    

f<])|([.?!]$)
  UndocumentedPublicClass:
    active: false
    searchInNestedClass: true
    searchInInnerClass: true
    searchInInnerObject: true
    searchInInnerInterface: true
  UndocumentedPublicFunction:
    active: false

complexity:
  active: true
  ComplexCondition:  #条件是否过于复杂
    active: true
    threshold: 4  临界值
  ComplexInterface:
    active: false
    threshold: 10
    includeStaticDeclarations: false
  ComplexMethod:  #方法复杂难懂
    active: false
    threshold: 10
    ignoreSingleWhenExpression: false
    ignoreSimpleWhenEntries: false

  NestedBlockDepth: # 嵌套深度检查
    active: true
    threshold: 4  #阈值
  StringLiteralDuplication:
    active: false
    threshold: 3
    ignoreAnnotation: true
    excludeStringsWithLessThan5Characters: true
    ignoreStringsRegex: '$^'
  TooManyFunctions: # 单个文件/对象内部的方法数限定,功能单一原则
    active: false
    thresholdInFiles: 11
    thresholdInClasses: 11
    thresholdInInterfaces: 11
    thresholdInObjects: 11
    thresholdInEnums: 11
    ignoreDeprecated: false
    ignorePrivate: false
    ignoreOverridden: false

empty-blocks:
  active: true
  EmptyCatchBlock:
    active: true
    allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
  EmptyClassBlock:  # 空的class,不需要{}
    active: true


exceptions:
  active: true
  ExceptionRaisedInUnexpectedLocation:
    active: false
    methodNames: 'toString,hashCode,equals,finalize'
  ThrowingNewInstanceOfSameException:
    active: false
  TooGenericExceptionCaught: #异常捕获范围太广
    active: false
    exceptionNames:
    - ArrayIndexOutOfBoundsException
    - Error
    - Exception
    - IllegalMonitorStateException
    - NullPointerException
    - IndexOutOfBoundsException
    - RuntimeException
    - Throwable
    allowedExceptionNameRegex: "^(_|(ignore|expected).*)"


formatting:
  active: true
  android: false
  autoCorrect: true
  MaximumLineLength:   #  单行最长的长度,暂时不启用检查
    active: false
    maxLineLength: 120
  ModifierOrdering:    #  修饰符一致性
    active: true
    autoCorrect: true
  NoBlankLineBeforeRbrace:
    active: false
    autoCorrect: true
  NoConsecutiveBlankLines:  #  没有连续的空行
    active: true
    autoCorrect: true

  NoWildcardImports:  # 没有通配符的倒导入
    active: true
    autoCorrect: true
#  PackageName:
#    active: true
#    autoCorrect: true
  ParameterListWrapping:
    active: true
    autoCorrect: true
    indentSize: 4


naming:
  active: true
  ClassNaming: # class name 首字母大写
    active: true
    classPattern: '[A-Z$][a-zA-Z0-9$]*'

  FunctionMinLength:
    active: false
    minimumFunctionNameLength: 3
  FunctionNaming:  #fun命名规则
    active: true
    functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
    excludeClassPattern: '$^'
    ignoreOverridden: true
  FunctionParameterNaming:  #fun param 命名规则 驼峰
    active: true
    parameterPattern: '[a-z][A-Za-z0-9]*'
    excludeClassPattern: '$^'
    ignoreOverriddenFunctions: true
  MatchingDeclarationName: # kt文件中,只有一个类的情况下,最好保持类名文件名一致,一个文件多个类的情况下,选择能描述这些类的文件名称
    active: true
  MemberNameEqualsClassName: # 成员名和类名混淆,暂时不起用
    active: false
    ignoreOverriddenFunction: true
  ObjectPropertyNaming:  # 对象属性命名规则
    active: true
    constantPattern: '[A-Za-z][_A-Za-z0-9]*'
    propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
    privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
  VariableNaming:
    active: true
    variablePattern: '[a-z][A-Za-z0-9]*'
    privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
    excludeClassPattern: '$^'
    ignoreOverridden: true

performance:
  active: true
  ArrayPrimitive:  # 数组类型避免开装箱开销
    active: true
  ForEachOnRange: # ForEach循环代码的检查
    active: true
  SpreadOperator:
    active: false
  UnnecessaryTemporaryInstantiation: #类型转化的性能检查
    active: true

potential-bugs:
  active: true
  ExplicitGarbageCollectionCall: # 垃圾回收的代码检查
    active: true
  InvalidRange: #非法的range
    active: false
  IteratorHasNextCallsNextMethod: #迭代器代码检查
    active: true
  IteratorNotThrowingNoSuchElementException: # 迭代器实现规范
    active: false
  MissingWhenCase: #when的case条件不全
      active: true
  LateinitUsage:
    active: true
    excludeAnnotatedProperties: ""
    ignoreOnClassesPattern: ""
  UnsafeCallOnNullableType: # 不安全的空指针判断
    active: true
  UnsafeCast: #不安全的类型转换
    active: true
  UselessPostfixExpression: # 无用的后缀表达式
    active: true

以上这些定制规则,只是截取的一部分,大家使用detekt时,可以根据自己的需要来选择。
规则文档

输出结果

当你在命令行运行gradlew detekt之后,代码检查的结果会出现在你之前配置的路径下,比如build目录下:


image

输出的代码检查报告有不同的格式:xml,html,txt等,本质上是对代码检查结果的不同组织形式。

一下时html格式在浏览器中打开的样子如下:

image

这样,你就很方便的看到项目中所有kotlin代码的问题了,根据它的提示一一改正之后,重新运行代码分析插件直到不再有错误即可。