优雅的函数设计

Posted 树懒很努力

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了优雅的函数设计相关的知识,希望对你有一定的参考价值。

文章目录


1. 拆分超大函数

当一个函数超过 80 行后,就属于超大函数,需要进行拆分。

  • 案例一:每一个代码块封装成一个函数

    • 每一个代码块必然有一个注释,用于解释这个代码块的功能。
    • 如果代码块前方有一行注释,就是在提醒你——可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。如果函数有一个描述恰当的名字,就不需要去看内部代码究竟是如何实现的。
    • 现象描述
    	public void liveDaily() 
            // 吃饭
            // 吃饭相关代码几十行
    
            // 编码
            // 编码相关代码几十行
    
            // 睡觉
            // 睡觉相关代码几十行
        
    
    • 建议方案
    	public void liveDaily() 
            // 吃饭
            eat();
    
            // 编码
            code();
    
            // 睡觉
            sleep();
        
    
        private void sleep() 
        
    
        private void code() 
        
    
        private void eat() 
        
    
  • 案例二:每一个循环体都可以封装为一个函数

    • 现象描述
    	public void live() 
            while (isAlive) 
    
                // 吃饭
                eat();
    
                // 编码
                code();
    
                // 睡觉
                sleep();
            
        
    
    • 建议方案
    	public void live() 
            while (isAlive) 
                // 每日生活
                liveDaily();
            
        
    
        private void liveDaily() 
            // 吃饭
            eat();
    
            // 编码
            code();
    
            // 睡觉
            sleep();
        
    
  • 案例三:每一个条件体都可以封装为一个函数

    • 现象描述
    	public void goOut() 
            // 判断是否周末
            // 判断是否周末: 是周末则游玩
            if (isWeekday()) 
                // 游玩代码几十行
            
            // 判断是否周末: 非周末则工作
            else 
                // 工作代码几十行
            
        
    
    • 建议方案
    	public void goOut() 
            // 判断是否周末
            // 判断是否周末: 是周末则游玩
            if (isWeekday()) 
                play();
            
            // 判断是否周末: 非周末则工作
            else 
                work();
            
        
        private void play() 
            // 游玩代码几十行
        
        private void work() 
            // 工作代码几十行
        
    
  • 主要收益

    • 函数越短小精悍,功能就越单一,往往生命周期较长;
    • 一个函数越长,就越不容易理解和维护,维护人员不敢轻易修改;
    • 在过长函数中,往往含有难以发现的重复代码。

2. 同一函数内代码块级别尽量一致

  • 现象描述
	public void liveDaily() 
        
        // 吃饭
        eat();
        
        // 编码
        code();
        
        // 睡觉
        // 睡觉相关代码几十行
    
  • 很明显,睡觉这块代码块,跟 eat(吃饭)和 code(编码)不在同一级别上,显得比较突兀。如果把写代码比作写文章,eat(吃饭)和 code(编码)是段落大意,而睡觉这块代码块属于一个详细段落。而在 liveDaily 这个函数上,只需要写出主要流程(段落大意)即可。
  • 建议方案
	public void liveDaily() 
        // 吃饭
        eat();
        // 编码
        code();
        // 睡觉
        sleep();
    
    // 睡觉
    private void sleep() 
        // 睡觉相关代码
    
  • 主要收益
    • 函数调用表明用途,函数实现表达逻辑,层次分明便于理解;
    • 不用层次的代码块放在一个函数中,容易让人觉得代码头重脚轻。

