快捷搜索:

演化架构与紧急设计: 积累惯用模式

在本系列第一期 “钻研架构和设计” 中,我曾断言每个较大年夜的项目都包括越过所有人料想的设计元素。具体斟酌一个问题时,经常会发明有些本以为艰苦的工作实际上却更轻易,有些本以为轻易的工作实际上却更艰苦。随后的几期则演示了发明暗藏的有趣的设计元素的一些措施。在本文中,我将那些思惟相结合,并供给一个扩展后的案例钻研,在该案例钻研中,应用一些对象和措施来发今世码库中被漠视然则同样紧张的部分。

在 “组合措施和 SLAP” 中,我先容了惯用模式(idiomatic patterns) 的观点。与四人组撰写的 Design Patterns一书遍及的老例设计模式(Design Patterns)比拟,惯用模式并不适用于所有项目。然则,它们是代码中普遍存在的、有代表性的常见设计常规。惯用模式的范围很广,从纯技巧模式(例如项目处置惩罚事务的要领),到问题域模式(例如 “处置惩罚订单前老是反省客户的信用”),都可所以惯用模式。发明这些模式是紧急设计的关键。

Big Design Up Front 设计措施学的支持者在开始编写代码前,要花费大年夜量的光阴确定当前利用法度榜样的所有必需的设计元素。体例的文档中的大年夜多半内容对付办理规划的总体设计仍是紧张的。然则,在实现软件的历程中,会赓续碰见意外。实现的每个设计元素与其他设计元素互相联系,形成极度繁杂的依附和关系收集。代码中有些方面本以为寻常不过,然则一旦要实现系统中其他所有必需的部分时,繁杂度又随之放大年夜。因为不能理解代码中不合设计元素之间繁杂的互相感化,导致在估算完成办理规划所需的努力时艰苦重重。在软件方面,估算仍是一种微妙的 “玄色艺术”,由于对付如斯繁杂的耦合和交互收集,其实是难以理解,因而也难以阐发。

依附于紧急设计的敏捷措施学则考试测验一种不合的措施。敏捷架构和设计在编写代码前也不会避开设计,然则它们的实际事情者已经知道,只有等到实现了紧张的部分后,才能彻底地舆解全部问题。紧急设计中的开拓技术使您可以推迟做抉择,直到掌握了更多的高低文。精益软件运动有一个很好的观点叫做 着末靠得住时候(last responsible moment):不是将抉择推迟到着末时候,而是着末靠得住时候。越因此后推迟设计抉择,就能掌握越多的信息,从而可以做出更精妙、更相符实际的抉择。

积累惯用模式

紧急设计要求在已有代码中发明设计元素。可以将那些元素看作有复用潜力的有效的抽象。积累那些惯用模式的一种技术是应用指标组合。为了演示这种技术,我将(像之前几期那样)应用 Apache Struts 代码库。之以是应用 Struts,并不是由于我觉得它存在缺陷(实际上恰好相反),而是由于它对照出名,并且是开源的。我觉得每个代码库都包括惯用模式,以是可以应用任何项目。

应用指标

在 “经由过程指标进行紧急设计” 中,我评论争论了应用指标来发明不认识的代码库中有趣的部分,作为重构的目标,以改进设计。我应用了两个指标:圈繁杂度(cyclomatic complexity) 和 传入耦合(afferent coupling)。圈繁杂度是衡量一个措施相对付另一个措施的相对繁杂度的指标。是以,该指标只有与其他圈繁杂度指标相对照才有用。然则,可以说,具有较低圈繁杂度的措施平日更简单。而传入耦合则表示其他类经由过程字段或参数引用当前类的次数。我应用 CJKM 指标对象网络 Struts 代码库上的这些数字。

对 Struts 2 代码库谋略这两个指标可获得图 1 所示的表,此中只显示关心的两个指标:

图 1. ckjm 指标结果表

图 2 显示相同的表,按 Weight Methods per Class(WMC)排序:

图 2. ckjm 指标,按 WMC 排序

单看这个结果,可以觉得 DoubleListUIBean 类是 Struts 代码库中最繁杂的类。这意味着可以将这个类作为重构的候选目标,试着削减一些繁杂性,并看看是否能发明可抽象的、重复的模式。然而,WMC 数字并不能奉告您是否值得花光阴重构这个类,以改进设计。留意这个类的 Ca(传入耦合)指标,它的值为 3。这意味着只有 3 个其他的类应用这个类。花费大年夜量的光阴改进这个类的设计大概并不值得。

图 3 显示相同的 CKJM 结果,这一次按 Ca 排序:

图 3. ckjm 结果,按 Ca(传入耦合)排序

这个组合的视图注解,Struts 中最常用的类是 Component(这并不稀罕,由于 Struts 是一个 Web 框架)。虽然 Component 不如 DoubleListUIBean 繁杂,然则有 177 个其他的类应用它,是以很得当作为改进设计的目标。使 Component 的设计变得更好,可以在很多其他的类上取得优越的连锁反映。

