耦合的本质

耦合(coupling)的定义 耦合是对coupling的中文翻译。而coupling是couple的变形,指a connection (like a clamp or vise) between two things so they move together。我相信这就是coupling最朴实的定义。请允许我将其翻译成中文:存在一种连接在两事物之间,以至于这两事物相互影响。 在本文中,耦合可以是一个名词——耦合度的同义词,也可以作为形容词——耦合性的同义词。 软件行业中,耦合从何而来? 至于中文书籍什么时候将coupling翻译成耦合,已经不那么重要了。因为耦合不是从“翻译”而来的。从coupling的定义出发,我们看出不论在哪个层次,不存在绝对不耦合的软件。 软件与业务是需要耦合的,如果不耦合,软件就失去了意义。库存系统是需要和电子商务前端(销售)系统耦合的,否则前端就无法确定是否能供货给用户。如果我们的应用需要使用commons-lang库的StringUtils类,那么我们的应用就要commons-lang耦合的,否则我们没法使用StringUtils中的方法。在函数式编程中,我们的确可以将所有的逻辑(不论技术逻辑还是业务逻辑)封装在没有副作用的一个个函数中,但是我相信这些函数之间还是需要在某个时间点进行耦合。 进而我认为:耦合是“天生”的。 那是不是说我们就可以随意耦合了?显然不是,上述的耦合的例子只是表明耦合的存在性,并没有说明耦合的程度在软件开发过程所起的作用。 所以,和复杂性[1]一样,从根本上来说,我们可以掌握这种耦合,但不能消除这种耦合。 不同级别的耦合 软件是由不同级别的概念层组成,不同级别的概念层具有不同的职责,不同级别的概念层中存在不同的耦合:有方法级别、类级别、包级别、协议级别、语言级别、数据流级别、数据库级别、业务级别…… 方法级别:这不用多说了。 类级别:如果你了解“组合优于继承”原则,你就应该理解什么叫类级别的耦合。这里并不是说继承不好,而面对不同的问题,你需要权衡是“组合”还是“继承”与问题模型更匹配。 语言级别:我们的业务必须使用某各种语言来表达,无论是DSL还是通用编程语言Java、C#。 数据流级别:数据流是指一种我们处理问题的方式:输入数据,然后数据在一条充满处理环节的链上流动,直到完全最后一个处理环节输出处理结果。这很像我们的面向切面编程。 如果P_A要求输入的数据中的日期格式是: YYYY-MM。某天一个新手不小心把在P1中把日期格式改了,那么P_A就会出错。而现实中,我就遇到过一个数据处理流中有将近20多个处理环节,目前几乎没人敢动那块逻辑。另一个例子就是项目使用了Spring的切面编程,由于“切”得太多,到后来调试起来,我不得不遍历所有的切面的逻辑,以确定数据流到哪个环节出的错。 数据库级别:如果你的应用使用到了数据库Oracle特有的特性,那么你的应用就是和Oracle数据库耦合的。想像一下阿里去O的过程。也可以看下这篇文章:http://tech.it168.com/a2015/0417/1720/000001720950.shtml。ORM框架的好处在这方面尤为突出。 通信协议级别:如果通信的双方只依靠协议通信,而不关心实现这个协议的是Ruby程序还是Java程序。这也正是基于HTTP的 RESTFul风格的架构的关键所在。而业界提的微服务思路,其实就是期望各个应用之间只在协议级别耦合,从而与语言、操作系统解耦。通信协议级别上,我们也需要考虑解耦,比如,你的应用能否轻松从HTTP协议迁移到HTTPS。 操作系统级别:文件的路径的写法在各个系统下的不同,所以Java才会有: File.separator 这个常量,在Windows环境下返回\,而在*nix环境下返回/。如果不用此常量来拼文件路径,那么,那部分就是与操作系统耦合的。像: for Windows: C:\windows\system32\cmd.exe for Linux: /var/log/ansible.log 有些人觉得这不如挂齿,看看这条新闻:坚持用 XP 的代价:美国海军付给微软上千万。 PS:使用Ruby的某些gem时就要小心,因为某些gem用到了操作系统的库。 如Nokogiri (鋸) 业务级别:业务级别上的耦合度越高,软件的风险系数越高。假如A公司的领导在职时采用代码行数来KPI程序员,代码行数越多,KPI越好。如果在为这家公司设计HR系统时,你就必须考虑如果A公司换领导了或者原领导反省了,换采用360度评估进行KPI。你的系统能否以相当小的代价实现?又比如政府的某些老系统将一代身份证的身份证号作为自然人在数据库中的ID。可是某天,二代身份证出了,身份证号变成了18位了。那么如果自然人要求更新系统中的身份证号,你怎么办? 比较好玩的是,有时我们的程序有时会和系统用户耦合。 apache-commons-io 下的设置文件的可读/可写权限时,如果使用的是root用户执行此程序会有问题,因为root可对所有的文件可读。 File tmpFile = File.createTempFile(getClass().getSimpleName(), “localities.xml”); tmpFile.deleteOnExit(); tmpFile.setReadable(false); Assert.assertFalse(tmpFile.canRead()); // It would be failed 这个assert将会失败,因为不论该文件是否可读,以root用户执行此程序时,tmpFile始终是可读的。 ...

2015-12-29 · 1 min · 95 words · 翟志军 Jack Zhai