陈灯可重用代码段管理器_利用可重用代码,第1部分

从本系列的前几期中您知道,我的争论是每个软件都包含可重用的代码块。 例如,贵公司处理安全性的方式在整个应用程序和多个应用程序中可能是一致的。 这是我称为惯用模式的示例。 这些模式代表构建特定软件时遇到的问题的通用解决方案。 惯用模式有两种样式:

  1. 技术模式 -这些模式包括交易,安全性和其他基础架构元素。
  2. 域模式 -这些模式包括针对单个应用程序或多个应用程序的业务问题的通用解决方案。

在前几期中,我将大部分精力集中在如何发现这些模式上。 但是,一旦发现它们,就必须能够将它们用作可重用的代码。 在本文中,我将研究设计与代码之间的关系,尤其是表达性代码如何使收集模式更容易。 而且您会看到有时可以通过更改抽象样式来解决看似棘手的设计问题,并简化代码。

设计就是代码

追溯到1992年,杰克·里夫斯(Jack Reeves)撰写了一篇有感性的文章,题为“什么是软件设计?”。 (请参阅参考资料以获取在线副本)。 在其中,他将传统工程(例如硬件工程和结构工程)与软件“工程”进行了比较,目的是为软件开发人员删除引号。 这篇文章得出了一些有趣的结论。

里夫斯的第一个观察结果是,工程工作的最终成果是“某种类型的文档 ”(斜体)。 设计桥梁的结构工程师无法交付实际的桥梁。 完成的工作是桥梁的设计 。 该设计将交给制造团队进行施工。 什么是软件的类似设计文件? 是餐巾纸涂鸦,白板涂鸦,UML图,序列图和其他类似的人工产物吗? 这些都是设计的一部分,但是这些集合不足以移交给制造团队以使事情变得真实。 在软件中,制造团队是编译器和部署机制,这意味着完整的设计是源代码,即完整的源代码。 其他工件可以帮助创建代码,但是最终的设计交付结果是代码本身,这表明软件中的设计不能从代码中抽象出来。

Reeves提出的下一个重点是制造成本,通常不将其视为工程工作的一部分,而是工程人工制品总体成本估算的一部分。 建造实体物品非常昂贵,通常是整个生产过程中最昂贵的部分。 相反,正如里夫斯所说:

“ ...软件的构建成本很低。它不算便宜;它是如此便宜,几乎是免费的。”

记住,他一直在经历C ++编译和链接周期,这是巨大的时间消耗。 现在,在Java™世界中,每当您停止键入文字时,一群精灵就会活跃起来并制作出您的设计! 现在,软件构建非常免费,几乎看不见。 与传统工程师相比,我们拥有巨大的优势,传统工程师可能希望能够自由构建自己的设计并玩假设游戏。 您能想象,如果桥梁工程师可以免费实时进行他们的设计,那么桥梁将是多么难以置信?

易于制造解释了为什么我们在软件开发中没有那么严格的数学原理。 传统的工程师开发了数学模型和其他复杂的技术以提高可预测性,因此不必强迫他们构建事物来确定其特征。 软件开发人员不需要那种级别的分析。 建立我们的设计和测试它们比建立正式的行为证明更容易。 测试是软件开发的工程严谨条件。 从里夫斯的文章中得出最有趣的结论:

鉴于软件设计相对容易实现,并且基本上可以免费构建,因此,毫无疑问的启示是,软件设计往往非常庞大和复杂。

实际上,我认为软件设计是人类曾经尝试过的更复杂的事情之一,尤其是考虑到我们正在构建的产品中,复杂度不断提高。 考虑到软件开发在过去仅50年来一直是主流,这令人震惊,我们在典型的企业软件中设法建立了多少复杂性。

Reeves文章的另一个结论是,软件设计(即编写整个源代码)是迄今为止最昂贵的活动。 这意味着设计时浪费的时间是最昂贵的资源的浪费。 这使我重新回到了紧急设计。 如果您在开始编写代码之前花费大量时间来预期所有需要做的事情,那么您总是会浪费一些时间,因为您还不知道自己不知道的东西。 换句话说,编写某些软件时总是会遇到意想不到的时间浪费,因为某些要求比您想象的要复杂,或者您在一开始就没有完全理解问题。 您可以推迟决策的时间越长,您做出更好决策的能力就越大-因为您获得的上下文和知识会随着时间而增加, 如图1所示:

图1.可以推迟决策的时间越长,决策就可以越具体化
决策延迟图

精益运动有一个很好的用语: 最后的责任时刻 -不是最后的时刻,而是决定的最后责任时刻。 您可以等待的时间越长,就越有机会进行更合适的设计。

表现力

