人们擅长处理好简单的事情,遇到复杂、繁杂的事情可以通过拆分成为易于处理的简单事情。
在《技术人员如何拥抱变化?》那边文章中提到了“Simple Design”。那么如何让Simple Design来让代码易修改呢?
先回顾下Simple Design指什么。
1. 实现功能;
2. 揭示意图;
3. 消除重复;
4. 最少元素
如果你真的实践过,但是感觉没有什么效果,可能是遇到了下面两个问题:
- 每次按照上面的流程做感觉一点都不人性,我觉得我们如何使用的问题;
- 按照Simple Design的推荐做了,代码易修改上确实有一定的提升,但是似乎又止步不前进了。
下面我们来分析以上两个问题:
1. 每次按照上面的流程做感觉一点都不人性,我觉得我们如何使用的问题;
当我们将一个方法应用到实践中时,其实需要做到两点:
- 正确理解方法的本身的目的和内容;
- 刻意练习后,根据实际场景灵活应用。
下面根据对Simple Design的熟悉程度,列举了两个使用Simple Design的实践过程。
- 对Simple Design并不熟悉,刚接触Simple Design或者平时Coding时有时会记得有时根本就忘了。
- 对Simple Design已经非常熟悉。
针对上面两场场景,我们看看如何应用Simple Design:
1.1 对Simple Design并不熟悉,刚接触Simple Design或者平时Coding时有时会记得有时根本就忘了。
对Simple Design不熟悉,那么就解决不熟悉的问题。我们可以按照下图的优先级来进行练习和应用。
首先实现业务功能,应为价值是我们做所有事情的核心,但并不能仅止于实现业务,因为后续是否易于修改取决于当前是否能够保持代码简单。
所以,在实现业务之后利用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可能能为代码带来一些改善。但随着软件越来越复杂,你或许会发现依然有很多不易修改的代码出现。出了我们之前《技术人员如何拥抱变化?》说的:使用设计原则发现问题,使用设计模式解决问题。还有两方面来提升代码的易修改性。
- 识别代码的Bad Smell
- 熟悉重构手法。
下面是坏味道和重构手法速查表:
坏味道(英) | 坏味道(中) | 重构手法 |
---|---|---|
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 来验收