敏捷编码

敏捷编码

任何一个笨蛋都能够让事情变得越来越笨重、越来越复杂、越来越极端。需要天才的指点以及许多的勇气,才能让事情向反方向发展。——John Dryden

 

在新项目刚开始着手开发的时候,她的代码很容易理解和上手。然而,随着开发过程的推进,项目不知不觉中演变为一个庞然大物。发展到最后,往往需要投入更多的精力、人力和物力来让他继续下去。解决这个问题最简单的方式就是在开发过程中细心的照看代码。在编写代码时,每天付出一点小的努力,就可以避免代码“腐烂”[在测试时,非不得已,不使用硬编码],并且保证应用程序不至于变的难以理解和维护。

 

代码要清晰地表达意图

魔鬼说:“可以工作而且易于理解的代码当然好,但是让你觉得聪明更加重要。别人给你钱是因为你脑子好使,让我们看看你到底有多聪明。”

 

Hoare谈软件设计

设计软件有两种方式。一种是设计得尽量简单,并且明显没有缺陷。另一种方式是设计得尽量复杂,并且没有明显的缺陷。

 

我们大概都见过不少难以理解和维护的代码,而且(最坏的是)还有错误。当开发人员们像一群旁观者见到UFO一样围在代码四周,同样也感到恐怖、困惑与无助时,这个代码的质量就可想而知了。如果没有人理解一段代码的工作方式,那这段代码还有什么用呢?

 

开发代码时,应该更注重可读性,而不是只图自己方便。代码阅读的次数要远远超过编写的次数,所以在编写的时候值得花点功夫让他读起来更加简单。实际上,从衡量标准上看,代码清晰程度的优先级应该排在执行效率之前。

 

如果别人给你的代码很容易理解,接下来的工作就省心多了。要尊重这个黄金法则,你欠他们一份情,因此也要让你自己的代码简单、便于阅读。

 

PIE原则(PIE=Program Intently and Expressively,即意图清楚而且表达明确地编程。)

代码必须明确说出你的意图,而且必须富有表达力。这样可以让代码更易于被别人阅读和理解。代码不让人迷惑,也就减少了发生潜在错误的可能,一言以蔽之,代码应意图清晰,表达明确。

 

Int result = val << 1;

用位移做乘法,是对代码进行不必要而且危险的性能优化。Result = val * 2;看起来更清晰,也可以达到目的,而且对于某种给定的编译器来说,可能效率更高。不要表现的好像很聪明似的,要遵循PIE原则:代码要清晰的表达意图。

 

在编写代码时,应该使用语言特性来提升表现力。使用方法名来传达意向,对方法参数的命名要帮助读者理解背后的想法。异常传达的信息是哪些可能会出问题,以及如何进行防御式编程,要正确地使用和命名异常。好的编码规范可以让代码变得易于理解,同时减少不必要的注释和文档。[注释简与繁的平衡]

 

天使说:“要编写清晰的而不是讨巧的代码。向代码阅读者明确表明你的意图。可读性差的代码一点都不聪明。”

 

使用符合当时情形的耦合。例如,通过散列表进行松耦合,这种方式适用于在实际状况中是松耦合的组件。不要使用散列表储存紧密耦合的组件,因为这样没有明确表示出你的意见。

 

用代码沟通

魔鬼说:“如果代码太杂乱以至于无法阅读,就应该使用注释说明。精确地解释代码做了什么,每行代码都要加注释,不用管为什么要这样编码,只要告诉我们到底是怎么做就好了。”

 

建立代码文档无外乎两种方式:利用代码本身;利用注释来沟通代码之外的问题。

 

不要用注释来包裹你的代码

应该文档化你所有的代码吗?在某种程度上说,是的。但这并不意味着要注释绝大部分代码,特别是在方法体内部。源代码可以被读懂,不是因为其中的注释,而应该是由于他本身优雅而清晰——变量名运用正确、空格使用得当、逻辑分离清晰,以及表达式非常简洁

 

如果必须通读一个方法的代码才能了解他做了什么,那么开发人员先要投入大量的时间和精力才能使用它。反过来讲,只需要短短几行注释说明方法行为,就可以让生活变得轻松许多。开发人员可以很快了解到他的意图。她的期待结果,以及应该注意之处——这可省了你不少劲儿。

 

