软件工程结课总结

提问回顾与个人总结

项目 内容
本作业所属课程 2020春季软件工程(罗杰 任健)
本作业要求 回顾开学初阅读《构建之法》的提问,并总结本学期的收获
我的课程目标 具备一个软件工程师所需要的素质
本作业帮助 回顾总结

一、第一次作业提问的回顾

  • 链接到以前提问题的博客。
  • 请尝试对自己曾经提出的问题进行解答,并阐明,是如何通过看书,实践,或者讨论弄清楚的。
  • 是否原来的问题还不明白?如果有,请分析。
  • 是否产生了新的问题?如果有,请提出。

原博客链接:【软件工程】《构建之法》 & Git+ & CI/CD

单元测试

原文

(P26)单元测试的运行/通过/失败/不依赖于别的测试,可以人为构造数据,以保持单元测试的独立性。

问题

面对需要处理大量数据的模块,人为构造数据就最造成很大的重复性工作。比如上学期写编译器的时候,为了测试我的词法分析模块的正确性,对一个十分简单的源程序,我就需要写上百行的期望输出,而且单元测试运行完之后,对那些错误部分进行分析,发现大部分并不是我要测试的语法分析模块的错误,反而而是我手动构造的期望输出的错误。在对语法分析部分进行测试的时候,这种情况尤甚。有的时候为了构造样例去写一些测试样例的生成器,但是生成器本身也会发生错误。

针对处理数据量较大的模块,我们该怎样手动构造测试样例呢?还是说,我们应该换一种单元测试的粒度?

现在的理解

即使测试比较麻烦也需要构造单元测试。

虽然测试的复杂性导致测试本身也会出错,此时测试和待测程序是一种互相验证。

但是相比于比较常规的单元测试,如果测试需要的样例/环境构造比较麻烦,,我认为可以将测试工作的优先级适当降低,先完成更重要的工作。

断言

原文

(P70)当你觉得某事肯定如何时,就可以用断言。

问题

虽然书中上下文比较的是断言和异常处理的问题,但是断言(assert)很长时间来是我用着很纠结的地方。就我以往用断言的经验来看,主要在以下一些地方:

  1. 自己不打算写单元测试,用断言来确定程序运行到某个部分确实是按照我所期望的;
  2. 为了获得IDE的辅助,比如当我确定某个向下转型是安全的时候,写了assert obj instanceof MyClass;,后面的语句需要将obj强制转换为MyCalss时,IDE就会帮我添上强转;
  3. 相当于一句注释;

但是有单元测试的情况下,第一类的情况还需要写断言吗?

现在的理解

需要,单元测试是从外部进行测试,而断言是内部进行测试。

构造单元测试的时候虽然是可以知道实现的内部的,但是却无法验证内部某个点的状态,只能验证函数结束后的状态是否正确。

虽然从外部看,如果通过了单元测试,就说明这个函数是正确的,不必验证内部状态是否如预期变化,但是在debug的时候能够方便定位问题。

结对编程

原文

(P80)总之,如果运用得当,结对编程可以取得更高的投入产出比。

问题

这句话给我的第一感觉,就像这句话一样:

如果方法得当,高考前一个月也可以提高300分。

虽然我的类比有些夸张,但意思是一样的:后半句话看起来那样美好,但是它的前提条件就有些说不太清楚了。

书中随后介绍了结对编程的流程与分工,也列举了一些注意事项,甚至还提到了一些结对编程中的交流技巧。但是却忽略了一个问题:什么样的人/团队适合结对编程呢?

后文提到了两人合作的阶段:萌芽\(\rightarrow\)磨合\(\rightarrow\)规范\(\rightarrow\)创造。但同时也指出了,并不是所有的合作都能走到最后一步,可能磨合太多后进入“解体”阶段。最终走向失败的合作有可能是是双方的方式不对,但也可能是双方口味不合,甚至有一方深深排斥自己工作的时候有另一个在边上。

