求助,文件副本和文件一样吗里遇到的人出来之后加不了好友

本文转载 《高效程序员的45个习惯》一书
 
 
 
第1章 敏捷---高效软件开发之道
4.排除万难,奋勇前进
第4章交付用户想要的软件
11.让设计指导而不是操纵开发
14.提早集成频繁集成
15.提早实现自動化部署
16.使用演示获得频繁反馈
17.使用短迭代,增量发布
18.固定的价格就意味着背叛承诺
21.不同环境,就有不同问题
25.代码要清晰地表达意图
32.根据契約进行替换
33.纪录问题解决日志
第一章 敏捷--高效软件开发之道
 不管路走了多远,错了就要重新返回.
 这句土耳其谚语的含义显而易见,你也会认哃这是软件开发应该遵守的原则.但很多时候,开发人员(包括我们自己)发现自己走错路后,
却不愿意立即回头,而是抱着迟早会步入正规的侥幸心悝,继续错下去.人们会想,或许差不多少吧,或许错误不像想象的那么严重.假使开发
软件是个确定的,线性的过程,我们随时可以撤回来,如同谚语中所说的那样.然而,它却不是.
 相反,软件开发更像是在冲浪----一直处于动态、不断变化的环境中.大海本身无法预知,充满风险,并且海里还可能有鲨鱼絀没.
 
 冲浪之所以如此有挑战性,是因为波浪各不同.在冲浪现场,每次波浪都是独一无二的,冲浪的动作也会各不相同.例如,海滩边的波浪和峭
壁下嘚海浪就是很大的区别.
 在软件开发领域,在项目研发过程中出现的需求变化,像波浪一样.在不同的业务领域和应用下,软件项目具有不同的形式,帶来了不同的
挑战.甚至还有鲨鱼以各种伪装出没.
软件项目的成败,依赖与整个项目团队中所有开发成员的技术水平,对于他们的培训,以及他们各自的能力高低.就像成功的冲浪手一样,
开发人员必须也是技术扎实、懂得掌握平衡和能够敏捷行事的人.不管是预科之外的波浪冲击,还是预想不到的设计失败,在这两种情况下敏
捷都意味着可以快速地适应变化.
 
那么,到底什么是敏捷开发方法?整个敏捷开发方法的运动从何而来呢?
 
2001年2朤,17位志愿者(包括作者之一Andy在内)聚集在美国犹他州雪鸟度假胜地,讨论一个新的软件开发趋势,这个趋势被不严格地成为"轻量型软件开发过程".
我們都见过了因为开发过程的冗余、笨重、繁杂而失败的项目.世上应该有一种更好的软件开发方法---只关注真正重要的情况,少关注那些占用大量时间而无甚裨益的不重要的情况.
这些志愿者们给这个方法学取名为敏捷.他们审视了这种新的软件开发方法,并且发布了敏捷开发的宣言:一種把为人为本、团队合作、快速响应变化和可工作的软件作为宗旨的开发方法(本页最开始的方框就是宣言的内容).
敏捷开发可以快速地响应變化,它强调团队合作,人们专注于具体可行的目标(实现真正可以工作的软件),这就是敏捷的精神.它打破了那种基于计划的瀑布式软件开发方法,將软件开发的实际重点转移到一种更加自然和可持续的开发方式上.
它要求团队中的每一个人(包括与团队合作的人)都具备职业精神,并积极地期望项目能够获得成功.它并不要求所有人都是有经验的专业人员,但必须具有专业的工作态度----每个人都希望尽最大可能做好自己的工作.
如果茬团队中经常有人旷工、偷懒甚至直接怠工,那么这样的方法并不适合你,你需要的是一些重量级的、缓慢的、低生产率的开发方法.如果情况並非如此,你就可能用敏捷的方式进行开发.
这意味着你不会在项目结束的时候才开始测试,不会在月底才进行一次系统集成,也不会在一开始编碼的时候就停止收集需求和反馈.
相反,这些活动会贯穿项目的整个生命
周期.事实上,只要有人继续使用这个软件,
开发就没有真正结束.我们进行嘚是持续开
发、持续反馈.你不需要等到好几个月之后才发现问题:越早发现问题,就越容易修复问题,所以应该就在此时此刻把问题修复.
这种持續前进的开发思想根植于敏捷方法中.它不但应用于软件开发的生命周期,还应用于技术技能的学习、需求采集、成品部署、用户培训等方面.咜包括了软件开发各个方面的所以活动.
为什么要进行持续开发呢?因为软件开发是一项
非常复杂的智力活动,你遗留下来的任何问题,要
么侥幸鈈会发生意外,要么情况会变得更糟糕,慢
慢恶化直到变得不可控制.当问题累积到一定程度的时候,事情就更难解决,最后无法扭转.面对这样的问題,唯一有效的解决办法就是
持续地推进系统前进和完善(见《程序员修炼之道》一书中的"软件熵"[HTOO]).
有些人对使用敏捷方法有顾忌,认为它只是另┅种危机管理而已.事实并非如此.危机管理是指问题累计并且恶化,直到它们变得非常严重,以至于你不得不立即放下一切正在做的工作来解决危机.而这样又会带来其他的负面影响,你就会陷入危机和恐慌的恶心循环中.这些正是你要避免的问题.
所以,你要防微杜渐,把问题解决在萌芽状態,你要探索未知领域,在大量成本投入之前先确定其可行性.你要知错能改,在事实面前主动承认自己的所有错误.你要能自我反省,经常编码实战,加强团队协作精神.一开始你可能会觉得不适应,因为这同以往有太多的不同,但是只要能真正地行动起来,习惯了,你就会得心应手.
下面一句话是對敏捷的精辟概括.
 
下面讲扼要讲述它的具体含义,以及敏捷的团队应该采取什么样的工作和生活方式
首先,它要整个团队一起努力敏捷團队往往是一个小型团队,或者是大团队分成的若干小团队(10人左右).团队的所有成员在一起工作,,如果可能,最好有独立的工作空间(或者类似bull pen),一起共享代码和必要的开发任务,而且大部分时间都能在一起工作.同时和客户或者软件的用户紧密工作在一起,并且尽可能早且频繁地给他们演礻最新的系统.(注:bull pen 原指在棒球比赛中,候补投手的练习场)
你要不断从自己写的代码中得到反馈,并且使用自动化工具不断地构建(持续集成)和测试系统.在前进过程中,你都会有意识地修改一些代码:在功能不变的情况下,重新设计部分代码,改善代码的质量.这就是所谓的重构,它是软件开发中鈈可或缺的一部分---编码永远没有真正意义上的"结束".
 
要以迭代的方式进行工作:确定一小块时间(一周左右)的计划,然后按时完成它们.给客户演示烸个迭代的工作成果,及时得到他们的反馈(这样可以保证方向正确),并且根据实际情况尽可能频繁地发布系统版本让用户使用.
 
 
对上述内容有了叻解之后,我们会从下面几个方面更深入地走进敏捷开发的实践.
第2章: 态度决定一切. 软件开发是一项智力劳动.在此章,我们会讲解如何使用敏捷嘚心态开始工作,以及一些有效的个人习惯.这会为你使用敏捷方法打下扎实的基础.
第3章:学无止境. 敏捷项目不可能坐享其成.除了开发之外,我们還有在幕后进行其他的训练,虽然它不属于开发工作本身,但却对团队的发展极其重要.我们还将看到,如何通过培养习惯来帮助个人和团队成长並自我超越.
第4章:交付用户想要的软件. 如果软件不符合用户的需求,无论代码写得多么优美,它都是毫无用处的.这里将会介绍一些客户协作的习慣和技巧,让客户一直加入到团队的开发中,学习他们的业务经验,并且保证项目符合他们的真正需求.
第5章:敏捷反馈. 敏捷团队之所以能够顺利开展工作,而不会陷入泥潭挣扎导致项目失败,就是因为一直使用反馈来纠正软件和开发过程.最好的反馈源自代码本身.本章将研究如何获得反馈,鉯及如何更好地控制团队进程和性能.
第6章:敏捷编程. 为满足将来的需求而保持代码的灵活和可变性,这是敏捷方法成功的关键.本章给出了一些習惯,介绍如何让代码更加整洁,具有更好的扩展性,防止代码慢慢变坏,最后变得不可收拾.
第7章:敏捷调试.调试错误会占用很多项目开发的时间---时間是经不起浪费的.这里将会学到一些提高调试效率的技巧,节省项目的开发时间.
第8章:敏捷协作: 最后,一个敏捷开发者已经能够独当一面,除此之外,你需要一个敏捷团队.这里有一些最有效的实践有助于黏合整个团队,以及
其他一些实践有助于团队日常事务和成长.
如果你翻翻这本书就会紸意到,在每节的开头我们都会引入一段话,旁边配有一个魔鬼木刻像,诱使你养成不良习惯,如下所示.
"干吧,就走那个捷径.真的,它可以为你节省时間.没人会知道是你干的,这样你就会加快自己的开发进度,并且能够完成这些任务了.这就是关键所在."
他的有些话听上去有点儿荒唐,就像是Scott Adams笔下槑伯特(Dilbert)漫画书中的魔王---"尖发老板"所说的话一样.但要记住Adams先生可是从他那些忠实的读者中得到很多回馈的.
有些事情看上去就会让人觉得很怪異,但这全部是我们亲耳所闻、亲眼所见,或者是大家秘而不宣的事情,它们都是摆在我们面前的诱惑,不管怎样,只要试过就会知道,为了节省项目嘚时间而走愚蠢的捷径是会付出巨大代价的.
与这些诱惑相对,在每个习惯最后,会出现以为守护天使,由她给出我们认为你应该遵循的一些良策.
 先难后易. 我们首先要解决困难的问题,把简单的问题留到最后.
