VIP编程规范

VIP编程规范

格式规约/OOP规约

Rule 4. 【推荐】类内方法定义的顺序,不要“总是在类的最后添加新方法”

  • 类内方法定义的顺序依次是:构造函数 > (公有方法 > 保护方法 > 私有方法) > getter/setter 方法。
    如果公有方法可以分成几组,私有方法也紧跟公有方法的分组。

  • 当一个类有多个构造方法,或者多个同名的重载方法,这些方法应该放置在一起。其中参数较多的方法在后面。

  • 作为调用者的方法,尽量放在被调用的方法前面。

方法设计

Rule 1.【推荐】方法的长度不要超过100行,如果超过需要重构

Rule 5.【推荐】方法参数最好不要超过4个,超过4个时需要抽取参数类

解决方案

  • 如果多个参数同属于一个对象,直接传递对象。
    例外: 你不希望依赖整个对象,传播了类之间的依赖性。
  • 将多个参数合并为一个新创建的逻辑对象。
    例外: 多个参数之间毫无逻辑关联。
  • 将函数拆分成多个函数,让每个函数所需的参数减少。

Rule 6.【推荐】下列情形,需要进行参数校验

  1. 调用频次低的方法。
  2. 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,代价更大。
  3. 需要极高稳定性和可用性的方法。
  4. 对外提供的开放接口,不管是RPC/HTTP/公共类库的API接口。
    如果使用Apache Validate 或 Guava Precondition进行校验,并附加错误提示信息时,注意不要每次校验都做一次字符串拼接。

Rule 7.【推荐】下列情形,不需要进行参数校验

  1. 极有可能被循环调用的方法。
  2. 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错 误不太可能到底层才会暴露问题。
    比如,一般DAO层与Service层都在同一个应用中,所以DAO层的参数校 验,可以省略。
  3. 被声明成private,或其他只会被自己代码所调用的方法,如果能够确定 在调用方已经做过检查,或者肯定不会有问题则可省略

Rule 12.【强制】正被外部调用的接口,不允许修改方法签名,避免对接口 的调用方产生影响

只能新增新接口,并对已过时接口加@Deprecated注解,并清晰地说明新接口是什么。

【推荐】参数列表中请尽量不取用true、false类参数

避免因为调用者不明确导致的问题

返回值

  • 返回调用函数方应该知道的消息,慎用void
  • 当函数中有很多种错误信息时,使用和调用方协调好的ErrorCode或Exception
  • 返回空的集合时使用Collections.empty***方法,不要直接返回null(建议)

【推荐】下列情形,需要进行参数校验

  • 调用频次低的方法。
  • 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,代价更大。
  • 需要极高稳定性和可用性的方法。
  • 对外提供的开放接口,不管是RPC/HTTP/公共类库的API接口。
    tips:如果使用Apache Validate 或 Guava Precondition进行校验,并附加错误提示信息时,注意不要每次校验都做一次字符串拼接。
//WRONG
Validate.isTrue(length > 2, "length is "+keys.length+", less than 2", length);
//RIGHT
Validate.isTrue(length > 2, "length is %d, less than 2", length);

【推荐】下列情形,不需要进行参数校验

  • 极有可能被循环调用的方法。

  • 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。
    tips: 比如,一般DAO层与Service层都在同一个应用中,所以DAO层的参数校验,可以省略。

  • 被声明成private,或其他只会被自己代码所调用的方法,如果能够确定在调用方已经做过检查,或者肯定不会有问题则可省略。
    tips: 即使忽略检查,也尽量在方法说明里注明参数的要求,比如vjkit中的@NotNull,@Nullable标识。

【推荐】禁用assert做参数校验

  • assert断言仅用于测试环境调试,无需在生产环境时进行的校验。因为它需要增加-ea启动参数才会被执行。而且校验失败会抛出一个AssertionError(属于Error,需要捕获Throwable)

  • 因此在生产环境进行的校验,需要使用Apache Commons Lang的Validate或Guava的Precondition。
    #【推荐】返回值可以为Null,可以考虑使用JDK8的Optional类

  • 不强制返回空集合,或者空对象。但需要添加注释充分说明什么情况下会返回null值。

  • 本手册明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。

类设计

【推荐】类的长度度量

类尽量不要超过300行,或其他团队共同商定的行数。

对过大的类进行分拆时,可考虑其内聚性,即类的属性与类的方法的关联程度,如果有些属性没有被大部分的方法使用,其内聚性是低的。

【推荐】 构造函数如果有很多参数,且有多种参数组合时,建议使用Builder模式

Executor executor = new ThreadPoolBuilder().coreThread(10).queueLenth(100).build();

