结对编程小结与收获

和不少人同样,此次编程是痛苦的……是崩溃的……
这篇博文中1. 2. 3.和咱们此前提交的结对编程项目总结十分类似,不想看的话从4.开始看起就能够了。html

1. 初始设计node

2. 遇到的问题c++

3. 最终实现git

4. 我的感悟github

5. 总结编程

## 1. 初始设计:

一开始,咱们错误地理解了题意,认为要实现两个独立的功能:对用户提供的表达式进行求值,和按照设置随机生成表达式json

对于表达式求值模块,咱们打算用最传统的方法,用运算符栈和运算数栈两个栈进行计算。这部分由个人队友实现。api

对于生成表达式模块,考虑到题目要求较高的灵活性,咱们打算使用二叉树表示算式,经过递归实现算式的生成、求值及翻译成字符串。
在这个部分,咱们最初打算划分红许多个模块。从上而下,首先是Generator模块,实现与用户的交互、二叉树的生成;而后是Setting类,考虑到各类设置信息不少,用一个类负责保存各类设置信息和解析xml;其次是Node,即二叉树结点;而后是Num类,用来将三种类型,即整数、小数、分数,封装在一个对象里,从而上层能够直接调用;最后实现分数类Fraction,封装了各类运算。
这部分由我实现。数组

## 2. 遇到的问题:

咱们遇到的第一个问题在于总体结构太过于繁琐。Generator, Setting, Node, Num, Fraction,五个模块、四层封装,太过于复杂;何况Num这一层次只是判断一下操做数类型,彷佛比较鸡肋。通过讨论以后,咱们去掉了Num这一层次,直接在Node中保存操做数信息。架构

咱们遇到的第二个问题在于,忽然从助教那里得知整数除法应该整除。咱们本来考虑丢弃不能整除的式子从新生成,后来发现这样速度太慢。
最后,咱们决定放弃绝对的随机性,而选择配凑。咱们考虑了好几种配凑方式,最后决定对于整数的除法,经过给定一个整数值、配凑生成表达式的方式,生成被除数与除数的算式。
事实上,咱们也考虑过总体更改成配凑方式,但配凑方式逻辑较复杂,并且难以处理乘方,因此咱们只打算对整数除法进行配凑。不过到了后来,咱们惊喜地发现乘方的幂次也能够配凑。

咱们遇到的第三个问题在于对于dll的使用很是不熟悉。咱们为此研究了好久,才终于攻克这一关。
首先,咱们对于dll的概念很是不了解,为了了解这些基础概念就花了好久。
而后,很是尴尬地,咱们本来打算导出Generator类,可是用dll导出类很是复杂。有两种可选的方案,一是将Generator及其属性的类所有导出,这样一来会暴露本身的内部实现,违反了封装原理;另一个是导出抽象类,可是这个概念咱们两人都不太熟悉,并且这样会增长一层调用,加大代码复杂度。
在咱们和ui进行了进一步沟通以后,咱们了解到ui其实只须要函数;再加上咱们编码中也出现了不知道如何让Setting对象成为全局对象这一问题,咱们决定直接使用全局变量与c函数接口。
最后,咱们生成dll后不知如何运行。查找了不少资料以后,咱们才学会经过另外一个项目,即testDll项目,调用本身生成的dll。

咱们遇到的第四个问题在于,咱们发现用std::string做为接口有必定问题。经过查找资料,咱们查到不适宜用stl做为dll接口,而最好用纯c。所以咱们提供了一个char数组的接口,本想删除std::string接口。不过考虑到彷佛不少ui都使用c++,这两个接口咱们都提供了。

另外,咱们还陆陆续续解决了一系列除零、模零、随机数范围有问题之类的bug,并知足了某组ui的提供文件接口的需求。

## 3. 最终实现:

Fraction模块:和最初设想同样,封装分数的运算与显示功能。

Node模块:二叉树结点类。有操做符结点与操做数结点两种,操做数结点又分整数、小数、分数三种。有递归计算、递归解析为表达式和判断两个表达式是否等效功能。
对于判断表达式是否等效,思路以下:首先判断根节点是否相等,而后若是两子树非空,递归分别判断两子树是否相等。假如两子树不等,但根节点为+或*,则交换两子树,分别递归判断是否相等(即,加法与乘法可交换)。

Setting模块:保存各个变量值,经过几个函数进行设置,有输入检查,若是输入不合法就丢弃输入、采用默认值。原本咱们打算用xml或json,但后来发现参数很少,并且ui组大多数没有使用xml / json,所以咱们也没有提供相应接口。

