第 5 章 Kotlin编码规范

第 5 章 Kotlin编码规范

俗话说:“没有规矩不成方圆”。编程工作往往都是一个团队协同进行,因而一致的编码规范非常有必要,这样写成的代码便于团队中的其他人员阅读,也便于编写者自己以后阅读。

提示 关于本书的Kotlin编码规范借鉴了Kotlin官方的编码规范1和raywenderlich.com的Kotlin编码规范2

1参考地址https://kotlinlang.org/docs/reference/coding-conventions.html

2这个编码规范是由来自raywenderlich.com团队多名成员共同完成的,参考地址https://github.com/raywenderlich/kotlin-style-guide

5.1 命名规范

程序代码中到处都是标识符,因此取一个一致并且符合规范的名字非常重要。

命名方法很多,但是比较有名的且被广泛接受的命名法包括下面两种。

  • 匈牙利命名,一般只是命名变量,原则是:变量名 = 类型前缀 + 描述,如bFoo表示布尔类型变量,pFoo表示指针类型变量。匈牙利命名还是有一定争议的,在Kotlin编码规范中基本不被采用。
  • 驼峰命名(Camel-Case),又称“骆驼命名法”,是指混合使用大小写字母来命名。驼峰命名又分为小驼峰法和大驼峰法。小驼峰法就是第一个单词是全部小写,后面的单词首字母大写,如myRoomCount;大驼峰法是第一个单词的首字母也大写,如ClassRoom。

除了包和编译期常量(const val声明的常量)外,Kotlin编码规范命名方法采用驼峰法,下面分类说明一下。

  • 包名:全小写字母,中间可以由点分隔开。作为命名空间,包名应该具有唯一性,推荐采用公司或组织域名的倒置,如com.apple.quicktime.v2。但Kotlin和Java核心库包名不采用域名的倒置命名,如kotlin.collections和java.awt.event。
  • 类和接口名:采用大驼峰法,如SplitViewController。
  • 文件名:采用大驼峰法,如BlockOperation.kt。
  • 变量名:采用小驼峰法,如studentNumber。
  • 运行期常量名(只读变量):采用小驼峰法,如yearLength。
  • 编译期常量名:全大写,如果是由多个单词构成,可以用下划线隔开,如YEAR和WEEK_OF_MONTH。
  • 函数名:采用小驼峰法,如balanceAccount、isButtonPressed等。

命名规范示例如下:

package com.a51work6

import java.lang.IllegalArgumentException

class Date : java.util.Date() {

    var size: Int = 0

    public override fun toString(): String {
        val year = super.getYear() + 1900
        val month = super.getMonth() + 1
        val day = super.getDate()
        //...
        return "$year-$month-$day"
    }

    companion object {

        private val DEFAULT_CAPACITY = 10

        fun valueOf(s: String): Date? {

            val yearLength = 4
            val monthLength = 2

            val firstDash: Int
            val secondDash: Int

            //...

            return null ?: throw IllegalArgumentException()
        }
    }
}

5.2 注释规范

