iso20000 第2部分_设计的环境注意事项,第2部分

上一部分中 ,我开始讨论软件设计的环境注意事项。 企业软件开发永远不会在真空中进行; 政治和其他外部因素使清晰的技术决策变得混乱。 本期文章延续了这一思路,涵盖了有关重构和隔离体系结构更改的问题。

智能重构

重构(Martin Fowler的开创性著作对此主题进行了定义 )(请参阅参考资料 ),是一种常见的,易于理解的改进代码的技术。 紧急设计的关键推动力之一是能够收获和使用您发现的惯用模式。 从机械意义上讲,这意味着重构。 但是重构还包含项目的环境因素。 现在,所有主要的IDE都支持重构-但是您不能依靠工具来执行智能重构,而只能依靠正确的重构。

在本系列的第四部分中,我讨论了单一抽象原理(SLAP),使用示例电子商务网站中的方法作为重构的目标,以提高其清晰度。 该方法显示在清单1中:

清单1.示例电子商务网站中的addOrder()方法
public void addOrder(ShoppingCart cart, String userName,
                     Order order) throws SQLException {
    Connection c = null;
    PreparedStatement ps = null;
    Statement s = null;
    ResultSet rs = null;
    boolean transactionState = false;
    try {
        s = c.createStatement();
        transactionState = c.getAutoCommit();
        int userKey = getUserKey(userName, c, ps, rs);
        c.setAutoCommit(false);
        addSingleOrder(order, c, ps, userKey);
        int orderKey = getOrderKey(s, rs);
        addLineItems(cart, c, orderKey);
        c.commit();
        order.setOrderKeyFrom(orderKey);
    } catch (SQLException sqlx) {
        s = c.createStatement();
        c.rollback();
        throw sqlx;
    } finally {
        try {
            c.setAutoCommit(transactionState);
            dbPool.release(c);
            if (s != null)
                s.close();
            if (ps != null)
                ps.close();
            if (rs != null)
                rs.close();
        } catch (SQLException ignored) {
        }
    }
}

在SLAP系列中,我演示了读取从一个抽象级别跳转到另一抽象级别的代码要困难得多,并且我重构了清单1中的代码以提高其可读性。 但是,在那一部分中,我展示了重构的最终结果,而不是清单2中显示的中间版本:

清单2. addOrder()方法的中间重构阶段
public void addOrder(ShoppingCart cart, String userName,
                     Order order) throws SQLException {
    Connection connection = null;
    PreparedStatement ps = null;
    Statement statement = null;
    ResultSet rs = null;
    boolean transactionState = false;
    try {
        connection = dbPool.getConnection();
        statement = connection.createStatement();
        transactionState =
                setupTransactionStateFor(connection,
                        transactionState);
        addSingleOrder(order, connection,
                ps, userKeyFor(userName, connection));
        order.setOrderKeyFrom(generateOrderKey(statement, rs));
        addLineItems(cart, connection, order.getOrderKey());
        completeTransaction(connection);
    } catch (SQLException sqlx) {
        rollbackTransactionFor(connection);
        throw sqlx;
    } finally {
        cleanUpDatabaseResources(connection,
                transactionState, statement, ps, rs);
    }
}

清单2中的代码显示了任何自动重构工具的固有弱点。 该工具必须遵守严格的约定:生成的代码必须仍然像以前一样工作。 例如,当执行提取方法重构时,该工具必须确保提取的方法所需的所有变量仍然可用-但只能通过参数传递来确保它们的存在。 解决相同问题的另一种方法是将共享变量移到类级别,使它们成为类的字段。 重构工具不会执行此操作,因为它无法考虑此类决定所带来的严重影响(例如线程问题,命名和其他方法的可用性)。 但是,开发人员可以考虑考虑任何此类问题,决定进行一轮手动重构。 手动重构addOrder()产生清单3所示的代码:

清单3.手动重构的,大大改进的addOrder()代码
public void addOrderFrom(ShoppingCart cart, String userName,
                     Order order) throws Exception {
    setupDataInfrastructure();
    try {
        add(order, userKeyBasedOn(userName));
        addLineItemsFrom(cart, order.getOrderKey());
        completeTransaction();
    } catch (Exception condition) {
        rollbackTransaction();
        throw condition;
    } finally {
        cleanUp();
    }
}

