译者注:文章的"我"均是指软件的作者

项目 objc_dep 主页在:https://github.com/nst/objc_dep

代码设计和疏耦合(loose coupling)

作为开发者,我们都喜欢干净的代码,但实际上我们大部分时间都是和糟糕的代码打交道。这些代码可能是最近写的,也可能是遗留下来的,可能是我们自己写的,可能是其他开发者写的。我们能认出什么是糟糕的代码,因为我们有代码的嗅觉(code smells)。换句话说,关于代码质量的启发式提问。在这些中,我们可以命名我写在这里这里的"死亡"的代码(dead code),也可以命名紧密耦合(tight coupling)。

紧密耦合是形容一个系统中,很多部件互相依赖。紧密耦合的代码令人讨厌,耦合的点上要考虑太多的关系,又或者这些耦合点横跨了几个不同的类,而不是单独有一个类。相反,疏耦合是一个更好的设计,可以提高单个耦合点的作用,和分散不同的关注点。疏耦合使代码更容易测试和维护。

在 Objective-C 中,可以通过 delegatesnotification来改善耦合程度。

类依赖的图示化

所以,我们怎么实现我们的代码的疏耦合?首先,我们需要更好地理解当前代码的耦合情况。让我们定义类依赖:A 类 依赖 B 类,如果 A 在头文件中导入(import) B 类。在这样的定义下,我们可以根据每个类的 #import 指令画一个类与类之间的依赖图。我们预设这里的文件名是根据它包含的类来命名的。

我写了 objc_dep.py,一个可以从 Objective-C 源代码中抽取 imports 信息的 Python 脚本。这个结果可以在 GraphVizOmniGraffle 上演示。这样,你就可以看到类与类之间依赖图示。注意我们也可以对耦合进行度量计算(compute metrics),但不是这里想要说的点。

用法举例

我们怎么从耦合图示中得出更好的设计?这里没有决定性的算法,而且也要根据你的项目而定。让我们试试把这个脚本用在 FSWalker 上,它是我很久以前写的一个小型 iPhone 文件浏览器。

1.产生图示

$ python objc_dep.py /path/to/FSWalker > fswalker.dot

2.在 OmniGraffle 打开它

在这里,我们可以看到以类为节点、通过线条连起来的依赖。
请输入图片描述

3.移除目录(categories)

我们可以安全地把 Objective-C 目录移除掉, 因为很多地方的目录引用(referencing categories)不是我们现在考虑的范围。
请输入图片描述

4.把相关的类分组

下一步,我们移动一下头部的节点,试着根据一些共同的依赖分组成不同的群集:
请输入图片描述

5.研究奇怪的依赖

图示现在给了我们一个全面的代码架构。控制器(controller)对象已经用红色标出, 模块(model)对象用黄色标出,网络的部分用蓝色标出。这样的图示可以令我们去发现奇怪的依赖,和思考代码的设计。我们可以首先注意到,FSWalkerAppDelegate 有太多的依赖。我们可以对这些依赖分来:
* 无引用的类和群集(unreferenced classes or clusters),这些是废弃的代码,我们可以移除它。不过这里没有无引用的类,尽管你可以轻易在一些大一点的项目中看到。
* 二路引用(two-ways references)
或者一个类不应该直接地引用另一个类,而应该引用别的类实现的协议。
我们这里有两个二路引用的例子:HTTPServer 和 HTTPConnection,RootViewController 和 FSWalkerAppDelegate。前者是 CocoaHTTPServer 项目的一部分,不是我们项目设计的问题(issue)。然而,后者是个问题。通过观察代码,我们会注意到,RootViewController 并没有真正使用 FSWalkerAppDelegate。这样的 import 可以安全地移除。

  • 奇怪的引用(weird references)
    一些 import 指令可能是不必要的,或者揭示设计问题。

并没有好的理由说明为什么 FSWalkerAppDelegate 要引用 FSItem,InfoPanelController也一样。代码检测会告诉我们,DetailViewController 和 InfoPanelController 不应该被 FSWalkerAppDelegate 引用,而应该被 RootViewController 引用。所以这个是最后的图示。FSWalker 的架构或者还需要优化,但你应该明白我的意思。
请输入图片描述

实际项目中的用法

这个是你可能设想到的带 100 多个类的项目的图示:
请输入图片描述

额外选项

% python objc_dep.py /path/to/repo -x "(^Internal|secret)" -i subdir1 subdir2 > graph.dot

上面会包括所有以 "Internal" 开头为名字的文件,或者包括包含 "secret" 这个词为名字的文件。所有在 subdir1 和 subdir2 目录下的文件会被忽略。

更多信息可查阅项目主页:https://github.com/nst/objc_dep


翻译整理: Segmentfault


JeOam
8.7k 声望167 粉丝

热爱互联网