Generate模块:调用Node与Setting,按照不一样的设置生成表达式。
有两种生成方式:

  1. generate_tree,递归随机生成操做符、操做数,从而递归生成二叉树。
  2. generate_int_node(int val),经过给定的值递归配凑生成一个表达式,但只能生成整数表达式
    这个模块提供了std::string接口、char数组接口与文件接口。
## 4. 我的感悟: ### 4.1. 关于编码:

虽然咱们成功地实现了全部功能,和UI对接的时候也伪装信心满满、咱们作出来的东西天下第一牛逼,但其实至少我内心很是清楚其实有至关多的缺陷。几个耿耿于怀的问题以下:

首先,咱们写了过多的重复逻辑
一开始,咱们提供了std::string的接口,咱们内部全部的字符串操做也是用std::string完成的。好比将分数转换成字符串,咱们采用的方法即是对分数重载<<操做符,用std::stringstream转换为字符串。
然而,后来考虑到std::string有兼容性问题,咱们增长了char数组的接口。当时考虑到须要防止越界访问,又想要增长效率,因而就用snprintf重写了一份;所以,对分数重载的<<操做符也用不了了,咱们只能又写了一个toString函数。
如今想来,其实直接调用std::string的接口,比较一下size,若是没有超出size的话strcpy进去便可;就算必定要重写一个函数,也彻底能够直接用stringstream的getline等函数。真的不知道本身当时脑子里在想什么。也许就是贪图那一点点效率吧,不过也是后来才意识到,这个做业中效率其实没有那么重要。
所以,最后的结果就是一样的代码逻辑被重复了好几遍,整个代码结构也变得至关混乱。到最后,虽然我已经检查了好几遍了,但我仍是不太肯定重复代码中逻辑是否是一致的、会不会有我注意不到的地方有不一致的地方,这样至关缺少可读性和可维护性。引觉得戒吧。

这也一样让我联想到《代码大全2》中说的,代码的各类优势有时候是负相关的,应该针对需求的特性去编程。此次为了增长一丁点点效率,我犯了重复代码逻辑的错误,从而下降了可读性和可维护性。一样,此次我也纠结了好久如何生成【绝对随机】的算式,却保证整数可以整除,后来才意识到绝对随机不是很必要。这说明,明确需求是很重要的!不能按本身的想法瞎写!

其次,此次咱们的测试作得不够到位。
一来是由于一开始写代码图快,有点懒得写测试集,本身人工测试也懒得把各类状况一一测试,因此很多问题是被UI调用后发现的……(在此隆重感谢康鑫同窗)。以后注意了增强了测试力度,问题也少了许多。
二来,是由于generate模块实在不知道如何测试……由于这个模块是随机生成式子,咱们无法给出预期值,若是另外写一段解析表达式、判断表达式是否知足要求的代码感受也不太现实,这段代码自己就可能会有不少bug……因此最后咱们仍是人工测试的。这里要感谢队友,他测试起来比我耐心多了=w=

最后,仍是dll没有导出类的问题。虽然用dll导出类会有不少麻烦、此次你们不少人都是直接导出的函数,可是以后考虑仍是以为导出类更合理。由于咱们建树时有动态申请内存,若是导出类能够在析构函数中清理内存,而如今这样导出函数只能在api文档中不停强调要调用clear函数记得调用clear函数啊必定要调用clear函数。
关于导出类的困难上面提过了,此次咱们畏难求快没有尝试抽象类,但我打算以后试试。

### 4.2. 关于结对:

必需要至关愧疚地说,开始工做前我和队友其实对结对的了解都不太深入,以为就各写各的、有问题交流一下,最后合并一下就能够了。咱们都以为一我的写一我的看是一个至关形式化没什么卵用的东西。不知道队友是怎么想的,反正我最后以为这个想法是很错误的,咱们此次由于结对合做的问题致使彻底没发挥结对应有的做用。

首先,我和队友在编码风格上不太同样,并且也没有事先约定。我偏向c++的编码风格,并且会很是夸张地空格、换行、分多文件……而个人队友比较偏c,代码写得比较紧凑。因此咱们一开始的合做花了不少力气纠结风格的不一致,而非好好工做。
这给个人感悟是,不少东西必定要事先约定好。确实存在的矛盾就是确实存在的,不可能写着写着就消失,若是一开始不约定好确定会浪费后面的时间。

