代码规范
引言
在软件开发中,代码规范是一组约定俗成的准则,它们指导开发人员编写结构清晰、易读、易维护的代码。
试想一下,一个几十万行代码的项目,存在几种不同的代码规范,阅读起来是什么感受?连代码缩进使用空格还是 Tab 都能引发不少开发者的争论,可以说统一代码规范是非常重要的事情。
统一代码规范除了刚才所说的两点外,还有其他好处:
- 规范的代码可以促进团队合作
- 规范的代码可以降低维护成本
- 规范的代码有助于 code review(代码审查)
- 养成代码规范的习惯,有助于程序员自身的成长
当团队的成员都严格按照代码规范来写代码时,可以保证每个人的代码看起来都像是一个人写的,看别人的代码就像是在看自己的代码(代码一致性),阅读起来更加顺畅。更重要的是我们能够认识到规范的重要性,并坚持规范的开发习惯。
命名风格
1.严禁使用中文或者中文拼音进行重命名(单词最好控制在三个以内)。
说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,纯拼音命名方式更要避免采用。
说明:beijing/ shanghai 等国际通用的名称,可视同英文。
正例 ✅ :
int studentCount;
void calculateAverageScore() {}
String username;
反例 ❌ :
xueGuanGuanLi[学管管理] / getChengJi() [成绩]
星级评定:⭐️⭐️⭐️
2.方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵 从驼峰形式。
正例 ✅ :
localValue / getHttpMessage() / inputUserId
反例 ❌ :
Local\_Value(不符合 lowerCamelCase 风格)
get\_http\_message()(使用下划线而不是驼峰形式)
InputUserID(首字母大写,不符合 lowerCamelCase 风格)
星级评定:⭐️⭐️⭐️
3.杜绝完全不规范的缩写,避免望文不知义,如果命名能够很清晰的表达出该方法或者该类名的含义,这也可以减少写注释。
反例 ❌ :AbstractClass“缩写”命名成 AbsClass;condition“缩写”命名成 condi,此类随意缩写严重 降低了代码的可阅读性。
星级评定:⭐️⭐️⭐️
4.为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词 组合来表达其意。
反例 ❌ :
public List<int[]> getThem(){
List<int[]> list1 = new ArrayList<>();
for (int[] x : theList){
if (x[0] == 4){
list1.add(x);
}
}
return list1;
}
theList是什么类型的东西?
theList下标条目的意义是什么?
值4的意义是什么?
我怎么使用返回的列表?
正例 ✅ :
public List<int[]> getFlaggedCells(){
List<int[]> flaggedCells = new ArrayList<>();
for (int[] cell : gameBoard){
if (cell[STATUS_VALUE] == FLAGGED){
flaggedCells.add(cell);
}
}
return flaggedCells;
}
比方说我们在开发一种扫雷的游戏,我们发现,盘面是名为gameBoard的二维数组
盘面上每个单元格都用一个简单数组表示
而该状态值为4表示"已标记"。只要改为有意义的名称,代码就会变得更加清晰
星级评定:⭐️⭐️⭐️
4.常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例 ✅ :MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
反例 ❌ :MAX_COUNT / EXPIRED_TIME
星级评定:⭐️⭐️⭐️
6.如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例 ✅ :
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
星级评定:⭐️⭐️⭐️
常量定义
- 不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
反例:String key = “Id#taobao_” + tradeId; cache.put(key, value); // 缓存 get 时,由于在代码复制时,漏掉下划线,导致缓存击穿而出现问题
星级评定:⭐️⭐️⭐️
- 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。 正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。
星级评定:⭐️⭐️⭐️
代码格式
1.函数设计要职责单一
举个例子:我们来校验下我们的额一些用户属性,当然这个校验就省略成判断是否为空了
正例 ✅ :
public void validateName(String name) {
if (name == null || "".equals(name.trim())) {
throw new IllegalArgumentException("姓名不能为空");
}
}
public void validatePhone(String phone) {
if (phone == null || "".equals(phone.trim())) {
throw new IllegalArgumentException("手机号不能为空");
}
}
public void validateEmail(String email) {
if (email == null || "".equals(email.trim())) {
throw new IllegalArgumentException("邮箱不能为空");
}
}
反例 ❌ :
public void validateUser(String userName, String phone, String email, String password) {
if (userName == null || "".equals(userName.trim())) {
throw new IllegalArgumentException("用户名不能为空");
}
if (password == null || "".equals(password.trim())) {
throw new IllegalArgumentException("密码不能为空");
}
if (phone == null || "".equals(phone.trim())) {
throw new IllegalArgumentException("手机号不能为空");
}
if (email == null || "".equals(email.trim())) {
throw new IllegalArgumentException("邮箱不能为空");
}
}
星级评定:⭐️⭐️⭐️
2.单个方法的总行数不超过 80 行(大约两个显示屏的高度)。
说明:
函数的代码太多和太少,都是不太好的
行数太多:
一个方法上千行,一个函数几百行,逻辑过于繁杂,阅读代码的时候,很容易就会看了后面忘了前面
行数太少:
在代码总量相同的情况下,被分割成的函数就会相应增多,调用关系就会变得更复杂,阅读某个代码逻辑的时候,需要频繁地在n多方法或者n多函数之间跳来跳去,阅读体验也不好。
星级评定:⭐️⭐️⭐️
3.避免函数或方法参数过多
说明:函数包含3、4个参数的时候还是能接受的,大于等于5个的时候,我们就觉得参数有点过多了,会影响到代码的可读性,使用起来也不方便。
public void updateBookshelf(String userId,String deviceId,String platform,String version,String bookshelf){
```
}
public void updateBookshelf(Object object){
```
}
星级评定:⭐️⭐️⭐️
4.单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
1)第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
2)运算符与下文一起换行。
3)方法调用的点符号与下文一起换行。
4)方法调用中的多个参数需要换行时,在逗号后进行。
5)在括号前不要换行,见反例。
反例 ❌ :
public void addFruits(){
List<String> fruits = new ArrayList<>();
fruits.add("Apple").add("Orange").add("Banana").add("Pear").add("Mango").add("Grape");
}
星级评定:⭐️⭐️⭐️
5.Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
正例 ✅ :“test”.equals(object);
反例 ❌ :object.equals(“test”);
星级评定:⭐️⭐️⭐️
6.防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
反例 ❌ :public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
2) 数据库的查询结果可能为 null。
3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
星级评定:⭐️⭐️⭐️
推荐代码质量管理工具:SonarLint
SonarLint是一款集成开发环境(IDE)插件,用于帮助开发人员在编写代码时进行静态代码分析和质量检查。
它能够在编码过程中即时检测和标记出潜在的代码缺陷、安全漏洞和代码异味(code smells),并提供相应的修复建议。
SonarLint提供了对多种编程语言的支持,包括但不限于C、C++、Java、JavaScript、TypeScript、Python、C#、Kotlin、Ruby、HTML和PHP。
它与多个常见的IDE集成,如IntelliJ IDEA、CLion、WebStorm、PHPStorm、PyCharm、Rider、Android Studio和RubyMine。
代码检查结果如下,可以在服务器中定制代码检查规则,将常用的规则优先级提高,一些不必要的规则优先级降低。
当然还有很多这种代码质量检测插件,可以选择适合自己的插件进行安装
代码注释:
说明:注释应该着重描述“做了什么”而不是“怎么做”。
好的代码是不需要写注释的。其实这种说法有点片面
我们平时强调的代码规范、项目规范、重构等等,就是为了减少沟通,提高开发效率
写注释的目的也是为了让代码更加容易理解,出问题了,也能快速定位问题,从而解决问题。
所以不是不写注释,而是不写垃圾注释。
正例 ✅ :
/**
* 将map对象转为url参数
* @param obj
* @return
*/
public static String objToUrlParams(Object obj) {}
反例 ❌ :
/**
* 将包含键值对的对象转换成url参数
* 对于每一个键值对,如果值为null或者空字符串,则不添加到url参数中
* 将键值对中的键和值按照URL参数的格式拼接起来,并以""&""符号分隔
* 最终返回的字符串形式为:key1=value1&key2=value2&key3=value3
* @param obj
* @return
*/
public static String objToUrlParams(Object obj) {}
多使用设计模式
使用常用的设计模式进行开发,可以重用代码、让代码更容易被他人理解、保证代码可靠性。
以下是各个模式的关键点:
单例模式:某个类只能有一个实例,提供一个全局的访问点。
简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。
适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
装饰模式:动态的给对象添加新的功能。
代理模式:为其他对象提供一个代理以便控制这个对象的访问。
亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
观察者模式:对象间的一对多的依赖关系。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
中介者模式:用一个中介对象来封装一系列的对象交互。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
Code Review 代码审查
代码审查是指让其他人来审查自己代码的一种行为。
审查有多种方式:例如结对编程(一个人写,一个人看)或者统一某个时间点大家互相做审查(单人或多人)。
代码审查的目的是为了检查代码是否符合代码规范以及是否有错误,另外也能让评审人了解被审人所写的功能。
经常互相审查,能让大家都能更清晰地了解整个项目的功能。
当然,代码审查也是有缺点的:一是代码审查非常耗时,二是有可能引发团队成员争吵。
参考:
评论区