《代码整洁之道》阅读笔记(四)——迭进

文章讲述了迭进设计原则在代码整洁和可测试性提升中的应用,包括运行所有测试、避免代码重复、明确意图、减少类和方法等,强调了测试、重构和命名的重要性和实践案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本次分享主要是第十一章,迭进,通过跌进设计达到整洁的目的。
根据书中的描述,只要遵循一下规则,设计就能变得“简单”:
1.运行所有测试;
2.不可重复;
3.表达了程序员的意图;
4.尽可能减少类和方法的数量;
以上规则按其重要程度排列。

1.运行所有测试

代码编写首先得保证它是能按照需求实现功能的,而检验这一标准的方法就是测试。测试编写得越多,越能帮助我们创建更好的设计。

2.重构

提升内聚性,降低耦合度,切分关注面,模块化系统性关注面,缩小函数和类的尺寸,选用更好的名称,如此等等。

3.不可重复

它代表着额外的工作、额外的风险和额外且不必要的复杂度。在遇到这种情况我们一般首先会想到共性抽取,抽出公用的方法。

4.表达力

把代码写的越清晰,其他人在理解代码上花的时间越少,从而减少缺陷,缩减维护成本。
可以通过一下方式提高表达准确性:
(1)选用好的名称来表达
(2)保持函数和类尺寸短小
(3)采用标准命名法
(4)编写良好的单元测试
(5)尝试

5.尽可能减少类和方法

这条规则优先级最低,但也很重要

6.代码分析

源码:

	**
     * 根据条件进行渠道路由,并记录在transfer的extraParams中
     * @param transfer
     */
    private void routeMethod(Transfer transfer) {
        BaseTransferParam transferParam;
        BankAccountInfo bankAccountInfo;
        if (TransferTypeEnum.WITHDRAW == transfer.getTransferType() ||
                TransferTypeEnum.DISTRIBUTE == transfer.getTransferType()) {
            transferParam = JSON.parseObject(transfer.getExtraParams(), WithdrawTransferParam.class);
            WithdrawTransferParam withdrawParam = (WithdrawTransferParam) transferParam;
            bankAccountInfo = convert2BankAccountInfo(withdrawParam);
            // 转账参数有cardToken,并且参数中没有收款银行信息时,通过pci查询卡资产
            if (StringUtils.isNotBlank(withdrawParam.getCardToken()) && StringUtils.isBlank(bankAccountInfo.getBankCode()) &&
                StringUtils.isBlank(bankAccountInfo.getSwiftCode())) {
                FundResult<BankAccountInfoDTO> result = pciExternalService.queryBankAccountInfo(withdrawParam.getCardToken(), transfer.getUserId());
                AssertUtil.isTrue(result.isSuccess(), FinResultCode.ACCOUNT_NOT_EXIST, "queryBankAccountInfo failed,cardToken:%s,errorMsg:%s",
                        withdrawParam.getCardToken(), result.getErrorMessage());
                bankAccountInfo = convert2BankAccountInfo(result.getModule());
                transferParam.setBankCode(bankAccountInfo.getBankCode());
                transferParam.setSwiftCode(bankAccountInfo.getSwiftCode());
                transfer.setExtraParams(JSON.toJSONString(transferParam));
            }
        } else if (TransferTypeEnum.ALLOCATE == transfer.getTransferType()) {
            transferParam = JSON.parseObject(transfer.getExtraParams(), AllocateTransferParam.class);
            bankAccountInfo = convert2BankAccountInfo(transferParam);
        } else {
            throw FundException.build(FinResultCode.BUSINESS_NOT_SUPPORT, "unknown transferType={}", transfer.getTransferType());
        }
        /**
         * 1. 根据收款账号信息,寻找同行的serviceChannel
         */
        if(StringUtils.isBlank(transfer.getServiceChannel())) {
            String beneficiaryBankName = transferParam.getBankName();

            List<String> serviceChannels = serviceChannelRoutingEngine.getServiceChannels(
                    beneficiaryBankName,
                    transferParam instanceof WithdrawTransferParam? ((WithdrawTransferParam) transferParam).getCardToken() : null
            );

            if(serviceChannels.size() == 1) {
                transfer.setServiceChannel(serviceChannels.get(0));
            }
        }

        /**2. 通过serviceChannel、转账条件,筛选可用的paymentMethod */
        if(IPAY_SERVICE_CHANNEL.equals(transfer.getServiceChannel())) {
            /** citi.transfer.001固定使用ipay.Promptpay */
            transfer.setPaymentMethod(PayMethodType.PromptPay);
            transfer.setExchangeType(ExchangeType.RT_BRIDGE);
         } else if (TransferTypeEnum.DISTRIBUTE == transfer.getTransferType()
                && StringUtils.isNotBlank(transfer.getUserId())
                && FluxTransferDiamondCache.getTransferDftMidList().contains(transfer.getUserId())) {
            /** 开环针对提现代发商户userId进行判断,如果是白名单内的商户,都走DFT */
            transfer.setPaymentMethod(PayMethodType.DFT);
            transfer.setExchangeType(ExchangeType.FLUX_BATCH);
        } else {
            boolean isBkt = serviceChannelRoutingEngine.isBkt(bankAccountInfo.getSwiftCode());
            int promptPayStatus = serviceChannelRoutingEngine.promptPayStatus(transfer.getUserId(), transfer.getServiceChannel(), bankAccountInfo.getBankCode(),
                    transfer.getTransferAmount());
            int fastStatus = serviceChannelRoutingEngine.fastStatus(bankAccountInfo.getSwiftCode());
            String paymentMethodRoute = serviceChannelRoutingEngine.paymentMethodRoute();
            Money moneyBoundDFT = serviceChannelRoutingEngine.getDFTMoneyConfig();
            if (isBkt) {
                //同行转账优先
                transfer.setPaymentMethod(PayMethodType.BKT);
                transfer.setExchangeType(ExchangeType.FLUX_BATCH);
            } else if(promptPayStatus >= 0) {
                transfer.setPaymentMethod(PayMethodType.PromptPay);
                transfer.setExchangeType(ExchangeType.RT_BRIDGE);

                //通过开关判断promptPay是否在维护,需要hold
                if(promptPayStatus == 0) {
                    transfer.setStatus(TransferStatus.HOLD);
                }
            } else if (StringUtils.isNotEmpty(paymentMethodRoute)) {
                transfer.setPaymentMethod(paymentMethodRoute);
                transfer.setExchangeType(ExchangeType.FLUX_BATCH);
            } else if (transfer.getTransferAmount().isGreaterThan(moneyBoundDFT)) {
                //超过DFT阈值的使用DFT
                transfer.setPaymentMethod(PayMethodType.DFT);
                transfer.setExchangeType(ExchangeType.FLUX_BATCH);
            } else if(fastStatus >= 0){
                transfer.setPaymentMethod(PayMethodType.FAST);
                transfer.setExchangeType(ExchangeType.FLUX_BATCH);
            } else{
                //其他使用ACH
                transfer.setPaymentMethod(PayMethodType.ACH);
                transfer.setExchangeType(ExchangeType.FLUX_BATCH);
            }
        }

        String exchangeCode = serviceChannelRoutingEngine.getExchangeCode(transfer.getServiceChannel(), transfer.getPaymentMethod());
        transfer.setExchangeCode(exchangeCode);

        transferRepository.update(transfer);
        transfer.setVersion(transfer.getVersion() + 1);
    }