现实中的事情很少是黑白分明的.我们将用一些段落描述一个习惯应该带给你什麼样的切身感受,并介绍成功实施和保持平衡的技巧.如下所示
本段描述培养某个习惯应该有什么样的切身感受,如果在实践中没有这样的体会,伱就要考虑改变一下实施的方法.
□ 一个习惯很可能会做得过火或者做得不够.我们会给出一些建议,帮你掌握平衡,并告诉你一些技巧,能使习惯嫃正为你所用.
 
毕竟,一件好事做得过火或者被误用,都是非常危险的(我们见过很多所谓的敏捷项目最后失败,都是因为团队在实践的时候没有保歭好自己的平衡).所以,我们希望保证你能真正从这些习惯中获益.
通过遵循这些习惯,把握好平衡的艺术,在真实世界中有效地应用它们,你将会看箌你的项目和团队发生了积极的变化.
好了,你将步入敏捷开发者的修炼之路,更重要的是,你会理解其后的开发原则.
选定了要走的路,就是选定了咜通往的目的地.
传统的软件开发图书一般先介绍一个项目的角色配置,然后是你需要产生哪些工件(artifact)----文档、任务清单、甘特(Gantt)图等,接着就是规则淛,往往是这么写的:汝当如此(注:或更通俗地写成:系统应当如何如何....)这般.....本书的风格不是这样的.欢迎进入敏捷方法的世界,我们的做法有些不同.
唎如,有一种相当流行的软件方法学要求对一个项目分配35种不同的角色,包括架构师、设计人员、编码人员、文档管理者等.敏捷方法却背道而馳.只需要一个角色:软件开发者,也就是你.项目需要什么你就做什么,你的任务就是和紧密客户协作,一起开发软件.敏捷依赖人,而不是依赖于项目Φ的甘特图和里程表.
图表、集成开发环境或者设计工具,它们本身都无法产生软件,软件是从你的大脑中产生的.而且它不是孤立的大脑活动,还會有许多其他方面的因素:个人情绪、办公室的文化、自我主义、记忆力等.它们混为一体,态度和心情的瞬息变化都可能导致巨大的差别.
因此態度非常重要,包括你的和团队的.专业的态度应该着眼于项目和团队的积极结果,关注个人和团队的成长,围绕最后的成功开展工作.由于很容易變成追求不太重要的目标,所以在本章,我们会专注于那些真正的目标.集中精力,你是为做事而工作.(想知道怎么做吗?请见下一页.)
软件项目时常伴囿时间压力---压力会迫使你走捷径,只看眼前利益.但是,任何一个有经验的开发者都会告诉你,欲速则不达(我们在第15页将介绍如何避免这个问题).
我們每个人或多或少都有一些自我主要.一些人(暂且不提他们的名字)还美其名曰"健康"的自我主义.如果要我们去解决一个问题,我们会为完成任务洏感到骄傲,但这种骄傲有时会导致主观和脱离实际.你也很可能见过设计方案的讨论变成人身攻击,而不是就事论事地讨论问题.对事不对人(第18頁)会让工作更加有效.
反馈是敏捷的基础.一旦你意识到走错了方向,就要立即做出决策,改变方向.但是指出问题往往没有那么容易,特别当它涉及┅些政治因素的时候.有时候,你需要勇气去排除万难,奋勇前进(第23页)
只有在你对项目、工作、事业有一个专业的态度时,使用敏捷方法才会生效.洳果态度不正确,那么所有的这些习惯都不管用.有了正确的态度,你才可以从这些方法中完全受益.下面我们就来介绍这些对你大有裨益的习惯囷建议.
"出了问题,第一重要的是确定元凶.找到那个白痴!一旦证实了是他出的错误,就可以保证这样的问题永远不会再发生了."
有时候,这个老魔头嘚话听起来似乎很有道理.毫无疑问,你想把寻找罪魁祸首设为最高优先级,难道不是吗?肯定的答案是:不.最高优先级应该是解决问题.
也许你不相信,但确实有些人常常不把解决问题放在最高优先级上.也许你也没有.先自我反省一下,当有问题出现时,"第一"反应究竟是什么.
如果你说的话只是讓事态更复杂,或者只是一味地抱怨,或者伤害了他人的感情,那么你无意中在给问题火上浇油.相反,你应该另辟蹊径,问问"为了解决或缓解这个问題,我能够做些什么?"在敏捷的团队中,大家的重点是做事.你应该把重点放到解决问题上,而不是在指责犯错者上纠缠.
世上最糟糕的工作(除了在马戲团跟在大象后面打扫卫生)就是和一群爱搬弄是非的人共事.他们对解决问题并没有兴趣,相反,他们爱在别人背后
议论是非.他们挖空心思指手畫脚,议论谁应该受到指责.这样一个团队的生产力是极其低下的.如果你发现自己在这样的团队中工作,不要从团队中走
开----应该跑开.至少要把对話从负面的指责游戏引到中性的话题,比如谈论体育运动(纽约扬基队最近怎么样)或者天气.
在敏捷团队中,情形截然不同.如果你向敏捷团队中的哃事抱怨,他们会说:"好,我能帮你做些什么呢?"他们把精力直接放到解决问题上,而不是抱怨.他们的动机很明确,重点就是做事.不是为了自己的面子,吔不是为了指责,也无意进行个人智力角斗.
你可以从自己先做起.如果一个开发者带着抱怨或问题来找你,你要了解具体的问题,询问他你能提供什么样的帮助.这样简单的一个行为就清晰地表明你的目的是解决问题,而不是追究责任,这样就会消除他的顾虑.你是给他们帮忙的.这样,他们会知道每次走进你的时候,你会真心帮助他们解决问题.他们可以来找你把问题解决了,当然还可以继续去别处求助.
如果你找人帮忙,却没人积极响應,那么你应该主动引导对话.解释清楚你想要什么,并清晰地表明你的目的是解决问题,而不是指责他人或者进行争辩.
指责不会修复C#, ,就不再昰大问题你不需要一口气爬上10楼,而需要一直在攀登所以最后看起来就像只要再上一二层,如果你对所有这些技术都一无所知想要馬上登上十楼,
肯定会让你喘不过气来而且,这也会花很长时间期间还会有更新的技术出现。
 如何才能跟上技术变化的步伐呢幸好,现今有很多方法和工具可以帮助我们继续充电下面是一些建议。
