我对写好事务代码的一些个人考虑

我对于一些僵硬的名词介绍怎么写好事务代码觉得十分难明,想要写好事务代码我觉得需求结合一些实践案例来理解究竟应该怎么写好事务代码

我在、csdn…阅览过一些文章,也总结了一些个人考虑

1.可读性问题

同办法下代码语义层级不一致导致的可读性变差

  • 反例

    下面的代码示例中,办法名是保存还款数据的,首要有3个过程:

    第一个过程是刺进事务防重表

    第二个过程是批量更新还款明细

    第三个过程是刺进异步使命

    经过代码咱们能够显着的发现第一步和第二三步是不在同一个语义层级上的,第一步把处理的具体细节暴露在了这一层,导致这一层的可读性下降了

public void saveRepaymentData(){
   //S1 :刺进防重表
   String uuid =BizUniqueUtils.getUUID(BusinessType.REPAYMENT.getValue(), reqVo.getRepaymentNo()+"_"+ reqVo.getPin()); 
   BizUnique bizUnique =newBizUnique();
   bizUnique.setUuid(uuid);
   bizUnique.setPin(reqVo.getPin());
   bizUnique.setBusinessId(reqVo.getRepaymentNo());//事务ID 还款单号
   bizUnique.setBusinessType(BusinessType.REPAYMENT.getValue());
   bizUnique.setCreateDate(newDate());
   bizUniqueDao.insert(bizUnique);
   //S2: 批量更新还款单明细
   repaymentDetailService.batchUpdateRepayDetail(repayDetails);
   //S3: 写分期单实收使命
   saveSyncTaskData(repaymentMsgs,TaskTypeForJT.REPAYMENT_SUCCESS.getValue(), repaymentVo);
...}
  • 正例

    正确的做法应该是将细节的处理独自封装在一个办法里边,确保这个办法里边的三个过程处在同一个语义层级里边

public void saveRepaymentData(){
   //S1 :刺进防重表
   insertBizUnique(reqVo.getPin(),reqVo.getRepaymentNo()); 
   //S2: 批量更新还款单明细
   repaymentDetailService.batchUpdateRepayDetail(repayDetails);
   //S3: 写分期单实收使命
   saveSyncTaskData(repaymentMsgs,TaskTypeForJT.REPAYMENT_SUCCESS.getValue(), repaymentVo);
   ...
}
private insertBizUnique(String pin,String repaymentNo){
   String uuid =BizUniqueUtils.getUUID(BusinessType.REPAYMENT.getValue(), repaymentNo +"_"+ pin);
   BizUnique bizUnique =newBizUnique();
   bizUnique.setUuid(uuid);
   bizUnique.setPin(pin);
   bizUnique.setBusinessId(repaymentNo);//事务ID 还款单号
   bizUnique.setBusinessType(BusinessType.REPAYMENT.getValue());
   bizUnique.setCreateDate(newDate());
   bizUniqueDao.insert(bizUnique);
}

3.单一责任问题

单一责任适用于事务范畴的区分、应用责任的区分、类功用的区分以及办法等级的功用区分等场景。

责任不单一导致功用不够内聚,可读性变差,不能复用等问题

  • 反例

    下面的对外接口类的名称是RepaymentResource,首要功用应该是处理还款相关的功用。其间的第1个办法 planRepayment 是处理还款冲销的逻辑,第2个

    办法 updateRepayAccountAsset 是接收到还款成功异步音讯后恢复账户额度的功用,第2个办法虽然是还款成功触发的,可是很显着它的功用和还款没有直

    接关系,由于这个办法首要处理的是账户相关事务。

public interface RepaymentResource{
     /**
     * 还款冲销事务逻辑
     */
    PlanRepaymentResVo planRepayment(RepaymentReqVo planRepaymentReqVo);
    /**
     * 还款成功后恢复额度
     */
    BaseResponseVo updateRepayAccountAsset(RepaymentSuccessMsg repaymentSuccessMsg);
    ...
 }
  • 正例

    将第二个办法放到和账户相关的接口类中

public interface RepaymentResource{
     /**
     * 还款冲销事务逻辑
     */
    PlanRepaymentResVo planRepayment(RepaymentReqVo planRepaymentReqVo);
    ...
 }
 public interface AccountResource{
    /**
     * 还款成功后恢复额度
     */
    BaseResponseVo updateRepayAccountAsset(RepaymentSuccessMsg repaymentSuccessMsg);
    ...
 }

3.重复代码的问题

重复代码问题或许呈现在不同体系间的重复代码,或许同一个体系内不同模块间的重复代码,或许同一个类中的不同办法之间的重复代码。重复代码的比如很多,

这儿就不独自举例了。重复代码会导致保护本钱高,代码逻辑不一致,漏改代码等安稳性问题。

一般来说呈现了重复代码时,需求比照一下差异,看是否能将差异的代码进行笼统,经过笼统将重复的代码逻辑进行统一。

4.命名不标准

命名问题看似是最简单的问题,实践上也是在代码中呈现最多的问题,命名不标准会导致代码可读性变差。

常见的问题有:命名过分笼统不能准确说明类或许办法的功用;命名和实践功用不匹配,举例如下

  • 反例

    这段代码中的checkPassword办法,字面上的含义是校验用户的暗码,不会有任何的副作用。

    可是这段代码在暗码不合法的情况下做了Session的初始化动作(Session.initialize();)。

    导致调用方或许在不知情的情况下无意间对Session进行了初始化。

public class UserValidator{
      private Cryptographer cryptographer;
      public boolean checkPassword(String userName,String password){ 
        User user =UserGateway.findByName(userName);
        if(user !=User.NULL){
          String codedPhrase = user.getPhraseEncodedByPassword();
          String phrase = cryptographer.decrypt(codedPhrase, password);
          if("Valid Password".equals(phrase)){
            Session.initialize();   // 和办法名不一致的行为
            returntrue; 
          }
        }
        returnfalse; 
      }
}
  • 正例

    为了确保办法名和实践的功用一致,能够将 Session初始化的功用(Session.initialize(); ) 从函数中移除。

    或则将函数的名称改为 checkPasswordAndInitializeSession(不过这违反了单一责任的准则)。

    由于这儿即做了暗码校验,又做了session的初始化,可是单一准则并不是不可打破的

public class UserValidator{
      private Cryptographer cryptographer;
      public boolean checkPasswordAndInitializeSession(String userName,String password){ 
        User user =UserGateway.findByName(userName);
        if(user !=User.NULL){
          String codedPhrase = user.getPhraseEncodedByPassword();
          String phrase = cryptographer.decrypt(codedPhrase, password);
          if("Valid Password".equals(phrase)){
            Session.initialize();   // 和办法名不一致的行为
            returntrue; 
          }
        }
        returnfalse; 
      }
}

5.缺乏模型笼统,无鸿沟操控

  • 什么是缺乏模型笼统?

所有的目标都是只负责寄存数据的Java目标。这导致的最大问题便是任何代码都能够无所顾忌的去修正目标的任何特点,没有任何鸿沟操控

这个目标在调用过程中还伴随着各种beanCopy动作,最终导致目标的特点改变轨道彻底无法盯梢。其他人在阅览代码的时候会耗费大量的时刻来分析。

  • 反例

    下面的代码中的S1过程对入参的request目标进行了更新,然后在S2中或许又对request目标进行了改变,然后在S3中又进行了beanCopy的动作。

    经过这一系列的操作,很难去排查和定位request的amount特点究竟变成什么值了。这样的不光可读性差,并且十分难保护,并且很简单呈现问题。

@Data
public class Request{
     privateString pin;
     privateBigdecimal amount;
}
public class Service1{
      privateService2 service2;
      public boolean doBizProcess(Request req){ 
        req.setAmount(100);  //S1 : 随意设置request的特点值
        service2.doBizProcess2(req);//S2 :这个办法里或许会把request值设置为 200 
        BeanUtils.copyProperties(request1, req);//S3: 这儿又从别的一个目标copy过来特点
        returnfalse; 
      }
}
  • 正例

    按照事务进行建模,一般来说恳求目标应该被建模为一个值目标(Value Object)。

    值目标的特点是不会改变的,这个能够经过去掉值类的setter办法来实现。

    假如调用Service2的办法也需求一个Request目标,这时需求重新创建一个新的值目标。

    别的,在事务逻辑处理的代码中要制止beancopy的运用。

@Getter
@Builder
public class RequestVo{
     privateString pin;
     privateBigdecimal amount;
}
public class Service1{
      privateService2 service2;
      public boolean doBizProcess(Request req){ 
        RequestVo r2 =Request.builder().pin(req.getPin()).amount(100).build();   //S1 : 重新构建一个值目标
        service2.doBizProcess2(r2 );//S2 :由于没有setters,这个办法里现已不能随意改变r2的特点 
        BeanUtils.copyProperties(request1, req);//S3: 制止beancopy操作
        returnfalse; 
      }
}