高级四则运算器—结对项目反思(193 & 105)
本周我和一位韩国同学(71061105)一起结对编程完成了我们的结对项目——高级的小学四则运算题目生成器。
PSP表格
PSP2.1 |
Personal Software Process Stages |
Time |
Planning |
计划 |
|
· Estimate |
· 估计这个任务需要多少时间 |
1.5h |
Development |
开发 |
|
· Analysis |
· 需求分析 (包括学习新技术) |
3h |
· Design Spec |
· 生成设计文档 |
5h |
· Design Review |
· 设计复审 (和同事审核设计文档) |
1h |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
2h (设计的时间太短,后面弊端就显现了) |
· Design |
· 具体设计 |
算法 2h 界面样式 3h |
· Coding |
· 具体编码 |
界面生成 6h 算法完成 8h xml读写 2h 封装dll 2~3h 拼接组合 2h 调试 2~3h |
· Code Review |
· 代码复审 |
4h |
· Test |
· 测试(自我测试,修改代码,提交修改) |
5h |
Reporting |
报告 |
|
· Test Report |
· 测试报告 |
TuT还没有 |
· Size Measurement |
· 计算工作量 |
0.5h |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
5h (包括了博客) |
合计:54h |
结对编程
结对编程现场证据
以下是我们结对在现场的"证据"。
第一张展示的是我通过某企鹅厂商提供的讨论组共享画面的功能,在为钟焕兄讲解相关的测试举例,代码功能等。
在这个讨论里,我为他进行了具体的分工与分割,决定让他从阅读源代码和测试开始进行c#方面的知识学习,并且让他提出代码阅读中遇到的几个问题。
这一张图里钟焕兄在向我汇报他的测试进度以及当前的代码覆盖率的情况,并且向我请教了一些测试用例的写法。
考虑了一下自己的美貌程度,所以郑重地把我们结对编程的照片放在最后。图为我在教钟焕兄使用Git。
在此重新说明一下我们的特殊情况,之前的队友我跟他多次联系无果,在国庆前联系到后,一起讨论了一下,最后让他做了一些测试,他也只做了一个函数的单元测试...并且用了四天...其他时间基本失去联系,所以我决定单方面切断与他的组员系。真的是太失望了。
因为我国庆回家的特殊关系,这位韩国队友玉钟焕是我在9.29号跟老师说明后争取到的新队友,所以实际上我们真正意义上面基的时间只有一天。其他时间基本上都是要么是我给他做出示例(通过某平台),要不就是我码代码的过程中对代码进行非常详细的注释。所以没能真正地结对编程,所以我将再进行一周的结对编程,完成 沉默的代码 老师在项目下的评论到的所有功能。并且使用Git提交,到时候会来完善博文。
结对编程优点和缺点
因为实际上面基的时间只有一天,我也没有真正体会过真人坐在我身边,我作为领航员的骄傲,以及两个人能够思想交流对撞产生火花的激情。只能说是在9.30那天,我在旁一步一步告诉了他算法的整个设计思路,为他指明了代码构造的大方向——其实没什么用,因为算法还是我一个人写的——但是这样他至少能学一些东西吧,我一直这么想。我还花了一些时间教了他关于Git提交的相关事宜,又因为害怕他看不懂,我为算法的部分基本上做到了一行代码一行注释的水准,并且每晚都通过某平台进行代码讲解。一个人码代码,一个在旁code revivew的情景很令人心醉,但是不得不承认的是,在帮助钟焕兄的过程中我对结对编程确实没有什么太深的感触。
但是由于之前跟何某涛有一次一起调操作系统实验bug、和何某松一起写算法设计作业的时候算是结对编程,我还是能大致地说说自己对结对编程的优点和缺点的看法。
结对编程的优点是非常显著的,首先就是高效与低错。这一点在跟何某松同学一起调试算法作业的时候体会到过,上学期的算法设计作业实际上是一个学期的作业,而就在那一天我们俩用了不到六小时的时间结对完成了它...几乎没有怎么调试,很多低能错误——我指的当然不是说括号匹配出错这种编译错误——都被很好地避免了。同时由于两个人一个人码,一个人代码复审,所以实际上对于我们来说每一行代码都是经过雪亮的两双眼睛目测过的。而两方同时犯错的概率比一个人要小一些——如果其中有高手的话会小很多。这是结对编程的一大优点——随时的代码复审提高了程序逻辑完全正确的概率。
而且由于两人有互相督促,所以谁都不会聊聊某鹅,刷刷某乎,发发某博,看看娱乐新闻什么的。大块大块的时间都可以被用来集中注意力于编程这一纯粹的事情上,从而减少了从没有状态到进入状态的转换过程,很好地减少了时间的浪费。我认为这是结对编程很重要的一点贡献——他人的监管转换为高效的执行力。
最后呢,结对编程还是一次思想对撞之旅,每个人都能从对方身上学到些什么——即使他可能编码能力上不如你。这同时也是一次算法优化之旅,在代码写的过程中就可以做一些自己之前并不知道但是结对伙伴比较熟悉的优化,通过这种不断的交流、学习,两个人的学识都能得到丰富。并且两个人的团队合作意识也会大大提升。正如古语所说三人行,必有我师。二人行,亦能学到许多。
结对编程的缺点我目前还没体会过,我觉得应该是集中于两个人的差异上:包括个人卫生习惯,编程习惯,心态等。比如有些同学在别人看他代码时就是写不了代码...我觉得吧,结对编程最怕的是没能遇上志同道合的人。如果有志同道合的人一起结对编程,那确实是高效的,是可行的,是思想的碰撞。但是如果没有志同道合的队友和自己一起结对编程,那和队友一起编程可能效率还不如自己编程效率高。结对编程不怕队友能力不够,只怕队友态度不行。
所以在此我想建议一下罗老师,能不能不要再使用随机结对编程的做法呢?据我了解,虽然我因为我自己的原因没能体验结对编程,但是很多人也一样没有!一部分同学基本上是一个人闷在宿舍不声不响地完成了所有的任务,另一部分同学虽然是分工完成的,但是基本上都是团队编程的模式,不是结对编程的模式。:( 您可以去调查一下的!
希望您能让大家按自主意愿自行组队,正如我所说的,结对编程志同道合很重要!您可以按照个人项目前百分之五十必须找后百分之五十的同学或者其他比较公平的分法,但是我非常希望能够有个人意愿为主导的结对编程团队!
结对伙伴的优点和缺点
由于没有真正地结对(希望开学后能亲自教一下韩国同学:D),对结对伙伴的卫生习惯并不了解(XD),以下列一下他的一些优点吧:
1、很积极。派发给他的任务他很快地就答应了,并且在让他远程观看我码代码的时候,他也很积极地参与。
2、他很理解了他所欠缺的地方。在我把任务细分下去派发的过程中,钟焕兄对一些他觉得不清楚的地方有所疑问,并且他只要有人引他入门,他能一直学习那个东西到学会为止。
3、他比较负责。由于我的VS版本在装那个代码覆盖率插件的时候装不上...而且由于家里的网十分受限(可能是运营商的缘故,大部分英文网站无法访问),所以安装VS的扩展工具时失败了,所以我这里没法测试单元覆盖率。而钟焕兄的VS是可以测试的,他每次都想让测试模块的覆盖率达到百分之百(其实我原先是想偷懒90%就算的),后来,有两个模块在他那里测试确实100%了。
当然他也有缺点,有个比较严重的缺点就是:不喜欢主动提问。
不知道为啥,我在下派一些很小的任务让钟焕兄去完成的时候,他有一些看不懂的源代码——即使我写了注释他也没有及时地问我。
只是大概笼统地说一下注释看懂了,但是代码有些没看懂,这一点让我比较费解——可能是他比较害羞?
一些原则与设计
在寻找与理解这些原则和设计的过程中,我越来越发现这些设计和原则和上学期被大家一致吐槽的面向对象课程中讲到的面向对象的几大原则有很多的相似之处。
信息隐藏(Information Hiding) 原则
维基里提到了,有些人把信息隐藏
和封装
看作是一种原则:一个软件通过,通过把信息封装到一个只提供接口的模块或结构中来实现信息隐藏。而这实际上就是我们面向对象课里所学到的关于封装的概念。
在面向对象编程中,封装可以通过把代码对预期不确定的实现的依赖转移到良好定义的接口上来,从而减少软件开发的风险。
封装也能保护模块内部状态的有效性,防止用户在手动初始化模块属性的时候直接就把模块的数据设置错误了。防止模块“生下来就是个错误”,那以后岂不是大错特错了...
封装的另一大好处就是可以通过限制软件各个部分之间的相互依赖关系从而减少系统的复杂性,增加系统的鲁棒性。
其实封装的概念在我们平时见得也很多了。像大部分的产品说明书,那都是一个个设计文档啊。比如一个小小的插座,产品说明书告诉你千万不要用手伸进去触摸插座
。但是它并不告诉你“不能用手触摸插座”的原因是零线火线,高压中电这种物理上的知识:因为它面对的不是物理学家,而是普通民众。
As can be seen by this example, information hiding provides
flexibility. This flexibility allows a programmer to
modify functionality of a computer program during normal
evolution as the computer program is changed to better
fit the needs of users. When a computer program is
well designed decomposing the source code solution into
modules using the principle of information hiding, evolutionary
changes are much easier because the changes
typically are local rather than global changes.
这就像我所理解的信息隐藏的功能一样,千万不要把使用你这个类的使用者当成一个极其高深的内行人一样——“我跟你讲啊,这两种算法的实现效率挺低的,你要不还是自己根据这俩算法改进一个?”
而是要把使用这个类的人当作是白痴客户
——你给出选项让他们选,而不是让他们自行创造选项——“选择算法1还是2?我只有俩选项”
而且我觉得封装本身实际上只是一种思想——让别人更容易使用,而信息隐藏可能包含的东西更多。包括隐藏不必要的实现细节,隐藏一些属性不能让人随意篡改——否则后面调试Bug的时候就可能会有这样的情况:加班调bug竟是因为别人乱改值而导致的。
这种信息隐藏的做法用于我们本次计算模块的设计是很好的,就是要设计一个公开的类,从这个public
的类中只能使用它已经实现的特定的某几个功能,其他的组合功能一概不能使用——因为能够组合成那些新功能的类或实现已经被隐藏起来了。
于是我在设计中就使用了一个Configure
类来作为中间层联系封装好的计算模块与界面模块。
接口设计(Interface Design)
接口设计其实本身我之前早已有所耳闻。接口设计的初衷当然是为了能够减少逻辑bug、确立正确的整体框架。如果接口的设计是良好的,那么代码重构的风险将大大降低——就不会出现什么代码码到一半,发现需求理解错了,需要重构的做法。在设计阶段好好地理解需求与设计接口,对于代码整体的框架,代码是否能行云流水地写成是有指导意义的。
好的接口设计包括以下六种原则:
- 单一职责原则
- 里氏替换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特法则
- 开闭原则
接口设计实际上强调的重点就在于用抽象构建框架,用实现扩展细节。(这段精辟的总结引用自http://blog.csdn.net/zhengzhb/article/details/7296944)
"对扩展开放,对修改关闭"也是这几种良好设计的核心思想,包括后面要提到的松耦合,契约式设计的做法,目标都是一致的:建立一个相对稳定的可扩展性强的抽象框架,并且可以做到需求的部分改动与增加不会对整个设计框架产生影响。
本次结对项目里我本来也是设计了两个接口作为检查类和生成类所要遵循的方法规范,后来发现良好的接口设计真是不容易啊。因为在最后我的两个类的方法,居然没有一个是与接口设计中的方法功能一样的以至于最后*舍弃了接口。这可能是因为新手上路,设计的时间尚浅,初期设计的接口是非常不合理的。方法的完善是在代码一点一点出现矛盾后,一一修改接口,重新设想才完成的。在这过程中接口*为更好的设计作出了让步。但是实际上即使最后我完全没有用上接口所定义的那些方法,但是总体框架与初步的设计是差不多的。
我最后发现:设计接口是个需要费时间的设计活,需要经过反复考虑。在设计时,即使最后没能设计得尽善尽美,但对于整体框架,类的交互的设计走向也是有指导意义的。
松耦合(loose coupling)
刚刚提到了接口设计的一大原则叫:迪米特法则,实际上就是松耦合原则。这个原则是由下面的问题引出的:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
一言以蔽之,本来就不直接联系的,就不要让他们发生联系。
我拿身边的例子来举吧,比如国家有某文件要下报,要求各个市级单位的最高*全部知晓。*部门当然是不能去每个市里找各个市的市长去直接送达的,否则国务院有多少总理秘书都不够用啊...所以要一级一级地下发,*到省级行政单位,省级再到市级单位。如果说国家跨过了省,承担了通知一半的市长的责任,那么这一半市长只要有人换届,国家就会收到通知。这样市长的频繁变更对国家的影响就会很大,这是不应该出现的。(也不知道理解对不对哈...)
这次我的设计里有地方就违反了这一原则。直到和鸣神的队伍进行结对编程后我才发现。我的设计中计算模块也接管了部分生成到文件的职能,这样相当于本来是前端很轻松就可以管理的东西,计算模块“越俎代庖”,这样只要想对文件有关修改一部分内容,就必须改计算模块,真是设计上的缺陷啊...
契约式设计(Design by Contract,Code Contract )
契约式设计的提出,主要基于软件可靠性方面的考虑。可靠性包括正确性和健壮性,正确性指软件按照需求规格执行的能力,健壮性指软件对需求规格中未声明状况的处理能力。健壮性主要与异常处理机制相关 。
引自http://www.cnblogs.com/riccc/archive/2007/11/28/design-by-contract-dbc.html
契约式设计要求提供一套机制,在客户程序与提供者之间明确的声明双方的职责与权利,即契约,并能够对这些契约进行验证。
契约式的设计身为提供者会觉得很烦...确实很烦,尤其对于设计者来说。而且上学期上面向对象课的时候不知道写了多少前置条件,后置条件,概览(overview),以及证明。
也只有自己用的时候才知道其好处多多啊...像在这一点上VS的库函数就很好。我在本次结对项目中,想捕获Stack
栈结构如果在为栈空时还执行操作Pop
时的异常。于是右键 转到函数定义,就会发现一段和蔼可亲的 异常抛出
列表,调用者不需要观察细节就可以方便地知道可能会发生的事。这种契约机制是一本万利的,即辛苦一时,造福很多世的工作。
关于单元测试
其实...说句实话,我并没有按照罗老师的要求来一步一步做。我在做完很小的部分后没有停留,再做一部分功能,等做到预期的某个大功能已经封装好后,再进行单元测试。并且每次做完每个模块后,我都要把模块的测试部分划分详细,给出一定的示例测试,然后交给了钟焕兄来做。但实际上,在他写测试之前我基本上都测试过,基本是确认了正确之后才写下一步。
但是我的VS有点问题—集成不了代码覆盖率插件。而家里的网受限严重没法安装,希望能宽限一天,到校后我会及时补充上代码覆盖率的图。
(现在已经补充)因为封装不太对...所以Core和CheckAnswer两个类的覆盖率都比较低,因为读写文件的那个测试不管怎么放文件都一直报错说找不到文件...我也非常奇怪,正在努力解决这个问题。
UML类图
UML类图是由VS自动生成的,感觉这图没啥用啊...
界面类
Core核心
算法核心
本次项目有关的经验我全部写在了这篇博文中:http://www.cnblogs.com/SivilTaram/p/4857271.html
这篇博文现在还极度不成熟,希望一周后的我能让它变得更加充实:D。