如何命名很重要。程序元素的命名是代码读者必读的部门。通过使用细心挑选的名称,可以向阅读者传递大量的意图。反过来讲,使用人造的命名范式(比如现在已经无人问津的匈牙利表示法)会让代码难以阅读和理解。这些范式中包括的底层数据型信息,会 硬编码在变量名和方法名中,形成脆弱、僵化的代码,并会在将来造成麻烦。

 

如何界定一个良好的命名?良好的命名可以向读者传递大量的正确的信息。不好的命名不会传递任何信息,糟糕的命名则会传递错误的信息

 

注释可以用来为读者指定一条正确的代码访问路线图。如:

目的:为什么需要这个方法?

需求(前置条件):方法需要什么样的输入,对象必须处于何种状态,才能让这个方法工作?

承诺(后置条件):方法成功执行后,对象现在处于什么状态,有哪些返回值?

异常:可能会发生什么样的问题?会抛出什么样的异常?

 

代码被阅读的次数要远远超过被编写的次数,所以在编写代码时多付出一点努力来做好文档,会让你在将来受益匪浅。

 

天使说:“用注释沟通。使用细心选择的,有意义的命名。用注释描述代码意图和约束。注释不能替代优秀的代码。”

 

注释就像是可以帮助你的好朋友,可以先阅读注释,然后快速浏览代码{代码与注释各自的关系},从而完成理解他做了什么,以及为什么这样做。

 

注释代码做了什么的注释用处不那么大。相反,注释要说明为什么会这样写代码。

 

动态评估取舍

魔鬼说:“性能、生产力、优雅、成本以及上市时间,在软件开发过程中都是至关重要的因素。每一项都必须达到最理想状态。”

 

你可能曾经身处这样的团队:管理层和客户将很大一部分注意力都放在应用的界面展示上。也有这样的团队,其客户认为性能表现非常重要。在团队中,你可能会发现,有这样一个开发主管或架构师,他会强调遵守“正确”的范式比其他任何事情都重要。对任何单个因素如此独断地强调,而不考虑他是否是项目成功的必要因素,必然导致灾难的发生。

 

没有最佳解决方案

没有适宜所有状况的最佳解决方案。你必须对手上的问题进行评估,并选出最合适的解决方案。每个设计都是针对特定问题的——只有明确地进行评估和权衡,才能得出更好的解决方案。

 

天使说:“动态评估权衡。考虑性能、便利性、生产力、成本和上市时间。如果性能表现足够了,就将注意力放在其他因素上,不要为了感觉上的性能提升或者设计的优雅,而将设计复杂化。”

 

即使不能面面俱到,你也应该觉得已经得到了最重要的东西——客户认为有价值的特性。

 

增量式编程

魔鬼说:“真正的程序员写起代码来,一干就是几个小时,根本不停,甚至连头都不抬。不要停下来去编译你的代码,只要一直往下写就好了!”

 

当你开车进行长途旅行时,两手把住方向盘,固定在一个位置,两眼直盯前方,油门一踩到底几个小时,这样可能吗?当然不行了,你必须掌控方向,必须经常注意交通状态,必须检查油量表、吃饭,准备其他必需品,以及诸如此类的活动。

 

如果不对自己编写的代码进行测试,保证没有问题,就不要连续几个小时,甚至连续几分钟进行编程。相反,应该采用增量式的编程方式。增量式编程可以精炼并结构化你的代码。代码被复杂化、变成一团乱麻的几率减少了。所开发的代码基于即使的反馈,这些反馈来自以小步方式编写代码和测试的过程。

 

采取增量式编程和测试,会倾向于创建更小的方法和更具内聚性的类。你不是在埋头盲目地一次性编写一大堆代码。相反,你会经常评估代码质量,并不时地进行许多小调整,而不是一次修改许多东西。在编写代码的时候,要经常留心可以改进的微小方面。这可能会改善代码的可读性。也许你会发现可以把一个方法拆成几个更小的方法,使其变得更易于测试。在重构的原则指导下,可以做出许多细微改善。关键在于持续做一些细小而有用的事情,而不是做一段长时间的编程或重构。

 

天使说:“在很短的编辑/构建/测试循环中编写代码。这要比花费长时间仅仅做编写代码的工作好得多。可以创建更加清晰、简单、易于维护的代码。”

 

在写了几行代码之后,你会迫切地希望进行一次构建/测试循环。在没有得到反馈时,你不想走得太远。

 

在编译和测试运行中,停下来想想,并暂时远离代码细节。这是保证不会偏离正确方向的好办法。

 