经由过程 图 3 所示的视图,可以逐个查看繁杂度和引用次数。要发明有设计寻衅的类,可以看看两个数字都对照高的组合(即被很多其他类应用的繁杂的类)。我首先选择的用于钻研的类是 UIBean 类,它的圈繁杂度是 53,传入耦合是 22。这是一个被很多其他类应用的繁杂的类,以是我将对它作进一步的钻研。

ckjm 申报的圈繁杂度数字表示这个类中所有措施的繁杂度之和。我想确定是什么使这个类如斯繁杂,以是必要各个措施的繁杂度数字。对这个类运行开源圈繁杂度对象 JavaNCSS,可获得图 4 所示的结果:

图 4. UIBean 类中各个措施的繁杂度数字

到今朝为止,最繁杂的措施是 evaluateParams(),其繁杂度为 43(也是代码行数最多的)。该措施显然是用于处置惩罚常见的作为哀求的一部分通报给 Struts 节制器的额外参数,将参数类型发送到实际的 Struts 类和组件。该代码中存在很多布局性重复,如清单 1 所示:

清单 1. evaluateParams() 措施的部分内容,此中有布局性重复

if (label != null) {

addParameter("label", findString(label));

}

if (labelPosition != null) {

addParameter("labelposition", findString(labelPosition));

}

if (requiredposition != null) {

addParameter("requiredposition", findString(requiredposition));

}

if (required != null) {

addParameter("required", findValue(required, Boolean.class));

}

if (disabled != null) {

addParameter("disabled", findValue(disabled, Boolean.class));

}

if (tabindex != null) {

addParameter("tabindex", findString(tabindex));

}

if (onclick != null) {

addParameter("onclick", findString(onclick));

}

// much more code elided for space considerations

该代码可作为改进的候选目标(见下一小节 改进代码,第 1 部分),然则我想再多看一下,该代码存在的缘故原由 是什么,为什么它包孕如斯多的繁杂性。

放眼其他圈繁杂度和传入耦合值都对照高的组合,我发清楚明了 WebTable,它的那两个值分手为 33 和 12。 对它运行 JavaNCSS,可以肯定我的狐疑:它的第二繁杂的措施是 evaluateExtraParams()。在这里我看到一个模式!看到这个重复的繁杂元素呈现在很多不合的类中,我狐疑有很多偶尔的与参数有关的繁杂性,以是我做一个实验。经由过程应用一点 UNIX® 敕令行魔术,我察看 Struts 中有若干类中包孕名为 evaluateParams() 或 evaluateExtraParams() 的措施:

find . -name "*.java" | xargs grep -l "void evaluate.*Params" > pbcopy

这个敕令查找当前目录以下的所有 Java™ 源文件,对付每个找到的文件,它在文件中搜索以 evaluate 开首,以 Params 结尾的措施定义。着末的重定向(>)将结果文件粘贴到剪贴板上(至少在 Mac 上是如斯)。当我粘贴结果时,看到了令我惊疑的事:

AbstractRemoteCallUIBean.java

Anchor.java

Autocompleter.java

Checkbox.java

ComboBox.java

DateTimePicker.java

Div.java

DoubleListUIBean.java

DoubleSelect.java

File.java

Form.java

FormButton.java

Head.java

InputTransferSelect.java

Label.java

ListUIBean.java

OptionTransferSelect.java

Password.java

Reset.java

Select.java

Submit.java

TabbedPanel.java

table/WebTable.java

TextArea.java

TextField.java

Token.java

Tree.java

UIBean.java

UpDownSelect.java

所有这些类中都包孕以上两个措施中的一个或两个!我发清楚明了一个惯用模式。显然,Struts 中的很多类必要覆盖和定制处置惩罚参数的行径,所有这些类各自认真定制。现在的问题是:若何使之变得更好?

改进代码,第 1 部分

在 UIBean 的 evaluateParams() 措施中,可以看到很多不合的布局性重复,我的一个同事称之为 “相同的空格,不合的值”。换句话说,布局相同,然则代入不合的类或变量名。这代表着一种代码味道,由于利用法度榜样中前后呈现实际上可以复制-粘贴的代码,这些代码区别很小。

修复布局性重复的一种常见的技术是应用元编程将重复的布局封装到一个地方。清单 2 显示一个新的措施,以及 evaluateParams() 措施中颠末改进的前一部分,这里应用反射供给所需的不合的值:

清单 2. 经由过程元编程打消布局性重复

protected void handleDefaultParameters(final String paramName) {

try {

Field f = UIBean.class.getField(paramName);

if (f.get(this) != null)

addParameter(paramName, findString(paramName));

} catch (Exception e) {

throw new RuntimeException(e.getMessage());

}

}

public void evaluateParams() {

addParameter("templateDir", getTemplateDir());

addParameter("theme", getTheme());

String[] defaultParameters = new String[] {"label", "labelPosition", "requiredPosition",

"tabindex", "onclick", "ondoubleclick", "onmousedown", "onmouseup", "onmouseover",

"onmousemove", "onmouseout", "onfocus", "onblur", "onkeypress", "onkeydown",

"onkeyup", "onselect", "onchange", "accesskey", "cssClass", "cssStyle", "title"};

for (String s : defaultParameters)

handleDefaultParameters(s);

您可能还会对下面的文章感兴趣: