整洁代码

代码是需求的精确性表达 代码不会消失

代码更多的时候是用来读

命名

名副其实:变量 函数 类的名称要充分体现它们的作用

避免误导:

Accounts accountList; // bad
List<Account> accountList; // good
var userPermissionControllService;
var userPermissionControllerService;
int l = 0;
int o = 1;
if (l == 0) return o = l;

有意义的区分:

void copy(StringBuffer s1, StringBuffer s2); // bad
void concat(StringBuffer source, StringBuffer target); // good
class Product{}
class ProductInfo{} // 加个Info并没有说明什么
class ProductDetail{}

使用读得出来的名称: 方便讨论

使用可搜索的名称:

double circleArea = 3.14 * Math.pow(radius, 2); // bad

double CIRCLE_PI = 3.1415926; // good
private static final double CIRCLE_PI = 3.14;

void calcArea() {
  final double PI = 3.14
  ...
}

避免使用编码:这些技巧在IDE智能的时代有它的用处

int iPort = 8080; // 该变量为int类型 bad
private List<Listeners> m_listeners; // bad
interface IUserService{} // bad
class UserServiceImp implements UserService {} // bad
class DefaultUserService implements UserService {} // good

避免思维映射:传统管用i j 表示循环计数器 其他情况下要避免使用单字母

for (int i=0;i<MAX;i++){...} // 遵循传统惯例
int r = calcArea(); // bad

类名与对象名应该是名词或者名词短语

class Customer{}
Processor processor;

方法名应该是动词或者动词短语

void getServerInfo();

命名时避免抖机灵

threadPoll.kill(); // bad
threadPoll.shutdown(); // good

使用概念一致的命名:

避免将同一术语用于不同概念

// bad
void addUser();
BigDecimal addPrice(BigDecimal target);

尽量使用技术性名称而非业务领域名称 是在没有技术名词 与问题领域更近的代码 可以采用业务领域的名称

Queue<Job> jobQueue; // 技术名词
DinnerOrder order; // 业务名词

如果无法通过类或者方法来给名称提供上下文 那么只能给名称添加前缀来添加上下文了

class Address {
  String username;
  String phone;
  String country;
  String postCode;
}
String addressCode; // 在一个没有上下文的环境中

短名称够清楚就行了 不要添加不必要的上下文

class ShopSystemUserService {} // bad

函数

短小:

String renderJsp(){
  var classCode = compileJsp();
  return executeJspService(classCode);
}
void badFunction() { // bad
  if (..) {
    while(){
      ...
      for(..){..}
    }
  }
}

只做一件事:函数内部的实现充分体现函数名称

确保函数中的语句在同一抽象层级上面

String renderJsp(){
  var classCode = compileJsp();
  return executeJspService(classCode);
}

使用多态取代switch语句

// bad
Money calcPay(Employee e){
  switch(e.type) {
    case MANAGER:
      return e.getPay() - 20%;
    case COMMON:
      return e.getPay() - 10%;
    ...
  }
}
// good
abstract class Employee{
  abstract Money getPay();
}
class CommonEmployee{
  Money getPay(){...}
}
class ManagerEmployee{
  Money getPay(){...}
}

使用描述性的名称能理性设计思路 帮助改进之

var result;
var searchResult;
var movieSearchResult; // best

函数参数:

public void convertAndSend(Object object){..}
public void correlationConvertAndSend(Object object, CorrelationData correlationData){..}
public void convertAndSend(String routingKey, final Object object, CorrelationData correlationData){...} // bad

exchange.send(String rotingKey,Object msg); // better
void submitTask(Task t, boolean flag){ // 尤其flag命名并不能说明做什么 改成isSync 可能好一点
  if (flag) {
    sync
  }else {
    async
  }
}
// good
void submitTaskAsync(){...}
void submitTaskSync(){...}
write(PrintWriter pw, String msg); // bad
printWriter.write(msg); // good

副作用:避免使用输出参数(out) 需要修改状态 就将该状态作为对象的属性

void removeNegative(List<Integer> list); // bad
list.removeIf(...); // good

分割指令与询问:函数要么做什么 要么回答什么 不能两者得兼

boolean set(String k, String v){ // bad 这个函数承担了两个职责
  if (exists){
    return true;
  }
  ...
  return false;
}
// good
boolean exists(String k);
void set(String k,String v);

异常代替错误码:

// bad
if (!err){
  if (!err){
    ...
  }
}
// good
try {

} catch (Error1){

} catch (Error2){

}
try {
  generateSearchResult();
} catch(){
  logError();
  sendErrorMsg();
}

别重复自己:重复可能是软件中一切邪恶的根源

结构化编程:单一出入口原则在大函数中才有明显的好处

注释

好的注释

interface SessionFactory {
  // 新建一个数据库连接并返回
  Session openSession();
}
// 寻找0到n的素数 根据数学证明 只要到n的平方根就行了
for(int i=0;i<Math.sqrt(n);i++){...}
// 发送对象为空,代表是一条广播消息
if (StringUtils.isEmpty(payload.getTo())){
  ...
} else {
  broadcast
}
// 该方法使用一个listener的确认 使用synchronized关键字保证只有一个线程能进入
public synchronized ConfirmResult sendTextMessage(String target, String text) {...}
// 向消息队列写入消息:订单 订单详情 TODO
void onMessage(ByteBuf buf){
  ...
  buf.release(); // 需要减少缓冲区的引用计数
}

