美文网首页码农的世界
从Simple Design入手让代码易修改

从Simple Design入手让代码易修改

作者: PageThinker | 来源:发表于2019-08-10 16:50 被阅读95次

    人们擅长处理好简单的事情,遇到复杂、繁杂的事情可以通过拆分成为异域处理的简单事情。

    《技术人员如何拥抱变化?》那边文章中提到了“Simple Design”。那么如何让Simple Design来让代码易修改呢?

    先回顾下Simple Design指什么。

    1. 实现功能;
    2. 揭示意图;
    3. 消除重复;
    4. 最少元素
    

    如果你真的实践过,但是感觉没有什么效果,可能是遇到了下面两个问题:

    1. 每次按照上面的流程做感觉一点都不人性,我觉得我们如何使用的问题;
    2. 按照Simple Design的推荐做了,代码易修改上确实有一定的提升,但是似乎又止步不前进了。

    下面我们来分析以上两个问题:

    1. 每次按照上面的流程做感觉一点都不人性,我觉得我们如何使用的问题;

    当我们将一个方法应用到实践中时,其实需要做到两点:

    1. 正确理解方法的本身的目的和内容;
    2. 刻意练习后,根据实际场景灵活应用。

    下面根据对Simple Design的熟悉程度,列举了两个使用Simple Design的实践过程。

    1. 对Simple Design并不熟悉,刚接触Simple Design或者平时Coding时有时会记得有时根本就忘了。
    2. 对Simple Design已经非常熟悉。

    针对上面两场场景,我们看看如何应用Simple Design:

    1.1 对Simple Design并不熟悉,刚接触Simple Design或者平时Coding时有时会记得有时根本就忘了。

    对Simple Design不熟悉,那么就解决不熟悉的问题。我们可以按照下图的优先级来进行练习和应用。

    simple.png

    首先实现业务功能,应为价值是我们做所有事情的核心,但并不能仅止于实现业务,因为后续是否易于修改取决于当前是否能够保持代码简单。

    所以,在实现业务之后利用2分钟来review一遍自己的的代码。看是否代码揭示了意图,是否有重复代码存在。如果有,那么就进行修改让代码揭示意图、消除重复代码。

    为了易于操作,我们建议先揭示意图,然后消除重复代码。

    我们举个实际例子:

    @GetMapping("/users")
    public Map<String, Object> getUsers() {
        List<User> us = userService.getAllUsers();
        
        List<UserDTO> uds = usersMapper.toDtoList(us);
        
        Map<String, Object> r = new HashMap<>();
        r.put("status", "success");
        r.put("users", uds);
        return r;
    }
    
    

    很显然上面的代码已经实现了业务功能。

    我们看看是否揭示了意图,揭示意图指的是业务意图。

    public Map<String, Object> getUsers() {
    

    通过第1行和第2行,通过方法名我们知道应该返回的user列表。但是返沪指却是Map类型,造成了疑问1:为什么返回Map呢?

    再继续往下读,因为方法实现很短,我们能够快速知道方法内做了什么事。

    先看下倒数第2行到倒数第5行,

    @GetMapping("/users")
    public Map<String, Object> getUsers() {
        ...
        
        Map<String, Object> r = new HashMap<>();
        r.put("status", "success");
        r.put("users", uds);
        return r;
    }
    

    通过Map中的Key原来是期望返回status和users两个信息。第二个问题是Map类型的变量名用了r表示,这是一个显而易见的问题,后面会同意说变量命名问题。

    返回status估计是想告诉调用方处理正常,这里开发者如果对HTTP状态码熟悉,完全可以使用HTTP状态码的200来表示请求处理正常,所以这里可以直接返回List<User>,而不用使用和方法名格格不入的一个Map类型。修改后的代码如下;

    @GetMapping("/users")
    public List<Users> getUsers() {
        ...
        
        return usd;
    }
    

    细心的同学或许早就发现,第3行、第5行的变量名的问题,以及刚才的Map类型的变量使用的是r表示。

        ...
        List<User> us = userService.getAllUsers();
        List<UserDTO> uds = usersMapper.toDtoList(us);
        Map<String, Object> r = new HashMap<>();
        ...
    

    获取有人觉得这样写省时间,毕竟开发时间有限。但是却暴露了一个对开发工具不熟悉的问题。如果真的是为了省时间,可以直接使用自动生成变量的快捷键,自动生成的命名不单单命名规范而且用时最短。这里我们将变量名重新命名后,代码如下:

    @GetMapping("/users")
    public Map<String, Object> getUsers() {
        List<User> users = userService.getAllUsers();
        List<UserDTO> userDTOs = usersMapper.toDtoList(users);
        return userDTOs;
    }
    

    结束了吗?还没有。

    我们发现中间的这些变量users、userDTOs,虽然定义了,但是均使用了一次,所以我们针对这个问题可以Inline。Inline后的代码如下:

    @GetMapping("/users")
    public Map<String, Object> getUsers() {
        return usersMapper.toDtoList(userService.getAllUsers());
    }
    

    Inline后发现仅剩下一行,为了让代码更加具有可读性。我们增加一些刻意的换行。

    @GetMapping("/users")
    public Map<String, Object> getUsers() {
        return usersMapper.toDtoList(
                    userService.getAllUsers()
        );
    }
    

    到现在,再看看这段代码是不是非常易读了,这也就是在上一遍文章《为什么要关注代码的可读性?》讲述的,可读性提升了降低了代码的修改成本。

    【误区:】

    关于消除重复上,很多开发着只能识别出工具类,提前或者有意识的创建工具类。但是重复不仅仅是指生成公共工具类这样。同时还包括业务上的重复、业务代码中的重复、技术上的重复。上面的例子中就是status其实就是自定义和HTTP 状态码上的重复。如果有业务需要我们当然建议添加status,但是如果只是告诉接口调用方状态,那么建议直接使用HTTP状态码即可。

    1.2 对Simple Design已经非常熟悉。

    当对Simple Design非常熟悉之后,其实并不是按照之前的说的顺序去做的。如下图:

    在编码时就会不自觉的考虑到揭示意图、消除重复、最少元素,很有可能Simple Design中的结构动作一气呵成。当完成后提交代后提交代码前的Review是,同样按照Simple Design中的建议内容去查看刚才的代码是否还有改进的空间。

    2. 按照Simple Design的推荐做了,代码易修改上确实有一定的提升,但是似乎又止步不前进了。

    在现有能力下,使用Simple Design可能能为代码带来一些改善。但随着软件越来越复杂,你或许会发现依然有很多不易修改的代码出现。出了我们之前《技术人员如何拥抱变化?》说的:使用设计原则发现问题,使用设计模式解决问题。还有两方面来提升代码的易修改性。

    1. 识别代码的Bad Smell
    2. 熟悉重构手法。

    下面是坏味道和重构手法速查表:

    坏味道(英) 坏味道(中) 重构手法
    Alternative Classes with Different Interfaces 异曲同工的类 改变函数声明,搬移函数,提炼超类
    Comments 注释 提炼函数,改变函数声明,引入断言
    Data Class 纯数据类 封装记录,移除设值函数,搬移函数,提炼函数,拆分阶段
    Data Clumps 数据泥团 提炼类,引入参数对象,保持对象完整
    Divergent Change 发散式变化 提炼函数,搬移函数,提炼函数,提炼类
    Duplicated Code 重复代码 提炼函数,移动语句,函数上移
    Feature Envy 依恋情结 搬移函数,提炼函数
    Global Data 全局数据 封装变量
    Insider Trading 内幕交易 搬移函数,搬移字段,隐藏委托关系,以委托取代子类,以委托取代超类
    Large Class 过大的类 提炼类,提炼超类,以子类取代类型码
    Lazy element 冗赘的元素 内联函数,内联类,折叠集成体系
    Long Function 过长函数 提炼函数,以查询取代历史变量,引入参数对象,保持对象完整,以命令取代函数,分解条件表达式,以多态取代条件表但是,拆分循环
    Long Parameters List 过长参数列 以查询取代参数,保持对象完整,引入参数对象,移除标记参数,函数组合成类
    Loops 循环语句 以管道取代循环
    Message Chains 过长的消息链 隐藏委托关系,提炼函数,搬移函数
    Middle Man 中间人 移除中间人,内联函数,以委托取代超类,以委托取代子类
    Mutable Data 可变数据 封装变量,拆分变量,移除语句,提炼函数,将查询函数和修改函数分离,移除设值函数,以查询取代派生变量,函数组合成类,函数组合成变换,将引用对象改为值对象。
    Mysterious Name 神秘命名 改变函数声明,变量改名,字段改名。
    Primitive Obsession 基本类型偏执 以对象取代基本类型,以子类取代类型码,以多态取代条件表达式,提炼类,引入参数对象。
    Refused Bequest 被拒绝的遗赠 函数下移,字段下移,以委托取代子类,以委托取代超类。
    Repeated Switches 重复的Switch 以多态取代条件表达式
    Shotgun Surgery 霰弹式修改 提炼函数,搬移字段,函数组合成类,函数组合成变换,拆分阶段,内联函数,内联类
    Speculative Generality 夸夸其谈通用性 折叠集成体系,内联函数,内联类,改变函数声明,移除死代码
    Temporary Field 临时字段 提炼类,搬移函数,引入特例。

    3,最为重要的

    读了上面的内容,最重要的还是了解后理解并实践。可以使用下面的格式列出清单后,去实践上文的内容:

    在 xxx 时间内
    针对 xxx 内容
    计划做 xxx
    并通过 xxx 来验收
    

    相关文章

      网友评论

        本文标题:从Simple Design入手让代码易修改

        本文链接:https://www.haomeiwen.com/subject/qlirjctx.html