3. 封装相同功能代码为函数

  • 案例一:封装相同代码为函数
    • 现象描述
    	public void disableUser() 
            // 禁用黑名单用户
            List<Long> userIdList = queryBlackUser();
            for (Long userId : userIdList) 
                User userUpdate = new User();
                userUpdate.setId(userId);
                userUpdate.setEnable(Boolean.FALSE);
                userDAO.update(userUpdate);
            
            // 禁用过期用户
            userIdList = queryExpiredUser();
            for (Long userId : userIdList) 
                User userUpdate = new User();
                userUpdate.setId(userId);
                userUpdate.setEnable(Boolean.FALSE);
                userDAO.update(userUpdate);
            
        
    
    • 建议方案
    	public void disableUser() 
            // 禁用黑名单用户
            List<Long> userIdList = queryBlackUser();
            for (Long userId : userIdList) 
                disableUser(userId);
            
            // 禁用过期用户
            userIdList = queryExpiredUser();
            for (Long userId : userIdList) 
                disableUser(userId);
            
        
        private void disableUser(Long userId) 
            User userUpdate = new User();
            userUpdate.setId(userId);
            userUpdate.setEnable(Boolean.FALSE);
            userDAO.update(userUpdate);
        
    
  • 案例二:封装相似代码为函数
    • 封装相似代码为函数,差异性通过函数参数控制。
    • 现象描述
    	// 通过工单函数
        public void adoptOrder(Long orderId) 
            Order orderUpdate = new Order();
            orderUpdate.setId(orderId);
            orderUpdate.setStatus(OrderStatus.ADOPTED);
            orderUpdate.setAuditTime(new Date());
            orderDAO.update(orderUpdate);
        
        // 驳回工单函数
        public void rejectOrder(Long orderId) 
            Order orderUpdate = new Order();
            orderUpdate.setId(orderId);
            orderUpdate.setStatus(OrderStatus.REJECTED);
            orderUpdate.setAuditTime(new Date());
            orderDAO.update(orderUpdate);
        
    
    • 建议方案
    	// 通过工单函数
        public void adoptOrder(Long orderId) 
            auditOrder(orderId, OrderStatus.ADOPTED);
        
        // 驳回工单函数
        public void rejectOrder(Long orderId) 
            auditOrder(orderId, OrderStatus.REJECTED);
        
        // 审核工单函数
        private void auditOrder(Long orderId, OrderStatus
                orderStatus) 
            Order orderUpdate = new Order();
            orderUpdate.setId(orderId);
            orderUpdate.setStatus(orderStatus);
            orderUpdate.setAuditTime(new Date());
            orderDAO.update(orderUpdate);
        
    
  • 主要收益
    • 封装公共函数,减少代码行数,提高代码质量;
    • 封装公共函数,使业务代码更精炼,可读性可维护性更强。

4. 封装获取参数值函数

  • 现象描述
	// 是否通过函数
	public boolean isPassed(Long userId) 
        // 获取通过阈值
        double thisPassThreshold = PASS_THRESHOLD;
        if (Objects.nonNull(passThreshold)) 
            thisPassThreshold = passThreshold;
        
        // 获取通过率
        double passRate = getPassRate(userId);
        // 判读是否通过
        return passRate >= thisPassThreshold;
    
  • 建议方案
	// 是否通过函数
    public boolean isPassed(Long userId) 
        // 获取通过阈值
        double thisPassThreshold = getPassThreshold();
        // 获取通过率
        double passRate = getPassRate(userId);
        // 判读是否通过
        return passRate >= thisPassThreshold;
    
    // 获取通过阈值函数
    private double getPassThreshold() 
        if (Objects.nonNull(passThreshold)) 
            return passThreshold;
        
        return PASS_THRESHOLD;
    
  • 主要收益
    • 把获取参数值从业务函数中独立,使业务逻辑更清晰;
    • 封装的获取参数值为独立函数,可以在代码中重复使用。

5. 通过接口参数化封装相同逻辑

  • 现象描述
	// 发送审核员结算数据函数
    public void sendAuditorSettleData() 
        List<WorkerSettleData> settleDataList = auditTaskDAO.statAuditorSettleData();
        for (WorkerSettleData settleData : settleDataList) 
            WorkerPushData pushData = new WorkerPushData();
            pushData.setId(settleData.getWorkerId());
            pushData.setType(WorkerPushDataType.AUDITOR);
            pushData.setData(settleData);
            pushService.push(pushData);
        
    

    // 发送验收员结算数据函数
    public void sendCheckerSettleData() 
        List<WorkerSettleData> settleDataList = auditTaskDAO.statCheckerSettleData();
        for (WorkerSettleData settleData : settleDataList) 
            WorkerPushData pushData = new WorkerPushData();
            pushData.setId(settleData.getWorkerId());
            pushData.setType(WorkerPushDataType.CHECKER);
            pushData.setData(settleData);
            pushService.push(pushData);
        
    
  • 建议方案
	// 发送审核员结算数据函数
    public void sendAuditorSettleData() 
        sendWorkerSettleData(WorkerPushDataType.AUDITOR, () -> auditTaskDAO.statAuditorSettleData());
    

    // 发送验收员结算数据函数
    public void sendCheckerSettleData() 
        sendWorkerSettleData(WorkerPushDataType.CHECKER, () -> auditTaskDAO.statCheckerSettleData());
    

    // 发送作业员结算数据函数
    public void sendWorkerSettleData(WorkerPushDataType dataType, WorkerSettleDataProvider dataProvider) 
        List<WorkerSettleData> settleDataList = dataProvider.statWorkerSettleData();
        for (WorkerSettleData settleData : settleDataList) 
            WorkerPushData pushData = new WorkerPushData();
            pushData.setId(settleData.getWorkerId());
            pushData.setType(dataType);
            pushData.setData(settleData);
            pushService.push(pushData);
        
    

    // 作业员结算数据提供者接口
    private interface WorkerSettleDataProvider 
        // 统计作业员结算数据
        List<WorkerSettleData> statWorkerSettleData();
    
  • 主要收益
    • 把核心逻辑从各个业务函数中抽析,使业务代码更清晰
      更易维护;
    • 避免重复性代码多次编写,精简重复函数越多收益越
      大。

