对话马丁·福勒(Martin Fowler)——第四部分:灵活性与复杂性
简介
在第四部分,Fowler 讨论了设计褪色(design decay)、灵活性和可重用性与复杂性的关系、简单系统的四个条件、以及接口设计(interface design)。
设计褪色与重构
Bill Venners:为什么设计会随着时间而褪色?
Martin Fowler:对于计划型设计来说,设计只在最开始时是正确的,随之不可避免地会褪色。生活中的许多事情都 会褪色。关键是,你要懂得如何避免设计褪色。而这恰恰是重构的目的——逆转褪色的过程。重构是一种相对来说比较新的技术。我们还未能完全掌握这项技术。不 过有一点可以肯定,你可以借助重构来防止设计褪色,甚而逆转褪色过程,使设计随着时间而变得越来越完善。
灵活性和复杂性
Bill Venners:在《重构》一书中你写道:“在学会重构之前,我总是力图找到灵活的方案。因为设计变动的代价非常高,因而我希望我的设计能够胜任我所能预知的变化。但问题是,灵活性是有代价的。”那么,灵活性的代价是什么?有什么解决之道么?
Martin Fowler:灵活性的代价就是复杂性。每次当你往代码中加入一些额外的东西以提高灵活性时,通常也使你的 代码变得更加复杂。假如你的预期是对的,未来确实需要这种灵活性,那么你的超前工作得到了回报。但如果你的预期是错的,那么你所引入的复杂性将使软件变得 更加难以改动,因而该灵活性是毫无意义的。
而这种预期是很容易出错的。比如,当需求发生变化时,你所以为的对灵活性的需求可能随之变化甚至有可能不复存在。再比如,你添加了一些额外的代码,指望它们能提高灵活性,但这些代码本身就有问题。结果是既增加了复杂性,又未能实现灵活性,真是“赔了夫人又折兵”。
而解决之道就是极限编程。事实上,你根本就不需要考虑灵活性。极限编程理论认为,既然我们的预期在大多数情况下都是错的,那么就把灵活性 放在一边好了。那种冒进式的提升设计的办法是拔苗助长;平稳地改进设计才是可取之道。事实上,设计的改进是一个自我强化(self- reinforcing)的过程。如果你能够使设计尽可能简洁,避免那些无谓的灵活性,那么你所要面对的复杂性就会小很多,也就越容易对代码做出改动。代 码会更容易被读懂和被改动,你也能够更快地对软件做出调整。
灵活性和可重用性
Bill Venners:那怎么看“可重用”呢?“灵活”是不是“可重用”的另一个代名词?
Martin Fowler:不,不是的。但问题是,为了使代码可重用,你必须使它灵活。很多时候,可重用性给你带来不了什么好处,或者是因为你根本就用不着它,或者是因为你所期待的可重用性与实际情况风马牛不相及。你在代码中所加入的灵活性往往并不是你最终所需要的灵活性。
Bill Venners:那么,是否有些时候需要使事情变得更可重用一些?我该怎么判断呢?
Martin Fowler:我认为可重用也是一个逐步实现和完善的过程。起先,为了解决某些实际问题,你开发了一个应 用。接下来,你可能开发了另一个类似的应用。这时,你就可以从两个应用中提取出一些公共的片段。假如你又开发了第三个类似的应用,那么你可能提取出更多的 公共片段。在此基础上,你可能就会得到一个类似“可重用的框架”的东西。我强烈建议,不要试图一开始就定义一个可重用的框架然后在此基础上开发应用,相 反,应该是在开发过程的过程中,逐渐形成和完善框架。
预先设计与重构
Bill Venners:你认为预先设计与重构应该各占多大比重?
Martin Fowler:在遇到重构之前,特别是在把重构与自动测试(automated testing)结合使用之前,我习惯于把设计看作是在开始阶段必须做好的一件事情。而现在,我认为不需要做很多的预先设计,大部分设计都是在某种进化过 程中完成的。预先设计逐渐让位于重构。比如说,在以前,我可能会倾向于80%的设计都是预先做好的,20%是在项目进程中完善的。而现在,这个比例可能要 倒过来。
Bill Venners:也就是说,进化型设计的比重要比预先设计大得多。
Martin Fowler:没错,更多的进化型设计,更少的预先设计。
简单系统的条件
Bill Venners:在《重构》一书中,你引用了 Kent Beck 给出的简单系统的四个条件:1)通过所有的测试;2)揭示所有的意图;3)没有重复代码;4)使用最少的类和方法。那么,对你来说,简单性是什么?
Martin Fowler:我想,很难给简单性一个定义。我很喜欢 Kent 的四个条件。第一条提醒我们必须要通过所有的测试。
Bill Venners:但是,这与简单性有什么关系呢?我完全可以把事情搞得很复杂,也一样通过所有的测试。
Martin Fowler:这倒是没错。但是,一个设计良好的系统,首先必须是一个能运行的系统。如果你把这个约束去了,那……
Bill Venners:噢,你是说,通过测试是必要条件但非充分条件。
Martin Fowler:没错。一旦测试通过,你接着就该问自己,是否所有的重复代码都被清除干净了?而另一个条件, 揭示所有的意图,则是非常难把握的一个条件,因为它太主观了。基本上说,你应该能够读懂代码,知道它在干什么。代码所包含的设计意图应该能够通过代码很明 显地体现出来。像是命名合理的方法就是一个简单的示例。与其把一个方法起名为 x74-3,然后通过注释来说明这个方法是干什么的,不如通过该方法的名字直接告诉人们它要做什么。这样更能起到揭示其意图的作用。问一下你自己,你的代 码结构、命名方式等等,是否揭示了代码的意图?
当上面提到的这些问题都解决了的时候,你显然还希望代码不要有多余的部分。注意,最小化所使用的类和方法一定是最后一步——在其他条件都满足之后。人们常常对于第二个和第三个条件的次序有不同看法。但通过所有的测试一定是第一位的,而最小化代码量则一定是最后一步。
设计接口
Bill Venners:当我想到设计的时候,总是习惯性地从接口这个角度去考虑。而在有关模式和敏捷方法的一些书籍 中,包括“四人帮”的《设计模式》(Design Patterns),Kent Beck 的《解析极限编程》(Extreme Programming Explained),还有你的《重构》,往往都是把接口和实现作为一个整体的代码来讨论。那么,在设计中是否还有必要从接口这个角度去考虑?
Martin Fowler:我想,从接口这个角度去考虑是非常基本的。事实上,我认为“四人帮”的那本书比其它任何一本关于面向对象设计的书都更强调接口的作用。
Bill Venners:为什么这么说?
Martin Fowler:其它关于面向对象设计的书也许会在某处这样说道:“啊,接口实在是太重要了。”而“四人帮” 的那本书,在前几章就开宗明义地指出,面向接口编程(program to an interface)到底意味着什么。而这本书后面的几乎每一个模式,都在说明接口是如何独立于实现而变化的,以及面向接口编程的好处。
我想,极限编程也是一样。测试优先设计(test-first design)—— Kent 称之为测试驱动开发(test-driven development)——的全部意义,或者说,最主要的驱动因素,就是接口。因为当你先编写测试的时候,你实际上所考虑的是接口。编写测试就是在设计 接口。当你先编写测试后实现代码的时候,你实际上是先定义清楚了接口,然后再实现接口。接下来你就可以编写一整套自动测试来描述接口是如何工作的。
Bill Venners:我在读这些文献的时候未曾体会到这一层。我所领会的是,首先设计接口,接下来有时候会先写测试,有时候会后写测试,有时候则不写测试。
Martin Fowler:没关系的。
Bill Venners:既然先写测试与考虑如何设计接口是相关的,那么它们是否是一回事?
Martin Fowler:可以这么说。不过我想,对许多程序员来说,先写测试的说法会更具体形象些。构思接口是件困难 的事情。但是,当你坐在那里对自己说“我需要写一个测试,以使这一个小功能得以实现”的时候,你就要考虑,怎样写这个测试最好?而这时,你就在考虑接口。 这是一个潜移默化的过程——你的的确确是在构思接口,而且是以一种渐进的方式。你不会对自己说,“啊,我需要构造这个类,让我们来把这个类的所有接口都搞 清楚,然后再实现之吧!”相反,你会说,“嗯,这个类需要实现这么一小块功能。来为此写个测试吧!”在编写测试的时候,接口就随之浮现出来。
分享到:
相关推荐
知攻善防-应急响应靶机-web2.z18
知攻善防-应急响应靶机-web2.z09
白色简洁风格的影视众筹平台整站网站源码下载.zip
内容概要:本文详细解析了HTTP请求的整个流程,包括用户请求发起、请求报文构建、服务器处理请求、响应报文生成、网络传输响应和浏览器接收响应六个阶段。每个阶段的内容均涵盖了关键步骤和技术细节,如DNS解析、TCP连接、缓存策略、HTTP/2性能提升、HTTPS加密等。通过这些内容,读者可以全面理解HTTP请求的完整流程。 适合人群:具备一定网络基础知识的前端、后端开发人员及IT运维人员。 使用场景及目标:适用于希望深入了解HTTP协议及其优化技术的技术人员,有助于提升系统的性能和安全性,优化用户体验。 阅读建议:本文内容详尽且涉及多个关键技术点,建议读者结合实际案例进行学习,逐步理解和掌握各个阶段的技术细节和优化方法。
白色简洁风格的电话通讯公司模板下载.zip
白色简洁风格的日历当日事件提醒整站网站源码下载.zip
一键制作 歌曲伴奏! 可以消人声 吉他 鼓 等 多轨道声音。相当好用。
知攻善防-应急响应靶机-web2.z04
NSDocumentError如何解决.md
白色宽屏风格的大气冲浪运动整站网站模板.rar
白色简洁风格的婴儿用品商城网站模板.zip
罗兰贝格2023未来营养趋势报告21页
预览地址:https://blog.csdn.net/qq_42431718/article/details/144749829 html+css 圣诞树代码html
1-100加减乘除出题生成器
白色简洁风格的网络实验室CSS模板.zip
白色简洁风格的企业产品展示整站网站源码下载.zip
内容概要:《etcd-metrics-latest.txt》文档记录了 etcd(一个分布式键值存储系统)的多个指标数据,包括但不限于集群版本、认证修订版、后端磁盘操作延时分布、租赁管理、键值操作统计、快照保存、网络通信、Go 运行时指标、gRPC 请求处理、操作系统资源使用以及进程资源使用等。这些指标提供了详细的性能监测数据,帮助运维人员和开发人员理解和优化 etcd 集群的运行状态。 适合人群:具有基础计算机科学知识的运维人员或开发人员,尤其是负责维护或开发基于 etcd 技术系统的专业人员。 使用场景及目标:主要用于监控 etcd 集群的健康状况,评估性能瓶颈,辅助故障排查,支持集群的持续优化和技术决策。 其他说明:文档中大量使用了指标和术语,建议读者对 etcd、Go 语言、gRPC 和操作系统基础知识有一定的了解,以便更好地解读文档中的数据。对于不熟悉这些技术的读者来说,可能需要额外查阅相关资料来辅助理解。
Java编写的计算器程序是一种基于Java编程语言实现的计算工具,常用于教学或个人项目中,以帮助用户执行基本的数学运算。在这个简单的计算器程序中,我们可能会遇到以下几个关键的Java知识点: 1. **基础语法与控制结构**:Java的基础语法包括变量声明、数据类型(如int、double等)、条件语句(if-else)和循环语句(for, while)。在计算器程序中,这些元素用于读取用户输入、判断操作类型以及重复执行某些计算过程。 2. **面向对象编程**:Java是一种面向对象的语言,因此计算器程序可能包含多个类,如Calculator类、Button类(模拟图形界面的按钮)和Display类(显示计算结果)。类之间可能存在继承关系,例如Button类可能继承自一个抽象的UIComponent类。 3. **输入/输出处理**:在命令行计算器中,Java的Scanner类用于获取用户输入,如数字和运算符。在图形用户界面(GUI)计算器中,可能使用事件监听器处理用户的点击事件,获取按钮上的文字信息。 4. **异常处理**:为了确保程序的健壮性,计算器可能包含异常处理代码,比如当
SystemExit.md
NavigationGuardError解决办法.md