【推荐】构造函数要简单,尤其是存在继承关系的时候

可以将复杂逻辑,尤其是业务逻辑,抽取到独立函数,如init(),start(),让使用者显式调用。

Foo foo = new Foo();
foo.init();

【推荐】 内部类的定义原则

当一个类与另一个类关联非常紧密,处于从属的关系,特别是只有该类会访问它时,可定义成私有内部类以提高封装性。

另外,内部类也常用作回调函数类,在JDK8下建议写成Lambda。

内部类分匿名内部类,内部类,静态内部类三种。

  1. 匿名内部类 与 内部类,按需使用:

在性能上没有区别;当内部类会被多个地方调用,或匿名内部类的长度太长,已影响对调用它的方法的阅读时,定义有名字的内部类。

  1. 静态内部类 与 内部类,优先使用静态内部类:

非静态内部类持有外部类的引用,能访问外类的实例方法与属性。构造时多传入一个引用对性能没有太大影响,更关键的是向阅读者传递自己的意图,内部类会否访问外部类。

非静态内部类里不能定义static的属性与方法。

【强制】POJO类必须覆写toString方法。

便于记录日志,排查问题时调用POJO的toString方法打印其属性值。否则默认的Object.toString()只打印类名@数字的无效信息。

【强制/推荐】hashCode和equals方法的处理,遵循如下规则:

13.1【强制】只要重写equals,就必须重写hashCode。 而且选取相同的属性进行运算。

13.2【推荐】只选取真正能决定对象是否一致的属性,而不是所有属性,可以改善性能。

13.3【推荐】对不可变对象,可以缓存hashCode值改善性能(比如String就是例子)。

13.4【强制】类的属性增加时,及时重新生成toString,hashCode和equals方法。

【强制】使用IDE生成toString,hashCode和equals方法。

使用IDE生成而不是手写,能保证toString有统一的格式,equals和hashCode则避免不正确的Null值处理。

子类生成toString() 时,还需要勾选父类的属性。

【强制】Object的equals方法容易抛空指针异常,应使用常量或确定非空的对象来调用equals

推荐使用java.util.Objects#equals(JDK7引入的工具类)

"test".equals(object);  //RIGHT
Objects.equals(object, "test"); //RIGHT

【推荐】得墨忒耳法则,不要和陌生人说话

以下调用,一是导致了对A对象的内部结构(B,C)的紧耦合,二是连串的调用很容易产生NPE,因此链式调用尽量不要过长。

obj.getA().getB().getC().hello();

控制语句

【推荐】少用if-else方式,多用哨兵语句式以减少嵌套层次

if (condition) {
  ...
  return obj;
}

// 接着写else的业务逻辑代码;

【推荐】限定方法的嵌套层次

所有if/else/for/while/try的嵌套,当层次过多时,将引起巨大的阅读障碍,因此一般推荐嵌套层次不超过4。

通过抽取方法,或哨兵语句(见Rule 2)来减少嵌套。

public void applyDriverLicense() {
  if (isTooYoung()) {
    System.out.println("You are too young to apply driver license.");
    return;
  }

  if (isTooOld()) {
    System.out.println("You are too old to apply driver license.");
    return;
  }

  System.out.println("You've applied the driver license successfully.");
  return;
}

【推荐】简单逻辑,善用三目运算符,减少if-else语句的编写

s != null ? s : "";

【推荐】表达式中,能造成短路概率较大的逻辑尽量放前面,使得后面的判断可以免于执行

if (maybeTrue() || maybeFalse()) { ... }

if (maybeFalse() && maybeTrue()) { ... }

【强制】switch的规则

1)在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;

2)在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。

String animal = "tomcat";

switch (animal) {
case "cat":
  System.out.println("It's a cat.");
  break;
case "lion": // 执行到tiger
case "tiger":
  System.out.println("It's a beast.");
  break;
default: 
  // 什么都不做,也要有default
  break;
}

【推荐】循环体中的语句要考量性能,操作尽量移至循环体外处理

1)不必要的耗时较大的对象构造;

2)不必要的try-catch(除非出错时需要循环下去)。

基本类型与字符串

链接地址
全篇需要关注

集合处理

Rule 1. 【推荐】底层数据结构是数组的集合,指定集合初始大小

底层数据结构为数组的集合包括 ArrayList,HashMap,HashSet, ArrayDequeue等。

数组有大小限制,当超过容量时,需要进行复制式扩容,新申请一个是原来 容量150% or 200%的数组,将原来的内容复制过去,同时浪费了内存与性 能。HashMap/HashSet的扩容,还需要所有键值对重新落位,消耗更大。

