前言
做开发也有了一段时间,好的代码和烂的代码也看了很多,所以想记录下来平日里面自己开发的一些规范,方便后续其他人阅读学习或借鉴。之前有读过《代码整洁之道》,感觉受益匪浅,里面讲的很多内容都是日常会犯的一些小毛病,而且书中教了大家该如何让代码看起来更舒服,细节决定了更多事情,如果一个人写代码的风格换来换去,只会让这一片代码受到污染,即使是小的空格和空行,也需要多加注意。先记录下来,慢慢更新吧!
或许是强迫症还是洁癖使然,让我对代码规范比较敏感
例如这里是否多个空行,那里是否少个空格
有问过其他同事,他们貌似对这些并无所谓,觉得能开发出来就好
开门见山
1. 空格
所有特殊符号前后都应有空格,最常见的可以直接使用 idea 自带的代码整理,自动帮忙规范。
规定:
- 定义变量的等于号前后都需要有空格
- 循环或者IF这种特殊词与括号都需要有空格来隔离。
String name="example";
int age= 1;
int age =1;
if(A ==B)
if(A<B)
String name = "example";
int age = 1;
if (A == B)
2. 类开始和结束的空行
有很多人喜欢类开始的时候,直接开始写代码,在结束的时候又莫名奇妙多出来很多空行,亦或是在分割方法的时候没有空行,或者多个空行,这样都会让代码显得很不规范。
规定:
- 在新的类写代码时,应先空一行,再开始编写代码
- 在类结束时,应该与上一个最近的方法紧贴,没有空行
- 每个方法之间只且需要一个空行来隔离
- 不同类型的变量应该用空行来隔离
- 变量与方法之间应该用空行来隔离
- 如果出现
List
类型的变量,应该以 xxxList 为命名,取消类似英文复数的定义,比如pages等,这种很容易让人引起歧义- 如果出现
Map
类型的变量,应该以 xxxMap 为命名
public class ApiRequestTotal extends BaseBasicDashboardCalculate {
private static final String NAME = "example";
private String name;
private String type;
@Override
public Map<String, Object> getConditions() {
Map<String, Object> map = new HashMap<>();
map.put("finalDecisionCode", "Accept");
return map;
}
@Override
public Map<String, Object> getNestedConditions() {
return new HashMap<>();
}
@Override
public BasicDashboardMetricTypeEnum getMetricType() {
return BasicDashboardMetricTypeEnum.API_REQUEST_TOTAL;
}
}
public class ApiRequestTotal extends BaseBasicDashboardCalculate {
private static final String NAME = "example";
private String name;
private String displayName;
@Override
public Map<String, Object> getConditions() {
Map<String, Object> map = new HashMap<>();
map.put("finalDecisionCode", "Accept");
return map;
}
@Override
public Map<String, Object> getNestedConditions() {
return new HashMap<>();
}
@Override
public BasicDashboardMetricTypeEnum getMetricType() {
return BasicDashboardMetricTypeEnum.API_REQUEST_TOTAL;
}
}
3. 属性和变量的定义
这块的定义其实是很糟糕的,经常看到很多代码,打开一个类的时候,迎头一棒全是定义,导致要一直往下翻找代码,且定义的很不规范,一样类型的内容应该遵守从上到下的结构。
规定:
- 顺序规范应遵从:public 常量 -> private 常量 -> private 属性
- 同一种类型的内容应该放在同一块去定义,且没有空行分割
- 不同类型的内容应该有空行进行分割
- 常量定义的名字都应该遵从全部大写,且使用下划线进行分割
@Resource
private BasicDashboardCalculateExecutor calculateExecutor;
public static final String CACHE_HOUR_KEY = "BasicDashboardWarningTask_Hour";
@Resource
private CacheService cacheService;
private static final String CACHE_NOTICE_KEY = "BasicDashboardWarningTask_Notice";
private static final List<String> ENABLE_METRICS = Stream.of(
BasicDashboardMetricTypeEnum.SCAN_TOTAL,
BasicDashboardMetricTypeEnum.SCAN_DENY_RATIO,
BasicDashboardMetricTypeEnum.SCAN_REVIEW_RATIO,
BasicDashboardMetricTypeEnum.API_REQUEST_TOTAL
).map(BasicDashboardMetricTypeEnum::name).collect(Collectors.toList());
@Resource
private WarningConfigCache cache;
public static final String CACHE_HOUR_KEY = "BasicDashboardWarningTask_Hour";
private static final String CACHE_NOTICE_KEY = "BasicDashboardWarningTask_Notice";
private static final List<String> ENABLE_METRICS = Stream.of(
BasicDashboardMetricTypeEnum.SCAN_TOTAL,
BasicDashboardMetricTypeEnum.SCAN_DENY_RATIO,
BasicDashboardMetricTypeEnum.SCAN_REVIEW_RATIO,
BasicDashboardMetricTypeEnum.API_REQUEST_TOTAL
).map(BasicDashboardMetricTypeEnum::name).collect(Collectors.toList());
@Resource
private CacheService cacheService;
@Resource
private WarningConfigCache cache;
@Resource
private BasicDashboardCalculateExecutor calculateExecutor;
4. 注释的使用
注释真的是一个很重要的东西,在写这一段的时候我有在思考,为什么注释五花八门,很多人都写不好,注释的意义到底是什么?我认为注释的意义在于让别人看懂这段东西的含义,但是现在注释存在着很多问题,可能他会影响本身代码的整洁性,亦或是存留在这里多年没有被人更新,最后导致最新的这块代码早已和之前写的注释互不相关,让人揪心。在好的代码中,应该通过命名来告诉他人这个是做什么的,在名字中即可提现具体的含义,注释不该被大量定义、解释重复的内容,这会造成大量的代码污染。
规定:
- 不得在行尾添加注释,不管什么注释都应该另起一行,尤其是
//
这种注释//
注释开始后,应该有一个空格,然后再继续写注释,双斜杠与文字之间不能紧连着- 注释也属于一块代码或者一个方法的整体,不能算作空行,例如方法之间要有空行,如果要在方法上写注释,额外起一行
- 注释应该频繁更新,不要造成注释与代码含义不同的情况,在更新代码的同时去更新注释
- 注释不该去解释重复的内容,例如
private String name;
,上边就不需要写注释了,经常有同事在上边还要写一个名字
意思的注释,感觉很奇怪- 尽可能减少注释,注释本身就不该被大量编写,好的代码在命名上即可体现出它所要干的事情
- 在编写
TODO
时,应保持以下结构// TODO | 存储至数据库
private String displayName; //展示名 无意义的注释、不该在行尾、没有空格分割
/**
* 展示名 无意义的注释
*/
private String displayName;
/**
* every hour/day, for example: time is 2023/x/x xx:00:00 00.00.00
* while storage all metric value in mysql when after aggregation
*/
@Scheduled(cron = "0 0 * * * ?")
public void hourTask() {
...
}
// TODO | async consumer trigger
5. 代码中的空行
代码中的空行是指在某一个方法中空行的使用,在代码编写习惯里面,经常会有人从上到下写完一个空行都没有,我的评价是连贯,太xx的连贯了,条理清晰,逻辑通畅,全文从上到下,我是没办法维护哈哈哈。这种代码看上去就像是一篇没有逗号和句号的文章一样,让人喘不上来气,也看不懂他究竟要做什么。更有甚者如果这个方法超过了100行将是地狱难度,所以应该在某个部分完成后,适当的使用空行来作为分割线,告诉后来人这里的代码告一段落,下一段要干什么事情。
规定:
- 方法应适当减少行数,尽可能保持在20行以内为最佳
- 多重复杂的代码应该对其进行拆分,在一个部分完成后,使用空行来分割
- 空行应只有一行,不要为了分割多搞好几个空行
- 在方法开始后直接写代码,不要有空行;代码写完后,也不要有空行
- 在最终即将要
return
什么内容的时候,上边需要有一个空行来承接(前提是之前有很多的大量代码逻辑)- 在某个
if
或者某个循环结束时,可以考虑在其后边增添一个空行
public Response<String> execute(ConsoleListAddCmd cmd){
StringBuffer rosterDefineName = new StringBuffer();
StringBuffer rosterDefineCode = new StringBuffer();
rosterDefineCode.append("console");
rosterDefineName.append("CONSOLE");
if(cmd.getListType()==1){
rosterDefineCode.append("_black");
rosterDefineName.append("黑名单");
}
if(cmd.getListType()==3){
rosterDefineCode.append("_white");
rosterDefineName.append("白名单");
}
//appMap 所有渠道
List<Map<String, Object>> list = commonService.getAPPs();
Map<String,String> appMap = new HashedMap();
for (Map<String, Object> map : list) {
appMap.put(map.get("name").toString(),map.get("dName").toString());
}
appMap.put("all","全部渠道");
rosterDefineCode.append("_"+cmd.getApp()).append("_"+cmd.getDataType());
rosterDefineName.append(appMap.get(cmd.getApp())+cmd.getDataType().toUpperCase());
List<RosterDefineDO> rosterDefineDOS = rosterDefineRepository.selectValidAll();
List<RosterDefineDO> collect = rosterDefineDOS.stream().filter(p -> p.getCode().equals(rosterDefineCode.toString())).collect(Collectors.toList());
Response addRosterDefineRes = new Response();
...
}
protected void sendMessage(List<String> messages) {
List<WarningUser> warningUserList = cache.getWarningUserList();
StringBuilder stringBuilder = new StringBuilder();
AtomicInteger times = new AtomicInteger(1);
messages.forEach(message -> {
stringBuilder.append(times.get())
.append(". ")
.append(message)
.append("\n");
times.getAndAdd(1);
});
Map<WarningTypeEnum, List<WarningUser>> collect = warningUserList.stream()
.collect(Collectors.groupingBy(warningUser -> WarningTypeEnum.valueOf(warningUser.getWarningType())));
String result = stringBuilder.toString();
if (StringUtils.isNotEmpty(result)) {
mailWarningSender.execute(result, collect.get(mailWarningSender.getWarningType()));
dingWarningSender.execute(result, collect.get(dingWarningSender.getWarningType()));
}
}
6. 注解的使用
在类上的注解,经常使人感觉难受,每个人都有不同定义的顺序,有时候经常出现乱塞,乱使用的情况,导致影响整体美感。应从下到上根据关联关系的重要性进行排序,使其让它更有意义。
规定:
- 注解的使用应保持从下到上的关联重要性排序
- 如果不确定关联重要性可根据注解长度进行排序
- 不要定义没有意义的注解,或定义不使用的注解
注: 第二条额外解释,例如在
SpringBoot
项目中,定义某个Service
时,@Service
注解应该离类最近,其次是其他的条件,然后是日志
@EnableScheduling
@SpringBootApplication
@Import(cn.hutool.extra.spring.SpringUtil.class)
public class StartApplication {
// @SpringBottApplication 应该与类最近
}
@Slf4j
@EnableScheduling
@MapperScan(basePackages = "com.domcer.infrastructure.mapper")
@SpringBootApplication(scanBasePackages = {"com.domcer"})
public class AthenaApplication {
}
@RequestMapping("/api/alert/config")
@RestController
public class AlertConfigController {
}
@Slf4j
@ConditionalOnClass(RedisOperations.class)
@Service
public class RiskEventStatServiceImpl implements RiskEventStatService {
}
7. 代码复杂度规范
复杂度在 sonar
中一直是个比较有意思的内容,它又名为 complex
,在 sonar
中规范一个方法中复杂度不可大于 15 以上。而计算方式并非是统计用到了多少个 IF 或者 FOR循环,当你在使用两个 IF 嵌套起来时,他们复杂度计算方式为 1+2=3
,所以在越深的层级里面,它所代表的复杂度权重就越高。应极力避免复杂度的出现,在公司修复标准产品线时,曾看见过约 200 复杂度的长方法,这种代码阅读、维护都需要消耗大量的成本,得不偿失。尽量拆解成各类小方法,使其具有唯一性,一个方法只敢一件事情,让方法共用,也可以让代码看着更简洁。
规定:
- 在同一个方法中,复杂度不可超过 15 以上
- 在同一个方法中,最多仅允许出现 3 层深度结构
- 在同一个方法中,请尽量减少
else
单独出现- 在同一个方法中,请尽可能保证代码行数在 25 以内
public static void main(String[] args) {
if (true) {
// do sth.
if (true) {
// do sth.
if (true) {
// do sth.
} else if (true) {
// do sth.
}
} else if (true) {
// do sth.
} else {
// do sth.
}
else {
// do sth.
}
} else {
// do sth.
}
}
public static void main(String[] args) {
if (true) {
// do sth.
return;
}
// do sth.
}
8. 方法的规范
有看过之前很多代码,很隐晦又难懂,好的方法应该如同变量一样,在名字上就能体现出它要做的事情,而且应该尽职尽责,何为尽职尽责?它应只做这一件事情,且只做这件事情,并把这件事情完整的做完,这才叫尽职尽责。往往有很多人抽离方法的时候,总是到一半就结束了,然后再通过其他代码去加工,显得特别奇怪。方法名也应该尽可能的表达出你要做的事情,不要使用简写等,一个好的方法让人一看就懂,不需要再点进去看具体写了什么代码,让阅读更加迅速。并且,在方法的入参上应该尽可能的减少,在 sonar
中,方法入参最大规定为 7 个,但是我认为,应该尽可能保持在 4 个左右为最佳,尽可能减少入参所带来不必要的成本。
规定:
- 方法应尽职尽责,只做一件事,且只做这件事,并做完这件事
- 方法名在定义上应保证可阅读,不管名字多长,只要完整的表达出即可
- 方法的入参应尽可能保证在 4 以下,以避免后续的维护成本
- 方法中的代码应该短小精悍,避免大量的糟糕代码出现,抽出可隔离的代码
public void test(A, B, C, D, E, F, G) {
}
public void storageTest(A, B) {
// do sth.
}
public void addCoins(Long coins) {
this.coins += coins;
}
9. stream流使用规范
10. 内部变量名字规范
all, list
后话
代码风格只是为了让代码看的更舒服,方便后续阅读,也可以让自己有一种风格,让别人看见代码就知道是谁写的。一个人的风格可以永久的保持下去,不管是到未来还是以后,都是对一些细节的较真。如果一个人写代码没有任何的风格,随便定义或修改代码,只会让代码越来越糟糕。代码的开发往往是小成本,在后期的更新维护才需要大量的成本投入。所以我这个人比较神经,包括但不限于:
- 经常看其他人代码
- 经常看自己已经写过的代码
- 经常优化自己的代码
- 经常翻找是否有冗余的内容(例如最上方多余的import或者变量定义)
- 经常在想如何更简洁
所以,尽可能保持自己的代码简洁吧!让自己和别人看见你的代码时,都会感觉舒爽!
未完结
1 条评论
??