总体上,本程序较好完成了课程设计要求,并在基本要求的基础上做了较强的接口和功能拓展。投入了较多课余时间进行调试,在投入之后也有很多收获。
首先相较于给定的程序框架该程序做了改良。
-
对开发者友好。拓展性很优秀,接口安全性较好,异常处理得当。toolbar和map做了严格区分而在区域内部有很高的自由度;
-
对用户友好。非常符合直觉的交互逻辑和选中反馈可以让用户更轻易地使用产品。
但是,这次课程设计仍然有众多不足。首先类设计存在不合理。
-
Button类作为Response的派生类与Point/Line/Polygen三类有非常大的行为差异,即Button类更应该看做“一个可以被点击的”显示矩形(Squareness)类,而不是一个“静止的”可操作(Response)类,但这样会破坏Display;
-
Line和Polygen类具有非常强的重合性,仅在删除/添加点等个别行为有差异,作为并行派生降低了代码重复利用率并增加了维护成本。
因此,应当在可操作性类之上再分出一层次级基类”Single”与”Multi”,将Point与Button归为”Single”,将Line与Polygen归为”Multi”,可以想到这样的分组可以显著提高代码复用率并非常符合直觉。
在进行这次课程设计的过程中,我对OOP的理解有很多深化,对如何使用Cpp开发的理解大大加深,以下是我遇到的问题从而总结的体会:
-
**将全局变量的定义放在一个单独的源文件中。**如果在多个源文件中定义了同名的全局变量,链接时可能会出现重定义错误。constexpr的编译特性很好,可以很好得承担全局变量的功能,一些需要全局传递的变量可以使用类封装。若无法封装,在必要时其他源文件中使用extern声明该变量。
-
头文件之间不要相互包含。在需要其他头文件中的定义时,写前向声明。
3) **优先提供抽象接口,在使用容器时使用基类指针。**抽象基类指针具有非常优良的性质,利用dynamic_cast可以实现非常强大的类型传递。但是由于其不能实例化的特点,哪怕在存放次级派生类的时候依然以存储派生类的指针为更优,因为很可能出现无法预测的情况下需要将不同的派生类归结到同一个指针表示,而一个实例化的派生类将会引起非常大的问题。
4) **在绝大部分情况下不要手动写inline。**当代Cmake对inline有相当不错的优化,手动inline往往只是加重程序员的负担和引起编译器的混淆。在这次设计中就出现了手动写inline导致函数被内联而对其他对象不可见。
- **在依赖对象构造顺序时要谨慎编译器的并行优化。**可在编译头添加#pragma omp single做保险。
6) **注意字符串的浅拷贝与深拷贝问题。**尤其在做字符串转换时往往会new出一个buffer存字符流,这时候如果通过浅拷贝传递一定要先完成赋值操作再delete []buffer。
7) **iterator****是非常优秀的工具,但一定只能在小作用域下使用。**Iterator虽然行为上是一个特殊的指针,但是作为STL的内容,具有一个contain只能被一个iterator指向的限制。将迭代器在大范围中传递非常容易造成访问冲突,应改为指针传递。同时也要避免嵌套过深。
- **切换任何状态都要先消除原先动作的影响,在行为结束后进行复原。**这是一个开发意识,通常“尽可能局部地声明变量”和“总是初始化变量”这两个原则可以很好的做到上述要求,但是一些时候这两条原则仍然不能有效保证,因此在实现功能前后一定要check一下。
9) **不仅要依靠被动防御,还要依靠主动防御。**这一条接在上一条后,除了在尽可能使用const和隐藏信息之外,多进行异常状态探测和初始值检查。
10) **不要进行不成熟的优化,不要过早优化。**正如Donald Knuth大神曾言“不成熟的优化是万恶之源”。“让一个正确的程序更快速,比让一个快速的程序正确要容易太多”,这次设计中就成多次出现过因为过度剪枝从而在之后传递的行为改变后程序无法正常运行的情况,对调试造成了非常大的压力。代码最优先的原则就是正确,其次是清晰和易读,其次才可能是效率。
11) **确保资源为对象拥有,使用显示的RAII。**在这次课设实践中,RAII多次为我指出潜在的错误,但是我对STL中使用智能指针存在阴影,下次改进。
12) **不要忽略警告,查看所有警告。**因为类型转换的错误是隐蔽的(卡了很多时间),要记得查看警告——他们往往会指出这一问题。
13) **在进行传递时尽量使用引用,对不会改变的量及时做const修饰。**在这次课设中我才第一次发现原来指针类型的传递也是需要引用的。三次出现成员函数没有按照预期改变都是因为没有使用引用造成的,有时候这很难意识到,所以尽量使用引用传递。
14) **在上手前应当对不熟悉的功能做一些测试而不是想当然使用。**这一条是完成课设后反思开发时发现许多次自己在使用不熟悉的特性时盲目自信,未经测试就将该特性部署,而大部分时候都是有错误的,从而又修改整个工程。应当先做实现确定实现的可行性再实现,从而减少反复修改和重复劳动。
以下是该工程已经实现但是仍然BUG的内容:
-
在折线/多边形的绘制过程中右键删除之前绘制点的不确定行为;
-
在折线/多边形的绘制结束后多次右键删除组成点时的不确定行为;
-
图形之间相互遮挡造成的选中时的不确定行为;
-
在拖动组成多边形的点时绑定边的跟随和静止状态显示错误
-
在图形过多时的刷新与选中异常/不确定行为;
以下是内核已经实现但是未通过测试或实现有待改善的功能:
-
在“选择模式”中左键单击对象显示元素的信息(坐标/面积/长度,备注);
-
在拖动组成折线的点时可能崩溃的不确定行为;
-
拖动过程中刷新闪烁;
以下是保留了接口但是仍未实现的功能:
-
在“选择模式”中中键单击对象修改对象信息(备注/颜色/形态);
-
各类边界错误等的异常行为;
我们学院的Cpp教学时间紧凑,内容详实,对大一学习者有不小的挑战,但同时也能在初期帮同学们在初期对Cpp有一个比较全面的概览。课设内容非常富有实际意义,以下是我的一些讨论:
-
关于对象的检测问题,我认为我在前文提供了一种优于课设文档的高效准确的检测方法,可以让同学们感受到算法的魅力。
-
关于类的继承关系问题,虽然从代码复用的角度将Polygon类从Line中派生是更简洁的,但是我认为这混淆了Polygon类承载的意义。即”Polygon is not a-kind-of Line”,而应该为组合关系,因此我在Line之下多分出了一个Borden类作为Line和Polygon ”contain”的元素,进而更符合OOP的设计哲学。像之前把圆从椭圆中派生,正方形从矩形中派生这样经典的案例在介绍给初识OOP的学生是需要谨慎的。
-
可以增加一些异常的内容,强化用户意识和安全意识。
最后,我要向王红平老师表示衷心的感谢。老师上课严肃负责,对我的问题耐心解答,对我本学期的学习提供了很多帮助。老师对课程内容的追求也让我没有走向画地为牢,对会的一些基本内容沾沾自喜。而是更多领略了Cpp和OOP设计哲学的魅力,对我树立良好的程序习惯和编程观产生了莫大的帮助。