国内为何很少有人做结对编程呢?是确实不好还是属于中国特色? - 小白的回答 - 知乎

这个回答中就提到,并不是所有人都能在交流中保持专注,也并不是所有人都喜欢在一个充满交流的环境中工作,甚至说,很多人走上了软件开发的道路就是因为自己需要安静的环境来保持专注。

选择结对编程,就意味着要让两个人去完成一个人的工作,这本身就是不利于投入产出比的,如果结对编程不能带来额外的好处,就是亏的。也就是说如果一组合作伙伴快速走向解体,那么就很可能是一次亏本的尝试。那么相较于关注怎样进行结对编程,我们是不是更应该关注哪些人适合结对、怎样挑选结对的伙伴呢?

现在的理解

在两次结对编程作业中,我感觉结对编程在“攻坚”方便拥有很大的优势,多人划分任务进行配合的时候,当遇到难点的时候,是很难获得其他队友的支持的,一方面,别人要了解你遇到的问题的详情是需要一定成本的,另一方面,别人也有自己的工作需要做,很可能不愿意放下手头的工作先去帮助别人,此时就往往只能一个人去解决一个困难的问题。但是结对编程的时候,面对一个困难的问题,两个人是可以马上开始讨论交流的,这在重思考轻编码的场景下是有效的。

但是结对编程也存在一些问题,一个是轻思考重编码/配置的场景下,尤其是有现代IDE辅助的情况下,两个人对着一台电脑进行开发工作,很容易走神,互相影响,效率是不如分头行动的。另一方面就是结对编程的环境,结对编程需要交流,需要发出声音,一方面会影响到别人,一方面别人都安静的时候两个人交流是很尴尬且放不开的,此外,结对编程需要两个人在同一时刻同一地点进行编程,这需要一定的妥协。不过最近CodeTogether的推出可以解决这个问题,两个人可以分别在家中进行结对编程,分别使用自己的电脑阅读代码也确实比一个人写另一个人在同一块屏幕上看体验更佳。

典型用户

原文

(P206)怎样才能定义典型用户呢?我们首先要定义用户的角色。正如戏剧中有正面和反面角色,软件系统中也有受欢迎的和不受欢迎的典型用户。

问题

受欢迎的用户是我们软件的目标,自然需要作为典型用户来分析。

但是为什么也要分析不受欢迎的用户?一方面,针对不受欢迎的用户的一些策略,如保密、网络安全等,本身就是软件质量需要考量的一部分,甚至是受欢迎用户的需求,单独把这部分用户拎出来分析也不会起到很大的补充效果。另一方面,确定会有哪些不受欢迎的用户并不容易,比如微信起初就没有把淘宝链接、抖音连接、有偿朋友圈转发等视为不欢迎的,这些问题是随着运营而产生/发现的。

对不受欢迎的用户的分析投入产出比并不高,可以将其去掉吗?

现在的理解

对用户的定义是一个不断完善的功能,随着产品的迭代会慢慢添加新的用户,所以因为不受欢迎的用户不容易进行分析而不去分析是不对的。

相比于被动地考虑如何构建安全的系统,主动地考虑一个不受欢迎用户会如何“攻击”系统是更好的方式,站在一个攻击者的角度更容易发现系统中的问题。

功能说明书

原文

(P215) 第一,定义好相关的概念。第二,规范好一些假设。第三,避免一些误解,界定一些边界条件。第四,描述主流的用户/软件交互步骤。第五,一些好的功能还会有副作用。第六,服务质量的说明。

问题

功能说明书的意义就在于严格地、无歧义的描述软件的外部功能,讲述交互的方式。但是这样的功能说明书必定是很长的,相信大部分用户也不会有闲心来详细阅读功能说明书。那么怎样能快速、有效地引导用户来按照我们设计的方式来与软件进行交互呢?这一部分应当是谁的工作呢?

现在的理解

功能说明书是严格而无歧义的,其最大的作用其实是作为一个标准而存在,应当是一个出现了问题来查阅的手册。

