华为Java语言编程规范 V5.0(试行)
可信理论、技术与工程实验室 发布 目录 1 概述 1.1 背景 1.2 目标和适用范围 1.3 总体原则 1.4 条款组织方式 2 代码风格 2.1 命名 2.1.1 标识符 G.NAM.01 标识符应由不超过64字符的字母、数字和下划线组成 2.1.2 包名 G.NAM.02 包名中的字母应小写,包名以点号分隔层级 2.1.3 类、枚举和接口 G.NAM.03 类、枚举和接口名应采用大驼峰命名 2.1.4 方法 G.NAM.04 方法名应采用小驼峰命名 2.1.5 常量 G.NAM.05 常量名采用全大写单词,单词间以下划线分隔 2.1.6 变量 G.NAM.06 变量采用小驼峰命名 G.NAM.07 避免使用具有否定含义布尔变量名 G.NAM.08 布尔型变量建议以表达是非意义的动词开头 2.2 注释 P.01 注释跟代码一样重要,应按需注释 2.2.1 Javadoc G.CMT.01 public或protected修饰的元素应添加Javadoc注释 G.CMT.02 顶层public类的Javadoc应该包含功能说明和创建日期信息 G.CMT.03 方法的Javadoc中应该包含功能说明,根据实际需要按顺序使用@param、@return、 @throws标签对参数、返回值、异常进行注释 G.CMT.04 不写空有格式的方法头注释 2.2.2 文件头注释 G.CMT.05 文件头注释应该包含版权许可信息 2.2.3 代码注释 G.CMT.06 注释与代码之间应该有空行或空格,注释符与注释内容之间应该有空格 G.CMT.07 不用的代码段包括import,直接删除,不要注释掉 G.CMT.08 正式交付给客户的代码不应包含TODO/FIXME注释 2.3 格式 2.3.1 源文件 G.FMT.01 源文件编码格式(包括注释)应该是UTF-8 G.FMT.02 一个源文件按顺序包含版权、package、import、顶层类,且用空行分隔 G.FMT.03 import包应该按照先安卓、华为公司、其它商业组织、其它开源第三方、net/org开源组织、 最后java的分类顺序出现,并用一个空行分组 G.FMT.04 一个类或接口的声明部分应该按照类变量、静态初始化块、实例变量、实例初始化块、构造 器、方法的顺序出现,且用空行分隔 2.3.2 大括号 G.FMT.05 在条件语句和循环块中应该使用大括号 G.FMT.06 对于非空块状结构,左大括号应该放在行尾,右大括号应该另起一行 G.FMT.07 应该避免空块,必须使用空块时,应采用统一的大括号换行风格 2.3.3 缩进 G.FMT.08 使用空格进行缩进,每次缩进4个空格 2.3.4 行内容 G.FMT.09 每行不超过一个语句 2.3.5 行宽 G.FMT.10 行宽不超过120个窄字符 2.3.6 换行 G.FMT.11 换行起点在点号、双冒号、类型&、catch块中管道之前,在方法左括号、逗号、lambda箭头 和其左大括号之后 2.3.7 空行 G.FMT.12 减少不必要的空行,保持代码紧凑 2.3.8 水平空格 G.FMT.13 用空格突出关键字和重要信息 G.FMT.14 不应插入空格水平对齐 2.3.9 枚举 G.FMT.15 枚举常量间用逗号隔开,换行可选 2.3.10 switch语句 G.FMT.16 case语句块结束时如果不加break,需要有注释说明(fall-through) 2.3.11 注解 G.FMT.17 应用于类、方法、类属性的每个注解独占一行 2.3.12 注释排版 G.FMT.18 块注释的缩进级别应与上下文代码相同 2.3.13 修饰符 G.FMT.19 类和成员修饰符(如果存在)按Java语言规范建议的顺序显示 G.FMT.20 数字字面量应该设置合适的后缀, long 类型应该使用L作为后缀 3 编程实践 3.1 声明和初始化 G.DCL.01 每行声明一个变量 G.DCL.02 局部变量被声明在接近它们首次使用的行 G.DCL.03 禁止C风格的数组声明 G.DCL.04 避免枚举常量序号的产生依赖于ordinal() 方法 G.DCL.05 禁止将mutable对象定义为常量 3.2 数据类型 3.2.1 整数 G.TYP.01 进行数值运算时,避免整数溢出 G.TYP.02 确保除法运算和模运算中的除数不为0 3.2.2 浮点数 G.TYP.03 禁止使用浮点数作为循环计数器 G.TYP.04 需要精确计算时使用BigDecimal,不要使用float和double G.TYP.05 浮点型数据判断相等不要直接使用==,浮点型包装类型不要用equals() 或者 flt.compareTo(another) == 0 作相等的比较 G.TYP.06 禁止尝试与NaN进行比较运算,相等操作使用Double或Float的isNaN() 方法 3.2.3 字符串 G.TYP.07 不要在代码中硬编码用于表示换行、文件路径分隔的字符 G.TYP.08 不要依赖平台默认的字符编码方式,使用UTF-8 G.TYP.09 字符串大小写转换、数字格式化为西方数字时,必须加上Locale.ROOT 或Locale.ENGLISH G.TYP.10 字符与字节的互相转换操作,要指明正确的编码方式 G.TYP.11 内存中的敏感信息使用完毕后应立即清0 3.2.4 类型使用与转换 G.TYP.12 基本类型优于包装类型,注意合理使用包装类型 G.TYP.13 明确地进行类型转换,避免依赖隐式类型转换 G.TYP.14 在引用类型向下转换前用instanceof 进行判断 3.3 表达式 G.EXP.01 不要在单个的表达式中对相同的变量赋值超过一次 G.EXP.02 用括号明确表达式的操作顺序,避免过分依赖默认优先级 G.EXP.03 在条件表达式?:的第2和第3个操作数使用相同的类型 G.EXP.04 表达式的比较,应该遵循左侧倾向于变化、右侧倾向于不变的原则 G.EXP.05 禁止直接使用可能为null的对象,防止出现空指针引用 G.EXP.06 代码中不应使用断言(assert) 3.4 控制语句 G.CTL.01 不要在控制性条件表达式中执行赋值操作或执行复杂的条件判断 G.CTL.02 含else if分支的条件判断应在最后加一个else分支 G.CTL.03 switch语句要有default分支 G.CTL.04 禁止使用空的无限循环 3.5 方法 3.5.1 方法设计 G.MET.01 方法要简短 G.MET.02 不要使用已标注为@Deprecated的方法 3.5.2 方法参数与返回值 G.MET.03 不应把方法的参数当做临时变量 G.MET.04 谨慎使用可变数量参数的方法 G.MET.05 对于返回数组或者容器的方法,应返回长度为0的数组或者容器,代替返回null G.MET.06 Java 8使用Optional 代替null作为返回值或者可能的缺失值;禁止对Optional 对象赋值为 null 3.6 类、接口与面向对象编程 3.6.1 类 G.OBJ.01 应避免public且非final的成员变量定义 G.OBJ.02 不要在父类的构造方法中调用可能被子类覆写的方法 G.OBJ.03 构造方法如果有多个,尽量重用 G.OBJ.04 避免在无关的变量或无关的概念之间重用名字,避免隐藏(hide)、遮蔽(shadow)和遮掩 (obscure) G.OBJ.05 避免基本类型与其包装类型的同名重载方法 G.OBJ.06 覆写equals方法时,同时覆写hashCode方法 G.OBJ.07 子类覆写父类方法或实现接口时必须加上@Override注解 G.OBJ.08 正确实现单例模式 G.OBJ.09 使用类名调用静态方法,而不要使用实例或表达式来调用 3.6.2 接口 G.OBJ.10 接口定义中去掉多余的修饰词 G.OBJ.11 Java 8中可在接口中加上静态方法表示相关的工厂或助手方法 3.7 异常处理 G.ERR.01 不要通过一个空的catch块忽略异常 G.ERR.02 不要直接捕获受检异常的基类Exception G.ERR.03 不要直接捕获可通过预检查进行消除的RuntimeException ,如NullPointerException 、 IndexOutOfBoundsException 等 G.ERR.04 防止通过异常泄露敏感信息 G.ERR.05 方法抛出的异常,应该与本身的抽象层次相对应 G.ERR.06 在catch块中抛出新异常时,避免丢失原始异常信息 G.ERR.07 一个方法不应抛出超过5个异常,并在Javadoc的@throws标签中记录每个抛出的异常及其条 件 G.ERR.08 在finally块中不要使用return、break或continue使finally块非正常结束 G.ERR.09 不要调用System.exit() 终止JVM G.ERR.10 尽量消除非受检的异常,不应该在整个类上使用SuppressWarning 3.8 并发与多线程 3.8.1 可见性和原子性 P.02 避免数据竞争data race 3.8.2 锁 P.03 使用相同的顺序请求和释放锁来避免死锁 G.CON.01 对共享变量做同步访问控制时需避开同步陷阱 G.CON.02 在异常条件下,保证释放已持有的锁 G.CON.03 避免在持有锁时执行耗时或阻塞性的操作 G.CON.04 避免使用不正确形式的双重检查锁 G.CON.05 禁止使用非线程安全的方法来覆写线程安全的方法 3.8.3 线程API G.CON.06 创建新线程时需指定线程名 G.CON.07 使用Thread对象的setUncaughtExceptionHandler 方法注册未捕获异常处理者 G.CON.08 不要依赖线程调度器、线程优先级和yield() 方法 G.CON.09 采用Java 5提供的新并发工具代替wait() 和notify() G.CON.10 线程中断由业务代码来协作完成,慎用Thread.interrupt 方法 G.CON.11 禁止调用Thread.run() G.CON.12 禁止使用Thread.stop() 来终止线程 3.8.4 线程池 G.CON.13 避免不加控制地创建新线程,应该使用线程池来管控资源 G.CON.14 线程池中的线程结束后必须清理自定义的ThreadLocal 变量 3.9 泛型和集合 G.COL.01 方法的设计可优先考虑泛型 G.COL.02 优先使用泛型集合,而不是数组 G.COL.03 声明一个泛型类通过限定符限制可用的泛型类型 G.COL.04 不要在foreach 循环中通过remove() / add() 方法更改集合 3.10 输入输出 P.04 在多用户系统中创建文件时指定合适的访问许可 G.FIO.01 使用外部数据构造的文件路径前必须进行校验,校验前必须对文件路径进行规范化处理 G.FIO.02 从ZipInputStream中解压文件必须进行安全检查 G.FIO.03 对于从流中读取一个字符或字节的方法,使用int类型的返回值 G.FIO.04 防止外部进程阻塞在输入输出流上 G.FIO.05 临时文件使用完毕必须及时删除 3.11 序列化 G.SER.01 尽量避免实现Serializable接口 G.SER.02 实现Serializable接口的可序列化类应该显式声明serialVersionUID G.SER.03 序列化对象中的HashMap、HashSet或HashTable等集合禁止包含对象自身的引用 G.SER.04 不要序列化直接指向系统资源的句柄 G.SER.05 禁止序列化非静态的内部类 G.SER.06 序列化操作要防止敏感信息泄露 G.SER.07 防止反序列化被利用来绕过构造方法中的安全操作 G.SER.08 禁止直接将外部数据进行反序列化 3.12 外部数据校验 P.05 外部数据使用前必须进行合法性校验 G.EDV.01 禁止直接使用外部数据来拼接SQL语句 G.EDV.02 禁止使用外部数据构造格式化字符串 G.EDV.03 禁止向Runtime.exec() 方法或java.lang.ProcessBuilder 类传递外部数据 G.EDV.04 禁止直接使用外部数据来拼接XML G.EDV.05 防止解析来自外部的XML导致的外部实体(XML External Entity)攻击 G.EDV.06 防止解析来自外部的XML导致的内部实体扩展(XML Entity Expansion)攻击 G.EDV.07 禁止使用不安全的XSLT转换XML文件 G.EDV.08 正则表达式要尽量简单,防止ReDos攻击 G.EDV.09 禁止直接使用外部数据作为反射操作中的类名/方法名 3.13 日志 G.LOG.01 日志的记录,不要使用System.out 与System.err 进行控制台打印,应使用Facade模式的 日志框架 G.LOG.02 日志工具Logger类的实例必须声明为private static final或者private final G.LOG.03 日志必须分等级 G.LOG.04 非仅限于中文区销售产品禁止用中文打印日志 G.LOG.05 禁止直接使用外部数据记录日志 G.LOG.06 禁止在日志中保存口令、密钥和其他敏感数据 3.14 性能和资源管理 3.14.1 性能 G.PRM.01 将集合转为数组时使用Collection<T>.toArray(T[]) 方法;Java 11后使用 Collection<T>.toArray(IntFunction<T[]>) G.PRM.02 使用System.arraycopy() 或Arrays.copyOf() 进行数组复制 G.PRM.03 初始化集合时,如果可预估元素数量,应该指定初始化大小 G.PRM.04 不要在频繁调用的场景中对正则表达式进行重复预编译 3.14.2 资源管理 G.PRM.05 禁止创建不必要的对象 G.PRM.06 将对象存入HashSet,或作为key存入HashMap(或HashTable)后,必须确保该对象的 hashcode值不变 G.PRM.07 进行IO类操作时,必须在try-with-resource或finally里关闭资源 G.PRM.08 禁止使用主动GC(除非在密码、RMI等方面),尤其是在频繁/周期性的逻辑中 G.PRM.09 禁止使用finalize() 方法 G.PRM.10 不要创建临时变量作为return语句的返回值 3.15 平台安全 P.06 对外部对象进行安全检查时需要进行防御性拷贝 G.SEC.01 进行安全检查的方法必须声明为private或final G.SEC.02 编写自定义类加载器时必须调用超类的getPermission() 方法 G.SEC.03 加载外部JAR文件时,不要依赖URLClassLoader和java.util.jar提供的默认自动签名检查机制 G.SEC.04 使用安全管理器来保护敏感操作 3.16 其他 G.OTH.01 安全场景下必须使用密码学意义上的安全随机数 G.OTH.02 必须使用SSLSocket代替Socket来进行安全数据交互 G.OTH.03 禁止代码中包含公网地址 4 附录 附录A SQL注入相关特殊字符 附录B 命令注入相关特殊字符 附录C 敏感异常 附录D 常见敏感异常的匿名化处理 附录E 典型编码问题及相关规范条款 1 概述 1.1 背景 优秀的代码不仅仅是正确的,还应该是简洁、可维护、可靠、可测试、高效、可移植的。 编程是一种创 造性的工作。本规范用于引导软件开发人员形成好的编程习惯,编写出风格一致、容易阅读、高质量的 Clean Code,从而提升产品竞争力、软件研发效率。 1.2 目标和适用范围 本规范参考业界标准及实践,华为编程实践总结,为提高代码的可读性,可维护性和安全性,提供编程 指南,力争系统化、易使用、易检查。 本规范适用于公司使用Java语言(Java 8+)编写的自研代码,对开源代码的编程规范遵循要求参考《华 为Clean Code指导书》。 产品线可信使能部应参考本规范并结合产品具体情况对代码风格制订一致性实施要求。 对于本规范未描述的例外情况,如果存在争议,由可信软件工程技术委员会进行仲裁。 1.3 总体原则 程序需要在保证功能正确的前提下,满足可读、可维护、安全、可靠、可测试、高效、可移植的特征要 求,详见《华为Clean Code指导书》。 1.4 条款组织方式 每个条款一般包含如下标题、级别、描述等组成部分。条款内容中的“正例”表示符合该条款要求的代码 片段,“反例”表示不符合该条款要求的代码片段,但不一定造成程序错误的结果。 标题 描述本条款的内容。 规范条款分为原则和规则两个类别,原则可以评价规则内容制定的好坏并引导规则进行相应的调整;规 则是需要遵从或参考的实践。通过标题前的编号标识出条款的类别为原则或者规则。 标题前的编号规则参见《安全工程规范内容总纲》,其中'P'为单词Principle首字母,'G'为单词 Guideline的首字母。原则条款的编号规则为P.Number。规则的编号方式为G.Element.Number,其中 Element为领域知识中关键元素(本规范中对应的二级目录)的3位英文字母缩略语。Number是从1开 始递增的两位阿拉伯数字,不足两位时高位补0。 Element 目录Element 目录 NAM 命名CON 并发与多线程 CMT 注释COL 泛型和集合 FMT 格式FIO 输入输出 DCL 声明和初始化SER 序列化 TYP 数据类型EDV 外部数据校验 EXP 表达式LOG 日志 CTL 控制语句PRM 性能和资源管理 MET 方法SEC 平台安全 OBJ 类、接口与面向对象编程OTH 其他 ERR 异常处理 描述 对条款的进一步描述,描述条款的原理,配合正确和错误的代码例子作为示范。有的条款还包含一些规 则不适用的例外场景。 级别 规则类条款分为两个级别:要求、建议。 要求:表示产品原则上应该遵从,但可以按照具体的产品版本计划和节奏分期实现。 建议:表示该条款属于最佳实践,有助于进一步消解风险,产品可结合业务情况考虑是否纳入,但 要保证实施一致的代码风格。 2 代码风格 代码风格一般包含标识符的命名风格、注释风格及排版风格。一致的编码习惯与风格,会使代码更容易 阅读、理解更容易维护。 下列情况,应风格一致性原则优先: 修改开源代码、第三方代码时,应该遵守开源代码、第三方代码已有规范,保持风格统一。 直接基于Android原生操作系统接口界面的软件,如Android Framework,保持与安卓风格统一。 2.1 命名 标识符的命名要清晰、明了,有明确含义,使代码更容易理解。 2.1.1 标识符 G.NAM.01 标识符应由不超过64字符的字母、数字和下划线组成 【级别】 建议 【描述】 类别命名风格 接口,类,注解,枚举类型大驼峰,测试类加Test后缀,文件名为顶层类名.java 类的属性,局部变量,方 法,方法参数 小驼峰,测试方法可有下划线_ 静态常量,枚举值全大写,下划线分割 泛型类型变量 单个大写字母,可接一个数字或者接下划线加若干大写字母,例如E、T、 T2、E_IN、E_OUT、T_CONS 异常加后缀Exception 或Error ,例如AccessException 易混淆的字符推荐的替代标识符 数字0 与 O(大写的o)、D (大写的d) obj、dgt 数字1 与 I (大写的i)、 l (小写L) it、ln、line 数字2 与 Z (大写的z) n1、n2 数字5 与 S (大写的s) xs、str 数字6 与 e (小写的E) ex、elm 数字8 与 B (大写的b) bt、nxt n (小写的N) 与 h (小写的H) nr、head、first rn (小写的RN) 与 m (小写的M) mbr、item 单个大写字母一般用于表示泛型类型变量,单个字母容易与数字混淆(如数字0与字母O/D,数字1与字 符I/l等,参考表2),所以应避免单个字符命名的标识符。对于简单的循环变量(如i、j、k)和catch块 中的异常变量可以使用单个字符。其他场景不应该使用单字符命名,包括Lambda表达式中的变量。有 效标识符可由正则表达式“\w{2,64}”匹配。 变量名的长度应正比于它的生命范围及承担的职责。 标识符中不建议使用特殊前缀或后缀。对于Android代码,允许使用s或m作为前缀,其中s应表示静态 属性,m应表示非公共且非静态属性。 为了实现代码自注释的目的,命名时应尽量使用完整的单词组合,禁止使用非约定俗成的缩写,但常见 词以及业务线的领域词汇是允许的,例如response:resp,request:req,message:msg。 标识符命名时不要使用java标准库中的public标识符(如公共类、接口、package等)。 Java的命名风格以驼峰命名为主,缩写词也按单个单词处理,以提高代码的可读性,例如 XmlHttpRequest , newCustomerId , supportsIpv6OnIos() 。 表1 常用标识符类型的命名风格列表 表2 常见易混淆字符列表 表示元素索引、数字变量时,推荐使用fst、snd、start/end、from/to、mid、idx、pos、size、cap、 count、total等。 【反例】 【正例】 2.1.2 包名 G.NAM.02 包名中的字母应小写,包名以点号分隔层级 【级别】 建议 【描述】 包名仅能使用小写字母、数字、下划线,下划线仅在一些特殊情况使用,如包名以数字开头或是java中 保留关键字时,如: int_.example 、com.example._123name 。一层包路径可以是多个单词的简单连 接(不用下划线连接)。 所有的源文件都要设置一个具体的package。自研代码推荐以com.huawei 开头、终端芯片业务部以 com.hisilicon 开头,进一步按项目、模块划分子包,做好package的合理规划。对于开源项目(例 如harmonyos,maple)、商业合作等特殊原因,可不以com.huawei 开头。 【正例】 2.1.3 类、枚举和接口 G.NAM.03 类、枚举和接口名应采用大驼峰命名 【级别】 建议 【描述】 1. 类名、接口名通常是名词或名词短语,接口名还可以是形容词或形容词短语,都应采用大驼峰命 名。例如: ImmutableList (类)、List (接口)、Readable (接口)等。类名不应使用动 词,也应该避免类似Data , Info 这样的模糊词。 2. 测试类以被测类的名字开头,并以Test结尾,如, HashTest 或HashIntegrationTest 。 3. 抽象类命名时推荐以Abstract或Base开头。 【反例】 【正例】 String name_; String mName; String s_name; String kName; String newCustomerID; String XMLData; String fileName; String newCustomerId; String xmlData; package com.huawei.mobilecontrol.views; package xxx.yyy.v2; class marcoPolo {} interface TAPromotion {} class info {} 2.1.4 方法 G.NAM.04 方法名应采用小驼峰命名 【级别】 建议 【描述】 方法名通常是动词或动词短语,采用小驼峰命名,具体格式如下: get + 非布尔属性名(); is + 布尔属性名(); set + 属性名(); has + 名词/形容词(); 动词(); 动词 + 宾语(); 回调方法(callback)允许介词+动词形式命名,如: onCreate() 、onDestroy() 、toString() ; 对于布尔型方法,应该以表达是非意义的动词如is 、has 、can 、should 等开头。 【反例】 【正例】 【例外】 单元测试方法的命名可采用多种形式,本规范不做限制,同一项目建议采用统一的命名风格。 2.1.5 常量 本规范中的常量是指不可被修改静态的field和枚举常量。“不可被修改”需要同时满足以下两个条件: field的值/对象不可被修改为其他的值/对象; field为对象类型时,对象在初始化完成后其属性不能被修改。 常量定义的一般格式为: [访问修饰符] static final 类型 常量名 = 常量值; 常见的常量定义如下所示: class MarcoPolo {} interface TaPromotion {} class OrderInfo {} public boolean Finished() public void visible(boolean) public void DRAW() public void KeyListener(Listener) boolean next() public boolean isFinished() public void setVisible(boolean) public void draw() public void addKeyListener(Listener) boolean hasNext() G.NAM.05 常量名采用全大写单词,单词间以下划线分隔 【级别】 建议 【描述】 1. 常量命名,应该由全大写单词与下划线组成,单词间用下划线分隔,如CONSTANT_CASE。常量命 名要尽量表达完整的语义。 2. 不要使用魔鬼数字(难以理解的数字或字符串),用有意义的常量代替。SQL或日志的字符串,不 应视为魔鬼数字,不需定义为字符串常量; 可通过如下方式避免使用魔鬼数字: 如果有现成的API,不要定义数字,比如判断集合内元素是否为空时,不应该使用size() == 0 ,应使用isEmpty() 方法;比如时间的比较判断,用java.time 中的API。 有命名模式的可以用枚举类型,参见G.FMT.15的枚举的使用场景。 3. 不建议将所有的常量定义到一个常量类中,而是按功能对常量进行管理,或就近定义常量。 【反例】 不应定义NUM_FIVE或NUM_5这样无意义的常量,如果NUM_5被粗心大意地改为50或55等,将导致 NUM_5常量预期是5而实际是50或55。 【正例】 【例外】 对于Logger,Lock等类型的常量,不强制要求全大写字母。
No comments:
Post a Comment