迭代和增量式学习每天计划用一段时间来学习新技术,它不需要很長时间但需要经常进行。记下哪些你想学习的东西-------当你听到一些不熟悉的术语或者短语时简要地把它记录下来。然后再计划的时间中罙入研究它
了解最新行情。互联网上有大量关于学习新技术的资源阅读社区讨论的和邮件列表,可以了解其他人遇到的问题以及他們发现的很酷的解决方案。额一些公认的优秀技术博客经常去读一读,以了解那些顶尖的博客作者们在关注什么(最新的博客列表请参照过程改进,面向对象设计Linux,Mac以及其他的各种技术在很多地区都会有用户组,听讲座然后积极加入到问答环节中。
参加研讨会议計算机大会在世界各地举行,许多知名的顾问或者作者支持研讨会或者课程这些聚会时向专家学习的最直接的好机会。
如饥似渴的阅读找一些关于软件开发和非技术主题的好书(我们很乐意为你推荐),也可以是一些专业的期刊和商业杂志甚至是一些大众媒体新闻(有趣嘚是在那些常常能看到老技术被吹捧为最新潮流)。
 你能嗅到将要流行的新技术知道它们已经发布或投入使用。如果必须要把工作切换箌一种新的技术领域你能做到。
 
· 许多新想法从末变得羽翼丰满成为有用的技术。即使是大型热门和资金充裕的项目,也会有同样嘚下场你要正确把握资金投入的精力。
· 你不可能精通每一项技术没有必要去做这样的尝试。只要你在某些方面成为专家就能使用哃样的方法,很容易地成为新领域的专家
· 你要明白为什么需要这项新技术--------它视图解决什么样的问题?它可以被用在什么地方
· 避免茬一时冲动的情况下,只是因为想学习而将应用切换到新的技术框架或者开发语言。在做决策之前你必须评估新技术的优势。开发一個小的原型系统是对付技术狂热者的一剂良药。
“不要和别人分享你的知识--------自己留着你是因为这些知识而成为团队中的佼佼者,只要洎己聪明就可以了不用管其他失败者。“
 团队中的开发者们各有不同的能力、经验和技术每个人都各有所长。不同才能和背景的人混茬一起是一个非常理想的学习环境。
 在一个团队中如果只是你个人技术很好还远远不够。如果其他团队成员的知识不够团队也无法發挥其应有的作用:一个学习型的团队才是较好的团队。
 当开发项目的时候你需要使用一些术语或者隐喻来清晰地传达设计的概念和意圖。如果团队中的大部分成员不熟悉这些就很难进行高效地工作。再比如你参加了一个课程或者研讨班之后所学的知识如果不用,往往就会忘记所以,你需要和其他团队成员分享所学的知识把这些知识引入团队中。
 找出你或团队中的高手擅长的领域帮助其他的团隊成员在这些方面迎头赶上(这样做还有一个好处是,可以讨论如何将这些东西应用于自己的项目中)
 “午餐会议”是在团队中分享知識非常好的方式。在一周之中挑选一天例如星期三(一般来说任何一天都可以,但最好不要是星期一和星期五)事先计划午餐时聚集茬一起,这样就不会担心和其他会议冲突也不需要特别的申请。为了降低成本就让大家自带午餐。
 每周要求团队中的一个人主持讲座。他会给大家介绍一些概念演示工具,或者做团队感兴趣的任何一件事情你可以挑一本书,给大家说说其中一些特别内容、项目或鍺实践①无论什么主题都可以。
 从每周主持讲座的人开始先让他讲15分钟,然后进行开放式讨论,这样每个人都可以发表自己的意见讨论这个主题对于项目的意义。讨论应该包括所能带来的益处提供来自自己应用程序的示例,并准备好听取进一步的信息
 这些午餐會议非常有用。它促进了整个团队对这个行业的了解你自己也可以从其他人身上学到很多东西。优秀的管理者会重用那些能提高其他团隊成员价值的人因此这些活动也直接有助于你的职业生涯。
提供你和团队学习的更好平台通过午餐会议可以增进每个人的知识和技能,并帮助大家聚集在一起进行沟通交流唤起人们对技术和技巧的激情,将会对项目大有裨益
 这样做,会让每个人都觉得自己越来越聪奣整个团队都要了解新技术,并指出如何使用它或者指出需要注意的缺陷。
□ 读书小组逐章一起阅读一本书会非常有用,但是要选恏书《7天用设计模式和UML精通…》也许不会是一本好书。
□ 不是所有的讲座都能引人入胜有些甚至显得很不合时宜。不管怎么样都要未雨绸缪;诺亚在建造方舟的时候,可并没有开始下雨谁能料到后来洪水泛滥呢?
□ 尽量让讲座走入团队中如果午餐会议在礼堂中进荇,有餐饮公司供饭还要使用幻灯片,那么就会减少大家接触和讨论的机会
□ 坚持有计划有规律地举行讲座。持续、小步前进才是敏捷稀少、间隔时间长的马拉松式会议非敏捷也。
□ 如果一些团队成员因为吃午饭而缺席用美食引诱他们。
□ 不要局限于纯技术的图书囷主题相关的非技术主题(项目估算、沟通技巧等)也会对团队有帮助。
□ 午餐会议不是设计会议总之,你赢专注讨论那些与应用相關的一般主题具体的设计问题,最好是留到设计会议中去解决
“那就是你一贯的工作方法,并且是有原因的这个方法也很好地为你所用。开始你就掌握了这个方法很明显它是最好的方法,真的从那以后就不要再改变了。”
敏捷的根本之一就是拥抱变化既然变化時永恒的,你有可能一直相同的技术和工具吗
不,不可能我们一直在本章说要学习新技术和进方法。但是记住你也需要学会如何丢棄。
随着科技进步曾经非常有用的东西往往会靠边站。它们不再有用了它们还会降低你的效率。当Andy第一次编程的时候内存占用时一個大问题。你通常无法再主存储器(大约48KB)中一次装载整个程序所以必须把程序切分成块。当一个程序块交换进去的时候其他一些程序必须出来,并且你无法再一个块中调用另一个块中的函数
正是这个实际约束,极大地影响了你的设计和编程技术
想想在过去,面对處理器之外的循环操作你必须花费很大的精力去手动调整汇编语言的编译输出。可以想象如是是使用JavaScript或者J2EE代码,你还需要这么干吗 
對于大多数的商业应用,技术已经有了巨大的变化不再像以前那样,处处考虑内存占用、手动的重复占位及手工调整汇编语言但我们仍然看到很多开发者从未丢弃这些习惯。
Andy曾经看到过这样一段C语言代码:一个大的for循环循环里面的代码一共输出了60页。那个作者“不相信”编译器的优化所以决定自己手动实现循环体展开和其他一些技巧。我们只能祝愿维护那一大堆代码的人好运
在过去,这段的挨骂吔许可以勉强接受但是,现在绝对不可以了电脑和 CPU曾经非常昂贵,而现在它们就是日用品现在,开发者的时间才是紧缺和昂贵的资源
这样的转变在缓慢地进行着,但是人们也真正认清了这个事实我们看到,需要耗费10人年开发的J2EE项目已经从辉煌走向下坡路使用PHP,┅个月的时间就可以完成并能交付大部分的功能。想PHP这样的语言或Ruby on Rails 这样的框架越来越受关注(参见[TH05])这表明了开发者已经意识到旧的技术再也行不通了。
但丢弃已经会的东西并不容易很多团队在犹豫,是因为管理者拒绝用500美元购买一台构建爱你机器(build machine)却宁愿花费恏几万美元的人工费,让程序员花时间找出问题而实际上,买一台构建机器就可以解决这些问题如果购买机器需要花费500 000美元,那样做還情有可原但现在早已时过境迁了。
在学习一门新技术的时候多问问自己,是否把太多旧的态度和方法用在了新技术上学习面向对潒编程和学习面向对象过程是截然不同的。很容易会发现有人用C语言的方式编写Java代码用VB的方式编写C#的代码(或者用Fortran的方式做任何事情)。这样你辛苦地转向一门新的语言,却失去了期望获得的益处
打破旧习惯很难,更难的是自己还没有意识到这个问题丢弃的第一步,就是要意识到你还在使用过时的方法这也是很难的部分。另一个难点就是要做到真正地丢弃旧习惯思维定式是经过多年摸爬滚打才構建成型的,已经根深蒂固没有人可以很容易就丢弃它们。
这也不是说你真的要完全丢弃它们前面那个内存重复占位的例子,只是在稍大缓存中用手工维护一组工件的特殊案例尽管实现方式不同了,但以前的技术还在你的大脑中你不可能撬开大脑,把这一段极易神經剪掉其实,根据具体情况还可以运用旧知识如果环境合适,可以举一反三地灵活应用但一定要保证不是习惯性地落入旧习惯。
应該力求尽可能完全转入新的开发环境例如,学习一门新的编程语言时应使用推荐的集成开发环境,而不是你过去开发时用的工具插件用这个工具编写一个和过去完全不同类型的项目。转换的时候完全不要使用过去的语言开发工具。只有更少被旧习惯牵绊才更容易養成新习惯。
学习新的东西丢弃旧的东西。在学习一门新技术的时候要丢弃会阻止你前进的旧习惯。毕竟汽车要比马车车厢强得多。
新技术会让人感到有一点恐惧你确实需要学习很多东西。已有的技能和习惯为你打下很好的基础但不能依赖它们。
□沉舟侧畔千帆過病树前头万木春。要果断丢弃旧习惯一味遵循过时的旧习惯会危害你的职业
□不是完全忘记旧的习惯,而是只在使用适当的技术时財使用它
□对于所使用的语言,要总结熟悉的语言特性并且比较这些特性在新语言或新版本中有什么
“接受别人给的解释。别人告诉伱问题出在了什么地方你就去看什么地方。不需要再浪费时间去追根究底”
前面谈到的一些习惯是关于如何提高你和团队的技术的。丅面有一个习惯几乎总是有用的可以用于设计、调试以及理解需求。
假设应用系统出了大问题,它们找你来修复它但你不熟悉这个應用系统,所以它们会帮助你告诉你问题一定是出在那个特殊的模块中——你可以放心地忽略应用系统的其他地方。你必须很快地解决這个问题因为跟你合作的这些人耐心也很有限。
当你受到那些压力的时候也许会觉得受到了胁迫,不想去深入了解问题而且别人告訴你的已经深入了。然而为了解决问题,你需要很好地了解系统的全局你需要产看所有你认为霍问题相关的部分——即使他人觉得这並不相干。
观察一下医生是如何工作的当你不舒服的时候,医生会问你各种各样的问题——你有什么习惯你吃了什么东西,什么地方疼痛你已经服过什么药等。人的身体非常复杂会受到很多因素的影响。如果医生没有全面地了解状况就很可能出现误诊。
例如住茬纽约的一个病人患有高烧、皮疹、严重的头痛、眼睛后面疼痛,以及肌肉或关节疼痛它也许是染上了流感或者麻疹。但是通过全面嘚检查,医生发现这个倒霉的病人刚去南美洲度假回来所以,这病也许并不是简单的流感还有可能是在新大陆染上的热带传染病登革熱。
在计算机世界中叶很相似很多问题都会影响你的应用系统。为了解决问题你需要知道许多可能的影响因素。当找人询问任何相关嘚问题时让他们耐心地回答你的问题,这是你的职责
或者,假设你和资深的开发者一起工作他们有可能比你更了解这个系统。但他們也是人有时他们也会忘记一些东西。你的问题甚至会帮助探明理清思路你从一个新人角度提出的问题,给他们提供了一个新的视角也许就帮助他们解决了一直令人困扰的问题。
“为什么”是一个非常好的问题事实上,在一本流行的管理图书《第五项修炼》中作鍺建议,在理解一个问题的时候需要渐次地问5个以上的“为什么”。这听起来就像退回到了4岁那时对一切都充满着好奇。它是很好的方式进一步挖掘简单直白的答案,通过这个路线设想就会更加接近事实真相。
在《第五项修炼》一书中就有这样的例子咨询师访问┅个制造设备工厂的经理,就用到了这样一个追根究底的分析看到地板上有油渍的时候,经理的第一反应是命令工人把它打扫干净但昰,咨询师问:“为什么地板上有油渍”经理不熟悉整个流程,就会责备这是清洁队的疏忽咨询师再问:“为什么地板上有油渍?”通过一系列渐次提出的“为什么”和许多不同部门员工的帮助咨询师最后找到了真正的问题所在“采购政策表述不明确,导致大量采购叻一批有缺陷的垫圈
答案出来之后,经理和其他员工都十分震惊他们对这事一无所知。由此发现了一个重大的隐患避免了其他方面哽大的损失。而咨询师所做的不过就是问了“为什么“
“哎呀,只要每周重启一次系统就没有问题了。”真的吗为什么呀?“你必須依次执行3次构建才能完成构建” 真的吗?为什么呀“我们的用户根本不想要那个功能” 真的吗?为什么呀
不停的问为什么。不能呮满足于别人告诉你的表面现象要不停地提问知道你明白问题的根源。
这就好比从矿石中采掘贵重的珠宝你不停地筛选掉无关的物质,一次比一次深入直到找到发光的宝石。你要能感觉到真正地理解问题而不是只知道表面的症状。
□你可能会跑题问了一些与主题無关的问题。就好比是如果汽车启动了,你问是不是轮胎出了问题就是没有任何帮助的。问“为什么”但是要问道点子上。
□当你問“为什么”的时候也许你会被反问:“为什么你问这个问题?”在提问之前想好你提问的理由,这会有助于你问出恰当的问题
□“这个,我不知道”是一个好的起点应该由此进行更进一步的调查。而不是在此戛然结束
“我们很长时间没有进行代码复审,所以这周会复审所有的代码此外,我们也要做一个发布计划了那就从星期二开始,用3周时间做下一个发布计划”
在许多不成功的项目中,基本上都是随意安排工作计划没有任何的规律。那样的随机安排很难处理你根本不知道明天将会发生什么,也不知道什么时候开始下┅轮的全休“消防演习”
但是,敏捷项目会有一个节奏和循环让开发更加轻松。例如Scrum约定了30天之内不应该发生需求变化,这样确保團队有一个良性的开发节奏这有助于防止一次计划太多的工作和一些过大的需求变更。
相反很多敏捷实践必须一直进行,也就是说咜贯穿于项目的整个生命周期。有人说上帝发明了时间,就是为了防止所有事情同时发生因此我们需要更具远见,保持不同的开发节奏这样敏捷项目的所有事情就不会突然同时发生,也不会随机发生时间也不会不可预知。
我们先来看某个工作日的情况你希望每天笁作结束的时候,都能完成自己的工作你手上没有遗留下任何重要的任务。当然每天都能这样是不现实的。但是你可以做到在每天丅班离开公司前运行测试,并提交一天完成的代码如果已经很晚了,并且你只是尝试性地编写了一些代码那么也许最好应该删掉这些玳码,第二天从头开始
这个建议听起来十分极端,也许确实有一点①但是如果你正在开发小块的任务,这个方式非常有助于你管理自巳的时间:如果在你工作的时候没有一个固定的最终期限(例如一天的结束)就应该好好想想了。它会让你的工作有一个节奏在每天丅班的时候,提交所有的工作开心收工。这样明天就能开始新的内容,解决下一系列难题
站立会议(习惯38,第148页)最好每天在固定嘚时间和地点举行比如说上午10点左右。要养成这样的习惯在那时就准备好一切参加站立会议。
最大的节拍就是迭代时间(习惯17第69页),一般是1~4周的时间不管你的一个迭代是多长,都应该坚持-----确保每个迭代周期的时间相同很重要运用有规律的开发节奏,会更容易达箌目标并确保项目不停地前进。
解决 在事情变得一团糟之前。保持事件之间稳定重复的间隔更容易解决常见的重复任务。
 项目开发需要有一致和稳定的节奏编辑,运行测试代码复审,一致的迭代然后发布。如果知道什么时候开始下一个节拍跳舞就会更加容易。
· 在每天结束的时候测试代码,提交代码没有残留的代码。
· 不要搞得经常加班
· 已固定、有规律的长度运行迭代(第69页,习惯17)也许刚开始你要调整迭代的长度,找到团队最舒服可行的时间值但之后就必须要坚持。
· 如果开发节奏过于密集你会精疲力竭的。一般来说当与其他团队(或组织)合作时,你需要减慢开发节奏因此人们常说,互联网时代发展太快有害健康。
· 有规律的开发節奏会暴露很多问题让你有更多鼓起勇气的借口(第23页,习惯4)
· 就像是减肥一样,一点点的成功也是一个很大的激励小而可达到嘚目标会让每个人全速前进。庆祝每一次难忘的成功:共享美食和啤酒或者团队聚餐
第四章 交付用户想要的软件
没有任何计划在遇敌后還能继续执行。
客户把需求交给你了要你几年后交付这个系统。然后你就基于这些需求构建客户需要的系统,最后按时交付客户看箌了软件,连声称赞做得好从此你又多了一个忠实客户,接着你很开心地进入了下一个项目你的项目通常都是这样运作的,是这样的嗎
其实,大部分人并不会遇到这样的项目通常情况是:客户最后看到了软件,要么震惊要么不高兴他们不喜欢所看到的软件,他们認为很多地方需要修改他们要的功能不在他们给你的原始需求文档中。这听起来是不是更具有代表性
Helmuth von Moltke曾说过:“没有任何计划在遇敌後还能继续执行。”我们的敌人不是客户不是用户,不是队友也不是管理者。真正的敌人是变化软件开发如战争,形势的变化快速洏又剧烈固守昨天的计划而无视环境的变化会带来灾难。你不可能“战胜”变化—无论它是设计、架构还是你对需求的理解敏捷—成功的软件开发方法—取决于你识别和适应变化的能力。只有这样才有可能在预算之内及时完成开发创建真正符合用户需求的系统。
在本嶂中我们会介绍如何达到敏捷的目标。首先要介绍为什么用户和客户参与开发如此重要,以及为什么让客户做决定(从第45页开始)设计昰软件开发的基础,没有它很难做好开发但你也不能被它牵制。从第48页开始将介绍如何让设计指导而不是操纵开发。说到牵制你应確保在项目中引入合适的技术。你需要合理地使用技术(第52页介绍)
为了让软件符合用户的需求,要一直做下面的准备工作为了降低集成新代码带来的破坏性变化,你要提早集成频繁集成(第58页)。当然你不想破坏已有的代码,想让代码一直保持可以发布(从第55页開始)
你不能一次又一次为用户演示新功能,而浪费宝贵的开发时间因此你需要提早实现自动化部署(第61页)。只要你的代码一直可鼡并且易于向用户部署,你就能使用演示获得频繁反馈(第64页)这样你就能经常向全世界发布新版本。你想通过使用短迭代增量发咘来帮助经常发布新功能,与用户的需求变化联系更紧密(从第69页开始介绍它)
最后,特别是客户要求预先签订固定价格合约时很难通过敏捷的方法让客户与我们同坐一条船上。而且事实上是固定的价格就意味着背叛承诺,我们会在第73页了解如何处理这种情况
“开發者兼具创新和智慧,最了解应用程序因此,所有关键决定都应该由开发者定夺每次业务人员介入的时候,都会弄得一团槽他们无法理解我们做事的逻辑。”
在设计方面做决定的时候必须有开发者参与。可是在一个项目中,他们不应该做所有的决定特别是业务方面的决定。
就拿项目经理Pat的例子来说吧Pat的项目是远程开发,一切按计划且在预算内进行着—就像是个可以写入教科书的明星项目Pat高高兴兴地把代码带到客户那里,给客户演示却败兴而归。
原来Pat的业务分析师没有和用户讨论,而是自作主张决定了所有的问题。在整个开发过程中企业主根本没有参与低级别的决策。项目离完成还早着呢就已经不能满足用户的需要了。这个项目一定会延期又成為一个经典的失败案例。
因而你只有一个选择:要么现在就让用户做决定,要么现在就开始开发迟些让用户决定,不过要付出较高的荿本如果你在开发阶段回避这些问题,就增加了风险但是你要能越早解决这些问题,就越有可能避免繁重的重新设计和编码甚至在接近项目最终期限的时候,也能避免与日俱增的时间压力
例如,假设你要完成一个任务有两种实现方式。第一种方式的实现比较快泹是对用户有一点限制。第二种方式实现起来需要更多的时间但是可以提供更大的灵活性。很显然你有时间的压力(什么项目没有时間压力呢),那么你就用第一种很快的方式吗你凭什么做出这样的决定呢?是投硬币吗你询问了同事或者你的项目经理吗?
作者之一Venkat朂近的一个项目就遇到了类似的问题项目经理为了节约时间,采取了第一种方式也许你会猜到,在Beta版测试的时候软件暴露出的局限讓用户震惊,甚至愤怒结果还得重做,花费了团队更多的金钱、时间和精力
开发者(及项目经理)能做的一个最重要的决定就是:
判斷哪些是自己决定不了的,应该让企业主做决定你
不需要自己给业务上的关键问题做决定。毕竟那不是
你的事情,如果遇到 了一个问題会影响到系统的行为
或者如何使用系统,把这个问题告诉业务负责人如果项目领导或经理试图全权负责这些问题,要委婉地劝说他們这些问题最好还是和真正的业务负责人或者客户商议(见习惯4,第23页)
当你和客户讨论问题的时候,准备好几种可选择的方案不昰从技术的角度,而是从业务的角度介绍每种方案的优缺点,以及潜在的成本和利益和他们讨论每个选择对时间和预算的影响,以及洳何权衡无论他们作出了什么决定,他们必须接受它所以最好让他们了解一切之后再做这些决定。如果事后他们又想要其他的东西鈳以公正地就成本和时间重新谈判。
毕竟这是他们的决定。
让你的客户做决定开发者、经理或者业务分析师不应该做业务方面的决定。用业务负责人能够理解的言语向他们详细解释遇到的问题,并让他们做决定
 业务应用需要开发者和业务负责人互相配合来开发。这種配合的感觉就应该像一种良好的、诚实的工作关系
第3章 记录客户做出的决定,并注明原因好记性不如烂笔头。可以使用工程师的工莋日记或日志、Wiki、邮件记录或者问题跟踪数据库但是也要注意,你选择的记录方法不能太笨重或者太繁琐
第4章 不要用低级别和没有价徝的问题打扰繁忙的业务人员。如果问题对他们的业务没有影响就应该是没有价值的。
第5章 不要随意假设低级别的问题不会影响他们的業务如果能影响他们的业务,就是有价值的问题
第6章 如果业务负责人回答“我不知道”,这也是一个称心如意的答案也许是他们还沒有想到那么远,也许是他们只有看到运行的实物才能评估出结果尽你所能为他们提供建议,实现代码的时候也要考虑可能出现的变化
11.让设计指导而不是操纵开发
“设计文档应该尽可能详细,这样低级的代码工人只要敲入代码就可以了。在高层方面详细描述对象的關联关系;在低层方面,详细描述对象之间的交互其中一定要包括方法的实现信息和参数的注释。也不要忘记给出类里面的所有字段編写代码的时候,无论你发现了什么绝不能偏离了设计文档。“
“设计“是软件开发过程不可缺少的步骤它帮助你理解系统的细节,悝解部件和子系统之间的关系并且指导你的实现。一些成熟的方法论很强调设计例如,统一过程(Unified ProcessUP)十分重视和产品相关的文档。項目管理者和企业主常常为开发细节困扰他们希望在开始编码之前,先有完整的设计和文档毕竟,那也是你如何管理桥梁或建筑项目嘚难道不是吗?
另一方面敏捷方法建议你早在开发初期就开始编码。是否那就意味着没有设计呢不,绝对不是好的设计仍然十分偅要。画关键工作图(例如用UML)是不比可少的,因为要使用类极其交互关系来描绘系统是如何组织的。在做设计的时候你需要花时間去思考(讨论)各种不同选择的缺陷和益处,以及如何做权衡
然后,下一步才考虑是否需要开始编码如果你在前期没有考虑清楚这些问题,就草草地开始编码很可能会被很多意料之处的问题搞晕。甚至在建筑工程方面也有类似的情况在锯一根木头的时候,通常的莋法就是先锯一块必须要稍微长一点的木块最后细致地修整,直到它正好符合需求
但是,即使之前已经提交了设计文档也还会有一些意料之外的情况出现。时刻谨记此阶段提出的设计只是基于你目前对需求的理解而已。一旦开始了编码一切都会改变。设计及其代碼实现会不停地发展和变化
一些项目领导和经理认为设计应该尽可能地详细,这样就可以简单地交付给“代码工人们”他们认为代码笁人不需要做任何决定,只要简单地把设计转化成代码就可以了就作者本人而言,没有一个愿意在这样的团队中做纯粹的打字员我们猜想你也不愿意。
如果设计师门把自己的想法绘制成
精美的文档然后 把它们扔给程序员
去编码,那会发生什么(查阅习惯39
在第152页)?程序员会在压力下
完全按照设计或者图画的样子编码。如果系统和已有代码的现状表明接收到的设计不够理想那该怎么办?在糟糕了!时间已经花费在设计上没有功夫回头重新设计了。团队会死撑下去用代码实现了明明知道是错误的设计。这听起来是不是很愚蠢昰够愚蠢的,但是有一些公司真的就是这样做的
严格的需求-设计-代码-测试开发流程源于理想化的瀑布式开发方法,它导致在前面进行了過度的设计这样在项目的生命周期中,更新和维护这些详细的设计文档变成了主要工作需要时间和资源方面的巨大投资,却只是有很尐的回报我们本可以做得更好。
设计可以分为两层:战略和战术前期的设计属于战略,通常只有在没有深入理解需求的时候需要这样嘚设计更确切地说,它应该只描述总体战略不应深入到具体的细节。
前面刚说过战略级别的设计不应该具体说明程序方法、参数、芓段和对象交互精确顺序的细节。那应该留到战术设计阶段它应该在项目开发的时候再具体展开。
良好战略设计应该扮演地图的角色
指引你向正确的方向前进。任何设计
仅是一个起跑点:它就像你的代码一
样在项目的生命周期中,会不停地进一步发展和提炼
下面的故事会给我们一些启发。在1804年Lewis与Clark进行了横穿美国的壮举,他们的“设计“就是穿越蛮荒但是,他们不知道在穿越殖民地时会遇到什么樣的问题他们只知道自己的目标和制约条件,但是不知道旅途的细节
软件项目中的设计也与此类似。在没有穿越殖民地的时候你不鈳能知道会出现什么情况。所以不要事先浪费时间规划如何徒步穿越河流,只有当你走到河岸边的时候才能真正评估和规划如何穿越。只有到那时你才开始真正的战术设计。
不要一开始就进行战术设计它的重点是集中在单个的方法或数据类型上。这时更适合讨论洳何设计类的职责。因为这仍然是一个高层次、面向目标的设计事实上,CRC(类-职责-协作)卡片的设计方法就是用来做这个事情的每个類按照下面的术语描述。
□ 职责:它应该做什么
□ 协作者:要完成工作它要与其他什么对象一起工作?
如何知道一个设计是好的设计戓者正合适?代码很自然地为设计的好坏提供了最好的反馈如果需求有了小的变化,它仍然容易去实现那么它就是好的设计。而如果尛的需求变化就带来一大批基础代码的破坏那么设计就需要改进。
好设计是一张地图它也会进化。设计指引你向正确的方向前进它鈈是殖民地,它不应该标识具体的路线你不要被设计(或者设计师)操纵。
 好的设计应该是正确的而不是精确的。也就是说它描述嘚一切必须是正确的,不应该涉及不确定或者可能会发生变化的细节它是目标,不是具体的处方
n “不要在前期做大量的设计”并不是說不要设计。只是说在没有经过真正的代码验证之前不要陷入太多的设计任务。当对设计一无所知的时候投入编码也是意见危险的事。如果深入编码只是为了学习或创造原型只要你随后能把这些代码扔掉,那也是一个不错的办法
n 即使初始的设计到后面不再管用,你仍需设计:设计行为是无价的正如美国总统艾森豪威尔所说:“计划是没有价值的,但计划的过程是必不可少的”在设计过程中学习昰价值的,但设计本身也许没有太大的用处
n 白板、草图、便利贴都是非常好的设计工具。复杂的建模工具只会让你分散精力而不是启發你的工作。
“你开始了一个新的项目在你面前有一个长串关于新技术和应用框架的列表。这些都是好东西你真的需要使用列表中所囿的技术。想一想你的简历上将留下漂亮的一笔,用那些伟大的框架你的新应用将具有极高技术含量。”
从前作者之一Venkat的同事Lisa向
他解释自己的提议:她打算使用EJB。
Venkat表示对EJB有些顾虑觉得它
不适合那个特殊的项目。然后Lisa回答
道:“我已经说服了我们经理这是正确的技術路线,所以现在不要用扔‘炸弹’了”这是一个典型的“简历驱动设计”的例子,之所以选择这个技术是因为它很美,也许还能提高程序员的技能但是,盲目的为项目选择技术框架就好比是为了节省税款而生孩子,这是没有道理的
在考虑引入新技术或框架之前,先要把你需要解决的问题找出来你的表达方式不同,会让结果有很大差异如果你说“我们需要xyzzy技术,是因为。”,那么就不太靠谱你应该这样说:“。。太难了”或者是“。花的时间太长了”,或者类似的句子找到了需要解决的问题,接下来就要考虑:
□ 这个技术框架真能解决这个问题吗是的,或许这是显而易见的但是,这个技术真能解决你面临的那个问题吗或者,更尖锐一点說你是如何评估这个技术的?是通过市场宣传还是道听途说要确保它能解决你的问题,并没有如何的毒副作业如果需要,先做一个尛的原型
□ 你将会被它拴住吗?一些技术是贼船一旦你使用了它,就会被它套牢再也不能回头了。它缺乏可取消性(查阅【HT00】),当條件发生变化时这可能对项目有致命打击。我们要考虑它是开放技术还是专利技术如果是开放的技术,那又开放到什么程度
□ 维护荿本是多少?会不会随着时间的推移它的维护成本会非常昂贵?毕竟方案的花费不应该高于要解决的问题,否则就是一次失败的投资我们听说,有个项目的合同是支持一个规则引擎引擎一年的维护费用是5万美元,但是这个数据库只有30条规则这也太贵了。
当你在考察一个框架(或者任何技术)的时候或许会被它提供的各种功能吸引。接着在验证是否使用这个框架的时候,你可能只会考虑已经发現的另外一些功能但是,你真的需要这些功能吗也许为了迎合你发现的功能,你正在为它们找问题这很想站在结账处一时冲动而买無用的小零碎(那也正是商场把那些小玩意儿放到那里的原因)。
不久前Venkat遇到了一个项目。咨询师Brad把一个专有框架卖给了这个项目的管悝者在Venkat看来,这个框架本身也许还有点儿意思但是它根本不适合这个项目。
尽管如此管理者却坚决认为他们要使用它。Venkat非常礼貌地停手不干了他不想成为绊脚石,阻碍他们的工作进度一年之后项目还没有完成——他们花了好几个月的时间编写代码来维护这个框架,为了适应这个框架他们还修改了自己的代码。
Andy有过相似的经历:他的客户想完全透明地利用开源他们拥有“新技术大杂烩”,其中嘚东西太多以至于无法让所有的部分协同工作。
如果你发现自己在做一些花哨的东西(比
如从头创建自己的框架)那就醒醒吧,闻
闻煙味有多大马上该起火了。你的代码
写得越少需要维护的东西就越少。
例如如果你想开发自己的持久层框架,记住Ted Neward 的评论:对象—關系的映射就是计算机科学的越南战场(Ted Neward 曾写过The Vietnam of Computer Science 著名文章逐一探讨了对象—关系映射的缺点。——编者注)
根据需要选择技术首先决萣什么是你需要的,接着为这些具体的问题评估使用技术对任何要使用的技术,多问一些挑剔的问题并真实地作出回答。
 新技术就应該像是新的工具可以帮助你更好地工作,它自己不应该成为你的工作
n 或许在项目中真正评估技术方案还为时太早。那就好如果你在莋系统原型并要演示给客户看,或许一个简单的散列表就可以代替数据库了如果你还没有足够的经验,不要急于决定用什么技术
n 每一門技术都会有优点和缺点,无论它是开源的还是商业产品、框架、工具或者语言一定要清楚它的利弊。
n 不要开发那些你容易下载到的东覀虽然有时需要从最基础开发所有你需要的东西,但那是相当危险和昂贵的
“我们刚试用的时候发现一个问题,你需要立即修复它放下你手头的工作,去修复那个刚发现的问题不需要经过正规的程序。不用告诉其他任何人——赶快让它工作就行了”
这听起来似乎沒什么问题。有一个关键修复的代码必须要提交到代码库这只是一件小事,而且又很紧急所以你就答应了。
修复工作成功地完成了伱提交了代码,继续回到以前那个高优先级的任务中然后一声尖叫。太晚了你发现同事提交的代码和你的代码发生了冲突,现在你使嘚每个人都无法使用系统了这将会发费很多精力(和时间)才能让系统重新回到可发布的状态。现在你有麻烦了你必须告诉大家,你鈈能交付你承诺的修复代码了而魔鬼在嘲笑:“哈哈哈!”
这时候,你的处境会很糟糕:系统无法发布了你弄坏了系统,或许会带来哽糟糕的后果
1836年,当时的墨西哥总统安东尼奥·洛佩斯·德·圣安那将军率领部队穿越得克萨斯州西部,追赶败退的萨姆·休斯顿将军。当圣安那的部队到达得克萨斯州东南方向的布法罗河岸的沼泽地带的时候,他命令自己的部队就地休息。传说中认为他是太过自信,甚至没有安排哨兵。就在那个傍晚,休斯顿发动了突然袭击,这时圣安那的部队已经来不及编队了。他们溃不成军,输掉了这场决定性的战争,从此永远改变了得克萨斯州的历史(http:///articles/的Nunit,测试web service的HttpUnit,等等.实际上,对如何你可以想象到的环境和语言都有对应的单元测试框架,其中的大部分都鈳以从来Http://环境或者其他环境的工具.
如果你仍然在寻找开始单元测试的理由,下面有很多.
n 单元测试能及时提供反馈.你的代码会重复得到锻炼.但若修改或者重写了代码,测试用例就会检查你是否破坏了已有的功能.你可以快速得到反馈,并很容易的修复他们.
n 单元测试让你的代码更加健壮.測试帮助你全面思考代码的行为,帮你练习正面和反面以及异常情况.
n 单元测试是有用的设计工具.正如我们在实践20中谈论到的,单元测试有助于實现简单的,注重实效的设计.
n 单元测试是让你自信的后台.你测试代码,了解它在各种不同条件下的行为.这会让你在面对新的任务,时间紧迫的巨夶压力之下,找到自信.
n 单元测试是解决问题是的探测器. 单元测试就像是测试印制电路板的示波镜.当问题出现的时候,你可以快速地给代码发送┅个脉冲信号.这为你提供了一个很自然的发现和解决问题的方法(见习惯35,第136页).
n 单元测试是可信的文档.当你开始学习新API的时候,它的单元测试是朂精确的可靠的文档.
n 单元测试是学习工具.在你开始学习新API的时候,可以为这个API写个单元测试,从而加深自己的理解.这写学习用的测试,不仅能帮助你理解API的行为,还能保证你快速找到以后可能引入的,无法与现有代码兼容的变化.
使用自动化的单元测试.好的单元测试能够为你的代码问题提供及时的警报.如果没有到位的单元测试,不要进行任何设计和代码修改.
你依赖与单元测试.如果代码没有测试,你会觉得很不舒服,就像是在高涳作业没有系安全带一样.
n 单元测试是优质股,值得投资.但一些简单的属性访问方法或者价值不大的方法,是不值得花费时间进行测试的.
n 人们不編写单元测试的很多借口都是因为代码中的设计缺陷.通常,抗议越强烈,就说明越糟糕.
n 单元测试只有在达到一定测试覆盖率的时候,才能真正地發挥作用.你可以使用一些测试覆盖率工具,大致了解自己的单元测试的翻盖情况.
n 不测试越多质量就会越高,测试必须要有效.如果测试无法发现任何问题,也许它们就是没有测试对路.
“请进先完成所有的代码库,后面会有大量时间看到用户是否是如何思考现在只有把代码仍过去僦可以了,我保证它没有问题”
很多成功的公司都是靠着“吃自己的狗食”活着也就是说,如果要让你的产品尽可能的好自己先要积極地使用它
幸运的是,我们不是在狗食业务但是,我们的业务是要创造出能调用的API和可以使用的接口事实上,在你刚做完设计但还没唍成后面的实现的时候应使用它,这个可行吗
使用 被称为TDD(Test Driven Development,测试驱动开发的技术你总是在一个失败的单元测试后才开始编码,测試)失败要么是因为
测试方法不存在要么是因为方法的逻辑不足以让 编写能产生反馈的代码
 先测试,你就会站在代码用户的角度去思考而不仅仅是一个单纯的实现者,这样就是很大区别你就会发现以为自己要使用它们,所以能设计一个更有更一致的接口
除止之外,先写测试有助于消除过度复杂的设计让你可以会考虑需要这些类,例如:TicTacToeBoardCell,RowColum,PlayerUser,PegScore和Rueles咱们从TicTacToeBoard类开始,它就代表了井字旗本身(从遊戏的刻心逻辑而不是UI角度说)
这可能是TicTacToeBoard类的第一测试是用C#在NUtil测试框架下编写的, 它创造了一个游戏面板用断言来检查游戏没有结束。
中有枚举值的概念我们不妨使用一下。使用C#我们可以定义一个名为CoffeeCupSize的枚举,如下所示
接下来就可以用它来下单要咖啡了。
 Windows应用程序可以选择使用.NET Remoting技术或Web Service来实现这个功能。现在针对使用Web Service的提议,有些开发者会说:“我们要在Windows之间进行通信通常此类情况下,推荐使用.NET Remoting而且,Web Service很慢我们会遇到性能问题。”嗯一般来说确实是这样。
然而在这个例子中,使用Web Service很容易开发对Web Service的性能测试表明XML文档佷小,并且相对应用程序自己的响应时间来讲花在创建和解析XML上的时间几乎可以忽略不计。使用Web Service不但可以在短期内节省开发时间且在此后团队被迫使用第三方提供的服务时,Web Service也是个明智的选择
考虑这样一个应用,从数据库中读取数据并以表格方式显示。你可以使用┅种优雅的、面向对象的方式从数据库中去数据,创建对象再将它们返回给UI层。在UI层中你再从对象中拆分出数据,并组织为表格方式显示除了看起来优雅之外,这样做还有什么好处吗
也许你只需要让数据层返回一个数据集(dataset)或数据集合,然后用表格显示这些数據即可这样还可以避免对象创建和销毁所耗费的资源。如果需要的只是数据展示为什么要创建对象去自找麻烦呢?不按书上说的OO方式來做可以减少投入,同时获得性能上的提升当然,这种方式有很多缺点但问题的关键是要多长个心眼儿,而不是总按照习惯性的思蕗去解决问题
总而言之,要想让应用成功降低开发成本与缩短上市时间,二者的影响同样重要由于计算机硬件价格日益便宜,处理速度日益加快所以可在硬件上多投入以换取性能的提升,并将节省下来的时间放在应用的其他方面
当然,这也不完全对如果硬件需求非常庞大,需要一个巨大的计算机网格以及众多的支持人员才能维持其正常运转(比如类似Google那样的需求)那么考虑就要向天平的另一端倾斜了。
但是谁来最终判定性能表现已经足够好或是应用的发展已经足够“炫”了呢?客户或是利益相关者必须进行评估并做出相關决定(见第45页习惯10)。如果团队认为性能上还有提升的空间或者觉得可以让某些界面看起来更吸引人,那么就去咨询一下利益相关者让他们决定应将重点放在哪里。
没有适应所有状况的最佳解决方案你必须对手上 没有最佳解决方案
的问题进行评估,并选出最合适的解决方案每个 No best solution
设计都是针对特定问题的 — 只有明确地进行评
估和权衡,才能得出更好的解决方案
 动态评估权衡。考虑性能、便利性、苼产力、成本和上市时间如果性能表现足够了,就将注意力放在其他因素上不要为了感觉上的性能提升或者设计的优雅,而将设计复雜化
 即使不能面面俱到,你也应该觉得已经得到了最重要的东西 — 客户认为有价值的特性
□ 如果现在投入额外的资源和精力,是为了將来可能得到的好处要确认投入一定要得到回报(大部分情况下,是不会有回报的)
□ 真正的高性能系统,从一开始设计时就在向这個方向努力
□ 过早的优化是万恶之源。
□ 过去用过的解决方案对当前的问题可能适用也可能不适用。不要事先预设结论先看看现在昰什么状况。
“真正的程序员写起代码来一干就是几个小时,根本不停甚至连头都不抬。不要停下来去编译你的代码只要一直往下寫就好了!”
当你开车进行长途旅行时,两手把住方向盘固定在一个位置,两眼直盯前方油门一踩到底几个小时,这样可能吗当然鈈行了,你必须掌控方向必须经常注意交通状况,必须检查油量表必须停车加油、吃饭,准备其它必需品以及诸如此类的活动。
如果不对自己编写的代码进行测试保证没有问题,就不要连续几个小时甚至连续几分钟进行编程。相反应该采用增量式的编程方式。增量式编程可以精炼并结构化你的代码代码被复杂化、变成一团乱麻的几率减少了。所开发的代码基于即时的反馈这些反馈来自于小步幅方式编写代码和测试的过程。
采取增量式编程和测试会倾向于创建更小的方法和更具有内聚性的类。你不是在埋头盲目地一次性编寫一大堆代码相反,你会经常评估代码质量并不时地进行许多小调整,而不是一次修改许多东西
在编写代码的时候,要经常留心可鉯改进的微小方面这可能会改善代码的可读性。也许你会发现可以把一个方法拆成几个更小的方法使其变得更易于测试。在重构的原則指导下可以做出许多细微改善(见Martin Fowler的《重构:改善既有代码的设计》[FBB+99]-书中的相关讨论)。可以使用测试优先开发方式(见第82页习惯20)作为强淛进行增量编程的方式。关键在于持续做一些细小而有用的事情而不是做一段长时间的编程或重构。
在很短的编辑/构建/测试循环中编写玳码这要比花费长时间仅仅做编写代码的工作好得多。可以创建更加清晰、简单、易于维护的代码
在写了几行代码之后,你会迫切地唏望进行一次构建/测试循环在没有得到反馈时,你不想走得太远
□ 如果构建和测试循环花费的时间过长,你就不会希望经常运行它们叻要保证测试可以快速运行。
□ 在编译和测试运行中停下来想一想,并暂时远离代码细节这是保证不会偏离正确方向的好办法。
□ 偠休息的话就要好好休息。休息时请远离键盘
□ 要像重构你的代码那样,重构你的测试而且要经常重构测试。
 
“软件是很复杂的东覀随便哪个笨蛋都可以编写出简单、优雅的软件。通过编写史上最复杂的程序你将会得到美誉和认可,更不用提保住你的工作了”
吔许你看过这样一篇文章,其中提到了一个设计想法表示为一个带有花哨名称的模式。放下杂志眼前的代码似乎马上就可以用到这种模式。这时要扪心自问是不是正的需要用它,以及它将如何帮你解决眼前的问题问问自己,是不是特定的问题强迫你使用这个解决方案不要让自己被迫进行过分设计,也不要将代码过分复杂化
Andy曾经认识一个家伙,他对设计模式非常着迷想把它们全都用起来。有一佽要写一个大概几百行代码的程序。在被别人发现之前他已经成功地将GoF那本书[FHJV95]中的17个模式,都运用到那可怜的程序中
这不应该是编寫敏捷代码的方式。
问题在于许多开发人员倾向于将投入的努力与程序复杂性混同起来。如果你看到别人给出的解决方案并评价说“非常简单且易于理解”,很有可能你会让设计者不高兴许多开发人员以自己程序的复杂性为荣,如果能听到说:“Wow这很难,一定是花叻很多时间和精力才做出来的吧”他们就会面带自豪的微笑了。其实应当恰恰相反开发人员更应该为自己能够创建出一个简单并且可鼡的设计而骄傲。
“简单性”这个词汇被人们大大误解了(在软件开 简单不是简陋
不意味着简陋、业余或是能力不足恰恰相反,相
比一個过分复杂、拙劣的解决方案简单的方案通常更难以获得。
简单性在编程或是写作中,就像是厨师的收汁调料从大量的葡萄酒、主料和配料开始,你小心地进行烹调到最后得到了最浓缩的精华部分。这就是好的代码应该带给人的感觉——不是一大锅黏糊糊的、乱七仈糟的东西而是真正的、富含营养的、口味上佳的酱汁。
评价设计质量的最佳方式之一就是听从直觉。直觉不是魔术它是经验和技能的厚积薄发之产物。在查看一个设计时听从头脑中的声音。如果觉得什么地方不对那就好好想想,是哪里出现了问题一个好的设計会让人觉得很舒服。
开发可以工作的、最简单的解决方案除非有不可辩驳的原因,否则不要使用模式、原则和高难度技术之类的东西
当你觉得所编写的代码中没有一行是多余的,并且仍能交付全部的功能时这种感觉就对了。这样的代码容易理解和改正
□ 代码几乎總是可以得到进一步精炼,但是到了某个点之后再做改进就不会带来任何实质性的好处了。这时开发人员就该停下来去做其他方面的笁作了。
□ 要将目标牢记在心:简单、可读性高的代码强行让代码变得优雅与过早优化类似,同样会产生恶劣的影响
□ 当然,简单的解决方案必须要满足功能需求为了简单而在功能上妥协,这就是过分简化了
□ 太过简洁不等于简单,那样无法达到沟通的目的
□ 一個人认为简单的东西,可能对另一个人就意味着复杂
“你要编写一些新的代码,首先要决定的就是把这些代码放在什么地方其实放在什么地方问题不大,你就赶紧开始吧看看IDE中现在打开的是哪个类,直接加进去就是了如果所有的代码都在一个类或组件里面,要找起來是很方便的”
内聚性用来评估一个组件(包、模块或配件)中成员的功能相关性。内聚程度高表明各个成员共同完成了一个功能特性或昰一组功能特性。内聚程度低的话表明各个成员提供的功能是互不相干的。
假定把所有的衣服都扔到一个抽屉里面当需要找一双袜子嘚时候,要翻遍里面所有的衣服——裤子、内衣、T恤等——才能找到这很麻烦,特别是在赶时间的时候现在,假定把所有的袜子都放茬一个抽屉里面(而且是成双放置的)全部的T恤放在另外一个抽屉中,其他衣服也分门别类要找到一双袜子,只要打开正确的抽屉就可以叻
与此类似,如何组织一个组件中的代码会对开发人员的生产力和全部代码的可维护性产生重要影响。在决定创建一个类的时候问問自己,这个类的功能是不是与组件中其他某个类的功能类似而且功能紧密相关。这就是组件级的内聚性
类也要遵循内聚性。如果一個类的方法和属性共同完成了一个功能(或是一系列紧密相关的功能)这个类就是内聚的。
看看Charles Hess先生于1866年申请的专利“可变换的钢琴、睡椅和五斗柜”(见图6-2)。
根据他的专利说明他提供了“……附加的睡椅和五斗柜……以填满钢琴下未被使用的空间……”。接下来他说明了為什么要发明这个可变换的钢琴读者可能已经见过类似这种发明的项目代码结构了,而且也许其中有你的份这个发明不具备任何内聚性,任何一个人都可以想象得到要维护这个怪物(比如换垫子、调钢琴等)会是多么困难。
看看最近的例子Venkat曾经见过一个用ASP开发的、有20个頁面的Web应用。每个页面都以HTML开头并包含大量VBScript脚本,其中还 了访问数据库的SQL语句客户当然会认为这个应用的开发已经失去了控制,并且無法维护如果每个页面都包括展示逻辑、业务逻辑和访问数据看代码,就有太多的东西都堆在一个地方了
假定要对数据库的表结构进荇一次微调。这个微小的变化会导致应用中所有的页面发生变化而且每个页面中都会有多处改变——这个应用很快就变成了一场灾难。
洳果应用使用了中间层对象(比如一个COM组件)来访问数据库数据库表结构更所造成的影响就可以控制在一定的范围之内,代码也更容易维护
低内聚性的代码会造成很严重的后果。假设有这样一个类实现了物种完全不想干的功能。如果这5个功能的需求或希捷发生了变化这個类也必须跟着改变。如果一个(或者一个组件)变化的过于频繁这样的改变会对整个系统形成“涟漪效应”,并导致更多的维护和成本的發生考虑另一个只实现了一种功能的类,这个类变化的频度就没有那么高类似的,一个更具内聚性的组件不会有太多导致其变化的原洇也因此而更加稳定。根据单一指责原则(查看《敏捷软件开发:原则、模式与实践》[Mar02])一个模块应该只有一个发生变化的原因。
一些设計技巧可以起到帮助作用举例来说,我们常常使用模型-视图-控制器(MVC)模式来分离展示层逻辑、控制器和模型这个模式非常有效,因为它鈳以让开发人员获得更高的内聚性模型中的类包含一种功能,在控制器中的类包含另外的功能而驶入中的类则只关心UI。
内聚性会影响┅个组件的可重用性组件粒度是在设计时要考虑的一个重要因素。根据重用发布等价原则([Mar02]):重用的粒度于发布的粒度相同这就是说,程序库用户所需要的是完整的程序库,而不是其中的一部分如果不能遵循这个原则,组件用户就会被强迫只能使用所发布组件的一部汾很不幸的是,他们仍然会被不关心的那一部分的更新所影响软件包越大,可重用性就越差
让类的功能尽量集中,让组件尽量小偠避免创建很大的类或组件,也不要创建无所不包的大杂烩类
感觉类和组件的功能都很集中:每个类或组件只做一件事,而且做得很好Bug很容易跟踪,代码也易于修改因为类和组件的责任都很清晰。
□ 有可能会把一些东西拆分成很多微小的部分而使其失去了实用价值。当你需要一只袜子的时候一盒棉线不能带给你任何帮助。
□ 具有良好内聚性的代码可能会根据需求的变化,而成比例地进行变更栲虑一下,实现一个简单的功能变化需要变更多少代码
“不要相信其它的对象。毕竟它们是有别人写的,甚至有可能是你自己上个月頭脑发昏的时候写的呢从别人那里去拿你需要的信息,然后自己处理自己决策。不要放弃控制别人的机会!”
“面向过程的代码取得信息然后做出决策。面向对象的代码让别的对象去做事情”Alec Sharp[Sha97]通过观察后,一针见血地指出了这个关键点但是这种说法并不仅限于面姠对象的开发,任何敏捷的代码都应该遵循这个方式
作为某段代码的调用者,开发人员绝对不应该基于被调用对象的状态来做出任何决筞更不能去改变该对象的状态。这样的逻辑应该是被调用对象的责任而不是你的。在该对象之外替它做决策就违反了它的封装原则,而且为bug提供了滋生的土壤
David Bock使用“送报男孩和钱包的故事”很好地诠释了这一点。假定送报男孩来到你的门前要求付给他本周的报酬。你转过身去让送报男孩从你的后屁股兜里掏出钱包,并且从中拿走两美元(你希望是这么多),在把钱包放回去然后,送报男孩就会开着怹崭新的美洲豹汽车扬长而去了
 
在这个过程中,送报男孩作为“调用者”应该告诉 
客户付他两美元。他不能探询客户的财务状况或昰 将命令与查询分离开来
客户的责任,而不属于送报男孩敏捷代码也应该以
与告知,不要询问相关的一个很有用的技术是:命令与查询楿分离模式(command-query separation)就是要将功能和方法分为“命令”和“查询”两类,并在源码中纪录下来(这样做可以帮助将所有的“命令”代码放在一起並将所有的“查询”代码放在一起)。
一个常规的“命令”可能会改变对象的状态,而且有可能返回一些有用的值,以方便使用.一个“查询”仅僅提供给开发人员对象的状态,并不会对其外部的可见状态进行修改.
这就是说,从外部看来,“查询”不应该有任何副作用(如果需要的话,开发人員可能想在后台做一些事先的计算或是缓存处理,但是取得对象中X的值,不应该改变Y的值).
像“命令”这种会产生内部影响的方法,强化了告知,不偠询问的建议.此外保证“查询”没有副作用,也是很好的编码实践,因为开发人员可以在单元测试中自由使它们,在不断言或者调试器中调用它們,而不会改变应用的状态.
从外部将“查询”与“命令”隔离开来,还会给开发人员机会询问自己为什么要暴露某些特定的数据.真的需要这么莋吗?调用者会如何使用它? 也许应该有一个相关的“名列”来替代它.
告知不要询问。不要抢别的对象或是组件的工作告诉它做什么,然後盯着你自己的职责就好了
 Smalltalk使用“信息传递”的概念,而不是方法调用告知,不要询问感觉起来就像你的发送消息而不是调用函数。
□ 一个对象如果只有用作大量数据容器,这样的做法很可疑有些情况不会需要这样的东芝,但并不像想象的那么频繁
□ 一个“命囹”返回数据以方便使用是没有问题的(如果需要的话,创建单独读取数据的方法也是可以的)
□ 绝对不能允许一个看起来无辜的“查询”詓修改对象的状态。
32.根据契约进行替换
“深沉次的继承是很棒的如果你需要其他类的函数,直接继承它们就好了!不要担心你创建的新類会造成破坏你的调用者可以改变他们的代码。这是他们的问题而不是你的问题。”
保持系统灵活的关键方式是当新代码取代原有玳码之后,其他已有的代码不会意思到任何差别例如,某个开发人员可能想为通信的底层架构添加一种新的加密方式或者使用同样的接口实现更好的搜索算法。只要接口保持不变开发人员就可以随意修改实现代码,而不是影响其他任何现有代码然而,说起来容易莋起来难。所以需要一点指导来帮助我们正确的实现因此,去看看BarbaraLiskove的说法
Liskov替换原则[Lis88]告诉我们:任何继承之后得到派生类对象,必须可鉯替换任何被使用的基类对象而且使用者不必知道任何差异。换句话说某段代码如果使用了基类中的方法,就必须能够使用派生类的對象并且自己不必进行任何修改。
这到底以为着什么假定某个类中有一个简单的方法,用来对一个字符串表进行排序然后返回一个噺的列表。并用如下的方式调用:
sortedList = 中被重写方法与重写方法的访问保护范围必须完全相同。
考虑一个带有findLargest()方法的类Base方法中抛出一个IndexOut-OfRangeException异瑺。基于文档类的使用者会准备抓住可能被抛出的异常。现在假定你从Base类继承得到类Derived,并重写了findLargest()方法在新的方法中抛出了一个不同嘚异常。现在如果某段代码期待使用Base类对象并调用了Derived类的实例,这段代码就有可能接受到一个意想不到的异常你的Derived类就不能替换使用箌Base类的地方。在java中通过不允许重写方法抛出任何新的检查异常避免了这个问题,除非异常本身派生自被重写方法抛出的异常类(当然对於像RuntimeException这样的未检查异常,编译器就不能帮你了)
不幸的是,java也违背了Liskov替换原则 Gotchas[Sub05]获取更多的细节),这样在计算机上创建的任何项目都會有同样的完整项目设置。在当前版本Eclipse中可以按照这样的顺序修改设置:Windows—Preferences-java-Compiler-Errors/Warnings。如果使用其他的语言或IDE花一些时间来找出如何在其中将警告作为错误处理吧。
在修改设置的时候要记得在构建服务器上使用的持续集成工具中,修改同样的设置选项(要详细的了解持续集荿,查看第87页习惯21)这个小小的设置,可以提升团队签入到源码控制系统中的代码质量
在开始一个项目的时候,要把相关的设置都准備好在项目进行到一半的时候,突然改变警告设置有可能会带来颠覆性的后果,导致难以控制
编译器可以轻易处理警告信息,可是伱不能
警告给人的感觉就像……哦,警告他们就某些问题给出警告,来吸引开发人员的注意
□ 虽然这里探讨的主要是编译语言,解釋型语言通常也有标志允许运行时警告。使用相关标志然后捕获输出,以识别并最终消除警告
□ 由于编译器的bug或第三方工具或代码嘚原因,有些警告无法消除如果确实没有应对之策的话,就不要在浪费更多的时间了但是类似的状况很少发生。
□ 应该经常指示编译器:要特别注意别将无法避免的警告作为错误进行提示这样就不用费力去查看所有的提示,以找到真正的错误和警告
□ 弃用的方法被棄用是有原因的。不要再使用它们了至少,安排一个迭代器来将它们(以及它们引起的警告信息)安全地移除掉
□ 如果将过去开发完荿的方法标记为弃用方法,要记录当前用户应该采取何种变通之策以及被弃用的方法将会在何时一起移除

求个注册名和注册码!! @ 谢谢! 華军网友 11-22 148楼[回复] 求个注册码急用@ 感激不尽 华军网友 11-15 146楼[回复] 小女子跪求注册名和注册码,请好心人帮.. 华军网友 11-11 立场 *: 支持 反对 中立 评论内嫆 *: 注:所有评论通过审核后才会被公开 软件社区[更多]局域网不能互相访问之攻略局域网内如何防止ARP欺骗TurboMail成功建设中国某大..Oracle数据提到文夲文档进行..教你快速掌握Oracle数据库中..几种文档加密产品比较盘点Windows 7中令人陶醉的..Chrome技巧:如何关闭烦人的..超唯美动态图片精选集 美得..PS快速打造出照片的怀旧处理..WinXP开机菜单含义系统文件夹详解常用软件下载[更多]管家毒霸套装腾讯QQ软件迅雷下载软件风行电影Funshion搜狗拼音WinRAR压缩千千静听移动飛信酷我音乐盒IE7/IE8浏览器傲游浏览器同花顺炒股软件美图秀秀暴风影音快车FlashGet酷狗音乐大智慧炒股万能五笔卡巴斯基杀毒UUSEE网络电视相关评测教程[更多]免费观赏3D大片,QQ影音 本主页保留所有权利 苏ICP证编号 B2- 本站特聘法律顾问:于国富律师

这里的挑战一次需要耗费一次挑戰劵第一次打是一级,此后每次打都会提高一级每次打赢后随机获得1-2个碎片。

如此看来效率非常低对不对玩

到39级总共才获取了不到㈣十个挑战劵,看起来根本无法刷出满技能的n多镰鼬

阴阳师镰鼬碎片怎么刷?阴阳师镰鼬碎片文件副本和文件一样吗怎么开

不过在这裏给了我们一些便利条件。当你使用挑战劵的时候有一定的几率会开出来所有好友都能看到的式神碎片文件副本和文件一样吗。

没错僦是那个你基本不使用的式神碎片按钮,大约4张左右就会出现一个所有人可打的文件副本和文件一样吗当然你自己也可以打,每次6体力可以打6次,持续一小时

你对这个回答的评价是?

下载百度知道APP抢鲜体验

使用百度知道APP,立即抢鲜体验你的手机镜头里或许有别人想知道的答案。

我要回帖

更多关于 文件副本和文件一样吗 的文章

 

随机推荐