将产品的功能展现给用户,并不应当依靠功能说明书,而应当设计更友好的引导。并且对于用户的引导,可以舍去很多细节,只需要展示大部分场景下的使用方法即可,当用户需要一些细节的时候,此时用户的主观能动性足够他去查阅文档。

引导用户正确地使用产品,首先应当考虑的是软件的设计,按钮、菜单、界面的排布,文字的描述,应当本身就具有解释性,好的设计应当能让用户在没有引导的情况下就能够猜出如何使用特定的功能。其次,考虑一些帮助说明,比如在不直观的功能边上添加说明、添加?按钮、鼠标悬停显示帮助等等。最后才是比较详细的说明书,这个说明书应当具有一定的索引功能,能够帮助用户快速检索到需要的帮助信息。

二、知识点总结

软件工程这门学问有很多 “知识点”, 这门课强调 “做中学” - 在实践中学习知识点。
请问你们在项目的 需求/设计/实现/测试/发布/维护阶段(一共6 个阶段)中都学到了什么“知识点”,每个阶段只要说明一个知识点即可。

需求阶段

需求阶段最重要的就是要进行实地调查,要找到用户真正需要的需求。这样的需求应当满足两个条件:

  1. 有一定规模的人有这样的需求;
  2. 现有的软件服务在某些方面无法完全满足人们这样的需求;

当我们明确这样的需求之后,我们才能在设计的时候有方向。比如我们做C语言的问答的时候,最开始为了环境配置、报错信息的处理做了额外的设计。但是当我们拿到CSDN提供的数据集后才发现大部分用户其实需要的是对代码的辅助分析,大部分提问中都有代码,用户更希望获得代码方面的支持。这就导致alpha阶段的很多设计打了水漂。

设计阶段

设计阶段最重要的是组员意见的充分交换。项目中,RESTful设计的时候,就主要是我一个人完成设计,然后再以类似“公示”的方式征求意见,这就导致RESTful设计没有经过充分的意见交换,后续需要频繁添改接口。

实现阶段

实现阶段需要注意的问题是要遵循设计。开发中我们在控制层和用户服务部分未能够遵循设计,控制层的工作是根据权限拦截请求,而用户服务负责用户的登录注册控制,而我们在实现的时候将两者的实现混合在了一起,用户服务更像是服务于控制层的辅助工具,所有用户控制全部由控制层完成,这就导致用户服务在测试的时候比较困难。

测试阶段

单元测试存在难以平衡的两个问题,一个是测试的有效性,一个是测试的复杂度。当我们要保证测试的有效性时,我们需要为每个模块定制单元测试,要对模块可能的输入、期望的输出进行覆盖,要为模块的依赖构建Mock对象,但是这样测试本身就具有相当的复杂度,甚至可能测试一个模块的代码比这个模块的实现还要多。而降低测试的复杂度,进行组粒度的测试,追求覆盖率的时候,对某些模块的测试就不够充分,当这个模块进行复用的时候,就存在潜在的问题。

在人手和时间有限的情况下,我认为应当先聚焦于关键模块、容易出错的复杂模块,优先测试实现比较复杂的模块,即使整体覆盖率较低,也可以保证系统的正确运行。在后续有富裕的人手的时候再补充次要部分的单元测试。

发布阶段

发布阶段一个重要的是宣传。我们组的宣传就停留在朋友圈,并没有吸引到多少用户来使用,但是很多其他组制作了视频、联系了相应科目的老师等等。宣传应当能够吸引人的注意,比如视频、图像,而且要向潜在用户较多的地方进行投放。

维护阶段