Kotlin中注释的语法有三种:单行注释(//)、多行注释(/*...*/)和文档注释(/**...*/)。本节介绍如何规范使用这些注释。

5.2.1 文件注释

文件注释就是在每一个文件开头添加注释。文件注释通常包括如下信息:版权信息、文件名、所在模块、作者信息、历史版本信息、文件内容和作用等。

下面看一个文件注释的示例:

/*
* 版权所有 2015 北京智捷东方科技有限公司
* 许可信息查看LICENSE.txt文件
* 描述:
*   实现日期基本功能
* 历史版本:
*   2015-7-22: 创建 关东升
*   2015-8-20: 添加socket库
*   2015-8-22: 添加math库
*/

上述注释只是提供了版权信息、文件内容和历史版本信息等,文件注释要根据本身的实际情况包括内容。

5.2.2 文档注释

文档注释就是指这种注释内容能够生成API帮助文档,称为Kdoc。Kdoc是通过一些工具从Kotlin源代码的文档注释中提取信息,并生成HTML文件,即Kdoc文档。文档注释主要对类(或接口)、属性和函数等进行注释。

提示 文档是要给别人看的帮助文档,一般是非私有的属性和函数,以及类或接口等。那些只给自己看的内容可以不用文档注释。

文档注释示例:

package com.a51work6

import java.lang.IllegalArgumentException

/**
 * 自定义的日期类,具有日期基本功能,继承java.util.Date
 * 实现日期对象和字符串之间的转换
 * @author 关东升
 */
class Date : java.util.Date() {

    /**
     * 容量
     */
    var size: Int = 0

    /**
     * 将日期转换为yyyy-mm-dd格式的字符串
     * @return yyyy-mm-dd格式的字符串
     */
    public override fun toString(): String {
        val year = super.getYear() + 1900 //计算年份
        val month = super.getMonth() + 1 /*计算月份*/
        val day = super.getDate()
        ...
        return "${year}-${month}-${day}"
    }

    companion object {

        private val DEFAULT_CAPACITY = 10

        /**
         * 将字符串转换为Date日期对象
         * @param s 要转换的字符串
         * @return Date日期对象
         */
        fun valueOf(s: String): Date? {

            val yearLength = 4
            val monthLength = 2

            val firstDash: Int
            val secondDash: Int

            ...


            return null ?: throw IllegalArgumentException()
        }
    }
}

上述的文档注释中用到了@author、@return和@param等文档注释标签,这些标签能够方便生成API帮助文档,表5-1所示是常用的文档注释标签。

表 5-1 文档注释标签

标签

描述

@author

说明类或接口的作者

@deprecated

说明类、接口或成员已经废弃

@param

说明函数参数

@return

说明返回值

@see

参考另一个主题的链接

@exception

说明函数所抛出的异常类

@throws

同@exception标签

@version

类或接口的版本

如果想生成Kdoc文档,则需要使用Dokka(https://github.com/Kotlin/dokka)工具,Dokka支持Java和Kotlin混合项目生成Kdoc文档,Dokka提供多种使用方式,可以配置到Gradle、Maven和Ant项目依赖插件中,直接在项目中生成,也可以直接使用命令行工具生成。如果使用命令行工具生成,则需要从https://github.com/Kotlin/dokka/releases/download/0.9.10/dokka-fatjar.jar下载文件,然后在命令提示行执行如下语句:

java -jar <dokka-fatjar.jar文件路径> <源代码目录>

例如:在IntelliJ IDEA的chapter5项目目录下执行命令,如图5-1所示,执行成功会在当前目录下生成out\doc文件夹,如图5-2所示其中index.html文件是Kdoc文档入口。

图5-1 执行Kdoc文档生成命令

{%}

图5-2 Kdoc文档生成成功

5.2.3 代码注释

程序代码中处理文档注释还需要在一些关键的地方添加代码注释,文档注释一般是给一些看不到源代码的人看的帮助文档,而代码注释是给阅读源代码的人参考的。代码注释一般是采用单行注释(//)和多行注释(/*...*/)。

示例代码如下:

package com.a51work6

import java.lang.IllegalArgumentException

/**
 * 自定义的日期类,具有日期基本功能,继承java.util.Date
 *
 * 实现日期对象和字符串之间的转换
 * @author 关东升
 */
class Date : java.util.Date() {

    /**
     * 容量
     */
    var size: Int = 0

    /**
     * 将日期转换为yyyy-mm-dd格式的字符串
     * @return yyyy-mm-dd格式的字符串
     */
    public override fun toString(): String {
        val year = super.getYear() + 1900 //计算年份                  ①
        val month = super.getMonth() + 1 /*计算月份*/          ②
        val day = super.getDate()
        //...
        return "${year}-${month}-${day}"
    }

    companion object {

        // 默认的容量,是一个常量
        private val DEFAULT_CAPACITY = 10                             ③

        /**
         * 将字符串转换为Date日期对象
         * @param s 要转换的字符串
         * @return Date日期对象
         */
        fun valueOf(s: String): Date? {

            val YEAR_LENGTH = 4
            val MONTH_LENGTH = 2

            val firstDash: Int
            val secondDash: Int

            //...

            /*                                                        ④
            * 判断d是否为空,
            * 如果为空抛出异常IllegalArgumentException,否则返回d。
            */
            return null ?: throw IllegalArgumentException()
        }
    }
}

上述代码第①行和第②行是尾端进行注释,这要求注释内容极短,应该再有足够的空白来分开代码和注释。代码第③行是多行注释,在注释的文字很多情况下使用,注释时要求与其后的代码具有一样的缩进层级。第④行采用了单行注释,要求与其后的代码具有一样的缩进层级。

5.2.4 使用地标注释

IntelliJ IDEA等IDE工具都为源代码提供了一些特殊的注释,就是在代码中加一些标识,便于IDE工具快速定位代码,称为“地标注释”。这种注释虽然不是Kotlin官方所提供的,但是主流语言和主流的IDE工具也都支持“地标注释”。

IntelliJ IDEA工具支持如下两种地标注释:

  • TODO:说明此处有待处理的任务,或代码没有编写完成。
  • FIXME:说明此处代码是错误的,需要修正。

示例代码如下:

fun findById(orderid: Int): Order? {
    // TODO 自动生成的函数
    return null
}

fun modify(order: Order): Int {
    // FIXME 函数无返回值
    return 0
}

fun remove(order: Order): Int {
    // TODO可采用数据连接池
    return 0
}

这些注释在IntelliJ IDEA工具的TODO视图查看,如果没有打开TODO视图,可以单击IntelliJ IDEA左下角按钮,如图5-3所示,在弹出的菜单中选择TODO,打开如图5-4所示的TODO视图,单击其中的TODO或FIXME 可跳转到注释处。

图5-3 打开TODO视图

图5-4 查看TODO视图

5.3 声明

在声明变量、常量、属性、函数、定义类和接口时也需要遵守一些规范。

5.3.1 变量或常量声明

首先变量或常量声明时,每行声明变量或常量的数量推荐一行一个,因为这样有利于写注释。示例代码如下:

推荐使用:

val level = 0
var size = 10

不推荐使用:

val level = 0; var size = 10

还有变量或常量的数据类型,如果有可能应尽量采用自动类型推导,这样代码更简洁。示例代码如下:

推荐使用:

val level = 0
var size = 10

不推荐使用:

val level: Int = 0
var size: Int = 10

如果不是默认数据类型,需要明确声明变量或常量的数据类型,示例代码如下:

val level: Long = 0
var size: Long = 10

指定数据类型时需要使用冒号(:),变量或常量与冒号之间没有空格,冒号和数据类型之间要有一个空格。示例代码如下:

推荐使用:

val level: Long = 0
var size: Long = 10

不推荐使用:

val level : Long = 0
var size:  Long = 10

5.3.2 类声明

除内部类或嵌套类外,一个源文件中推荐声明一个类。当保存简单数据时,推荐使用数据类。示例代码如下:

不推荐使用:

class Order {
    // 订单ID
    var id: Long = 0
    // 订单日期
    var date = Date()
}

推荐使用:

data class Order(var id: Long, var date: Date)

5.4 代码排版

代码排版包括空行、空格、断行和缩进等内容。代码排版内容比较多,工作量很大,也非常重要。

5.4.1 空行

空行用以将逻辑相关的代码段分隔开,以提高可读性。空行使用规范:

  • 包声明之后保留一个空行,见示例Date.kt代码第①行。
  • import语句块之后保留一个空行,见示例Date.kt代码第②行。
  • 代码注释(尾端注释外)之前保留一个空行。见示例Date.kt代码第③行和第④行。
  • 函数的第一条语句之前保留一个空行。见示例Date.kt代码第⑤行。
  • 函数之后保留一个空行。见示例Date.kt代码第⑧行。
  • 一个函数内的两个逻辑段之间。见示例Date.kt代码第⑨行。
  • 类声明或接口声明之间保留两个空行。见示例Date.kt代码第⑩行。

示例Date.kt代码如下:

/*
* 版权所有 2015 北京智捷东方科技有限公司
* 许可信息查看LICENSE.txt文件
* 描述:
*   实现日期基本功能
* 历史版本:
*   2015-7-22: 创建 关东升
*   2015-8-20: 添加socket库
*   2015-8-22: 添加math库
*/

package com.a51work6
①
import java.lang.IllegalArgumentException
②
/**
 * 自定义的日期类,具有日期基本功能,继承java.util.Date
 * 实现日期对象和字符串之间的转换
 * @author 关东升
 * @version 1.2
 */
class Date : java.util.Date() {
      ③
    /**
     * 容量
     */
    var size: Int = 0
      ④
    /**
     * 将日期转换为yyyy-mm-dd格式的字符串
     * @return yyyy-mm-dd格式的字符串
     */
    public override fun toString(): String {
⑤
        val year = super.getYear() + 1900 //计算年份     ⑥
        val month = super.getMonth() + 1 /*计算月份*/  ⑦
        val day = super.getDate()
        ...
        return "${year}-${month}-${day}"
    }
     ⑧
    companion object {

        // 默认的容量,是一个常量
        private val DEFAULT_CAPACITY = 10

        /**
         * 将字符串转换为Date日期对象
         * @param s 要转换的字符串
         * @return Date日期对象
         */
        fun valueOf(s: String): Date? {

            val yearLength = 4
            val monthLength = 2

            val firstDash: Int
            val secondDash: Int

            ...
            ⑨
            /*
            * 判断d是否为空,
            * 如果为空抛出异常IllegalArgumentException,否则返回d。
            */
            return null ?: throw IllegalArgumentException()
        }
    }
}

⑩
interface B {

}

5.4.2 空格

代码中的有些位置是需要有空格的,这个工作量也很大。下面是使用空格的规范。

  1. 赋值符号“=”前后各有一个空格。var或val与标识符之间有一个空格。

    var a = 10
    val c = 10
    
  2. 所有的二元运算符都应该使用空格与操作数分开。

    a += c + d
    
  3. 一元运算符:负号“-”、自增“++”和自减“--”等,它们与操作数之间没有空格。

    var b = 0
    var a = -b
    a++
    --b
    
  4. 小左括号“(”之后,小右括号“)”之前不应有空格。

    a = (a + b) / (c * d)
    
  5. 大左括号“{”之前有一个空格,示例如下:

    while (a == d) {
        n += 1
    }
    
  6. 在函数名与第一参数之间没有空格,后面的参数前应该有一个空格,参数冒号与数据类型之间也有一个空格。

    public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence  {
        ...
    }
    
  7. 子类继承父类或实现接口时,它们之间的冒号前要有一个空格。

    class Date : java.util.Date() {
        ...
    }
    
  8. Lambda表达式中, 大括号左右要加空格,箭头左右也要加空格。

    ary.filter { it > 10 }.map { element -> element * 2 }
    
    

5.4.3 缩进

4个空格常被作为缩进排版的一个单位。虽然在开发时程序员使用制表符进行缩进,而默认情况下一个制表符等于8个空格,但是不同的IDE工具中一个制表符与空格对应个数会有不同。IntelliJ IDEA和Eclipse中默认是一个制表符对应4个空格。

在函数、属性、Lambda、控制结构等包含大括号“{}”的代码块中,代码块的内容相对于首行缩进一个单位(4个空格)。

示例如下:

class Date : java.util.Date() {

    ...
    val string: String?
        get() {

            val year = super.getYear() + 1900
            val month = super.getMonth() + 1
            val day = super.getDate()

            if (longName1 == longName2 || (longName3 == longName4 && longName3 > longName4
                    && longName2 > longName5)) {

            }

            return null
        }
}

5.4.4 断行

一行代码的长度应尽量不要超过80个字符,如果超过则需断行,可以依据下面的一般规范断开:

  1. 在一个逗号后面断开。一个运算符前面断开,要选择较高级别的运算符(而非较低级别的运算符)断开。

    下面通过一些示例说明:

    longName1 = longName2 * (longName3 + longName4 - longName5)
            + 4 * longName6   ①
    longName1 = longName2 * (longName3 + longName4
            - longName5) + 4 * longName6  ②
    
    fun format(obj: Any, toAppendTo: StringBuffer,
                        fieldPosition: FieldPosition): StringBuffer { ③
        ...
    }
    
    if ((longName1 == longName2)
             || (longName3 == longName4) && (longName3 > longName4)
             && (longName2 > longName5)) {  ④
    
    }
    
    

    上述代码第①行和第②行是带有小括号运算的表示式,其中代码第①行的断开位置要比第②行的断开位置要好。因为代码第①行断开处位于括号表达式的外边,这是个较高级别的断开。

    代码第③行函数名断开是在参数逗号之后。

    代码第④行是if等判断结构表达式中,由于可能有很多长的条件表达式,断开的位置应在逻辑运算符处。

  2. 在类声明时断行

    如果有少数几个参数的类可以写成一行。

    class Student(id: Int, name: String)
    
    

    如果类头比较长,可以是每个主构造函数参数单独一行,并缩进一个级别,右括号应该另起一行。

    class Student (
        id: Int,
        name: String,
        schoolname: String
    ) : Person (name) {
      ...
    }
    
    

5.5 省略规范

Kotlin代码追求简洁,Kotlin代码中有一些习惯省略的地方。

  1. 省略分号

    Kotlin代码中一条语句的结束可以有分号,也可以省略。示例代码如下:

    推荐使用:

    val level = 0
    var size = 10
    
    

    不推荐使用:

    val level = 0;
    var size = 10;
    
  2. 省略Unit

    如果一个函数的返回类型是Unit,则需要省略。Kotlin的Unit关键字相当于Java中的void,表示返回空的数据,用于函数的返回类型声明。示例代码如下:

    推荐使用:

    fun printString(param: String) {
        println(param)
    }
    
    

    不推荐使用:

    fun printString(param: String): Unit {
        println(param)
    }
    
  3. Lambda表达式中省略参数声明

    在非嵌套lambda表达式中,最好省略参数声明使用隐式参数it。示例代码如下:

    推荐使用:

    val ary = arrayListOf<String>("A", "B", "C")
    ary.forEach { println(it) }
    
    

    不推荐使用:

    val ary = arrayListOf<String>("A", "B", "C")
    ary.forEach { element -> println(element) }
    
    

    关于规范事实上还有很多,不能穷尽,这里不再赘述。

本章小结

通过对本章内容的学习,读者可以了解到Kotlin编码规范,包括命名规范、注释规范、声明规范、代码排版和省略规范等内容。

目录

  • 目录
  • 简介
  • 前言
  • 第 1 章 开篇综述
  • 第 2 章 开发环境搭建
  • 第 3 章 第一个Kotlin程序
  • 第 4 章 Kotlin语法基础
  • 第 5 章 Kotlin编码规范
  • 第 6 章 数据类型
  • 第 7 章 字符串
  • 第 8 章 运算符
  • 第 9 章 程序流程控制
  • 第 10 章 函数
  • 第 11 章 面向对象编程
  • 第 12 章 继承与多态
  • 第 13 章 抽象类与接口
  • 第 14 章 函数式编程基石——高阶函数和Lambda表达式
  • 第 15 章 泛型
  • 第 16 章 数据容器——数组和集合
  • 第 17 章 Kotlin中函数式编程API
  • 第 18 章 异常处理
  • 第 19 章 线程
  • 第 20 章 协程
  • 第 21 章 Kotlin与Java混合编程
  • 第 22 章 Kotlin I/O与文件管理
  • 第 23 章 网络编程
  • 第 24 章 Kotlin与Java Swing图形用户界面编程
  • 第 25 章 轻量级SQL框架——Exposed
  • 第 26 章 反射
  • 第 27 章 注解
  • 第 28 章 项目实战1:开发PetStore宠物商店项目
  • 第 29 章 项目实战2:开发Kotlin版QQ2006聊天工具