代码编写规范

第一章、代码编写规范(强制要求)

1.1、代码中不要出现莫名其妙的数字

很多程序员在实现功能的时候会分配一个没有明确意义的数字,用于特定的目的,写完这个数字后没有即使去做注释,然后过两天当程序出现问题时,翻看代码碰到这个数字,连自己都忘了它的意义。当程序需要固定的数值参与时,给予特定的名称。

案例:

//错误的示范:
 for(let i = 0; i < 50; i++){
  //do something
 }
//正确的示范:
 let NUMBER_OF_STUDENTS= 50
 for(let i = 0; i < NUMBER_OF_STUDENTS; i++){
  //do something
 }

1.2、深度嵌套

有些程序需要使用到无数次的嵌套循环,看着看着就晕了,这时可以将所有循环提取到单独的函数中。假设有一个包含两层嵌套的数组,想要拿到最后一个数组的值?一般情况下开发者都会使用嵌套循环,但不是最佳的办法。可以尝试使用函数的反复调用来完成操作执行相同操作的,这样一来代码重复性更小,更易于阅读、重用性高。

对于单数组的多层循环,建议使用while(),注意控制退出条件,避免产生死循环

// 错误示范
 const array = [ [ ['Shoaib Mehedi'] ] ]
 array.forEach((firstArr) =>{
  firstArr.forEach((secondArr) => {
  secondArr.forEach((element) => {
  console.log(element);
  })
  })
 })
 
// 正确示范
 const array = [ [ ['Shoaib Mehedi'] ] ]
 const getValuesOfNestedArray = (element) => {
  if(Array.isArray(element)){
  return getValuesOfNestedArray(element[0])
  }
  return element
 }
 getValuesOfNestedArray(array)

1.2.1补充,尽量不要用死循环,给循环一个退出条件。

我们做业务,不是做竞赛,写代码的目标是简介、清晰、可维护。不要写花里胡哨,但是实际上特别难理解的代码。一个简洁清晰的代码,是你看一遍就能明白它在做什么的代码。

很多算法书都会推荐死循环,也会在示例里用死循环,因为算法领域里面,需要缩减行数来达到最简。我们做业务,代码死循环,只能kill这个服务。否则,只能堆栈溢出或者把资源耗完,这种都是代价很大的。这还不考虑,死循环如果影响了数据库,造成的不可逆的数据错误。所以,给循环一个结束条件,让它能正常的结束循环。不要用while(ture),然后在循环里if去break,如果if的判断失效,就没有能解开循环的方法了,把判定条件写在括号里。

1.2.2补充,不要省略if和for的大括号

我知道这种写法很酷,但是,别人读代码,改代码,解决本地不能调试,只能线上调试的bug的时候,一定骂你的。

1.2.3补充,优先判定,提早退出的原则

public String translateName(User user) {
    //好的写法
    if(Objects.isNull(user)) {
        return null;
    }
    if (StringUtils.isEmpty(user.getName())) {
        return null;
    }
    return tanlate(user.getName());
     
    //差点的写法。这样会造成多层循环
    //而且每个判定,你都要猜到底是true还是false
    if(Objects.nonNull(user)) {
        if (StringUtils.isEmpty(user.getName())) {
            return tanlate(user.getName());
        }
    }
}

1.2.4补充,防御性编程原则

你应该在方法内部判定完整,健壮。如果这个方法不会别的类用,你就声明private。对于public的方法,一定要多判定。因为你不知道,调用方会传什么参数进来。你现阶段看到可能入参,后面可能后面会变。最常见的例子,是你写的方法,现阶段入参一定不为空。后面业务调整,别人新加的代码调用你这个方法,这个值就是给你传空。一调就是空指针,他还得说你代码逻辑上有bug,不会说他业务调整导致的控制,甩锅给你。调你的方法,还要改你的代码,不要给他甩锅的机会。

1.3、注释

注释是一个老生常谈的话题,很多人不重视,为了快速完成项目忽略注释,导致后期修改和复用出现很大问题,得不偿失。

对于复杂的逻辑方法,应注明内部逻辑;对于方法应该写明方法的作用。

1.4、避免创建大函数

当一个函数或一个类需要实现的功能太多时,建议将其分成多个小的函数。

假设我们需要加减两个数字。我们可以用一个功能做到这一点。但是好的做法是将它们分为两部分。如果有单独的功能,则可以在整个应用程序中重复使用。

// 错误示范
 const addSub = (a,b) => {
  // add
  const addition = a+b
  // sub
  const sub = a-b
  // returning as a string
  return `${addition}${sub}`
 }
//正确示范
// add
 const add = (a,b) => {
  return a+b
 }
 // sub
 const sub = (a,b) => {
  return a-b
 }

1.5、重复代码

重复代码也是常见的问题,如遇到此情况,将重复代码提取到函数中。继续用上面的第2点“深层嵌套”中的例子来举例。

// 错误示范
 const array = [ [ ['Shoaib Mehedi'] ] ]
 array.forEach((firstArr) =>{
  firstArr.forEach((secondArr) => {
  secondArr.forEach((element) => {
  console.log(element);
  })
  })
 })
  
