博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

结对编程收获

Posted on 2018-04-20 17:53  HelenL  阅读(159)  评论(1编辑  收藏  举报

《结对编程收获》

#关于类与C++

  虽然自学过一点C++,但这是第一次付诸实践。C++的一个关键在于创建对象。所以对于这个项目,我们该创建哪些对象便是一个重大的难题。刚开始看C++的例子的时候,感觉对象的选择和创建都是显而易见的,但事实并非如此。当我们面对的是一个比较抽象的题目的时候,或者说没有比较清晰的现实世界中的对象被映射的时候,抽象这件事就变得难了起来。这并非说抽象这件事本身很难,而是指怎么抽象。抽象的选择有很多,但如何抽象,可以使得对象与对象之间的耦合不要太紧密,如何让对象与对象的概念的区分是清晰的,如何让对象的方法之所以隶属于这个对象显得较为合理,都是对象的建立需要考虑的。

  考虑到我们是用表达式二叉树来储存一个表达式并检查其是否重复,最后我们选择了建三个类,分别是setting,设置类,generate,产生类,这也是最外层的类,结点类,node类,负责计算和数字运算符表达和转化。其中setting类一开始我们设定为一个结构体,也就是它的信息是可以共享到整个代码段的,但后来我们发现,我们有时候需要基于这些数据做一个独立的计算函数,并且有时候我们需要强制修改参数(我们在生成结点时有时候要修改这个)于是我们将其修改成一个类。在修改过后不久,我便些许意会了类的好处和意义,那就是generate和node不需要具体了解setting内的数据结构,这在我们后来修改setting的结构的时候带来了很大的好处,由于其他类不需要了解setting的结构,故generate和node方法不需要做改动,只需要在setting的方法做改动即可。这真的大大降低了程序出错的概率,而这种错误有很多是编译器并没法发现的,因为根本就没有语法错误。这是维护代码很重要的方法,设想一下,如果setting.operator_type在代码的各个地方暴露,那么当我对setting.operator_type的定义进行了修改,我就需要在整个代码段里寻找setting.operator_type然后根据我自己的设定进行修改,而如果利用类进行信息封装,我只需要修改setting::getOperator_type()。

  我们对这三个类的设计使得setting 被generate和node调用而不调用其他的类,node只调用setting,并只被generate和node调用,generate 调用node和setting而不被其他类调用。

类使得整个代码更有合理性,更容易进行测试,使得模块代码比较不容易出错,比如说,num2string()这个函数,我们单独对它测试,并根据要求的更新对其进行修改的思路显得特别清晰。但分块也有一个问题,那就是函数与函数的衔接有时候没有考虑好,这启发我们在单独测试或者编写这个函数时,要全面的考虑这个函数可能被调用的所有情况,以及应用的所有情景,当然我们不可能事事都不遗漏,所以再把所有代码全部写好的时候,按照代码执行逻辑顺序再把整个程序过一遍,可以发现很多考虑不周的问题。

  不得不说第一次尝到了使用类的甜头,但显然这种层次的使用还非常的低级,很多问题显而易见,比如说类的方法过多,这意味着我们可以创建更多的类,或者创建子类。并且我们并没有用到继承,泛型等应用,由于不熟悉C++的原因,我们对其的使用还非常的片面。这还需要大量的练习。

  我们对分数,小数和整数进行了形式上的统一,然后将运算符重载。这让我又一次体会到了封装的意义。事实上不封装的代码量可能是差不多的。但这使得代码看起来思路更为清晰和简洁。

  我们在用string类的时候不知道为什么就莫名报错了,两个人折腾了好久,以为是头文件包含的问题,最后问别人之后,才知道是没有写using namespace std;真的让人痛心疾首,这告诉我们基本功的重要性,同时也告诉我们身边有大佬有多么的重要,并且遇到问题要及时向别人请教。

#关于封装与API

  封装虽然麻烦,比如说我们要写很多set(),get(),函数但是这有利于代码维护,这就不多说了。而提供接口是这次编程的key,我们要如何提供接口而使得UI组不需要关心我们内部的具体结构,不需要关注core组的实际细节。这次的作业让我们

  对于输入,我们想的办法就是用一个函数来接受他们的输入,也就是参数作为函数参数传进来。这样子他们只需要调用这个函数就行就可以进行参数的传递,而不需要知道参数是如何被储存的。

  但事实上这有一个问题,鉴于大家写的都是C++,不存在语言的切换问题,这样传参应该说是最简单的。但是假设有人不是用C++写的就比较麻烦,所以如果都用Ascaii码就不存在这种问题,就如被提到的xml,但是鉴于没有时间再学新的东西(时间实在不够),我们就采用了函数传参的方式。

  对于输出,我们采取了两种输出模式,一种是内存法,一种是文件法,共UI选择。文件法是最开始写的,是因为一开始就有UI说要读文件,后来在对接的时候有UI说需要用内存读,于是我们又写了内存版的对接,文件输出就不需要输出对接代码,而内存版我们给了两个指针,事实上这也是建立在我们双方都使用C++的基础上。

  关于对接,一开始没有商量好接口标准,是一个让人很头大的问题,一千个组会产生一千种API。这启发我们在做团队项目工作前,事实上不管有没有人组织进行确立API的讨论,讨论接口问题都是必须的,作为core,我们要提前了解UI需要什么样的接口。虽然接口这种东西是独立于核心代码的一种东西,我们可以在不改动核心代码的基础上修改接口,但频繁的修改接口是没有意义的。

  正如《程序员的自我修养》里所说,接口的另一个层面的含义是协议,接口是一种约定,所以,这是core和UI组需要事先商量好的,我想这是通过这个项目我们学到的东西。有时候我们疏于沟通,是怕麻烦,但事实上不沟通会造成更大的麻烦。

#关于Debug技能的收获

  事实上虽然我知道基本的断点设置,单步调试,条件断点等Debug方法,面对“程序终止运行”时,或者程序陷入死循环,我都不知道从何入手。好在队友的Debug速度很快,看了几次调试过程之后,我大概也能够跟上Debug的思路,能够比较快的找到问题。希望以后不再畏惧调试程序。我想这是结对编程的好处之一把,就是相互学习。并且知道了有时候程序出错是不会报错崩溃的,有时候就直接停在了那里。这应该是由于调用了某些系统函数的原因,比如说申请内存,然后内存申请完了,无法申请,于是程序就停止运行,这时候无法知道程序到底在哪里崩溃了,我们没法利用IDE提供的错误信息,这就增加了Debug的难度。

  如果我们无法知道是到底在第几次调用出了问题,那么我们可以采取命中次数(或条件断点)的形式,用二分法的方法比较快地查出哪次调用出错。

  并且在产生随机数的时候,由于电脑的随机数是伪随机数列,所以为了方便Debug,可以先不srand(),用计算机一开始的默认的种子,等到程序调试基本没问题后再加上srand()。

 

#结对编程的优点

事实上我们并没有在每一部分代码都做到一个人看的同时一个人写。但是因为我们是在一块儿写的代码,在每一块代码写之前,我们都会进行讨论,所以我们对对方写的代码的逻辑也比较清楚,所以我们对对方的代码上理解是比较快的,并且我们会对对方的代码做一次检查,顺带理解。所以我们的Debug工作不管由谁进行,错误在谁写的代码上发生,都可以进行程序的修改。我想这要感谢队友的合作。

两双眼睛确实来的比一双眼睛明亮,而有时候很明显又很低级的错误自己发现不了会被队友一眼看出,这大大的降低了Bug的概率,并且两个人商量出来的算法或者结构往往可以互补优化,彼此的知识可以互相补充,并且两个人做一件事就会比较有信心,是很大地提高了编程的效率。我认为结点编程看起来是两个人同时完成一个任务,仿佛降低了生产力,但事实上这大大减少了Debug的时间,有时候我们查Bug的时间远远多于我们的代码,并且结对的代码质量也相对较高,不得不说这个一个非常棒的方法。

#其他

  最后应该就是第一次建DLL,并且了解到什么是动态链接库,它的作用是什么。并且自己写了测试文件,成功调用了dll。

#写在最后

  虽然很辛苦,但是收获还是很大的。写代码是需要实践的,我们需要在实践中学习,不是看书看了就会写的。虽然很辛苦,但相信没有UI辛苦,UI要辛苦地跟我们core组对接。还要感谢大佬们,给出建议并帮忙解决了一些看起来很小但确实把我们难住的问题。大概正如邓老师所说“技术后备团”也是很重要的。还要感谢队友的理解和支持。