默认构造函数使用默认的数组大小,比如ArrayList默认大小为10,HashMap 为16。因此建议使用ArrayList(int initialCapacity)等构造函数,明确初始化大小。

HashMap/HashSet的初始值还要考虑加载因子:

为了降低哈希冲突的概率(Key的哈希值按数组大小取模后,如果落在同一个 数组下标上,将组成一条需要遍历的Entry链),默认当HashMap中的键值对 达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需 要设定 100/0.75 +1=135 的数组大小。vjkit的MapUtil的Map创建函数封装了该计算。如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。

Rule 5. 【强制】当对象用于集合时,下列情况需要重新实现hashCode()和 equals()

  1. 以对象做为Map的KEY时;
  2. 将对象存入Set时。

上述两种情况,都需要使用hashCode和equals比较对象,默认的实现会比较是否同一个对象(对象的引用相等)。
另外,对象放入集合后,会影响hashCode(),equals()结果的属性,将不允许修改。

Rule 6. 【强制】高度注意各种Map类集合Key/Value能不能存储null值的情 况

MapKeyvalue
HashMapNullableNullable
ConcurrentHashMapNotNullNotnull
TreeMapNotNullNotNull

由于HashMap的干扰,很多人认为ConcurrentHashMap是可以置入null值。 同理,Set中的value实际是Map中的key。

Rule 7. 【强制】长生命周期的集合,里面内容需要及时清理,避免内存泄漏

长生命周期集合包括下面情况,都要小心处理。

  1. 静态属性定义;
  2. 长生命周期对象的属性;
  3. 保存在ThreadLocal中的集合。

如无法保证集合的大小是有限的,使用合适的缓存方案代替直接使用 HashMap。
另外,如果使用WeakHashMap保存对象,当对象本身失效时,就不会因为它在集合中存在引用而阻止回收。但JDK的WeakHashMap并不支持并发版本,如果需要并发可使用Guava Cache的实现。

Rule 8. 【强制】集合如果存在并发修改的场景,需要使用线程安全的版本

  1. 著名的反例,HashMap扩容时,遇到并发修改可能造成100%CPU占用。
    推荐使用 java.util.concurrent(JUC) 工具包中的并发版集合,如 ConcurrentHashMap等,优于使用Collections.synchronizedXXX()系列函数 进行同步化封装(等价于在每个方法都加上synchronized关键字)。
    例外:ArrayList所对应的CopyOnWriteArrayList,每次更新时都会复制整个 数组,只适合于读多写很少的场景。如果频繁写入,可能退化为使用 Collections.synchronizedList(list)。
  2. 即使线程安全类仍然要注意函数的正确使用。
    例如:即使用了ConcurrentHashMap,但直接是用get/put方法,仍然可能会 多线程间互相覆盖。
//WRONG
E e = map.get(key);
if (e == null) {
e = new E();
map.put(key, e); //仍然能两条线程并发执行put,互相覆盖 }
return e;
//RIGHT
E e = map.get(key);
if (e == null) {
  e = new E();
  E previous = map.putIfAbsent(key, e);
  if(previous != null) {
	return previous;
  }
}
return e;

Rule 11. 【推荐】如果Key只有有限的可选值,先将Key封装成Enum,并使用EnumMap

EnumMap,以Enum为Key的Map,内部存储结构为Object[enum.size],访问时以value = Object[enum.ordinal()]获取值,同时具备HashMap的清晰结构与数组的性能。

public enum COLOR {
  RED, GREEN, BLUE, ORANGE;
}

EnumMap<COLOR, String> moodMap = new EnumMap<COLOR, String> (COLOR.class);

Rule 12. 【推荐】Array 与 List互转的正确写法

// list -> array,构造数组时不需要设定大小
String[] array = (String[])list.toArray(); //WRONG;
String[] array = list.toArray(new String[0]); //RIGHT
String[] array = list.toArray(new String[list.size()]); //RIGHT,但list.size()可用0代替。


// array -> list
//非原始类型数组,且List不能再扩展
List list = Arrays.asList(array); 

//非原始类型数组, 但希望List能再扩展
List list = new ArrayList(array.length);
Collections.addAll(list, array);

//原始类型数组,JDK8
List myList = Arrays.stream(intArray).boxed().collect(Collectors.toList());

并发处理

Rule 1. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯

1)创建单条线程时直接指定线程名称

Thread t = new Thread();
t.setName("cleanup-thread");

2) 线程池则使用guava或自行封装的ThreadFactory,指定命名规则。

//guava 或自行封装的ThreadFactory
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + "-%d").build();
ThreadPoolExecutor executor = new ThreadPoolExecutor(..., threadFactory, ...);