保持简单

魔鬼说:“软件是很复杂的东西,随便哪个笨蛋都可以编写出简单、优雅的软件。通过编写史上最复杂的程序,你将会得到美誉和认可,更不用提保住你的工作了。”

 

Andy曾经认识一个家伙,他对设计模式非常着迷,想把他们全部都用起来。有一次,要写一个大概几百行代码的程序。在被别人发现之前,他已经成功地将GoF那本书中的17个模式,都运用到那可怜的程序中。

 

问题在于,许多开发人员倾向于将投入的努力与程序复杂性混同起来。开发人员更应该为自己能够创建出一个简单并且可用的设计而骄傲。

 

简单不是简陋

“简单性”这个词汇被人们大大误解了。他并不是意味着简陋、业余或是能力不足。恰恰相反,相比一个过分复杂、拙劣的解决方案,简单的方案通常更难以获得。

 

怎样才算优雅?

优雅的代码第一眼看上去,就知道他的用处,而且很简洁。但是这样的解决方案不是那么容易想出来的。这就是说,优雅是易于理解和辨识的,但是要想创建出来就困难得多了。

 

天使说:“开发可以工作的、最简单的解决方案。除非有不可辩解的原因,否则不要使用模式、原则和高难度技术之类的东西。”

 

当你觉得所编写的代码中没有一行是多余的,并且仍然能交付全部的功能时,这种感觉就对了,这样的代码容易理解和改正。

 

一个人认为简单的东西,可能对另一个人就意味着复杂。

 

编写内聚的代码

魔鬼说:“你要编写一些新的代码,首先要决定的就是把这些代码放在什么地方。其实放在什么地方问题不大,你就赶快开始吧,看看IDE中现在打开的是哪个类,直接加进去就是了。如果所有的代码都放在一个类或组件里面,要找起来是很方便的。”

 

内聚性用来评估一个组件(包、模块或配件)中成员的功能相关性。内聚程度高,表明各个成员共同完成了一个功能特性或是一组功能特性。内聚程度低的话,表明各个成员提供的功能是互不相干的。

 

低内聚性的代码会造成很严重的后果。假设有这样一个类,实现了五种完全不相干的功能。如果这5个功能的需求或细节发生了变化,这个类也就必须跟着改变。如果一个类变化得过于频繁,这样的改变会对整个系统形成“涟漪效应”,并导致更多的维护和成本的发生。考虑另一个只实现了一种功能的类,这个类变化的频度就没有这么高。类似的,一个更具内聚性的组件不会有太多导致其变化的原因,也是因此而更加稳定。根据单一职责原则,一个模块应该只有一个发生变化的原因。

 

内聚性会影响一个组件的可重用性。组件粒度是在设计时要考虑的一个重要因素。根据重用发布等价原则:重用的粒度与发布的粒度相同。就是说,程序库用户所需要的,是完整的程序库,而不是其中的一部分。如果不能遵循这个原则,组件用户就会被强迫只能使用所发布组件的一部分。很不幸的是,他们仍然被不关心的那一部分的更新所影响。软件包越大,可重用性就越差。

 

天使说:“让类的功能尽量集中,让组件尽量小。要避免创建很大的类或组件,也不要创建无所不包的大杂烩类。”

 

有可能会把一些东西拆分成很多微小的部分,而使其失去了实用价值。当你需要一只袜子的时候,一盒棉线不能给你带来任何帮助。(你可以把这个叫作“意大利面OO”系统)

 

具有良好内聚性的代码,可能会根据需求的变化,而成比例地变化。考虑一下,实现一个简单的功能变化需要变更多少代码。例如:

有这样一个系统,向一个表单中添加一个字段,需要16名团队成员和6名经理的同意。这是一个很清晰的警告信号,说明系统的内聚性很差。

 

告知,不要询问

魔鬼说:“不要相信其他的对象。毕竟,他们是由别人写的,甚至有可能是你自己上个月头脑发昏的时候写的呢。从别人那里去拿你需要的信息,然后自己处理,自己决策。不要放弃控制别人的机会。”

 

“面向过程的代码取得信息,然后做出决策。面向对象的代码让别的对象去做事情。”Alec Sharp通过观察后,一针见血地指出了这个关键点。但是这种说法并不仅限于面向对象的开发,任何敏捷的代码都应该遵循这个方式。

 