问题:
1.方法代码过长
2.部分if…else缺少注释
3.没有做方法抽取
上面的代码因为方法里面包含很多if…else,所以对这个方法的测试也跟着些得很复杂,有很多重复代码。

	@Test
    public void testCreateTransferIfRouteMethod1() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MockitoAnnotations.initMocks(this);

        Transfer transfer = getTransfer();
        transfer.setStatus(TransferStatus.ACCEPTED);
        transfer.setTransferId("test");

        FundResult<BankAccountInfoDTO> mockResult = mock(FundResult.class);
        Mockito.doReturn(mockResult).when(pciExternalService).queryBankAccountInfo(any(String.class), any(String.class));
        Mockito.when(transferDomainService.create(transfer)).thenReturn(any());
        Mockito.when(serviceChannelRoutingEngine.isBkt("test")).thenReturn(true);
        Mockito.when(serviceChannelRoutingEngine.promptPayStatus("1", "2", "3", Money.ofMinorUnit("THB", 1))).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.fastStatus("1")).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.getDFTMoneyConfig()).thenReturn(Money.ofMinorUnit("THB", 1));
        Mockito.when(serviceChannelRoutingEngine.getExchangeCode("1", "2")).thenReturn("any");
        //convert2BankAccountInfo()
        CreateTransferRequest transferRequest = getCreateTransferRequest();
        transferRequest.setTransferTypeEnum(TransferTypeEnum.DISTRIBUTE);
        String result = transferService.createTransfer(transferRequest);
        Assert.assertEquals(result, null);
    }

    @Test
    public void testCreateTransferIfRouteMethod2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MockitoAnnotations.initMocks(this);

        Transfer transfer = getTransfer();
        transfer.setStatus(TransferStatus.ACCEPTED);

        FundResult<BankAccountInfoDTO> mockResult = mock(FundResult.class);
        Mockito.doReturn(mockResult).when(pciExternalService).queryBankAccountInfo(any(String.class), any(String.class));
        Mockito.when(transferDomainService.create(transfer)).thenReturn(any());
        Mockito.when(serviceChannelRoutingEngine.isBkt("test")).thenReturn(true);
        Mockito.when(serviceChannelRoutingEngine.promptPayStatus("1", "2", "3", Money.ofMinorUnit("THB", 1))).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.fastStatus("1")).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.getDFTMoneyConfig()).thenReturn(Money.ofMinorUnit("THB", 1));
        Mockito.when(serviceChannelRoutingEngine.getExchangeCode("1", "2")).thenReturn("any");
        List<String> serviceChannels = new ArrayList<>();
        serviceChannels.add("citi.transfer.001");
        Mockito.when(serviceChannelRoutingEngine.getServiceChannels("BankName_test",null )).thenReturn(serviceChannels);

        CreateTransferRequest transferRequest = getCreateTransferRequest();
        transferRequest.setServiceChannel("");
        transferRequest.setTransferTypeEnum(TransferTypeEnum.ALLOCATE);
        String result = transferService.createTransfer(transferRequest);
        Assert.assertEquals(result, null);
    }

    @Test
    public void testCreateTransferIfRouteMethod3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MockitoAnnotations.initMocks(this);

        Transfer transfer = getTransfer();
        transfer.setStatus(TransferStatus.ACCEPTED);

        FundResult<BankAccountInfoDTO> mockResult = mock(FundResult.class);
        Mockito.doReturn(mockResult).when(pciExternalService).queryBankAccountInfo(any(String.class), any(String.class));
        Mockito.when(transferDomainService.create(transfer)).thenReturn(any());
        Mockito.when(serviceChannelRoutingEngine.isBkt("test")).thenReturn(true);
        Mockito.when(serviceChannelRoutingEngine.promptPayStatus("1", "2", "3", Money.ofMinorUnit("THB", 1))).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.fastStatus("1")).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.getDFTMoneyConfig()).thenReturn(Money.ofMinorUnit("THB", 1));
        Mockito.when(serviceChannelRoutingEngine.getExchangeCode("1", "2")).thenReturn("any");
        List<String> serviceChannels = new ArrayList<>();
        serviceChannels.add("citi.transfer.001");
        Mockito.when(serviceChannelRoutingEngine.getServiceChannels("BankName_test",null )).thenReturn(serviceChannels);

        CreateTransferRequest transferRequest = getCreateTransferRequest();
        transferRequest.setServiceChannel("");
        transferRequest.setTransferTypeEnum(mock(TransferTypeEnum.class));
        try{
            transferService.createTransfer(transferRequest);
        }catch (FundException e){
            Assert.assertEquals("BUSINESS_NOT_SUPPORT", String.valueOf(e.getErrorCode()));
        }

    }

    @Test
    public void testCreateTransferIfRouteMethod4() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MockitoAnnotations.initMocks(this);

        Transfer transfer = getTransfer();
        transfer.setStatus(TransferStatus.CLEARED);

        FundResult<BankAccountInfoDTO> mockResult = mock(FundResult.class);
        Mockito.doReturn(mockResult).when(pciExternalService).queryBankAccountInfo(any(String.class), any(String.class));
        Mockito.when(transferDomainService.create(transfer)).thenReturn(any());
        Mockito.when(serviceChannelRoutingEngine.isBkt("test")).thenReturn(false);
        Mockito.when(serviceChannelRoutingEngine.promptPayStatus(null, "PAYMENT_WALLET_TMN_001", "BankCaode_test", Money.ofMinorUnit("THB", 1))).thenReturn(0);
        Mockito.when(serviceChannelRoutingEngine.fastStatus("1")).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.getDFTMoneyConfig()).thenReturn(Money.ofMinorUnit("THB", 1));
        Mockito.when(serviceChannelRoutingEngine.getExchangeCode("1", "2")).thenReturn("any");
        //convert2BankAccountInfo()
        CreateTransferRequest transferRequest = getCreateTransferRequest();
        String result = transferService.createTransfer(transferRequest);
        Assert.assertEquals(result, null);
    }

    @Test
    public void testCreateTransferIfRouteMethod5() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MockitoAnnotations.initMocks(this);

        Transfer transfer = getTransfer();
        transfer.setStatus(TransferStatus.CLEARED);

        FundResult<BankAccountInfoDTO> mockResult = mock(FundResult.class);
        Mockito.doReturn(mockResult).when(pciExternalService).queryBankAccountInfo(any(String.class), any(String.class));
        Mockito.when(transferDomainService.create(transfer)).thenReturn(any());
        Mockito.when(serviceChannelRoutingEngine.isBkt("test")).thenReturn(false);
        Mockito.when(serviceChannelRoutingEngine.promptPayStatus(any(), anyString(), anyString(), any())).thenReturn(-1);
        Mockito.when(serviceChannelRoutingEngine.fastStatus("1")).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.getDFTMoneyConfig()).thenReturn(Money.ofMinorUnit("THB", 0));
        Mockito.when(serviceChannelRoutingEngine.getExchangeCode("1", "2")).thenReturn("any");
        //convert2BankAccountInfo()
        CreateTransferRequest transferRequest = getCreateTransferRequest();
        String result = transferService.createTransfer(transferRequest);
        Assert.assertEquals(result, null);
    }

    @Test
    public void testCreateTransferIfRouteMethod6() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MockitoAnnotations.initMocks(this);

        Transfer transfer = getTransfer();
        transfer.setStatus(TransferStatus.CLEARED);

        FundResult<BankAccountInfoDTO> mockResult = mock(FundResult.class);
        Mockito.doReturn(mockResult).when(pciExternalService).queryBankAccountInfo(any(String.class), any(String.class));
        Mockito.when(transferDomainService.create(transfer)).thenReturn(any());
        Mockito.when(serviceChannelRoutingEngine.isBkt("test")).thenReturn(false);
        Mockito.when(serviceChannelRoutingEngine.promptPayStatus(any(), anyString(), anyString(), any())).thenReturn(-1);
        Mockito.when(serviceChannelRoutingEngine.fastStatus(anyString())).thenReturn(1);
        Mockito.when(serviceChannelRoutingEngine.getDFTMoneyConfig()).thenReturn(Money.ofMinorUnit("THB", 2));
        Mockito.when(serviceChannelRoutingEngine.getExchangeCode("1", "2")).thenReturn("any");
        //convert2BankAccountInfo()
        CreateTransferRequest transferRequest = getCreateTransferRequest();
        String result = transferService.createTransfer(transferRequest);
        Assert.assertEquals(result, null);
    }

    @Test
    public void testCreateTransferIfRouteMethod7() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        MockitoAnnotations.initMocks(this);

        Transfer transfer = getTransfer();
        transfer.setStatus(TransferStatus.CLEARED);

        FundResult<BankAccountInfoDTO> mockResult = mock(FundResult.class);
        Mockito.doReturn(mockResult).when(pciExternalService).queryBankAccountInfo(any(String.class), any(String.class));
        Mockito.when(transferDomainService.create(transfer)).thenReturn(any());
        Mockito.when(serviceChannelRoutingEngine.isBkt("test")).thenReturn(false);
        Mockito.when(serviceChannelRoutingEngine.promptPayStatus(any(), anyString(), anyString(), any())).thenReturn(-1);
        Mockito.when(serviceChannelRoutingEngine.fastStatus(anyString())).thenReturn(-1);
        Mockito.when(serviceChannelRoutingEngine.getDFTMoneyConfig()).thenReturn(Money.ofMinorUnit("THB", 2));
        Mockito.when(serviceChannelRoutingEngine.getExchangeCode("1", "2")).thenReturn("any");
        //convert2BankAccountInfo()
        CreateTransferRequest transferRequest = getCreateTransferRequest();
        String result = transferService.createTransfer(transferRequest);
        Assert.assertEquals(result, null);
    }

如果routeMethod这个方法在逻辑不变且正常工作的前提下进行细化拆分,那么它的测试方法也可以简单很多,而全面测试并持续通过所有测试的系统,才是可测试的系统。在迭进设计的四条规则中,运行所有测试的优先级排在最前,说明程序保持可持续测试的重要性。

总结

书中介绍的规则均来自作者多年的实践经验,涵盖从命名到重构的多个编程方面,虽为一家之言,然诚有可资借鉴的价值。从《代码整洁之道》中可以学到:好代码和糟糕的代码之间的区别:如何编写好代码,如何将糟糕的代码转化为好代码:如何创建好名称、好函数、好对象和好类;如何格式化代码以实现其可读性的最大化:如何在不妨碍代码逻辑的前提下充分实现错误处理;如何进行单元测试和测试驱动开发。从书中收获良多,也意识到需要在今后的开发工作中不断实践不断学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值