Java项目工程代码深度刨析总结

目录
  • 一、背景
  • 二、衡量代码好环的原则
    • 2.1 评判代码指标
    • 2.2 指导理论
  • 三、代码实现技巧
    • 3.1 抽像能力
    • 3.2 组合/聚合复用原则
  • 四、总结

一、背景

  最近我们团队有幸接了两个0到1的项目,一期项目很紧急,团队成员也是加班加点,从开始编码到完成仅用了一星期多一点点,期间还不断反复斟酌代码如何抽象代码,如何写得更优雅,一遍又一遍的调整,我也是一次又次的阅读每个团队成员的代码,虽然还有些不如意,但整体来说还算是满意,参与项目的成员经过不断琢磨,对一些功能不断抽像,团队进步也是非常明显,以下举了几个样例。

  那么这次我为什么对工程代码抓得更严,主要是之前交接了不少其它团队的工程,由于当时设计不够好,维护起来非常痛苦,也正是因为这些工程,我阅读了非常多的代码,对自己也有很大的启发和感想,因此希望我自己的团队能尽可能写好代码,减少维护上的一些痛苦。另外就是我们写的代码除了给机器执行外,更多的时候是给人读的,这个读代码的可能是后来的维护人员,所以呢也顺便总结一下。

二、衡量代码好环的原则

2.1 评判代码指标

  实际上,咱们平时嘴中常说的“好”和“烂”,是对代码质量的一种描述。“好”笼统地表示代码质量高,“烂”笼统地表示代码质量低。对于代码质量的描述,除了“好”“烂”这样比较简单粗暴的描述方式之外,我们也经常会听到很多其他的描述方式。这些描述方法语义更丰富、更专业、更细化。我搜集整理了一下,罗列在了下面,一般有几下几标准,分别是可读性、可维护性、可扩展性、可复用性 、灵活性、可测试性等等

可读性 readability

  软件设计大师 Martin Fowler 曾经说过:“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”翻译成中文就是:“任何傻瓜都会编写计算机能理解的代码。好的程序员能够编写人能够理解的代码。”Google 内部甚至专门有个认证就叫作 Readability。只有拿到这个认证的工程师,才有资格在 code review 的时候,批准别人提交代码。可见代码的可读性有多重要,毕竟,代码被阅读的次数远远超过被编写和执行的次数。

  我个人认为,代码的可读性应该是评价代码质量最重要的指标之一。我们在编写代码的时候,时刻要考虑到代码是否易读、易理解。除此之外,代码的可读性在非常大程度上会影响代码的可维护性。毕竟,不管是修改 bug,还是修改添加功能代码,我们首先要做的事情就是读懂代码。代码读不大懂,就很有可能因为考虑不周全,而引入新的 bug。

  既然可读性如此重要,那我们又该如何评价一段代码的可读性呢?我们需要看代码是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等等。你应该也能感觉到,从正面上,我们很难给出一个覆盖所有评价指标的列表。这也是我们无法量化可读性的原因。

  实际上,code review 是一个很好的测验代码可读性的手段。如果你的同事可以轻松地读懂你写的代码,那说明你的代码可读性很好;如果同事在读你的代码时,有很多疑问,那就说明你的代码可读性有待提高了

  • 可维护性 maintainability

  一般指的是在不破坏原代码设计的前提下,快速修改bug或增加代码,不会带来新bug,表明该代码的维护性比较好。落实到编码开发,所谓的“维护”无外乎就是修改 bug、修改老的代码、添加新的代码之类的工作。所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新的 bug 的情况下,能够快速地修改或者添加代码。所谓“代码不易维护”就是指,修改或者添加代码需要冒着极大的引入新 bug 的风险,并且需要花费很长的时间才能完成。

  • 可扩展性 extensibility

  代码面对未来新需求的变化能力,一般来说,开发新需求的时候,不修改原代码或很少修改,即可达到需求开发的能力,通常会预留一些功能扩展点。

  • 可复用性 reusability

  尽量避免重复造轮子,即能够沉淀出一些通用的代码逻辑,保持与上层业务代码的解耦

  • 灵活性 flexibility

  这个词比较宽泛。通常与可维护性、可扩展性以及可复用性类似

  • 可测试性

  主要反映在写单测的时候。从两个方面体现:

1.单元测试是否容易编写;

2.写单元测试的时候,不能依赖环境,远程调用其他服务的借口,尽可能进行mock数据,保持服务之间的解耦。虽然要团队每人都按这个规范走很难,但我们团队有一个强制要求,就是每个功能函数不能超过50行代码,而且要求代码越短越好。

