Clean Code第八章 边界 --阅读与讨论

尼克徐 发布于 2016年01月14日
tinyfool 等1人欣赏。

alt text

图片来源:Chapter 8 Boundaries

有时,我们会购买第三方程序包或使用开放源代码,有时我们依靠公司其他团队打造组件或子系统。这一章讨论如何把这些外来代码干净利落地整合进自己地代码中。

一, 使用第三方代码

在接口提供者和使用者之间,存在着与生俱来的张力。

第三方程序包和框架提供者追求普适性,这样就能在多个环境中工作,吸引广泛的用户。而使用者则只想根据其特定需求而使用某些接口。

这种张力会导致系统边界上出现问题。

例如java.util.Map,Map有着广阔的接口和丰富的功能,虽然这些功能很有用,但也要付出代价。

比如,应用程序可能构造一个Map对象并传递它,其初衷是Map对象的所有接收者不要删除Map中的任何东西,但Map中正好有个clear()方法,使得任何使用者读能清除Map里的内容。

clear() void – Map
containsKey(Object key) boolean – Map
containsValue(Object value) boolean – Map entrySet() Set – Map
equals(Object o) boolean – Map
get(Object key) Object – Map
getClass() Class – Object hashCode() int – Map
isEmpty() boolean – Map
keySet() Set – Map
notify() void – Object
notifyAll() void – Object
put(Object key, Object value) Object – Map putAll(Map t) void – Map
remove(Object key) Object – Map
size() int – Map
toString() String – Object
values() Collection – Map
wait() void – Object
wait(long timeout) void – Object
wait(long timeout, int nanos) void – Object

另外,如果你的Map对象里需要保存某类对象,例如Sensor类的对象,该Map的创建如下:

Map sensors = new HashMap();

当其他代码需要访问这些Sensor对象,就会有如下代码:

Sensor s = (Sensor)sensors.get(sensorId);

这行代码一再出现。代码的调用端承担了从Map钟取得对象并转换成为正确类型的职责。这不是整洁代码。

通过对范型的使用,这段代码可读性提高很多:

Map sensors = new HasMap();
...
Sensor s = sensors.get(sensorId);

不过,Map提供了超出所需/所愿功能的问题,还是没有得到解决。

另外,如果Map的接口被修改,你所有的相关代码都会被修改。

所以,使用自定义Sensors类,是一个更整洁的使用Map的方式。

public class Sensors {
    private Map sensors = new HashMap();
    public Sensor getById(String id) { 
        return (Sensor) sensors.get(id);
}
//snip }

使用Sensors类后,Sensors用户不必关心其内部是否使用了范型或Map,而且隐藏了不需要的Map接口。

并不建议总是用这种方式封装Map的使用,但如果需要将Map类对象在系统中到处传递,可以考虑这样封装。

二,使用尚不存在的代码

另外还有另一种边界。

在代码中,总有许多地方是我们的知识未及之处,例如,软件中有一个通信子系统,但该子系统的开发者还没来得及定义良好的API。

这是,我们可以通过定义自己的接口API,并把该通信子系统包装起来,使得该子系统处于我们自己的控制之下。

并且,还可以通过调用用此包装API以及模拟的信号输入,来模拟该通信子系统。

一旦通信子系统的API被定义出来,我们就编写Adapter(适配器模式)来跨接。见下图。

alt text 图片来源:Clean Code with Boundaries

三,总结

边界上的代码需要进行清晰的分割和定义测试,应该避免我们的代码过多的了解第三方代码中的特定信息。

依靠你能控制的东西,好过依靠你控制不了的东西,免得日后受它控制。

问题:

1, 你是如何管理第三方代码的?有什么样的经验教训?

Clean Code所有讨论章节链接

共6条回复
wangxl 回复于 2016年01月16日 | 更新于 2016年01月22日
Map sensors = new HasMap(); 
...
Sensor s = sensors.get(sensorId);

应该是

Map<Sensor> sensors = new HasMap<Sensor>();
...
Sensor s = sensors.get(sensorId);

:-)

wangxl 回复于 2016年01月16日

感谢 @尼克徐 兄,也感觉这书的作者

一直没有怎么细想过例子中的这个问题,想来还是我个人的“代码洁癖”不够厉害。 :D

好长时间以来一直比较讨厌 Java 语言,感觉它不够灵活,所以才转为Python开发的。

徐哥分享的第一条中的例子让我对Java顿生好感,感觉还是自己不会玩Java,学艺不精。


把自己写的代码尽量维护于一个“安全孤岛”上,尽量摒弃外界的干扰。自己的岛屿,自己说了算,自己来控制,自己来抽像。好管理,不出乱,美丽繁荣容易出现。

(嘿嘿,我好像说是的美丽的新加坡,而不是在说《 Clean Code第八章 边界》)

brambles 回复于 2016年01月16日

所以来玩Haskell吧

Haskell的纯函数不是尽量屏蔽外界干扰,而是理论条件下完全根本不存在外界干扰。

wangxl 回复于 2016年01月16日

3楼 @brambles 哈哈,同意!!我正在学习中

尼克徐 回复于 2016年01月17日

1楼 @wangxl 其实原书里就是那么写的。

你的写法很对,但原书那么写,也不算错吧。

因为如果包装起来了,内部用什么类做容器,从外界就看不出来了。

从外界看,该类已经定义好必须用什么接口了。

cnsoft 回复于 2016年01月22日

Mark一下.

暴露过多 就会有被滥用的风险... 所以越简洁越好. 像这种. Helloworld.hello().world().helloworld() 太邪恶了...

还有就是名字起的模糊,就容易让人联想翩翩..其实没那么高大功能。

登录 或者 注册