Rule 3. 【强制】线程池不允许使用 Executors去创建,避资源耗尽风险

Executors返回的线程池对象的弊端 :

1)FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool 和 ScheduledThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

应通过 new ThreadPoolExecutor(xxx,xxx,xxx,xxx)这样的方式,更加明确线程池的运行规则,合理设置Queue及线程池的core size和max size,建议使用vjkit封装的ThreadPoolBuilder。

Rule 4. 【强制】正确停止线程

Thread.stop()不推荐使用,强行的退出太不安全,会导致逻辑不完整,操作不原子,已被定义成Deprecate方法。

停止单条线程,执行Thread.interrupt()。

停止线程池:

  • ExecutorService.shutdown(): 不允许提交新任务,等待当前任务及队列中的任务全部执行完毕后退出;

  • ExecutorService.shutdownNow(): 通过Thread.interrupt()试图停止所有正在执行的线程,并不再处理还在队列中等待的任务。

最优雅的退出方式是先执行shutdown(),再执行shutdownNow(),vjkit的ThreadPoolUtil进行了封装。

注意,Thread.interrupt()并不保证能中断正在运行的线程,需编写可中断退出的Runnable,见规则5。

并发处理

日志规约

Rule 2. 【推荐】对不确定会否输出的日志,采用占位符或条件判断

//WRONG
logger.debug("Processing trade with id: " + id + " symbol: " + symbol);

如果日志级别是info,上述日志不会打印,但是会执行1)字符串拼接操作,2)如果symbol是对象,还会执行toString()方法,浪费了系统资源,最终日志却没有打印。

//RIGHT
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);

但如果symbol.getMessage()本身是个消耗较大的动作,占位符在此时并没有帮助,须要改为条件判断方式来完全避免它的执行。

//WRONG
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol.getMessage());

//RIGHT
if (logger.isDebugEnabled()) {
  logger.debug("Processing trade with id: " + id + " symbol: " + symbol.getMessage());
}

Rule 3. 【推荐】对确定输出,而且频繁输出的日志,采用直接拼装字符串的方式

如果这是一条WARN,ERROR级别的日志,或者确定输出的INFO级别的业务日志,直接字符串拼接,比使用占位符替换,更加高效。

Slf4j的占位符并没有魔术,每次输出日志都要进行占位符的查找,字符串的切割与重新拼接。

//RIGHT
logger.info("I am a business log with id: " + id + " symbol: " + symbol);

//RIGHT
logger.warn("Processing trade with id: " + id + " symbol: " + symbol);

Rule 8. 【推荐】使用warn级别而不是error级别,记录外部输入参数错误的情况

如非必要,请不在此场景打印error级别日志,避免频繁报警。

error级别只记录系统逻辑出错、异常或重要的错误信息。

其它规约

Rule 2. 【推荐】时间获取的原则

1)获取当前毫秒数System.currentTimeMillis() 而不是new Date().getTime(),后者的消耗要大得多。

2)如果要获得更精确的,且不受NTP时间调整影响的流逝时间,使用System.nanoTime()获得机器从启动到现在流逝的纳秒数。

Rule 6. 【推荐】正确使用反射,减少性能损耗

获取Method/Field对象的性能消耗较大, 而如果对Method与Field对象进行缓存再反复调用,则并不会比直接调用类的方法与成员变量慢(前15次使用NativeAccessor,第15次后会生成GeneratedAccessorXXX,bytecode为直接调用实际方法)

//用于对同一个方法多次调用
private Method method = ....

public void foo(){
  method.invoke(obj, args);
}

//用于仅会对同一个方法单次调用
ReflectionUtils.invoke(obj, methodName, args);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
华为C++语言编程规范包括以下几个方面的内容: 1. 程序块的分界符应独占一行并位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。\[1\] 2. 头文件的包含顺序应按照以下顺序:当前.cpp文件直接关联的头文件、C库文件、C++库文件、其他项目的头文件、本项目中的其他头文件。头文件应向稳定的方向包含。\[2\] 3. 对于转换运算符和单参数构造函数,建议使用explicit关键字来明确指定其作用。这样可以避免隐式类型转换带来的潜在问题。\[3\] 以上是华为C++语言编程规范的一些主要内容。遵循这些规范可以提高代码的可读性和可维护性。 #### 引用[.reference_title] - *1* [华为C/C++编码规范](https://blog.csdn.net/weixin_67336587/article/details/130940891)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [C++编程规范(参考Google、华为)](https://blog.csdn.net/qq_39632811/article/details/124098935)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值