《CC》的感后感

其实以前在zte,当时团队一堆人再争着看CC,自己并没有当回事儿,因为当时觉得,还是先把技术功底打好,多学多写代码。然而,自己忽视了代码的本质。没错,代码的本质是可阅读性。还好,不晚

零、一个简单的范例

 // 求二叉树的深度
 // 递归解法:
 // (1)如果二叉树为空,二叉树的深度为0
 // (2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
public int depth(node node) {
  if (node == null) // 递归出口
   return 0;
  int l = depth(node.lchild);
  int r = depth(node.rchild);
  return l > r ? (l + 1) : (r + 1);
};

思考:我们有什么不对?

一、起始的一些思想

1、代码是根,无法丢弃
2、烂代码毁灭一切
3、勒布朗法则:稍后等于永不()
4、要保持专业精神
5、读与写的比例:10:1 (
)
6、“让营地比你来时更干净”
7、六大原则:SRP、OCP、DIP、LOD、LSP、ISP、

二、命名

public List getThem(){
    List list1 = new ArrayList<>();
    for(int i = 0; i

思考:这会影响什么?

1、准确的命名
2、最好不要用List的后缀(或者是平台的专用词,工具类名)
3、getAccount()与getAccountInfo()的问题
4、可读性、课所搜性
5、Java无需要在名称中记住变量类型
6、尽量不要用i,j,k这些个变量进行循环计数
7、类名应该为名词或者是名词短语(Account)
方法名应该为动词或者是动词短语
8、add(a、增加新东西;b、连接现有的两个词),这种一语双关的命名就不是很好

@Data
class PersonInfo{

    private String firstName;

    private String lastName;

    private String phone;

    private String street;

    private String city;

    private String state;

}

public class UserHandle{
    ......

    public void handle(PersonInfo p){
        ......
        String s = p.getState();
        ......
    }
    ......
}

思考:getState是什么Gui?

9、添加有意义的语境,例如,再firstName、street、city、state这样的一组词汇里面,大概知道我们 要表达的是地址,可是单独吧state这个单词拿出来,并不知道我们是要说的地址相关

三、函数

public Money calculatePay(Employee e) throws Exception{
    switch(e.type){
        case COMMISSIONED:
            return calculateCommissionedPay(e);
        case HOURLY:
            return calsulateHourlyPay(e);
        default:
            throw new InvalidEmployee(e.type);
    }
}

思考:这个违反了什么原则?

1、尽量短小
2、只做一件事情(同一抽象层级上面)
3、switch尽量在较低的抽象层级,且永远不重复(向下规则:从上面向下面读代码)

private PlainResult updateStockWhenTakeupProduct(GoodsOperateParam goodsOperateParam) {
    ......
        //对多个门店上架时候设置库存
        logger.info("stock setting:kdtId:{};deptId:{};goodsId:{}", kdtId, deptId, goodsId);
        List stockList = stockMap.get(deptId);
        //更新每个商品的sku库存
        List goodsItemSkuDOS = getGoodsItemSkuDOS(kdtId, deptId, goodsId, stockList);//1
        logger.info("goods item sku database param:{}", JsonUtils.toJson(goodsItemSkuDOS));
        goodsItemSkuDAO.updateBatch(kdtId, deptId, goodsId, goodsItemSkuDOS);
        //这里要设置商品总库存,就是设置分店中商品每个goodsId对应的库存总量,这个库存是对应每个sku库存相加所得
        List sumStock = getSumStock(goodsId, stockList);//2
        logger.info("dept service item database param:{}", JsonUtils.toJson(sumStock));
        deptServiceItemDAO.batchUpdateDisplayStatusAndStock(kdtId, deptId, goodsOperateParam.getShelveType(), sumStock);

        synESDeptStock(deptId, sumStock, goodsId, goodsOperateParam);//3
    ......

    }

private List getGoodsItemSkuDOS(Long kdtId, Long deptId, Long goodsId, List stockList) {
        List result = new ArrayList<>();

    stockList.forEach(skuStock -> {
        GoodsItemSkuDO goodsItemSkuDO = new GoodsItemSkuDO();
        goodsItemSkuDO.setDeptId(deptId);
        goodsItemSkuDO.setKdtId(kdtId);
        goodsItemSkuDO.setGoodsId(goodsId);
        goodsItemSkuDO.setSkuId(skuStock.getSkuId());
        goodsItemSkuDO.setTotalStock(skuStock.getStockNum().intValue());
        result.add(goodsItemSkuDO);
    });


    return result;
}

private List getSumStock(Long goodsId, List goodsStockParamList) {
    List sumStock = new ArrayList<>();
    Long sum = goodsStockParamList.stream().map(GoodsOperateParam.DeptStockParam::getStockNum).reduce(0L, Long::sum);
    GoodsQuery.GoodsIdStock goodsIdStock = new GoodsQuery.GoodsIdStock(goodsId, sum);
    sumStock.add(goodsIdStock);
    return sumStock;
}

private void synESDeptStock(Long deptId, List sumStock, Long goodsId, GoodsOperateParam goodsOperateParam) {
    EsGoodsDTO esGoodsDTO = new EsGoodsDTO();
    esGoodsDTO.setDeptId(deptId);
    esGoodsDTO.setGoodsId(goodsId);
    esGoodsDTO.setOnShelve(goodsOperateParam.getShelveType());
    esGoodsDTO.setStockNum(sumStock.get(0).getStockNum()); //@author jicheng 这里面细节进行统计每一个sku的库存累加,所以不用乘以所有sku的size了
    if (esGoodsDTO.getStockNum() > 0) {
        esGoodsDTO.setSoldStatus(GoodsStatusEnum.ON_SHELVE.getValue());
    }
    esGoodsManager.update(esGoodsDTO);
}

思考:还有什么改进点?请指教

4、尽量少入参
5、无副作用(不能即检查密码的正确性,又初始化系统)
6、避免使用输出参数
7、使用异常代替返回错误码
8、把trycatch主题抽出成为一个函数
9、代码尽量不要重复
10、一步步来改进代码,好代码不是一次就能成就的

......
    //重复了 暂时还没考虑抽查来
    if (plainResult.isSuccess()) {
        PlainResult updateResult = null;
        if (inputServingItemParam.getDeptId() == MultiShopType.HEADQUARTERS.deptId()) {
            updateResult = goodsManager.updateServingItemV2(inputServingItemParam, serviceItemDOResult.getData());
        }else {
            updateResult = deptServiceItemManager.updateDept(inputServingItemParam);
        }
        plainResult.setCode(updateResult.getCode());
        plainResult.setMessage(updateResult.getMessage());
        plainResult.setSuccess(updateResult.isSuccess());
        plainResult.getData().setId(updateResult.getData());
    }
    if (plainResult.isSuccess()) {
        PlainResult updateDBResult = null;
        if (inputServingItemParam.getDeptId() == MultiShopType.HEADQUARTERS.deptId()) {
            updateDBResult = goodsManager.updateHeadGoodsInDB(inputServingItemParam);
        }else {
            updateDBResult = goodsManager.updateDeptGoodsInDB(inputServingItemParam);
        }
        if (!updateDBResult.isSuccess()) {
            logger.warn("update service error,param={}", JsonUtils.toJson(inputServingItemParam));
        }
    }
......

思考:前人种树后人乘凉?

四、注释

    // find out if gift already given 
    boolean isAlreadyGiven = false; 
    for (String given : giftsGiven) { //this is a loop
        if(gift.equals(given)){ 
            isAlreadyGiven = true; 
            break; 
        } 
    }

    private boolean isGiftNotAlreadyGiven(String gift) {
        boolean isAlreadyGiven = true;
        for (String given : giftsGiven) {
            if(gift.equals(given)){
                isAlreadyGiven = false;
                break;
            }
        }
        return isAlreadyGiven;
    }

思考:什么情况下我们需要注释?

1、不要因为代码乱而写注释,首先要改好老代码!
2、最好用代码的优秀的命名来取代注释
3、使用注释的场景罗列:法律信息、提供信息注释、对意图的解释或是阐释、警示作用、TODO注释
4、不好的注释:可怕的废话、类方法上的Javadoc注释、注释掉的代码、HTML注释、很多注释(信息过多 )、位置标记、显而易见的说明

/**
 * Created by zhaoyifei.
 *
 * Time 2017/3/2 01:23
 *
 * Desc Goods对外的接口
 */
public interface  GoodsService {

......


    /**
     * 总店 商品上下架, 支持批量, 以这个接口为基础,要拓展两个接口
     * @update jicheng
     * @author hunterPan
     */
    PlainResult batchOperateByItemId(GoodsOperateParam goodsOperateParam);

......

}

思考:以上的接口注释好吗?

五、格式

1、短文件比长文件易于理解
2、垂直方向上的间隔(主要表现在方法之间、属性之间、还有逻辑之间)
3、少用protected,因为使用到的变量和方法尽量要靠近
4、调用与被调用的函数尽量要靠近
5、水平宽度建议:120字符(刚好一瓶)
6、遵守团队规则

六、对象与数据结构

1、解释两个名词:

  • 对象:将数据隐藏于抽象之后,暴露操作数据的函数。
  • 数据结构:暴露数据本身,不提供有意义的函数。
/*
Law of Demeter
定义:一个对象应该对其他对象保持最少的了解。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
解决方案:尽量降低类与类之间的耦合。
解释:德米忒耳定律有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对
象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的
方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的
朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在
类的内部。
*/
//违反的案例

class CompanyManager{  
  
    public List getAllEmployee(){  
        List list = new ArrayList();  
        for(int i=0; i<30; i++){  
            Employee emp = new Employee();  
            //为总公司人员按顺序分配一个ID  
            emp.setId("总公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
      
    public void printAllEmployee(SubCompanyManager sub){  
        List list1 = sub.getAllEmployee();  
        for(SubEmployee e:list1){  
            System.out.println(e.getId());  
        }  
  
        List list2 = this.getAllEmployee();  
        for(Employee e:list2){  
            System.out.println(e.getId());  
        }  
    }  
} 

//符合的案例
class CompanyManager{  
    public List getAllEmployee(){  
        List list = new ArrayList();  
        for(int i=0; i<30; i++){  
            Employee emp = new Employee();  
            //为总公司人员按顺序分配一个ID  
            emp.setId("总公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
      
    public void printAllEmployee(SubCompanyManager sub){  
        sub.printEmployee();  
        List list2 = this.getAllEmployee();  
        for(Employee e:list2){  
            System.out.println(e.getId());  
        }  
    }  
} 

思考:返回值“好吗”?

2、Demeter定律(德米忒耳定律):类C的方法f,应该调用一下对象方法:(*)

  • C
  • 由f创建的对象
  • 作为参数传递给f的对象
  • 由C的实体变量持有的对象
final String filedir = ctct.getOptions().getScratchDir().getAbsolutePath();
/*
如果上面的ctct是一个对象就会违反Demeter定律,如果是一个数据结构就不会违反
属性访问就不会涉及到Demeter定律的问题
*/

七、错误处理

1、返回错误码的方式不是很好、尽量抛异常
2、尽量使用不可控异常(运行时异常),因为如果函数改成可控异常,那么调用者都要修改,必然会违反OCP
3、将较多的异常类型捕获,放到脚底的抽象层次里面
4、以下的代码有点不好,不要在异常处理块中做业务逻辑

    try{
        MealExpenses ex = experseReportDao.getMeals(employee.getId());
        mTotal += ex.getTotal();
    }catch(Exception e){
        mTotal += getMealPerDiem();
    }

6、不要返回null,并且不要传递null

八、边界

//前世
Map sensors = new HashMap();
dealSensors(sensors);

//今生
Sensors sensors = new Sensors();
dealSensors(sensors);

class Sensors{
    private Map sensorContainer = new HashMap<>();

    public Sensor getSensor(Long id){
        return sensorContainer.get(id);
    }
}

思考:什么是较低的抽象?

1、尽量不要将公共API当参数进行传递,有节制的进行封装
2、学习性测试(第三方的库)

九、单测

1、TDD流程

  • 先不写生产代码
  • 编写刚好不通过的单测
  • 编写使得单测刚好通过的生产代码

2、测试代码与生产代码同等重要
3、测试代码非常关键的一点在于:可读性
4、每个测试尽量使用一个断言
5、FIRST(快速、独立、可重复、自足验证、及时)

十、类

public class Stack{
    private int topOfStack = 0;
    List elements = new LinkedList<>();

    public int size(){
        return topOfStack;
    }

    public void push(int element){
        topOfStack++;
        elements.add(element);
    }

    public int pop() throws PopedWhenEmpty{
        if(topOfStack == 0){
            throw new PopedWhenEmpty();
        }
        int element = elements.get(--topOfStack);
        elements.remove(topOfStack);
        return element;
    }
}

思考:以上代码满足了什么原则?

1、类应该短小,权责尽量的少
2、内聚、SRP、OCP
3、大函数拆分成小函数,往往是拆分为多个小类的时机

十一、系统

1、构造与使用分开(DIP、工厂)
2、关注切面(AOP)
3、消除紧耦合

十二、逐步改进

1、自上而下重要点

  • 运行所有的测试
  • 不可重复
  • 表达程序员的意图
  • 尽可能减少类和方法的数量

十三、一些其余的点



enum CheckType{
    INTEGER(0, "integer"),
    STRING(1, "string");

    private int value;

    private String name;
}

interface Checker{
    void check();
}
class IntegerChecker implements Checker{
    public void check(){}
}

class StringChecker implements Checker{
    public void check(){}
}

public CheckerHandler{

    private static Map checkers = new HashMap(){{
        put(CheckType.INTEGER.getValue(),(Checker)(new IntegerChecker()));
        put(CheckType.STRING.getValue(),(Checker)(new StringChecker()));
    }};

    public void beginToCheck(){
        for(CheckType checkerType : CheckType.values()){
            Checker checker = checkers.get(checkerType.getValue());
            checker.check();
        }
    }
}

思考:还有什么方式取代if/else,switch/case

1、用改对象的状态来代替输出参数
2、干掉死函数
3、用多态代替ifelse与switch
4、用命名常量代替魔法数字
5、避免用否定条件
6、掩蔽时序性耦合
7、函数应该只在一个抽象层级上面
8、较高层级放可配置的数据,用参数向下传递
9、使用枚举

你可能感兴趣的:(《CC》的感后感)