6. 减少函数代码层级

如果要使函数优美,建议函数代码层级在 1-4 之间,过多的缩进会让函数难以阅读。

  • 案例一:利用 return 提前返回函数
    • 现象描述
    	// 获取用户余额函数
        public Double getUserBalance(Long userId) 
            User user = getUser(userId);
            if (Objects.nonNull(user)) 
                UserAccount account = user.getAccount();
                if (Objects.nonNull(account)) 
                    return account.getBalance();
                
            
            return null;
        
    
    • 方案建议
    	// 获取用户余额函数
        public Double getUserBalance(Long userId) 
            // 获取用户信息
            User user = getUser(userId);
            if (Objects.isNull(user)) 
                return null;
            
            // 获取用户账户
            UserAccount account = user.getAccount();
            if (Objects.isNull(account)) 
                return null;
            
            // 返回账户余额
            return account.getBalance();
        
    
  • 案例二:利用 continue 提前结束循环
    • 现象描述
    	// 获取合计余额函数
        public double getTotalBalance(List<User> userList) 
            // 初始合计余额
            double totalBalance = 0.0D;
            // 依次累加余额
            for (User user : userList) 
                // 获取用户账户
                UserAccount account = user.getAccount();
                if (Objects.nonNull(account)) 
                    // 累加用户余额
                    Double balance = account.getBalance();
                    if (Objects.nonNull(balance)) 
                        totalBalance += balance;
                    
                
            
            // 返回合计余额
            return totalBalance;
        
    
    • 方案建议
    	// 获取合计余额函数
        public double getTotalBalance(List<User> userList) 
            // 初始合计余额
            double totalBalance = 0.0D;
            // 依次累加余额
            for (User user : userList) 
                // 获取用户账户
                UserAccount account = user.getAccount();
                if (Objects.isNull(account)) 
                    continue;
                
                // 累加用户余额
                Double balance = account.getBalance();
                if (Objects.nonNull(balance)) 
                    totalBalance += balance;
                
            
            // 返回合计余额
            return totalBalance;
        
    
  • 主要收益
    • 代码层级减少,代码缩进减少;
    • 模块划分清晰,方便阅读维护。

7. 封装条件表达式函数

  • 案例一:把简单条件表达式封装为函数
    • 现象描述
    	// 获取门票价格函数
        public double getTicketPrice(Date currDate) 
            if (Objects.nonNull(currDate) && currDate.after(DISCOUNT_BEGIN_DATE)
                    && currDate.before(DISCOUNT_END_DATE)) 
                return TICKET_PRICE * DISCOUNT_RATE;
            
            return TICKET_PRICE;
        
    
    • 方案建议
    	// 获取门票价格函数
        public double getTicketPrice(Date currDate) 
            if (isDiscountDate(currDate)) 
                return TICKET_PRICE * DISCOUNT_RATE;
            
            return TICKET_PRICE;
        
    
        // 是否折扣日期函数
        private static boolean isDiscountDate(Date currDate) 
            return Objects.nonNull(currDate) &&
                    currDate.after(DISCOUNT_BEGIN_DATE)
                    && currDate.before(DISCOUNT_END_DATE);
        
    
  • 案例二:把复杂条件表达式封装为函数
    • 现象描述
    	// 获取土豪用户列表
        public List<User> getRichUserList(List<User> userList) 
            // 初始土豪用户列表
            List<User> richUserList = new ArrayList<>();
            // 依次查找土豪用户
            for (User user : userList) 
                // 获取用户账户
                UserAccount account = user.getAccount();
                if (Objects.nonNull(account)) 
                    // 判断用户余额
                    Double balance = account.getBalance();
                    if (Objects.nonNull(balance) &&
                            balance.compareTo(RICH_THRESHOLD) >= 0) 
                        // 添加土豪用户
                        richUserList.add(user);
                    
                
            
            // 返回土豪用户列表
            return richUserList;
        
    
    • 方案建议
    	// 获取土豪用户列表
        public List<User> getRichUserList(List<User> userList) 
            // 初始土豪用户列表
            List<User> richUserList = new ArrayList<>();
            // 依次查找土豪用户
            for (User user : userList) 
           

    以上是关于优雅的函数设计的主要内容,如果未能解决你的问题,请参考以下文章

    重构.改善既有代码的设计11处理概括关系更优雅的继承

    土豪福音?模块化Mac Pro2019年发布!

    REST API 设计 - 提现余额动作

    如何设计账户余额的数据准确性?

    如何设计账户余额的数据准确性?

    如何设计账户余额的数据准确性?