坏注释

// 提交任务
boolean success = submitTask();

编写代码时 着重于代码的表现力 而非加之以注释

// bad 等待timeout个时间 然后关闭
void close(int timeout){
  wait(timeout);
  close();
}
// 注意!!! ////////////

特别重要才使用 使用多的话 就会被淹没在大量斜杠中

对于大函数或许才有意义

try{
  if (){
    while(){

    }//while
  } // if
}catch{

} // catch
// 提交任务 每隔5分钟运行一次 这里的5分钟跟这个函数毫无关系
void submitTask();

格式

原始代码其代码风格和可读性仍会影响到其可维护性和可扩展性

垂直格式

短文件比常文静更易于理解

// 紧密在一起的代码代表概念相关
DeliveryInfoDO deliveryInfoDO = new DeliveryInfoDO();
deliveryInfoDO.setBuilding(deliveryDTO.getBuilding());
deliveryInfoDO.setDetail(deliveryDTO.getDetail());
                                        // 使用空白行隔开 每个空白行都是一个线索
deliveryRepository.save(deliveryInfoDO);

if (deliveryDTO.getDefaultDelivery() != null && deliveryDTgetDefaultDelivery()) {
    consumerDeliveryRepository.resetDefaultDelivery(consumer.getUserId());
}

垂直距离:

关系密切的概念应相互靠近

横向格式

尽力保持代码行短小

使用空格分割相关性较弱的元素:

int[] data = new int[10];
deliveryService.updateDelivery(token, deliveryId, deliveryDTO);

使用缩进表现源文件的继承结构 缩进可以快速展现出当前的范围

对象和数据结构

过程式代码容易在不改动数据结构的情况下增加函数

面向对象则容易在不改动函数的情况下增加新类

迪米特法则:模块不应了解它所操作对象的内部情形

// bad
String url = host.getContext().getServlet().getName();

避免在DTO中塞入逻辑 保持简单setget即可

错误处理

try-catch定义了一个范围 使用TDD开发剩下的逻辑

可控异常违反了开闭原则 底层的修改会直接贯穿到高层

try {

} catch(ThirdPartException e){
  throw new BusinessException(e);
}
// bad
try {
  getEmployee().run();
} catch(NullPointerException e){
  ...
}
void getEmployee(){
  maybe return null;
}
// good
void getEmployee(){
  normal return new Employee();
  sometimes return new EmptyEmployee();
}

边界

整洁的边界应该避免我们的代码过多了解第三方代码中的信息

第三方包

封装第三方API来避免在系统中传递使用第三方接口

学习性测试:通过编写测试来学习第三方API

使用尚不存在的代码

通过适配器适配尚未实现的接口 来进行已知与未知的隔离

// 未知
interface ThirdPartInterface{...}

// 未知与已知的交界处
interface ThirdPartAdapter extends ThirdPartInterface{...}

组织:

public static int PORT = 8080;
private static String MAGIC_NUMBER = 0XCAFE_BABE;
private String instanceName;
protected String subName;

public void run(){...}
private void innerRun(){...}

尽可能进行封装 除非玩不得以 否则不要暴露

类应该短小:

判断类短小的标准使用职责数来衡量 而非代码行数

系统应该由许多短小的类而非少量巨大的类组成

类应只由少量实体变量组成 这些变量如果同时被越多的函数操作 就代表这个类内聚性越高

通过拆分函数以及函数相关的实体变量到其他类来将一个大类拆分为几个小类

方便修改的组织:

当采取诸如DIP等原则时 系统各个组件的耦合就已经非常低了 此时也方便测试

系统

分离系统的构造与使用

使用main组件:

2020910143051

使用工厂控制对象的创建

使用依赖注入容器来管理对象

测试驱动系统架构

代码层面与架构关注面分离开 避免侵入性代码

没有必要先做大设计

延迟决策

使用DSL

填平了领域与实现之间代码的壕沟

迭进

简单设计原则:

并发编程

并发解耦了目的与时机

一些问题:

防御原则

谨记数据封装 限制数据作用域 严格限制多个线程访问的共享数据

使用数据复本 有些情况下的并发可以只读 这个时候可以使用复制的方式避免共享

线程应尽可能独立 不与其他线程共享数据

执行模型

大部分并发问题都是下列模型的变种:

建议

不要在客户端调用一个对象的多个同步方法: 这可能造成多个线程下的数据不一致问题

解决:

synchronized(lock){
  obj.f1();
  obj.f1();
}
// AtomicInteger
public int incrementAndGet(){}

保持同步区域微小

尽早考虑关闭问题

测试

将偶然的失败看做线程问题

先确保单线程代码可工作

配置多线程代码在不同的配置环境下执行

在代码里插入试错代码:sleep yeild

速查

注释

环境

系统构建与运行单元测试应只需一个指令

函数

一般性问题

Java

名称

测试