Reeves的论文的另一个结论围绕着可读性设计的重要性,即转换为更具可读性的代码。 在代码中查找惯用模式已经足够困难,但是如果您的语言增加了额外的麻烦,它将变得更加困难。 例如,在汇编语言代码库中查找惯用模式非常困难,因为该语言包含了许多不透明元素,您必须能够看到这些元素才能“看到”设计。

因为设计是代码,所以您应该选择最有表现力的语言。 利用语言的表现力,可以更轻松地看到惯用模式,因为设计媒介更加清晰。

这是一个例子。 在本系列的前一部分(“ 组合方法和SLAP ”)中,我对一些现有代码进行了重构练习,应用了组合方法和单层抽象 (SLAP)原理。 我派生的顶级方法显示在清单1中

清单1. addOrder()方法的改进抽象
public void addOrderFrom(ShoppingCart cart, String userName,
                     Order order) throws SQLException {
    setupDataInfrastructure();
    try {
        add(order, userKeyBasedOn(userName));
        addLineItemsFrom(cart, order.getOrderKey());
        completeTransaction();
    } catch (SQLException sqlx) {
        rollbackTransaction();
        throw sqlx;
    } finally {
        cleanUp();
    }
}

// remainder of code omitted for brevity

这看起来很适合作为惯用模式进行收获。 清单2中所示的第一遍是使用“本地”语言(Java语言)完成的:

清单2.重构惯用的“工作单元”模式
public void wrapInTransaction(Command c) {
    setupDataInfrastructure();
    try {
        c.execute();
        completeTransaction();
    } catch (RuntimeException ex) {
        rollbackTransaction();
        throw ex;
    } finally {
        cleanUp();
    }
}

public void addOrderFrom(final ShoppingCart cart, final String userName,
                         final Order order) throws SQLException {
    wrapInTransaction(new Command() {
        public void execute() {
            add(order, userKeyBasedOn(userName));
            addLineItemsFrom(cart, order.getOrderKey());
        }
    });                
}

在这个版本中,我已经抽象的样板代码到wrapInTransaction()方法,使用四的命令设计模式的刚(见相关主题 )。 现在, addOrderFrom()方法更具可读性-该方法的本质(最里面的两行)更加明显。 但是,为了达到这种抽象水平,Java语言迫使很多技术人员陷入困境。 您必须了解匿名内部类的工作方式( Command子类的内联声明),并了解execute()方法的含义。 例如,在匿名内部类的主体内只能调用外部类的最终对象引用。

如果我用更具表现力的现代Java语言编写相同的代码怎么办? 清单3显示了使用Groovy重写的相同方法:

清单3.用Groovy重写的addOrderFrom()方法
public class OrderDbClosure {
   def wrapInTransaction(command) {
     setupDataInfrastructure()
     try {
       command()
       completeTransaction()
     } catch (RuntimeException ex) {
       rollbackTransaction()
       throw ex
     } finally {
       cleanUp()
     }
   }
   
   def addOrderFrom(cart, userName, order) {
     wrapInTransaction {
       add order, userKeyBasedOn(userName)
       addLineItemsFrom cart, order.getOrderKey()
     }
   }
}

该代码(尤其是addOrderFrom()方法)更具可读性。 Groovy语言包括Command设计模式。 Groovy中用花括号( { }分隔)的任何代码都将自动成为一个代码块,可以通过在保存代码块引用的变量之后加上开括号和闭括号的语法来执行。 这种内置模式允许addOrderFrom()方法的主体更具表现力(由于使用了较少的addOrderFrom()代码)。 Groovy还允许您消除参数周围的一些括号,从而减少噪声字符。

清单4显示了类似的转换,这次是在Ruby中(通过JRuby):

清单4.转换为Ruby的addOrderFrom()方法
def wrap_in_transaction
  setup_data_infrastructure
  begin
    yield
    complete_transaction
  rescue
    rollback_transaction
    throw
  ensure
    cleanup
  end
end

def add_order_from
  wrap_in_transaction do
    add order, user_key_based_on(user_name)
    add_line_items_from cart, order.order_key
  end
end

该代码比Java版本更类似于Groovy代码。 Groovy代码和Ruby代码之间的主要区别在于Command模式特征。 在Ruby中,任何方法都可以采用代码块,该代码块通过方法体内的yield调用执行。 因此,在Ruby中,您甚至不需要指定特殊类型的基础结构元素-语言中存在的功能可以处理这种常见用法。

抽象风格

不同的语言以不同的方式处理抽象。 阅读本文的每个人都熟悉几种普遍的抽象样式,例如结构化,模块化和面向对象的样式,它们以多种语言出现。 当您长时间使用一种特定的语言时,它就变成了金锤:每个问题看起来都像钉子一样,可以被您的语言中的抽象驱动。 这在或多或少的纯粹面向对象的语言(例如Java语言)中尤其如此,因为主要的抽象是层次结构和可变状态。