生产环境不同于开发环境,是要实际面对用户的隐私和潜在的攻击的。所以要注意要禁用包含敏感信息的日志,系统或各个组件的权限按需分配。在维护的时候不免需要多次进行部署操作。为了避免部署时的疏漏,有两个方法可以帮助部署:

  1. 构建部署脚本,甚至直接自动持续部署,而不是手动部署。脚本可以反复测试,并且只要辅助脚本构建正确,使用脚本部署发生疏漏的概率就远远小于手动部署;
  2. 不同环境使用不同的配置文件。在实现的时候,将可以配置的选项做成配置文件的形式,这样就可以通过不同的配置文件来区分开发环境和生产环境的配置,避免忘记修改某些参数导致的隐私泄露问题。

三、项目总结与心得

结合自己在个人项目/结对编程/团队项目的经历,谈谈自己的理解或心得。

一路走来,关于如何做一个“软件”,总的来说经历了一个从“编码是最重要的一步”到“编码只是重要的一步”的转变,不可否认完成一个软件,编码绝对是主要花费时间的事情,但是有很多其他需要做的事情往往也起着决定性作用。

我简单用这样一个公式来描述一个软件的质量:

\[软件质量 = 设计质量 \times 编码质量 \times 运维质量 \]

这样就可以比较好地描述我现在对于编码和软件的理解,编码固然是重要的一环,但是差劲的设计或者差劲的维护依然可以让这个“程序”成为一个质量低下的“软件”——没人用,或者用户体验不佳,无法长期留住用户。设计、编码、运维这三个之间是乘算关系,就是为了说明,其中每一步都做得好,软件整体就会变得特别好,只要一步做得很差,就能导致整个软件被拖累,一个软件的生命周期中,我们应当兼顾着三个层面,不应当过度牺牲某一步。

设计,包括需求分析、功能设计、结构设计,以及UI设计。

  • 需求分析,是面向“人”,即用户的,要做的是从日常的观察中找到潜在的需求,然后通过实际的调研明确具体的用户痛点,以及市场现状,要明确用户需要软件在哪些方面做得好,那些方面达到“及格”水准就行,以及确认“及格”水准是个什么样子。
  • 功能设计就是通过一系列功能的设计,功能间的配合,来满足用户的需求,罗列出需要哪些功能,勾勒出一个大致的形状。
  • 结构设计就是从编码出发,设计一个高内聚低耦合的可维护系统,能够提供设计中的需求。
  • UI设计,交互设计,包括软件的交互逻辑,以及用户看到的界面,这是我最近渐渐意识到一个重要的方面。我们的团队项目,alpha阶段的UI设计得很糟糕,不仅功能的排布比较困难,而且看起来就很简陋,没有使用的欲望,但是beta阶段我们专门开讨论会研究了UI的设计,一起画出了期望的UI的样子,beta阶段不仅功能的排布比较从容,而且用户界面就讨喜得多。好的界面设计可以让用户直观地了解到交互方法,好的交互设计应当是自然而明确的。在人们审美越来越高的今天,UI的设计也越发地重要,苹果就凭借着流场而自然的软件UI设计获得了大量用户的青睐,无论IOS还是macOS,软件的加分确实成为了苹果占据如今地位重要的一环。如今各大手机厂商也都注意到UI设计的重要性,从以前专注于硬件的差异化,慢慢到现在开始注意用户交互体验上,都说明一个好的UI十分重要。

运维,维护,是对于程序中存在的问题的修复、新的功能的添加、用户问题的反馈,运营,则是要加上程序配套的说明书/教程、宣传。在完成团队项目的时候,我们有很多功能要借助第三方的类库/软件来实现,在同类别进行比较的时候,我是按照“哪一个软件/类库能够尽快用上”作为标准来选择的,但是在最后回过头来看,被我选中的软件/类库,都是其文档/引导做的比较好的。在更大的范围内,一个友好的欢迎页,容易找到的说明书文档,能够(相对)及时获得反馈的技术支持,能够很大程度上地吸引用户使用我们的软件。