这几个维度是评判代码维度比较重要的几个指标。

2.2 指导理论

  高内聚低耦合几乎是每个程序员员都会挂在嘴边的,但这个词太过于宽泛,太过于正确,所以聪明的编程人员们提出了若干面向对象设计原则来衡量代码的优劣:

  • 开闭原则 OCP (The Open-Close Principle)
  • 单一职责原则 SRP (Single Responsibility Principle)
  • 依赖倒置原则 DIP (Dependence Inversion Principle)
  • 最少知识原则 LKP (Least Knowledge Principle)) / 迪米特法则 (Law Of Demeter)
  • 里氏替换原则 LSP (Liskov Substitution Principle)
  • 接口隔离原则 ISP (Interface Segregation Principle)
  • 组合/聚合复用原则 CARP (Composite/Aggregate Reuse Principle)

  这些理论想必大家都很熟悉了,是我们编写代码时的指导方针,按照这些原则开发的代码具有高内聚低耦合的特性,换句话说,我们可以用这些原则来衡量代码的优劣。

三、代码实现技巧

  我相信每个工程师都想写出高质量的代码,不想一直写没有成长、被人吐槽的烂代码。那如何才能写出高质量的代码呢?针对什么是高质量的代码,我们刚刚讲到了七个最常用、最重要的评价指标。所以,问如何写出高质量的代码,也就等同于在问,如何写出易维护、易读、易扩展、灵活、简洁、可复用、可测试的代码,但要写好代码,也不是一蹴而就,需要非常多的实践与积累,下面简举例说明:

3.1 抽像能力

  抽象思维是我们工程师最重要的思维能力,因为软件技术本质上就是一门抽象的艺术。我们工程师每天都要动用抽象思维,对问题域进行分析、归纳、综合、判断、推理,从而抽象出各种概念,挖掘概念和概念之间的关系,然后通过编程语言实现业务功能,所以,我们大部分的时间并不是在写代码,而是在梳理需求,理清概念,对需求有一个全局的认知。而抽像能力让我及团队切身感受到,它给我们在编码和设计上带来的质的变化。

案例一:异步Excel导出

其实导出Excel功能在我们工程里随处可见,特别是咱们的运营希望一次性导出越多数据越好,为了不给我们系统带来太大压力,对于大数据量的导出一般异步进行,针对于这样一个简单的功能,那么应该如何抽像呢?

普通的写法:

public String exportXXX(参数) throws Exception {
	//业务实现
}
public String exportXXX2(参数) throws Exception {
	//业务实现
}

抽像写法:

我们其实可以把每个异步导出看作是一个异步任务,而每个任务可导出的内容是不一样的,因此完全可以把导出抽像一个方法,由每个具体实现类去实现导出不同的内容,具体如下:

