结对项目(附加题)开发总结
这次附加题的任务就是把两个小组写的独立的UI和CORE模块打乱重组,拼接成一个新的APP。
我们小组选择了大神刘乾所在小组和我们合作交换。一拿到乾神的核心代码,我不得不说,乾神就是乾神,代码一出,直接把我镇住了,各种参数看不懂咋办?咋写UI?
还好我们的乾神同志给我们提供了帮助文档,介绍了各个参数的作用,现摘录如下:
对于计算表达式的值(CALC模式): AnswerGet(string s)其中s是给定的算式,以等号结尾。 调用生成器时,使用: public GenerateExp( bool hasNegative/*负数*/, bool hasMultDiv/*乘除法*/, bool hasBrack/*括号*/, bool hasFrac/*分数*/, int opCount/*运算符个数上限*/, int exerciseCount/*数量上限*/, int leftRange/*左值域*/, int rightRange/*右值域*/, ref int FactCount/*实际产生值*/ ) 实际产生值这玩意你们可以随便定义一下,原来是为了出错处理的。 这个如果产生不了预期那么多,会抛出一个异常为 TooManyException(); 调用检查器时,如下使用: CheckAnswer.Grade(ExePath,AnswerPath);
有了这个参数,就相当于给了我们三个函数调用接口,我们只需要设计一个UI,收集用户的输入数据,在后台传给这三个函数,得到结果显示到屏幕上就可以了。
先上个图看看最后的效果(没有花太多精力做界面美化,因为大多数时间花在CORE的BUG调试上了,求见谅……)
界面还是比较简(chou)洁(lou)的。
鉴于UI的代码量很少,而且基本看懂了参数就会写,所以我觉得不必把UI的设计细节作为重点说明,就截一部分代码在这了:
private void button1_Click(object sender, EventArgs e) { bool hasNegative = checkBox1.Checked; bool hasMultDiv = checkBox2.Checked; bool hasBrack = checkBox3.Checked; bool hasFrac = checkBox4.Checked; int opCount = int.Parse(textBox1.Text); int exerciseCount = int.Parse(textBox2.Text); int leftRange = int.Parse(textBox3.Text); int rightRange = int.Parse(textBox4.Text); int FactCount = 0; new WindowsFormsApplication2.Calc.GenerateExp( hasNegative, hasMultDiv, hasBrack, hasFrac, opCount, exerciseCount, leftRange, rightRange, ref FactCount); MessageBox.Show("生成完毕"); }
好,下面说说我们两个组的模块拼在一起后遇到的问题。
1、我们两个小组都是先在组内单独开发自己的UI和CORE,由于老师的软件需求有些地方不是特别明晰(实际情况都如此,没有100%清楚的用户需求描述),因此在有些功能的取舍方面我们两个组的策略是不同的,比如对于Calc模式,我们小组支持对小数和分数混合进行运算处理,我们还定义了最后结果的小数位数,而刘乾小组则倾向于把所有的数据都转换成分数,保留精确结果。这样导致我们自己的UI里关于CALC窗体中的小数位数这一文本框必须移除,且要保证数据的输入以分数形式进行。还比如,刘乾小组考虑得很细致,他们认为当生成的题目量比较大而数值的范围比较小时,由于软件要求不能生成重复的算式,那么可能出现无法生成足够多的算式这种情况,因此他们定义了一个FactCount变量来记录实际生成了多少算式,当生成算式不够时,抛出一个异常。而我们在UI设计时认为这种极端情况一般不会出现,所以没有为异常专门定义行为,导致有些时候程序会陷入崩溃。
2、这次附加题的设置目的是让我们体会松耦合的程序设计方法。关于松耦合的解释,我在另一篇博客中已经做出了解释,在实际编程开发中,要做到这一点其实是很不容易的。有些时候,我们会在设计一个独立的模块中需要引用另外一个模块的某些局部变量,这就导致了一个模块功能的实现依赖于另一个模块的具体细节,不满足松耦合的原则。具体到我们的模块拼接,刘乾组的CORE模块有这么一行代码:
FileStream stream = new FileStream(MainForm.path, FileMode.Create);
其中的MainForm其实指的是他们组的UI设计的中的主窗体文件,因此,MainForm.path指的也是他们组的主窗体文件所在的目录,所以拿到我们的代码中就连编译都通不过。
我们分析其原因,按照松耦合的设计原则,CORE模块中的代码不应该直接引用UI模块中的数据,否则会导致单向依赖,不利于CORE的移植。
我们修改如下:
FileStream stream = new FileStream(path, FileMode.Create);
其中path是此段代码所在的函数的参数,也就是说,path的获得是通过参数传进来的,可以根据需要传入具体的路径来创建文件,这样就保证了CORE代码的独立性。
这应该是我们第一次进行真正意义上的团队编程——各自独立开发一个模块,然后进行拼接和整体测试。在拼接过程中两组的代码出现的“不兼容”的现象让我明白了一个道理:统一的规格、统一的参数说明、详尽的帮助文档是多么的重要,其实这三点反映到一点上就是程序开发中的契约化。在实际开发中,我们没有那么多的精力去细致地分析别人的每一行代码的含义,我们需要通过一些说明迅速知道对方的某一个功能块的作用,根据其提供的接口来使用它。我想起我这两年来写程序从来都是只求自己看懂,没有任何帮助文档,极少有注释,有些时候甚至过段时间自己都看不懂了。看看微软这些专业的软件公司,他们开发的每一款软件都有详尽的帮助手册,而且在其公开的源码中注释也常常比代码还要多。我觉得,要从一个低级程序员向高级工程师进步,应首先从看、写帮助文档和注释做起。
最后说说乾神真是不简单,每一行代码都是精益求精,从中可以看出一个软件工程师的情怀。见贤思齐,这也是我们应该去做的。