而编码,则是考研开发人员对技术的掌握能力,将设计落地实现的过程。我把测试归在编码质量中,因为测试是验证程序是否实现了规划的功能的依据,测试是从使用者的角度来考虑一个模块/程序,开发是从模块内部的实现来考虑这个模块/程序,开发和测试应当是相辅相成的。API文档也应当在编码这一步产生,API文档不同于设计阶段的文档,它的目的是为了代码的可维护性,让后来接受项目的人或未来的自己能够明白一个模块的作用,它是代码的自然语言抽象,也应当和开发工作绑定在一起的。一个能够持续的编码过程,应当就是开发、测试、API文档同时推进的过程。

经过这一学期的软工的学习和尝试,让我对软件的生命周期有了新的认识,再加上这学期的团队项目有些意难平(一方面最后选择的题目我确实有些不喜欢,另一方面时间有些短,很多技术也是现学的,实现有些糟糕),让我有些手痒痒,所以我最近在打算开发一个在线的工具集,作为长期的一个练手项目。

我给它的定位就是一个小的工具集,能够解决很多不大不小的问题,来锻炼我自己不断发现痛点、给出解决方案的能力,也锻炼一下我对软件生命周期的控制。我希望它能够解决那些不够大不够频繁,以至于特地为其开发一个软件有些浪费,但是又确实存在的问题。

当前能够想到的需求有这些:

  1. 本地用markdown写完博客之后,上传博客的时候,需要将图片一个一个单独传上去,而无法保留原本的路径,我希望有一个图床工具,能够将和图片一起打包成zip的markdown博客进行解析,将图片适当压缩保存在图床中,将markdown文档中的相关路径替换成图床的url,这样在上传博客的时候只需要将zip上传到图床,然后将转换后的markdown粘贴进博客网站即可。将来如果要建博客站,也可以调用它来获得更好的服务。
  2. 文件编码的识别和统一工具。当代码中存在中文的时候,文件的编码就比较重要了(比如上学期写编译器的时候因为要输出中文,所以上传OJ的时候要统一编码),但是主流IDE等工具作为“英语世界”的开发者开发的,对于文件编码的支持其实并不是那么好,比如无法方便地将工程中已经存在的文件的编码转换为特定的编码。我希望有一个转换工具,能够批量识别文件的编码并转换成特定编码,我希望能够指定字符集,比如常用简体中文,能够排除掉一些备选的编码格式,这个格式似乎合法,但实际上按照这个编码格式来转码会把整个文件的中文编程一坨人类无法阅读的东西。

后续可能还会有新的需求被发现,添加进来。

结构设计上,我计划采用微服务的思路,有一个统一的用户管理服务,有一个统一的主页和导航,然后每一个小工具都是一个微服务,这样在添加新的工具的时候不至于大动干戈,而且这样也利于其它感兴趣的开发者参与开发,他们完全可以独立地开发一个微服务,而不至于对其余部分造成破坏。

宣传规划上,一方面通过SEO(搜索引擎优化),让搜索引擎能够爬取到小工具的信息,并且爬取到的信息能够匹配尽可能多的相关关键词,另一方面通过知乎、百度经验、百度知道、思否、博客园、csdn、简书、B站等问答社区、内容社区来引流。

物理支持上,前些天趁着618优惠购买了一些云资源,打算等项目推进一段时间后再进行备案。

项目管理上,使用github,虽然网络时不时有些不稳定,需要科学方法,但是相比于国内比较方便的gitee,github上大多数功能面向开源软件都是免费的,尤其是CI/CD工具,而gitee在这方面似乎都是收费的。而gitlab的话,如果在云服务器搭建一个gitlab似乎会占用不少资源,而且对部署的探索搞不好会把服务器搞崩几次,所以最后还是选择了github来作为主仓库,考虑在gitee创建镜像仓库。

这个项目可能会持续很久,可能会有很多年,希望自己能够坚持下去。

仓库地址:https://github.com/SnowPhoenix0105/ToolSite

目前还是个空壳子,还没有开始是因为我需要先学一下前端技术:)

posted @ 2021-06-30 20:54  SnowPhoenix  阅读(190)  评论(6编辑  收藏  举报