// export excel
public interface IExcelExportTask {
    String export(BizCommonExportTask exportTask) throws Exception;
}
//样例实现类
XXXXExportTask implements IExcelExportTask {
	String export(BizCommonExportTask exportTask) throws Exception{
    	public String export(BizCommonExportTask exportTask) throws Exception {
    	//组织数据筛选条件
        TestReq queryReq = GsonUtils.toObject(exportTask.getInputParams(),TestReq.class);
        String fileName = String.format("%s%s%s", exportTask.getUploadFileName(),System.currentTimeMillis(),".xlsx");
        String downUrl = excelService.uploadExcel(fileName, null, new Fetcher<PreOccupyModel>(PreOccupyModel.class) {
        	//循环获取数据
            @Override
            public List<TestModel> fetch(int pageNo, int pageSize) throws OspException{
                TestQueryResp resp = testFethchLogic.fetchRecord(queryReq);
                return pageNo > resp.getPageNum() ? Collections.emptyList() :toExcelModel(resp);
            }
        });
        return downUrl;
    }
}
public class XXXXExportTask1 implements IExcelExportTask {
    @Override
    public String export(BizCommonExportTask exportTask) throws OspException {
        TestQuery query = GsonUtils.toObject(exportTask.getInputParams(), TestQuery .class);
        String fileName = String.format("%s%s%s", exportTask.getUploadFileName(), System.currentTimeMillis(), ".xlsx");
        return excelService.uploadExcel(fileName, null, new Fetcher<ExportItemModel>(TestModel.class) {
            @Override
            public List<TestModel> fetch(int pageNo, int pageSize) throws OspException {
                return XXXXLogic.queryExportItem(query, pageNo, pageSize);
            }
        });
    }
}
//导出任务分发器
public class ExcelTaskDispacther extends ApplicationObjectSupport {
	public boolean dispacthTask(Long taskId) throws OspException {
        updateTaskStatus(exportTask,CommonExportStatus.CREATING,TransferExportStatus.CREATING,StringUtils.EMPTY);
        try {
            String beanName =  getBeanName();
            ExportTaskHandler exportTaskHandler = getApplicationContext().getBean(beanName , IExcelExportTask .class);
            if(exportTaskHandler == null) {
                log.warn(String.format("任务ID[%s]写入配置错误!", taskId));
                return false;
            }
            updateTaskStatus(exportTask,CommonExportStatus.CREATE_SUCCESS,TransferExportStatus.CREATE_SUCCESS,StringUtils.EMPTY);
            log.info(String.format("任务ID[%s]RFID为[%s]处理成功", exportTask.getId(),rfid));
            return true;
        } catch(BusiException ex) {
            log.info("任务ID[{}]失败,原因:{}", exportTask.getId(),ex.getMessage(),ex);
            updateTaskResult();
        } catch(Exception ex) {
            log.info("任务ID[{}]失败,原因:{}", exportTask.getId(),ex.getMessage(),ex);
            updateTaskResult();
        }
        return false;
    }
}

案例二:系统通知

  在微服务化流行的今天,为了提升系统吞吐量,系统职责越来越细,各系统模块需要频繁交互数据,那么对于复杂的数据交互场景,比如我们调拨单,调拨单在扭转的过程中需要与很多系统交互,跟门店、仓库、库存模块有非常多的交互,我们又该如何抽像呢,以下是调拨与各系统交互的代码示例

//接口定义
public interface BizNotificationHandler {
    /**
     * 抛异常会当失败处理
     * 是否需要重试由BizNotificationStatus返回状态来决定
     * @param bizNotification
     * @return
     * @throws OspException
     */
    BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException;
}
//推送调拨差异数据给库存系统
public class SyncDiffToSimsAndBackQuotaHandler implements BizNotificationHandler {
    @Override
    public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException {
        //业务逻辑实现
        return BizNotificationStatus.PROCESS_SUCCESS;
    }
}
//占用库存
public class TransferOccupyInventoryHandler implements BizNotificationHandler {
    @Override
    public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException {
        //业务实现
    }
}
//在GPDC生成新条码
public class GpdcGenerateNewBarcodeHandler implements BizNotificationHandler {
    @Override
    public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException {
        //业务代码实现
    }
}

其实我们在与其它系统交互的时候,我们可以把每一个交互动作抽像成一个通知事件,每次交互的时候,写一个事件通知事件即可。

3.2 组合/聚合复用原则

  关于组合/聚合复用原则,其实我们在项目过程会经常遇到,比如项目里会经常管理各种单据,像采购单、调拨单、收货单等,而对于每种单据都会有各种各样的较验,我们先来看一段建调拨单代码,具体如何下:

//接口定义
public interface TransferValidator {
    boolean validator(CreateTransferCtx ctx) throws OspException;
}
//接口实现1
public class W2sCrossPoQtyValidator implements TransferValidator {
    @Override
    public boolean validator(CreateTransferCtx ctx) throws OspException {
        //较验器代码实现
    }
//接口实现2
public class W2sStoreBarcodeSaleLimitValidator implements TransferValidator {
    @Override
    public boolean validator(CreateTransferCtx ctx) throws OspException {
        //较验器代码实现
    }
}
//较验器组装
public class TransferValidators {
    public ValidatorChain newChain() {
        return new ValidatorChain();
    }
    public class ValidatorChain {
        private final List<TransferValidator> validators = new ArrayList<>();
        public ValidatorChain qtyValidator() {
            validators.add(qtyValidator);
            return this;
        }
        public ValidatorChain transferRouteCfgValidator() {
            validators.add(transferRouteCfgValidator);
            return this;
        }
        public ValidatorChain prodValidator() {
            validators.add(prodValidator);
            return this;
        }
        public ValidatorChain w2sWarehouseStoreValidator() {
            validators.add(w2sWarehouseStoreValidator);
            return this;
        }
        public ValidatorChain w2sStoreBarcodeSaleLimitValidator() {
            validators.add(w2sStoreBarcodeSaleLimitValidator);
            return this;
        }
        public ValidatorChain w2sAssignPoValidator() {
            validators.add(w2sAssignPoValidator);
            return this;
        }
        public ValidatorChain w2sCrossPoValidator() {
            validators.add(w2sCrossPoValidator);
            return this;
        }
        public ValidatorChain w2sCrossPoQtyValidator() {
            validators.add(w2sCrossPoQtyValidator);
            return this;
        }
        public ValidatorChain w2sCross4XupValidator() {
            validators.add(w2sCross4XupValidator);
            return this;
        }
        public ValidatorChain repeatLineValidator() {
            validators.add(repeatLineValidator);
            return this;
        }
        public ValidatorChain sstradeBarcodeValidator() {
            validators.add(sstradeBarcodeValidator);
            return this;
        }
        public ValidatorChain s2wWarehouseStoreValidator() {
            validators.add(s2wWarehouseStoreValidator);
            return this;
        }
        public boolean validator(CreateTransferCtx ctx) throws OspException {
            for (TransferValidator validator : validators) {
                if (!validator.validator(ctx)) {
                    return false;
                }
            }
            return true;
        }
    }
}
//业务代码使用
public interface TransferCreator {
    boolean createOrder(CreateTransferCtx ctx) throws OspException;
}
public abstract class DefaultTransferCreator implements TransferCreator {
     @Override
    public boolean createOrder(CreateTransferCtx ctx) throws OspException {
        validator(ctx)
        //实现业务逻辑
    }
    protected abstract boolean validator(CreateTransferCtx ctx) throws OspException;
 }
//店仓调拨单
public class S2wRefundCreator extends DefaultTransferCreator {
	//较验器自由组装
    @Override
    protected boolean validator(CreateTransferCtx ctx) throws OspException {
        return transferValidators.newChain()
                .qtyValidator()
                .transferRouteCfgValidator()
                .prodValidator()
                .validator(ctx);
    }
}

通过上面的示例,其实抽像并不难,难的是我们要花时间去思考,去理解,只有自己花足够的多时间,反复训练我相信比较容易做到,最近在两个新项目,我们团队的部分成员反馈做梦都在想如何实现更合理。

四、总结

  写出满足这些评价标准的高质量代码,我们需要掌握一些更加细化、更加能落地的编程方法论,包括面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等。而所有这些编程方法论的最终目的都是为了编写出高质量的代码。

  比如,面向对象中的继承、多态能让我们写出可复用的代码;编码规范能让我们写出可读性好的代码;设计原则中的单一职责、DRY、基于接口而非实现、里式替换原则等,可以让我们写出可复用、灵活、可读性好、易扩展、易维护的代码;设计模式可以让我们写出易扩展的代码;持续重构可以时刻保持代码的可维护性等等,以上示例仅供参考,也希望大家更多参与讨论。

到此这篇关于Java项目工程代码深度刨析总结的文章就介绍到这了,更多相关Java工程代码内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java比较器实现方法项目案例

    本文实例讲述了Java比较器实现方法.分享给大家供大家参考,具体如下: 1 需求 一个项目,展示监控数据列表,数据来源于接口,不需要分页,目前可时长排序: 客户希望可先对[状态]分组,然后再对[时长]排序. 2 分析 考虑以下方案: ①.编写 js 脚本,在前端做分组排序. ②.利用 Java 比较器,在后端做分组排序,前端直接渲染即可. 比较后发现使用 Java 比较器实现,更方便些. 3 Java 比较器 Java 中有两种比较器的实现方式:Comparable(内部比较器) 与 Compa

  • Java程序测试上传Maven工程代码示例解析

    创建普通Maven工程 导入所需依赖坐标: <dependencies> <!-- https://mvnrepository.com/artifact/net.oschina.zcx7878/fastdfs-client-java --> <dependency> <groupId>net.oschina.zcx7878</groupId> <artifactId>fastdfs-client-java</artifactId

  • Java项目工程代码深度刨析总结

    目录 一.背景 二.衡量代码好环的原则 2.1 评判代码指标 2.2 指导理论 三.代码实现技巧 3.1 抽像能力 3.2 组合/聚合复用原则 四.总结 一.背景   最近我们团队有幸接了两个0到1的项目,一期项目很紧急,团队成员也是加班加点,从开始编码到完成仅用了一星期多一点点,期间还不断反复斟酌代码如何抽象代码,如何写得更优雅,一遍又一遍的调整,我也是一次又次的阅读每个团队成员的代码,虽然还有些不如意,但整体来说还算是满意,参与项目的成员经过不断琢磨,对一些功能不断抽像,团队进步也是非常明显

  • Java 关于时间复杂度和空间复杂度的深度刨析

    目录 1.算法效率 2.时间复杂度 2.1时间复杂度的概念 2.2大O的渐进表示法 2.3常见时间复杂度计算 2.3.1常用的时间复杂度量级 2.3.2常见示例举例 2.3.2示例答案及分析 3.空间复杂度 1.算法效率 算法效率分析分为两种:第一种是时间效率,第二种是空间效率.时间效率被称为时间复杂度,而空间效率被称作空间复杂度. 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间 如今我们更关注的是时间复杂度,而对空间复杂度已不再关注. 2.时间复杂度 2

  • Java设计模式之组合模式深入刨析

    目录 1.基本介绍 2.结构 3.组合模式解决的问题 4.组合模式解决学校院系展示 5.组合模式的注意事项和细节 1.基本介绍 1)组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系 2)组合模式依据树形结构来组合对象,用来表示部分以及整体层次 3)这种类型的设计模式属于结构型模式 4)组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象 2.结构 组

  • Java面向对象特性深入刨析封装

    目录 1.认识封装 2.控制访问权限-访问修饰符 3.理解封装必须要知道-包 3.1理解包的概念 3.2 导入包中的类 3.3 自定义包 3.4 包的访问权限控制 3.5 java中常见的包 前面已经提过了 Java是一门面向对象(oop)的进行编程的语言, 面向对象的编程,有很多的好处,比如更容易开拓思维,分工合作,提高开发效率, 最主要的是 可重用性高,也就是下面将要提到的这三个核心特性(封装,继承,多态). 可扩展性,易于管理. 1.认识封装 简单的一句话就是套壳屏蔽细节. 比如说一部手机

  • Java集合框架之List ArrayList LinkedList使用详解刨析

    目录 1. List 1.1 List 的常见方法 1.2 代码示例 2. ArrayList 2.1 介绍 2.2 ArrayList 的构造方法 2.3 ArrayList 底层数组的大小 3. LinkedList 3.1 介绍 3.2 LinkedList 的构造方法 4. 练习题 5. 扑克牌小游戏 1. List 1.1 List 的常见方法 方法 描述 boolean add(E e) 尾插 e void add(int index, E element) 将 e 插入到 inde

  • Java集合框架之Stack Queue Deque使用详解刨析

    目录 1. Stack 1.1 介绍 1.2 常见方法 2. Queue 2.1 介绍 2.2 常见方法 3. Deque 3.1 介绍 3.2 常见方法 1. Stack 1.1 介绍 Stack 栈是 Vector 的一个子类,它实现了一个标准的后进先出的栈.它的底层是一个数组. 堆栈只定义了默认构造函数,用来创建一个空栈.堆栈除了包括由 Vector 定义的所有方法,也定义了自己的一些方法. 1.2 常见方法 方法 描述 E push(E item) 压栈 E pop() 出栈 E pee

  • Java 十大排序算法之冒泡排序刨析

    目录 冒泡排序原理 冒泡排序API设计 冒泡排序的代码实现 冒泡排序的时间复杂度分析 冒泡排序原理 ①比较相邻的元素,如果前一个元素比后一个元素大,则交换这两个元素的位置 ②对每一对相邻的元素循环上面的步骤,最终最后面的元素就是最大值 冒泡排序API设计 类名 Bubble 构造方法 Bubble:创建Bubble对象 成员方法 1.public static void sort(Comparable[] a):对数组内元素进行排序 2.private static void greater(C

  • Java 十大排序算法之选择排序刨析

    目录 选择排序原理 选择排序API设计 选择排序代码实现 选择排序的时间复杂度 选择排序原理 ①假设第一个索引处的元素为最小值,和其他值进行比较,如果当前的索引处的元素大于其他某个索引处的值,则假定其他某个索引处的值为最小值,最后找到最小值所在的索引 ②交换第一个索引处和最小值所在的索引处的值 选择排序API设计 类名 Selection 构造方法 Selection():创建Selection对象 成员方法 1.public static void sort(Comparable[] a):对

  • Java 十大排序算法之插入排序刨析

    目录 插入排序原理 插入排序API设计 插入排序代码实现 插入排序的时间复杂度分析 插入排序原理 ①把所有元素分成已排序和未排序两组 ②找到未排序组的第一个元素,向已经排序的组中进行插入 ③倒序遍历已经排好的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待插入元素放到这个位置,其他元素向后移动一位 插入排序API设计 类名 Insertion 构造方法 Insertion():创建Insertion对象 成员方法 1.public static void sort

  • Java 十大排序算法之归并排序刨析

    目录 归并排序原理 归并排序API设计 归并排序代码实现 归并排序的时间复杂度分析 归并排序原理 1.尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止. ⒉将相邻的两个子组进行合并成一个有序的大组. 3.不断的重复步骤2,直到最终只有一个组为止. 归并排序API设计 类名 Merge 构造方法 Merge():创建Merge对象 成员方法 1.public static void sort(Comparable[] a):对数组内的元素进行

随机推荐