作为某段代码的调用者,开发人员绝对不应该基于被调用对象的状态来做出任何决策,更不能去改变该对象的状态。这样的逻辑应该是被调用对象的责任,而不是你的。在该对象之外替他做决策,就违反了他的封装原则,而且为BUG提供了滋生的土壤。

 

David Bock使用“送报男孩和钱包的故事”很好地诠释了这一点。假定送报男孩来到你的门前,要求你付给他这周的报酬。你转过身,让送报男孩从你的后屁股兜里掏出钱包,并且从中拿走两美元(你希望是这么多),再把钱包放回去。然后,送报男孩就会开着他崭新的美洲豹汽车扬长而去了。

 

将命令与查询分离开来

与告知,不要询问相关的一个很有用的技术是:命令与查询相分离模式。就是要将功能和方法分为“命令”和“查询”两类,并在源码中记录下来(将所有命令代码放在一起,将所有查询代码放在一起)。一个常规的“命令”可能会改变对象的状态,而且有可能返回一些有用的值,一个查询仅仅提供给开发人员对象的状态,并不会对其外部的可见状态进行修改。

 

天使说:“告知,不要询问。不要抢别的对象或组件的工作。告诉他做什么,然后盯着你自己的职责就好了。”

 

决不能允许一个看起来无辜的“查询”去修改对象的状态。

 

根据契约进行替换

魔鬼说:“深层次的继承是很棒的。如果你需要其他类的函数,直接继承他们就好了!不要担心你创造的新类会造成破坏,你的调用者可以改变他们的代码。这是他们的问题,而不是你的问题。”

 

Liskov 替换原则告诉我么:任何继承后得到的派生类对象,必须可以替换任何被使用的基类对象,而且使用者不必知道任何差异。换句话说,某段代码如果使用了基类中的方法,就必须能够使用派生类的对象,并且自己不必进行任何修改。(这里说的就是基类和派生类的多态性)

 

要遵守Liskov替换原则,相对基类的对应方法,派生类服务(方法)应该不要求更多,不承诺更少;要可以进行自由的替换。在设计类的继承层次时,这是一个非常重要的考虑因素。

 

继承是OO建模和编程中被滥用最多的概念之一。,如果违反了Liskov替换原则,继承层次可能仍然可以提供代码的可重用性,但是将会失去可扩展性。类继承关系的使用者现在必须要检查给定对象的类型,以确定如何针对其进行处理。当引入了新的类之后,调用代码必须经常重新评估并修正。这不是敏捷的方式。

 

针对is-a关系使用继承;针对has-auses-a关系使用委托(包含)

使用包含代替继承,在类中显示的使用包含的类的方法,来实现需要实现的方法。

 

天使说:“通过替换代码来扩展系统。通过替换遵循接口契约的类,来添加并改进功能特性。要多使用委托而不是继承。”

 

相对继承来说,委托更加灵活,适应力也更强

 

6 敏捷编码 体会:

 

1 需要天才的指点以及许多的勇气。勇气很重要,要去考虑一些东西,不要越墨守城规,并把自己的想法告诉团队。

 

2在编写代码时,每天付出一点小的努力,就可以避免代码“腐烂”。所以在编写的时候值得花点功夫让他读起来更加简单。体会:在测试时,非不得已,不使用硬编码]

 

3注释简与繁的平衡。怎么来平衡?代码与注释各自的关系:可以先阅读注释,然后快速浏览代码用注释沟通。使用细心选择的,有意义的命名。用注释描述代码意图和约束。注释不能替代优秀的代码。

 

4没有适宜所有状况的最佳解决方案。你必须对手上的问题进行评估,并选出最合适的解决方案。

 

5 增量式编程:在很短的编辑/构建/测试循环中编写代码。这一点值注意:避免一天的工作像跑不完的马拉松,应该是具有阶段性。

 

6 内聚性:

a)注意对象、方法的职责的分配---比如,不往衣柜里放碗、筷。

          b)同时注意代码的粒度:当你需要一只袜子的时候,一盒棉线不能给你带来任何帮助

 

      7 多态:

                      Liskov 替换原则强调的是多态。在.Net中多态的实现,主要用接口与继承。

 

继承、接口、委托的使用:

针对is-a关系使用继承;针对has-auses-a关系使用委托(包含)。

 

顺便说明:

对于‘父类’[概念父类]一套相关方法,当然这些方法‘子类’通用,我们更倾向于使用接口;对于‘父类’[概念父类]一个相关方法,我们更倾向于使用委托。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值