第二个问题在于,咱们一开始错误地理解了题意,个人队友一开始写的工做全都是无用功……(为何咱们两个都很是默契地一样地理解错了题意呢,果真是队友么)等到咱们终于意识到题意理解错了的时候,咱们两人都完成了各自要写的基本功能了,因此队友只能来理解个人代码,在个人基础上继续工做。
然而,一来咱们基本没有一我的写、一我的看,二来咱们风格相差太大,并且c与(半吊子)c++之间有着巨大的鸿沟,因此到后面队友也一直没有彻底理解个人代码,致使咱们合做很是尴尬。
理解错题意是真的没办法,因此这里须要吸收的经验主要在于怎样可以快速止损。
首先,一我的写一我的看仍是有必要的,真的是有必要的。我由于本身编码习惯不太良好,会常常性地乱写、写到通常想起什么就去写别的之类的,因此不太想让别人看我写,本身由于懒也不想看别人写。可是后来才意识到,偏偏是习惯很差才须要别人看着写,才能纠正一些坏习惯。另外,看别人写代码的过程才能更好地理解代码的逻辑,也方便找出可能的错误、在现有代码基础上继续工做,而研究一个已经完成的代码真的很是痛苦。(我看过,真的让我绝望)假如咱们从一开始就一我的看一我的写,后面的工做会容易不少。
其次,风格问题真的要从一开始就约定。(这是第二遍提了,这真的是血的教训)我以后问过队友,看代码的障碍究竟在哪里,才意识到他花了不少时间在处理咱们二人风格上的不一致,而非理解个人代码逻辑。

最后,咱们在沟通上也有不少问题,不少时候我也想固然地觉得队友可以理解个人代码、而没有给出不少的帮助(我大概高估了本身代码的可读性吧),致使中途有一段时间咱们相互拖累。之前一直觉得沟通只是套话官话,如今才明白真的很重要。

总结而言,咱们在一开始没有作好功课,对结对编程理解很肤浅、觉得只是形式化的东西而已,因此根本没有从结对中得到太多的好处,总体体验和我的做业没什么差异。
无法写什么积极的收获了,但消极的收获也算是收获吧……

### 4.3. 关于对接:

关于对接,我从一开始就很迷茫,不太理解到底如何对接;在群里看到dll后,我就立马查了不少资料,花了好一段时间改为了dll,而后就很是热心地向团体项目的作UI的队友兜售个人半成品Core,由于我真的很是惧怕对接出错,想早点对接。
对接过程总体而言是比较顺的,可是仍是有几个问题:

首先,我发布出去的dll没有通过足够多的测试,我常常只是试了试可以工做、某几种状况没问题就随便发出去了。这种敷衍之后应该避免=w=

第二,我dll更新太多,给一开始跟我对接的UI组带来了很大的困扰……其实我如今也不太明白更新应该是怎样的频率,确实时不时就有些新发现的bug须要修复,可是太快更新确实不合适。仍是说这说明咱们发布dll太早了?但愿有人可以指教。

第三,我一开始没太关注UI组的需求,具体表如今某组UI强烈要求我使用文件接口但我以为这样很差就坚持用std::string接口=w=后来仍是增长了文件接口,并且意识到这样不太好。应该尽可能知足UI需求。

对接还有一个要点就是要及时回应UI反映的问题,不过这个我自我感受挺好的……基本上UI发的消息只要我在线就秒回,就算身边没电脑也立马用手机去github上面查代码有没有出错,以致于最后几天我一看到有UI的人跟我发消息就紧张……

### 4.4. 关于重构:

咱们的初始规划、中间过程和最后架构上面提到了,能够看到我又重构了……上次作我的做业我就很是崩溃,被本身重构到想哭了,当时几乎要发誓坚定不重构。然而这一次仍是重构了。

就很是崩溃,不知道为何一开始的架构老是错的。至今为止的编程,有时候架构多了、总体臃肿而冗余,有时候架构少了、各个功能过分耦合。有的功能能够合并有的功能应该分开。道理天然都知道,可是怎么判断是多了仍是少了仍是正好?怎么判断什么功能应该合并什么应该分开?No idea.

以后群里也讨论这个问题,看到某位大佬说重构是很正常的事情,新手很难一开始就选择正确的架构的。豁然开朗,也感到了安慰,看来不是个人问题,实践是检验真理的第一标准,不少东西要写了才知道有问题。架构很大程度上不是一个理性的东西,而是一个感性的东西,须要不少经验积累,慢慢养成一种直觉。(因此说之后仍是要重构……)

具体到此次来讲,我学到的经验和上次差很少,不要盲目地增长封装的层数。

## 5. 总结:

总之虽然此次写得很糟心但仍是有不少收获…… 谢谢看到这里!

相关文章
相关标签/搜索