// 正确示范
 const array = [ [ ['Shoaib Mehedi'] ] ]
 const getValuesOfNestedArray = (element) => {
  if(Array.isArray(element)){
  return getValuesOfNestedArray(element[0])
  }
  return element
 }
 getValuesOfNestedArray(array)

1.6、变量命名

变量命名也是有讲究的,好的变量命名可以让代码更加通俗易懂,一般来说命名遵循以下标准,名称以小写字母开头,之后的每一个单词首字母都用大写,比如骆驼的大小写为camelCase函数和变量都必须遵循此规则。

示例代码:

let camelCase = ''const thisIsCamelCase = () => {
 //so something
}

 

1.7、for循环里面做rpc调用,或者是数据库交互

不要在循环里面做rcp调用和数据库交互,这样会造成系统性能和数据库压力。

1.8、方法体和类行数太多

一个方法体的代码行数最好不要超过100行,一个类文件不要超过1500行,太大的方法体和类,对于代码阅读来说都是一种灾难。

 

1.9、程序分层之间功能要明确

程序分层之后,各个层之间的功能要明确清楚,

controller层:数据校验,参数预处理,返回参数的封装;

server层:数据调用和数据集合操作,以及相关的逻辑处理判断操作逻辑代码,不要添加跟数据库相关的代码,跟数据库相关处理的功能交给dao层来处理;

dao层:只负责和数据库的数据交换。

1.10、抽象父类方法的抽取

公共抽象父类和泛型父类的抽取,尽量以解决业务复杂度的目标来进行抽取,或者是统一工具处理类的方式来定义,尽量不要再controller、server、mapper层使用,导致业务使用过程中的代码混乱,以及调用参数不明确等一系列问题。

1.10.1、补充,抽象父类和方法要想的长远一点

不要看见一点公用的部分,就想抽象。抽象的时候,就抽象得彻底一点。别抽象的方法里还有定制代码。别人想继承,又继承不了。

public abstract class AbstractUser() {
 
    private String userName;
     
    public void doSomething() {
        if ("admin".equals(userName)) {
            //做特殊操作
        else {
            //做常规操作
        }
    }
 
    abstract public Role getRole();
 
    public static final class Role{
 
    }
}

 

 

1.11、类的定义的单一职责的原则

对于创建的java类对象,应该尽量遵守单一职责的原则,一个类只实现某一种或者是某一类相关的功能操作,例如:人员数据的增删改查,人员信息的统计,人员信息和角色数据的聚合。

1.12、服务之间调用规范

业务上涉及到服务内部或多系统之前的多业务数据处理业务,应该在程序设计时考虑到服务间调用的耦合关系,尽量降低服务间的耦合情况,能在一个类或者是一个方法里处理的单一功能,尽量在一个方法体或者类里面处理完。对于处理不了或者是本身就需要多服务调用的部分,

应采用例如:切片编程、链式编程、事件通知、事件回调、mq消息队列、spi等方式实现服务间的调用解耦。服务解耦后如果存在数据一致性问题,也应该充分的考虑数据的回滚等相关的操作异常的补偿机制。

注:对于耦合处理过的代码,应该在代码注释中明确的注明响应的隔离机制,及影响到的服务范围,降低代码升级带来的风险。

1.13、功能规范

功能设计之初,应该考虑到功能与服务内部其他功能之间的关联关系,和服务后期迭代升级过程中的难度和代价。对于独立性较强的功能服务,应该从原有服务相对的抽离出来,比如创建较为独立的模块(model)或者是建立相对独立的文件夹系统(package),保证独立功能的相对独立性。

1.14、统一异常处理

对于所有的异常,应该有相对应的异常类,异常类可以使用公有jar包里的提倡类,但是全局异常捕获必须在自己的方法里实现,不能直接使用共有异常捕获类。

捕获异常一定要用自己写的全局异常处理,不要用切面。

1.15、参数校验

参数校验统一使用@Validated 进行简单的传参校验,在统一异常处理里处理异常报错信息。

1.16、rest接口定义规范

新增数据接口,统一不传id,id由后端统一生成;

更新数据接口传全量数据,但是如果只修改关联数据,则关联数据应该有单独的更新结构;

删除接口默认只传id。

1.17、数据库脚本定义规范

每次服务存在sql脚本的变动,都需要在服务的doc目录下新建一下【日期+分支名】的文件夹,文件夹在保存脚本文件,脚本文件中应该尽量添加注释,说明功能和备注信息。

1.18、尽量不要使用Lambda 表达式

虽然Lambda 表达式能提升代码的编程效率,但是会导致在代码出现异常时,寻找报错行数造成一定的困扰,增加排除问题的难度。

1.19、rpc调用

尽量把所有的外部调用(grpc、dubbo、rest)放到统一的包或者是类里面,这样可以更加方便的做到外部调用管理。特别是通过httputils直接访问url的形式。很难做到调用链的溯源。

posted @ 2024-01-31 15:55  扰扰  阅读(3)  评论(0编辑  收藏  举报