现在,Java世界对功能语言(例如Scala和Clojure)表现出了极大的兴趣。 使用功能语言进行编码时,您会以不同的方式考虑问题的解决方案。 例如,大多数功能语言中的默认值创建不可变变量而不是可变变量,这与Java方法完全相反。 在Java代码中,默认情况下数据结构是可变的,并且您必须添加更多代码以使它们不变。 这意味着用函数式语言编写多线程应用程序要容易得多,因为不可变的数据结构固有地与线程进行了清晰的交互。

抽象并非纯粹是语言设计师的领域。 在2006年OOPSLA提出了一个有趣的论文,题为“协同扩散:编程Antiobjects”(见相关信息 ),推出了antiobject,这是一个对象,做什么,我们认为它应该做相反的概念。 这种方法解决了本文中阐明的一个问题: 通过使我们尝试创建受现实世界启发太多的对象,对象的隐喻可能会走得太远。

本文的重点是,陷入一种特定的抽象风格太容易了,这使问题比原本应有的困难。 通过将解决方案编码为反对象,可以通过更改观点来解决一个更简单的问题。

本文引用的示例很好地说明了这一概念-1980年代初的原始Pac-Man视频游戏机(如图2所示):

图2.原始的《吃豆人》视频游戏
吃豆人屏幕截图

最初的《吃豆人》游戏比某些现代手表具有更少的处理器能力和内存。 由于资源有限,游戏设计师面临一个严重的问题:如何计算迷宫中两个移动物体之间的距离? 他们没有足够的处理器能力来解决这个问题,因此他们采取了一种反对象的方法,将所有游戏情报都融入了迷宫本身。

吃豆人游戏中的迷宫是一个状态机,其中每个单元都为电路板的每次迭代运行规则。 设计师发明了吃豆人气味的概念。 吃豆人角色所占据的任何单元都具有最大的吃豆人气味,并且最近腾出的单元具有的吃豆人最大气味为负1,并且气味Swift衰减。 幽灵(追求吃豆人并且可以稍微移动一些的人)随机地伪装游荡,直到遇到吃豆人的气味,这时他们进入了较强的牢房。 为鬼动作添加一些随机性,您将获得“吃豆人”。 这种设计的一个副作用是鬼魂无法切断吃豆人:他们看不到他来了,他们只能说出他去过的地方。

对问题的这种简单思考使基础代码变得更加简单。 通过将抽象背景更改为背景,Pac-Man设计师在高度受限的环境中实现了他们的目标。 当遇到一个特别棘手的问题时(尤其是在从过于复杂的代码中重构时),请问自己是否有一种反对象方法可能更有意义。

结论

在本期中,我一直在研究为什么表达性很重要,以及代码中表达性的体现。 我同意杰克·里夫斯的工程比较。 我认为完整的源代码是软件中的设计工件。 一旦您了解了它,它就会解释很多过去的失败(例如,模型驱动的体系结构,该体系结构试图直接从UML工件转换为代码,但由于图表语言的表达能力不足以捕获所需的细微差别而失败)。 这种理解有几个副作用,包括认识到设计(即编码)是您可以执行的最昂贵的活动。 这并不意味着您不应该在开始编码之前就使用初步工具(例如UML或类似工具)来帮助您理解设计,但是一旦进入该阶段,代码便成为真正的设计。

可读的设计很重要。 您的设计越富表现力,就越容易对其进行修改,并最终通过紧急设计从中收获惯用模式。 在下一部分中,我将继续这一思路,并提供具体方法来利用从代码中收获的设计元素。


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
陈灯重用代码管理器为一款个人软件作品,其可作为一款个人和团队内部的代码管理软件使用,实现了可重用代码的入库、搜索和共享等功能。软件具有代码搜索准确方便、代码颜色标识、搜索词导航以及类似Google的智能搜索框等特点。 软件包括桌面版本和插件版本两种版本,桌面版本为独立的应用程序,不与IDE集成,使用范围更加广泛,目前最新版本为2.3;插件版本集成在Visual Studio(5.0~10.0)集成开发环境中,使用更为方便。该发布版本为插件版4.0版本。 该版本完成了以下工作: 1、实现了类似Google的智能搜索框,能够根据关键词提示并自动完成搜索词的输入,使得搜索更加方便、准确。 2、增加了对重复入库代码的过滤功能。 3、代码搜索界面中增加了“最近入库”和“最近使用”选项卡,使用户能够方便的查看最近入库和最近使用的代码。 4、软件界面中添加了工具提示,简化用户的使用。 5、搜索范围扩展到了代码分类、开发语言和开发环境。 6、代码库导入完成后显示导入代码条数信息。 7、提供了多种搜索算法供用户选择。 8、增加了开发环境管理和开发语言管理功能。 9、解决了代码搜索不准确的问题。 10、解决了使用过程中,代码信息中的单引号会增加的问题。 11、解决了前版本中存在的其它若干bug。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值