跟小伙伴一起重构一段 UI,试图将用户界面和业务代码分离的时候,小伙伴试图在业务代码中直接调用 UI。我们当然都知道这会产生耦合,于是小伙伴试图定义一些属性、变量或接口来解决这个耦合。虽然在代码的静态分析中,这一的耦合消失了,但我始终觉得不妥。觉得耦合依然存在,只是不再能被静态分析了。
我想到一个词——“语义耦合(Semantic Coupling)”,搜索发现也有很多小伙伴在关心这个问题。而且,从他们的文章和讨论中,我也了解到更多关于语义耦合的种类和危害。
什么是语义耦合
这是区别于常规意义上的“耦合”而言的。
即类 Foo 依赖于类 Bar,即是常规意义上的耦合。静态代码分析工具就可以为我们发现这种耦合。如果将 Bar 拆开成两个部分,一是类 Bar 的实现本身,另一个是接口 IBar;现在 Foo 依赖的是接口 IBar,那么 Foo 就没有依赖类 Bar了。在静态代码分析工具中就会发现这样的依赖就解除了。
在静态代码分析工具认为没有耦合的情况之下,如果两个类之间还交换带有隐含意义的数据,假设对方已为自己完成了某种工作,暗示对方执行期望的代码,那么这两个类在语义上还存在着耦合。
我们说耦合的危害是修改一个类的时候,另一个类也需要做对应的修改。显式耦合有工具帮我们做重构时的解耦,而语义上的耦合却很难有准确帮助我们的工具。一些变态的工具(例如 ReSharper)能够帮助我们解决一部分。
哪些代码算作语义耦合
按照上面的定义,语义耦合的概念依然模糊,但都有一个统一的核心——在实现细节上存在依赖,而不是在调用上存在依赖。
交换带有隐含意义的数据
在这段代码中,Bar
依赖于 Foo
,他们都依赖于 FooInfo
。至少静态代码分析工具是这么认为的。
public class Foo
{
public void Do(object arg)
{
var info = (FooInfo) arg;
// 后续代码。
}
}
public