这是我最终在“ 利用可重用代码,第1部分 ”中将其重构为自己惯用模式的代码 。 它比原始代码好得多,因为您可以看到它在做什么,从而可以回收可重复使用的部分。

您不能盲目地期望一种工具可以为您做出正确的决定。 它可以确保正确的代码,但不能保证最佳的代码。 为了有效地找到可重复使用的资产,您经常必须超出自动化工具所能提供的范围。 您还必须收获其他团队成员的集体智慧。

集体代码所有权

在整个系列文章中,我一直在争辩说,在软件中您不能将设计与编码分离。 完整的源代码是唯一真正准确的设计工件,这表明与一群人一起进行软件项目是一项协作设计工作。 将软件创建视为协作设计会使有关软件开发的一些令人困惑的事实突然变得更加有意义。 例如,业界早就知道通信对于软件项目至关重要。 (实际上,许多敏捷方法都认为这是成功的关键条件。)如果编写软件就像制造软件一样,您将需要更少的通信开销。 协同设计需要成员之间的沟通。

协同设计还建议开发人员应对他们创建的整个应用程序各部分的正确性和质量负责。 正确性有两个方面:遵守业务要求和技术适用性。 业务需求的正确性取决于公司生态系统中采用何种验证机制来判断软件是否适合编写要解决的问题。 技术上的正确性留给开发团队。

不同的团队采用了自己的程序和机制来确保技术质量,例如通过持续集成来运行代码审查和自动度量工具。 许多敏捷团队采用的一种实践是紧急设计的关键推动力是集体代码所有权 ,这表明项目中的每个人应对所有代码负责,而不仅仅是他或她编写的代码。

集体代码所有权要求您对项目代码的持续质量有一定的了解,尤其是为了追求和修复过时的抽象和破损的窗口 。 更具体地说,它要求:

  • 经常进行代码审查(或诸如配对编程之类的实时代码审查),以确保每个人都利用共同的惯用模式和集体小组的其他有用的设计发现。
  • 每个人对项目的意识至少是项目各个部分的某些细节。
  • 无论代码的原始作者如何,项目上的任何人都愿意跳进来并修复破损的窗户。

机械和环境方面的重构问题都会影响紧急的设计决策。 紧急设计功效的另一个环境关注点在于隔离体系结构更改。

隔离架构变更

在本系列文章中,我将设计问题与体系结构问题分开了,但是,在现实世界中,这种分开当然很难。 您的体系结构是所有设计决策的基础,并且体系结构方面的考虑会影响您使用我所讨论的某些新兴设计技术的能力。

猖amp的通用性

软件中的一种常见的体系结构疾病是泛泛的通用性 ,这一思想体现在以下想法中:如果添加许多层进行扩展,则以后可以轻松地在其上进行更多构建。 的确,尽早采用扩展机制可以使以后的扩展更加容易。 但是,一旦添加这些层,就增加了复杂性,而不是在开始使用它们时就增加了。 在开始显示项目的实际收益之前,这种额外的开销是一种偶然的复杂性。

紧急设计的一部分是从现有代码中查看和收获惯用模式的能力。 如果您将所有代码都放在一个物理层中,那么该技能将很难开发,而当您开始采用逻辑上是一回事但由于架构决策而分成多个层的工件时,则变得更加困难。 例如,假设您有一个Country类,该类具有针对国家/地区名称长度的验证规则。 理想情况下,您希望利用局部性并将验证代码尽可能靠近域类,也许放在批注中(此情况的一个示例出现在“ 利用可重用代码,第2部分 ”中),如图所示在清单4中:

清单4. Country类的MaxLength属性
public class Country {
	private List<Region> regions = new ArrayList<Region>();

	private String name;
	
	public Country(String name){
		this.name = name;
	}
	
        @MaxLength(length = 10)
	public void setName(String name){
		this.name = name;
	}
	
	public void addRegion(Region region){
		regions.add(region);
	}
	
	public List<Region> getRegions(){
		return regions;
	}
}

只要您的代码都在一个逻辑层中, 清单4中的代码就可以很好地工作。 但是,当体系结构坚持认为执行验证的代码必须来自服务层时,会发生什么? 现在,您将面临一个双赢的决定:是否使属性了解您的应用程序的体系结构层,并调用服务层进行验证? 还是将所有验证代码移入服务层,将其从应该属于该知识所有者的域类中删除?

