OO第四次博客
4
1.测试与正确性论证的比较
这两者在我看来不可比较。前者是对代码正确性的验证,是对代码实际运行结果与预期运行结果的比较。后者是检查代码逻辑与规格逻辑的一致性。
在我看来,前者的优点在于,当“预期”具有正确性时,它可以确确实实地保证代码的正确性。缺点在于,“预期”本身有时候难以得到,当程序过于庞大、复杂,例如一台每秒几万响应的大流量服务器上的程序,对于它而言,“预期”是极难通过人工获得的。
而后者,正确性论证的优点在于它不需要进行测试那样的穷举操作,顶层的正确性可以分割成底层的正确性,论证复杂度逐层降低,易于实施。缺点在于哪怕程序通过了正确性论证,它也不一定是对的,因为一方面规格本身不一定正确,不一定能够得出预期的结果;另一方面,人工进行正确性论证这件事情本身就极有可能出错。
2.OCL语言
OCL: Object Constraint Language.它是UML标准的一部分。wikipedia里面的说法是:
OCL はUMLを補うものであり、自然言語の曖昧さを排していると同時に複雑な数学的記法を扱わなくてもよいという特徴がある。OCL は、図に基づいたモデルのためのナビゲーション言語でもある。
翻译过来就是,OCL是对UML的补足,在去除了自然语言的暧昧性的同时,也避免了复杂的数学记法,是用于基于图的模型的标记语言。
与JSF的相似之处……都是描述性语言?OCL通常也使用前置条件、后置条件、不变式来描述方法、类。
与JSF的区别之处在于OCL没有抛弃自然语言,它考虑到它自身是一种标记语言,更多的是为人所阅读,而没有采用极为复杂的数学记法。JSF刚好走了相反极端。
3.UML图
4.总结
4.1四个单元的联系
第一个单元讲的是OO,第二个单元是多线程,第三个单元是规格,第四个单元是测试&正确性论证。
第一单元所讲的面向对象思想是实现后三个单元的基础,否则实现复杂度会过高。
第二单元的多线程,这个说实在的我觉得和我们的课程没有太大关系,在我看来这只是为了提高作业难度而额外拓展出来的课程内容罢了。
第三单元的规格,是第一单元OO思想的延伸与补足,也是第四单元正确性论证的基础。在代码的基础上再追加一层逻辑抽象,而这一层抽象相较于代码而言更加容易验证。
第四单元的测试、正确性论证,是一、二单元的收尾。无论是OO,还是规格,其初衷归根结底都是为了实现“正确的程序”,这个正确性包含了现有逻辑本身的正确性、拓展后的正确性。第四单元的测试、正确性论证从两个角度去尝试实现这一目的。
4.2自身成长的梳理
那我就实实在在,不掺半点水分地梳理一遍吧。
最早的多项式、单线程电梯那里,我依然延续以前的风格——尽可能多地考虑到后续可能的拓展。我会这么做是因为我以前写的代码都是我自用的。这种代码的初次编写需要较高的耗时,测试也往往较为繁琐。不过优点在于后续拓展往往是简单的,虽然手续上可能较为繁琐,但是逻辑上相对简单。测试也往往因为较低的逻辑复杂度而偏向于平凡测试。这符合我自用的需求——没有DDL,且我随时都可能会有新的想法要去实现。
后来的多线程电梯我也依然延续这种风格,不过因为多线程接触得较少,所以那次我花了极多的时间在设计上,在设计这个层面花了极多的精力去避免多线程陷阱。结果也很好,事实上多线程电梯中我基本完全复用了单线程电梯时的代码,虽然代码层次越来越深,但是每个层次的复杂度依然很低。追加的线程部分的代码与多部电梯的调度逻辑,都很好地和原来的代码分离了开来。在实现多线程电梯的过程中,注释、标记、日志的使用让我为之一振,它们的出现虽然让代码看起来变得繁琐,但是在复杂的多线程测试中,它们的存在极大地降低了测试复杂度。输入输出的重定向实现,测试线程的应用也让我由原本的批处理测试转向了具有更高复杂度的自动测试。
是的,在这一阶段,我觉得我每一次作业都在成长。虽然这份成长恐怕无法体现在我找到的别人bug、别人找到的我的bug上。
但是这之后的IFTTT,让我从此停止了这种风格的代码——我不再多写,也不再追求测试的完备性。原因很简单,我没那么多时间。需求变更过于频繁,推翻重来成了家常便饭,许多需求甚至无法明确。我察觉到了以后当我成为社畜以后这会变成常态。如果我依然延续以前那种完备的代码风格的话,以后我可能会变成一个码农,最后过劳死。如我之前所说,虽然我原本的代码风格因为冗余的设计而易于拓展,但是每次修改的手续是复杂的,这无法适应频繁变更的需求——每一次修改我都需要耗费一两个小时去改,而往往我刚刚改完,需求就又变了。
我确确实实地察觉到了我的代码风格不适合这种需求环境。于是我改变了我的代码风格、测试风格。代码中虽然依然保持各个逻辑的分离,但是不再进行任何冗余设计,尽可能地将每次微小改动局限在一小段代码中,哪怕可能是要重写整片代码,哪怕逻辑可能复杂——但是修改量很小,修改时间很短,只是对“我”的要求很高。测试风格也不再追求高覆盖率,否则每次需求变更我就几乎每次都要重新准备测试样例。我转而仅仅只针对需求的每一条独立地进行测试,在这之上再进行随机测试。
这种风格的转变在之后的若干次出租车一直延续了下去——我不知道何种风格是好的,我只是选择了一种“我能完成一份有效作业,同时不会花费过多时间”的方法。对于我而言,“进步”这个概念在IFTTT之前都确实存在着,但是这之后,我可能更多地是在适应。
4.3对工程化开发的理解
按照我的理解,现在我遇到一个矛盾:
- 工程化开发应该力求逻辑的简单,“傻子也会敲的代码”,我一直都是如此要求自己的,这种做法能更好地进行协作、测试,也更易于维护。
- 工程化开发应该力求变更的快速,这一点是我从IFTTT中明白的,如此要求不是为了代码的质量,而是我自己的生活质量。如果我不能适应快速的需求变更,那等待着我的就是生活时间的被压榨。
这两者是否可以并存?我也不知道,我缺乏经验。或许我需要作为一个人、作为一个敲代码的,逐渐寻找这两者的平衡点。
4.4对课程的建议
请思考以下几个情景:
- 假设有两位同学在第一次出租车作业的申诉过程中遇到不可调解的矛盾,这个矛盾的根源是指导书的不明确,他们两人申请了仲裁,然而这份仲裁的结果直到第四次出租车作业都还没出来。他们其中一人每次都按照自己对指导书的理解去编写代码,这四次出租车作业都因为那一个“指导书的不明确”而被报bug,而每次都申请了仲裁。最后这位同学在最后一次博客作业的时候收到自己4个仲裁都输了的消息。
- 一位同学虽然每次自己代码没怎么不好好写,但是他非常热衷于互测,每次都会反反复复地发issue、发微信,和多位助教积极沟通,由此获得了较好的成绩。
- 一位同学虽然每次都认真地写自己的代码,但是他为人非常心软,不舍得扣别人的bug,往往虽然测出来一堆bug,但是最后想了想也只挂了一个bug,把测出来的bug都写在了备注里告诉对方。最后他的成绩非常靠后。
以上情景都是我胡扯的,和具体人物没任何关系。
我想说的有很多,浓缩成一句话的话就是:还好这门课挂不挂科和排名没关系,如果规定倒数多少名直接挂科的话,这门课——不,可能这个系就完了。