LCMS Code Review

概述

lcms项目前期开发人员最多时有10人,而且时间跨度大(从开始开发到最终上线有1年)。多人合作下,代码的质量也会有影响。加上大众对交付物有一些硬性要求,所以code review很有必要。

本文记录了一些在code review中,一些写代码的不好的习惯,以及对SSH的不理解或者理解不深的地方。

Code Review

  • 代码书写
    大众有专门的checkstyle规范(svw_checkstyle_configuration.xml)、代码模板(svw_codeStyle_codeTemplates_configuration.xml)和书写格式(svw_codeStyle_formatter_configuration.xml)要求,以及findBugs要求。
    在IDE中安装对应的checkstyle和findbugs插件,可以有效减少开发过程中的一些简单错误
  • 输入条件边界检验
    很多输入参数需要做非空判断后再使用,防止空指针等异常

  • 异常不处理
    开发中,异常很容易被忽略
    对于无需处理的异常,需要打印日志logger.error(“异常详细”, e)

    public void inputExcel() {
        try {
            //业务代码
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • DAO异常错误处理
    很多前台页面的保存、更新操作,需要给客户反馈成功与否,所以就会出现这样的代码
    /**
     * 
     * <p>
     * Description:添加或者更新对象
     * </p>
     * 
     * @param model 对象
     * @return 布尔值
     */
    @Override
    public boolean saveOrUpdate(ContainerTransmit model) {
        boolean containerTraFlag;
        containerTraFlag = true;
        //执行添加或者修改
        try {
            Session session;
            session = this.sessionFactory.getCurrentSession();
            session.saveOrUpdate(model);
            session.flush();
        } 
        catch (HibernateException e) {
            logger.error("", e);
            containerTraFlag = false;
        }
        return containerTraFlag;
    }

异常被catch到,返回false,然后前台提示“保存失败”;表面上看上去没有问题,但一个处理流程往往不止一个方法操作数据库,异常没被抛出,事务不会回滚,导致被修改或保存的数据出现问题。
修改后的做法为把异常抛出,然后通过统一的异常管理,提示客户保存失败。

  • IO流未关闭,或者错误关闭
/**
     * 下载
     * 
     * @date 2015-10-29 下午2:25:39
     * @param request 用户请求
     * @param response 服务器响应
     * @param storeName 服务器上文件名
     * @param contentType 文件类型
     * @param realName 导出文件名
     * @throws IOException 输入流异常
     */
    public static void download(HttpServletRequest request, HttpServletResponse response, String storeName,
            String contentType, String realName) throws IOException {

        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            response.setContentType("text/html;charset=UTF-8");
            request.setCharacterEncoding("UTF-8");

            String ctxPath;
            ctxPath = request.getSession().getServletContext().getRealPath(UPLOADDIR);
            String downLoadPath;
            downLoadPath = ctxPath + FILE_SEPARATOR + storeName;

            long fileLength;
            fileLength = new File(downLoadPath).length();

            response.setContentType(contentType);
            response.setHeader(CONTENT_DISPOSITION, ATTACHMENT
                    + new String(realName.getBytes(GBK), "ISO8859-1"));
            response.setHeader("Content-Length", String.valueOf(fileLength));
            response.setCharacterEncoding(ISO8859);
            bis = new BufferedInputStream(new FileInputStream(downLoadPath));
            bos = new BufferedOutputStream(response.getOutputStream());
            byte[] buff;
            buff = new byte[FILE_SIZE];
            int bytesRead;
            while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
                bos.write(buff, 0, bytesRead);
            }

        } catch (FileNotFoundException e) {
            logger.error(storeName + "文件找不到" + e.getMessage(), e);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        } finally {
            //关闭流
            if (bis != null) {
                bis.close();
            }
            if (bos != null) {
                bos.close();
            }

        }
    }
  • 错误使用finally,使得try中的异常被finally覆盖
 /**
     * 
     * <p>
     * Description: 导入库存盘点记录
     * </p>
     * 
     * @param file 文件
     * @param name 文件名
     * @param request 请求
     * @param response 响应
     * @param checkDesc 盘点描述
     * @param checkDate 盘点日期
     * @throws IOException 异常
     * @throws IllegalStateException 异常
     * @return 返回页面
     */
    @SuppressWarnings({ "finally" })
    @RequestMapping(value = "/importStockCheck.do")
    public ModelAndView importStockCheck(String name, @RequestParam("file") MultipartFile[] file,
            HttpServletRequest request, HttpServletResponse response, @RequestParam("checkDesc") String checkDesc,
            @RequestParam("checkDate") String checkDate) throws IllegalStateException, IOException {
        ModelAndView model;
        model = new ModelAndView();

        String checkFileOutPath;
        checkFileOutPath = null;
        List<List<Object>> inputList;
        inputList = new ArrayList<List<Object>>();
        ReadSheet readSheet;
        readSheet = new ReadSheet();
        InputStream fis;
        fis = null;
        OutputStream toClient;
        toClient = null;
        String realPath;
        realPath = null;
        File fileIn;
        fileIn = null;
        String error;
        error = null;
        // 取得文件名
        SysUser user;
        String fileName = null;
        boolean isDelete;
        isDelete = true; // 是否删除临时模版,默认删除
        //获得当前登录人的信息
        user = getCurrentUser(request);
        logger.info("importStockCheck begin......");
        try {
            for (MultipartFile myfile : file) {
                if (myfile.isEmpty()) {
                    logger.info("file is empty");
                } else {
                    fileName = myfile.getOriginalFilename();
                    // 生成唯一的模版名称,防止多人同时导入共用模版而发生的并发问题
                    fileName = StrUtil.getUniqueFileName(fileName);
                    realPath = request.getSession().getServletContext().getRealPath("/export");
                    //可以使用FileUtils来保存文件,这里不再列出代码
                    FileUtils.copyInputStreamToFile(myfile.getInputStream(),
                            new File(realPath, fileName));
                    fileIn = new File(realPath, fileName);
                    //校验导入条数
                    String rowLimitStr;
                    rowLimitStr = this.dictService.findDictValueByDictKeyAndParentKey("IMPORT_EXP_COUNT", "SYS_CONFIG");
                    if (rowLimitStr == null || "".equals(rowLimitStr.trim()) || "0".equals(rowLimitStr)) {
                        logger.info("请设置导入限制条数字典值");
                        model.addObject("dowlonded", "false");
                        model.addObject("checkFileOutPath", "rowNumDict");
                    } else {
                        int rowNumLimit;
                        rowNumLimit = Integer.parseInt(rowLimitStr);
                        boolean rowNum;
                        rowNum = AbstractValid.checkExcelRowNum(fileIn, rowNumLimit);
                        if (!rowNum) {
                            logger.info("导入数据不能超过" + rowNumLimit + "条");
                            model.addObject("dowlonded", "false");
                            model.addObject("checkFileOutPath", "rowNum");
                            model.addObject("rowNumLimit", rowNumLimit);
                        } else {
                            StockCheckExcel stockCheckExcel;
                            stockCheckExcel = new StockCheckExcel();
                            error = stockCheckExcel.checkFile(fileIn, user);
                            checkFileOutPath = stockCheckExcel.getFileOutPath();
                            if (StockStandardExcel.CONTENT_ERROR_FLAG.equals(error)
                                    || StockStandardExcel.LOCATIONNUM_NOT_EXIST.equals(error)
                                    || StockStandardExcel.EXCEPTION_ERROR_FLAG.equals(error)
                                    || StrUtil.EMPTY_ERROR_FLAG.equals(error)) {
                            } else {
                                //如果不是以上错误正确数据,进行插入操作
                                logger.info("readExcelForData......");
                                inputList = readSheet.readExcelForData(fileIn);
                                //转换并保存数据
                                this.stockCheckService.inputExcel(this.stockCheckService.turnData(inputList), 
                                        user, checkDate, checkDesc, file[0]);
                            }

                        }
                    }
                }
            }
        } catch (IOException e) {
            logger.error("", e);
        } finally {
            logger.info("importStockCheck end......");
            return importFinally(fis, toClient, fileIn, checkFileOutPath, model, error, isDelete);
        }

    }

现象:页面导入Excel,提示成功,但数据库没有记录。
原因:catch仅仅是一个IOException,其他异常均没有catch并写入日志,这样一旦非IOException发生,均会被finally给覆盖,这样看日志是看不出问题的。(发生了一个Excel字段类型转换错误)

  • 在service层使用session
//ForecastWeekImpl.java
 /**
     * 零件预测转换需求
     * <p>
     * Description: task
     * </p>
     * 
     * @param versionNumList 版本号集合
     * @param flag 布尔值
     * @param containerVersionNum 料箱版本号
     * @return 版本号集合
     */
    @SuppressWarnings("rawtypes")
    @Override
    public List<String> convertWeek(List<String> versionNumList, boolean flag, String containerVersionNum) {
        List<String> returnVersion;
        returnVersion = new ArrayList<String>();
        IBaseDao baseDao;
        baseDao = (BaseDaoImpl) SpringContextUtil.getBean("baseDao");
        Session session = null;
        Transaction tx = null;
        String minStartWeek = null;
        Map<String, String> loopSizeList;
        try {
            session = baseDao.getCurrentSessionFactory().openSession();
            tx = session.beginTransaction(); //开启事物
            minStartWeek = this.getMinStartWeek(session); //获得最低的起始周数据并都根据今后进行计算
            ......
/**
     * 
     * <p>
     * Description: 获得最小的起始周数据
     * </p>
     * 
     * @param session session对象
     * @return 结果集
     */
    @SuppressWarnings("unchecked")
    private String getMinStartWeek(Session session) {
        SQLQuery query;
        String sql;
        sql = "select min(t.start_week) startWeek from TI_TT_SB2_SUPP_AGGREGATE_DATA t"
                + " where t.version_no in (select max(tts.version_no) versionNum"
                + " from TI_TT_SB2_SUPP_AGGREGATE_DATA tts" + " group by tts.plant)";
        query = session.createSQLQuery(sql).addScalar("startWeek", StandardBasicTypes.STRING);
        List<String> list;
        list = new ArrayList<String>();
        list = query.list();
        if (list.isEmpty()) {
            return null;
        } else {
            return list.get(0);
        }
    }
  • 在DAO层处理业务
//SupplierInverstDaoImpl.java
  @Override
    public void traInvestStock(AssetStock assetStock, Stock stock, SupplierInverstDetail invDetail,
            SupplierInverst model, SysUser user) {
        AssetStock assetSideStock;
        String type;
        Long quantity;
        quantity = invDetail.getQuantity();
        type = model.getType();
        //若类型为调整-,库存减
        if ("3".equals(type)) {
            quantity = 0 - invDetail.getQuantity();
        }
        assetSideStock = this.findAssetStock(assetStock);
        if (assetSideStock != null) {
            assetSideStock.setQuantity(assetSideStock.getQuantity() + quantity);
            assetSideStock.setUpdateDate(new Date());
            assetSideStock.setUpdateUser(user.getEmployeeNum());
            try {
                this.assetStockDao.saveOrUpdate(assetSideStock, user);
            } catch (StaleObjectStateException e) {
                // 乐观锁异常,提示数据已被修改,请重新提交
                throw new BusinessException("资产方库存数据已被修改,请重新提交。");
            }
        } else {
            assetStock.setDelFlag(StrUtil.NOT_DEL);
            assetStock.setCreateDate(new Date());
            assetStock.setAssetNum(model.getAssetNum());
            assetStock.setPlant(model.getLocation().getPlant());
            assetStock.setContainerNum(invDetail.getContainerNum());
            assetStock.setCreateUser(user.getEmployeeNum());
            assetStock.setQuantity(0 + quantity);
            try {
                this.assetStockDao.saveOrUpdate(assetStock, user);
            } catch (ConstraintViolationException e) {
                // 唯一性约束异常
                throw new BusinessException("唯一性约束异常。");
            }
        }
        Stock sto;
        sto = this.findStock(stock);
        if (sto != null) {
            sto.setStock(sto.getStock() + quantity);
            sto.setUpdateDate(new Date());
            sto.setUpdateUser(user.getEmployeeNum());
            try {
                this.stockDao.saveOrUpdate(sto, user);
            } catch (StaleObjectStateException e) {
                // 乐观锁异常,提示数据已经被修改,请重新提交
                throw new BusinessException("工位器具库存数据已被修改,请重新提交。"); 
            }
        } else {
            stock.setDelFlag(StrUtil.NOT_DEL);
            stock.setCreateDate(new Date());
            stock.setLocationNum(model.getLocationNum());
            stock.setContainerNum(invDetail.getContainerNum());
            stock.setCreateUser(user.getEmployeeNum());
            stock.setStock(0 + quantity);
            try {
                this.stockDao.saveOrUpdate(stock, user);
            } catch (ConstraintViolationException e) {
                // 唯一性约束异常,提示业务主键冲突
                throw new BusinessException("唯一性约束异常,提示业务主键冲突。");
            }
        }
    }

破坏了MVC结构,使代码不容易理解,同时有可能造成一些bug。

  • 在Controller层中处理业务
    //ContainerCmcController.java
    /**
     * <p>
     * Description: 添加工位器CMC 生成发箱记录并打印
     * </p>
     * @param traDateStr 发出日期
     * @param ifPlantHidden 分工厂产权
     * @param containerTransmit 工位器具
     * @param request 请求
     * @param response 响应
     * @throws Exception 异常
     * @return 结果
     */
    @RequestMapping(value = "/toCmcSendAll.do")
    @ResponseBody
    public Object toCmcSendAll(@RequestParam(value = "traDateStr") String traDateStr,
            @RequestParam(value = "ifPlantHidden") String ifPlantHidden,
            @ModelAttribute ContainerTransmit containerTransmit, 
            HttpServletRequest request,
            HttpServletResponse response) throws Exception {

      ...

      //执行添加或更新操作
      this.containerCmcService.saveCmcSend...
      //生成pdf并打印
      this.containerCmcService.createCmcPdf...
      ......    

    }

因为controller是没有事务保护的,所以调用的每个service方法执行完毕后都会立即提交事务,一旦一个service方法出现异常,已经执行过的方法也不会回滚。

  • Controller、Dao中使用全局变量
    在Struts2的action中,可以写全局变量,因为action是多实例的;而springMVC默认是单实例的。dao属于spring bean,默认scope(bean作用域)是singleton
@Controller
@RequestMapping(value = "/user")
public class UserController extends BaseController<SysUser> {
    /**
     * userService
     */
    @Autowired
    @Qualifier("userService")
    private IUserService userService;
//    /**
//     * 用户查询条件
//     */
//    private SysUser filter;

//    /**
//     * viewModel
//     */
//    private ModelAndView viewModel;
......
@Repository("userDao")
public class UserDaoImpl extends BaseDaoImpl<SysUser, Serializable> implements IUserDao {
    /** 日志 **/
    private static Logger logger = Logger.getLogger(UserDaoImpl.class);

//    /**
//     * 用于查询的对象
//     */
//    private Query query;

    /** likes 查询使用 % **/
    private final String likes = "%";
    ......
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值