层使设计决策更加困难。 我不建议您放弃分层架构(甚至跨越逻辑域层的架构)。 相反,我建议分层体系结构提供的好处是有代价的,您应该在添加诸如服务层之类的体系结构元素之前评估成本。 这就带来了另一个问题:什么时候应该添加可能对设计有影响的建筑元素?

建筑与设计

在整个系列中,我一直依赖于软件中体系结构的简单而准确的定义:“体系结构是以后很难更改的东西。” 当您查看项目的各个部分时,您可以通过询问“以后很难更改它吗?”来标识架构元素。 在此定义下,物理层或逻辑层都是体系结构的,因为以后很难更改(或删除)它们。

一些层使开发(和设计)更加容易。 使用“四人帮”一书中的Model-View-Controller之类的模式来分离技术职责(请参阅参考资料 ),可以更轻松地隔离路由,视图和域逻辑。 但是,某些体系结构进一步将域层分开,创建了多个层以实现代码的集中化。 例如,面向服务的体系结构创建不同类型的层以更改应用程序的拓扑。 太多的体系结构层次(特别是在需要层次结构之前)会使设计更加困难,因为您改变了必须依靠的基础。 如果很难在单个虚拟机中运行的代码中看到可收获的惯用模式,请考虑识别功能并弄清楚如何对其进行跨层重用的难度。

由于我们不正确地激励建筑师,因此至少会出现此问题。 架构师的一项重要动机是即使面对不断变化的需求和能力,也要确保整个企业生态系统继续运行。 在软件方面,我们受到美国前国防部长拉姆斯菲尔德(Donald Rumsfeld)所说的“未知未知数”的困扰。 软件充斥着未知的未知数,您一开始就认为自己了解这些情况,只是发现问题与您的第一印象有所不同(通常更难)。 您如何处理架构中未知的未知数? 您尝试对所有内容进行过度设计。 我深信,在2000年代初期,许多项目都包含Enterprise Java™Bean(EJB)技术,因为这样一个问题:“我们是否需要声明性的分布式事务?” 回答说:“我们不知道。” 然后安全的选择是将EJB包含在项目中,以防以后需要它们。 但是,这从一开始就使项目变得更加复杂。 具有讽刺意味的是,为可扩展性建立的许多机制使项目的初始版本处于危险之中,因为额外的复杂性迫使该项目花费时间和预算。

这是我参与的一个项目的示例。 该项目的硬要求之一是国际化-在该项目的第二版中。 该项目从处理国际化的代码开始,因为它最初被视为架构决策:它是如此普遍,以后将很难更改。 此功能本身表现为意外的复杂性:即使简单的故事也变得复杂,因为我们必须适应此要求。 由于花费了太多时间,因此开始危及第一版的发布日期。 技术负责人决定将所有国际化代码从项目中剔除,并意识到如果我们从不发行第一版,我们将不需要第二版中的该功能! 最后,当需要使用第二版时,一位开发人员提出了一种巧妙的方法,即使用元编程将国际化代码编织到代码库中。 换句话说,他设法将建筑元素转换为设计元素,使之在以后很难更改的地方。 抢先将某些东西作为建筑元素(以后很难更改)有时会使您看不到它是轻量级设计元素的样子。

建筑与设计之间存在着不安的张力。 理想情况下,您希望将设计决策推迟到最后一个负责任的时刻。 但是,架构决策会影响您可以做出的设计决策。 记住体系结构定义的第二部分:“应该尽量减少这些东西。” 尽力将架构决策推迟到最后一个负责任的时刻,即使它们可能产生更大的影响。 您会感到惊讶的是,似乎很难改装的东西毕竟还不是那么糟糕。

结论

在本期和上一期中,我讨论了环境问题及其对紧急设计的影响。 重构显然是紧急设计的重要工具-涉及机械和环境方面的问题。 确保您进行智能重构,而不仅仅是依靠自动化重构工具。 并设置您的项目环境以实现集体代码所有权,从而充分利用项目中所有人员的力量。

架构也会对您的设计选项产生影响。 与设计元素一样,尝试将架构决策推迟到最后一个负责任的时刻。 在开始使用这些元素之前,放置了一些体系结构元素就可以很容易地在将来扩展为偶然的复杂性。

下一个Evolutionary体系结构和紧急设计部分将总结该系列。 在其中,我将总结概念并从将近两年的撰写和介绍紧急设计方面得出结论。


翻译自: https://www.ibm.com/developerworks/java/library/j-eaed18/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值