Featured image of post 《现代软件工程》课程 个人博客作业 1——就《现代软件工程》提 5 个问题

《现代软件工程》课程 个人博客作业 1——就《现代软件工程》提 5 个问题

快速阅读《构建之法 现代软件工程》 的全部或部分章节,提出五个问题

如题所示,这篇博客是一项课程作业——我需要在读完《构建之法》这本书之后提出 5 个问题。

其实一路读下来,我产生的问题远不止五个,毕竟我一个学心理出身的,没有接受过什么计算机科班的培训、编程经验全靠大量自娱自乐的项目积累,这本书里提到的很多概念我并没有接触过、或者是见过但从没想过要去进一步了解。不过,针对这些陌生的概念去提问题然后写一篇博客实在是太水了,这部分内容我还是计划随着课程开展慢慢去学习;在这篇博客里,我更想提问的是一些在过去开发中就已经隐隐约约让我迷惑、在阅读本书过程中让我更深入思考但仍没有得到解答的一些问题。

1 单元测试的编写

需要澄清,我理解并认可单元测试的重要性,但是我不太能理解的是,如果按照书里第二章说的那样,“最好在设计的时候就写好单元测试”,难道不会让我们陷入过度设计的陷阱吗?

在一开始开发的时候,很有可能的情况是我的脑子里根本就没有一个完整的清晰的架构——我知道我想要做一个什么东西,我知道我大概想要给这个东西设计一些什么功能,但我不知道它的全貌应该是什么样子。这种时候,我认为更应该专注于写一个可以运行的最小 demo,而不是一边设计一边做单元测试。

比如说我之前用 Flutter 写了一个移动端心理学实验编程的框架,我知道我要效仿那些经典的实验框架做一个事件系统,但是这个事件系统应该包含哪些事件、事件的回调函数应该要求用户传什么值进去,我没有办法确定;我知道我要学习 jspsych 设计一个插件系统,但是创建一个插件的实例的时候要让用户传什么值进去、这个通用的插件系统内部的架构该怎么设计,我也不知道。

过去那些框架可能是基于 JavaScript 或者是 Pygame,具体的实现细节和 Flutter 差别甚远,直接照搬要么导致我写不下去、要么导致最后的产品用户用不下去。为了解决这些问题,我必须先有一个框架的雏形,然后自己使用一下这个框架,才能知道一些接口该怎么设计。

我的理解是,单元测试的好处之一是强迫我们编写高内聚低耦合的代码,但是如果我一开始就花大量时间设计我的代码,设计完发现这个架构不太合适然后再推倒重做,不是耽误事吗?

再有,我既然要写单元测试,针对这种框架类的程序,我肯定要考虑用户往里传入各种奇奇怪怪的东西然后进行相应的 error handling,那我就又将大量时间花在考虑用户可能搞什么幺蛾子而不是先做一个 demo 出来。然后我精心设计一通,做完 demo 后又发现不能这样那样设计,更新了设计之后又得去更新错误处理,这样是不是很低效呢?

2 关于结对编程

关于书中说的结对编程,如果我没有理解错的话,是要一个人在这写,另一个人在旁边随时审。

那这是否就意味着,随着审核的那位一直看着编程那位的进展,前者的思维也是随着后者的代码编写而逐渐推进的。如果现在二人在编写一个极其复杂的逻辑,那审核的那位会不会也被编写程序的那位的错误逻辑带进坑里呢?毕竟很多看似很愚蠢的错误在错误产生伊始看起来并不明显。我觉得 code review 的一大好处就是避免当局者迷,但是结对编程这种模式不是让两个人都陷进去了吗?

我之前打数学建模比赛的时候,甚至是三个人一起编程,一个人写,两个人审,当时我们写的那个逻辑蛮复杂的,负责编写代码的那位同学就一边写一边讲代码的实现逻辑,把我们俩都绕进去了,最后是我听得犯困走神了开始回味刚才听的那些代码逻辑,才反应过来这里面有些地方不对劲。

这又引申出一个问题,如果我作为审核的那个人,不能立刻指出问题在哪、只是模糊地感觉到这段逻辑可能存在问题,我是要打断我的搭档吗?这样是不是在浪费时间呢?我该如何让我的搭档明白这个我自己都说不清楚的问题呢?

所以我觉得结对编程就是一个很理想的做法。如果两个人水平接近,那真的很容易出现俩人都看不出来的问题;如果有一个人水平明显高出来很多,那这个时候另一个人的存在,是不是有点凑热闹的嫌疑?

3 开源的团队模式

第五章提到了开源社区的很多种团队模式。

我给一些项目贡献代码的时候,maintainer 的态度就是这个功能不错,可以加上,这种团队氛围就比较轻松。但是后续就是,这个工具里面多出了一大堆乱七八糟、几乎没什么人会用到的功能,出于兼容性的考虑不敢随便砍掉,但是放在那又给维护增加了大量的负担。

有些项目的维护者则是纯纯独裁者,比如 Bram 维护 vim 就是非常独断专行,然后社区分裂出了 neovim;alacritty 的作者针对很多人都在请求添加的 Ctrl + c 也可以用来复制文本的功能视而不见,导致有人直接在 issue 里面提出单独创建一个 fork 支持这个功能(甚至,我自己都干过,有人提 pr 要我的 neovim 配置引入一个新的插件,我说这个插件加进来没什么用吧并顺手 close 掉了这个 pr,然后过一段时间我看他就自己 fork 了一个版本自己加新功能了)。这种团队就相对比较容易导致生态的割裂。

再有就是核心贡献者掌握重要决策权,这个在 linux 团队、neovim 团队这两个我相对了解一些的团队中都有出现。毋庸置疑这些开发者水平极高,但是他们在一起就变得越来越排外,甚至是对“圈外”的人极具攻击性,比如说 Linus 是公然说 C++ 是垃圾,比如说我自己在 neovim 项目里提 issue 被人算是比较客气地评价“您的这个建议没什么意义”,以及我记得 emacs 也是因为类似的原因更新的频率越来越不行了,那这种少数精英在一起“自嗨”真的是一种健康的模式吗?

我感觉很多情况下,这种模式最后就变成了极客的狂欢,项目下聚集的就是一群 nerd,新人呢?新人被吓跑了。这种做法是保证了代码质量,但是生态怎么办?如果采取更宽松的态度,代码质量怎么保证?这个权衡怎么去做?

4 什么是产品的“典型用户”

这个问题源于第十章,虽然可能和章节主要内容无关——书中提到要从多个角度定义不同的典型用户,并提及了一个“捣蛋鬼”的形象。

这种情况下,我觉得一个“杞人忧天”的团队很容易就陷入一种“自己吓唬自己”的状态:我做了一个网站,我害怕别人 DDoS 我的网站,所以我加入了安全验证机制比如说验证码;我觉得还不够,然后又加入了一种安全机制……理论上 DDoS 防不胜防,但我既不能直接开摆完全不做防护,也不能这样一直加码因为总会损害到普通用户的使用体验(比如说,我相信我的读者们大概率遇到过访问一个网站频繁被 cloudflare captcha 或者谷歌人机验证卡住的情况)。

问就是我真做过类似的事情。疫情期间我妈妈要组织一个班级的线上的高数考试,于是我做了一个简单的在线平台。然后我开始审视这群考生——这些大一新生连电脑都用不明白,我是不是可以直接做一个静态网页把试卷呈现上去、然后让同学们把答案拍照发给我妈妈就行➡️不对,线上考试包有人作弊的,为了防止他们对答案,试题要打乱,于是我就需要做一个登陆认证功能,把试题顺序和考生信息绑定,防止他们刷新网页后试题顺序又乱了➡️这不够,要是想作弊可以找枪手,我要锁 IP 锁设备,一个同学的卷子只能在他自己的设备上打开➡️不行,可以拍照发给枪手,双机位吧➡️……

最后,我发现这一套确实把作弊防住了,但是把这群小孩也折腾得够呛,原因包括:

  • 考试平台链接是通过 qq 发送的,但我没有时间给我的服务器备案,所以交给他们的是 ip 地址也没有做 https,结果好多人直接在 qq 里打开这个网站收到提示网站不安全
    • 剩下的人中,一部分人没见过 ip 地址形式的网址,于是把它输入到了百度里面
  • 有些人一开始在手机打开了这个网站,发现观看体验不好(因为高数那些式子蛮长的),想要换到电脑上查看,结果因为我根据 header 锁了设备,导致在电脑上打不开
  • 我那时候刚学 web 不久,没什么处理并发的经验,而这个班级有 100 多人,于是考试一开始他们直接把我的服务器的下行带宽挤爆了,半数以上的人一开始加载不出试卷
    • 有稍微会用一点电脑的人猜测是不是浏览器问题,在卡住之后换了个浏览器,然后被我的 request header 校验机制卡住了

结果就是,我的手机被群里的考生们的消息疯狂轰炸,我因为差点搞出教学事故来被吓得够呛。

所以在这个案例中,我的典型用户怎么定义?如果只是事后诸葛亮的话,那我的典型用户自然是一群电脑都用得不熟练、可能压根不敢作弊或者是没有能力绕过双机位限制作弊的大一新生,但是一开始我编写这套系统的时候我并不知道他们当中有多少是电脑玩得很溜、为了成绩可以无所不用其极的人,我该不该花精力去防他们?我该不该为此牺牲那些不会用电脑的同学的考试体验?

那么回到这一节一开始提出的问题,这种时候,我该怎么去权衡这个“度”?会以多大程度努力来破坏我的网站的用户才应该同样被划入“典型用户”?

5 如何写好 spec

同样是第十章,关于 spec 这个问题,书中以系鞋带为例的时候提到要“定义好相关概念”。如果我没有理解错的话,spec 其实就是给用户的文档,那么用户文档该怎么设计才能做到满足不同层次的用户呢?

比如说 Django 的文档,我在刚学 web 的时候觉得根本看不懂,因为 tutorials 那里涉及到了一堆我根本不知道是什么意思的概念;但后来,我在通过别的途径学了后端之后,又觉得这个文档其实不错,跟着做下来是可以大概理解 Django 的一些基础部分的,那么这个文档算不算好呢?

再比如 Arch Linux 的文档,非常非常详细,甚至其他 Linux distro 的用户都将其奉为圭臬,但是一个新手在刚阅读这份文档的时候,很可能因为大量的内容而感到 overwhelmed——明明只想查一个很简单的内容,其对应的解决方案可能也就是一两句话的事情,但是因为内容过于详细所以对该去哪里找到想要了解的内容感到迷茫——这种文档算不算好呢?

这方面我自己也是比较有体会的。我在给我的 neovim 配置写文档的时候(顺便把文档放在这里,供大家评判一下算不算是好的文档)就在想,愿意来直接用别人的 neovim 配置的基本是不太熟悉 neovim 的,那我的文档是不是涉及了太多不基础的概念?但是也有不少熟练的 neovim 用户给我留言,说我的配置值得学习,那对于这部分人来说,我的这份文档是不是太啰嗦呢?

我感觉写文档和做教学可能也挺类似的,我以前给一个课题组的同学们培训一个心理学实验编程框架的时候要讲 JavaScript,我就开始讲:“在 js 中,定义变量可以使用 let 关键字……” 接着我就被那个组里的老师打断了:“绍彬,我们组里好多学生没接触过编程,你能给他们讲讲什么是变量吗?”

在那之前,我写过一版使用该框架的教程;在那次培训之后,我就同样的内容写了一本书,但因为那次教学经验,我的书的内容量相比之前的教程至少翻了一番。自然,基础不好的人会去看我的书,已经有了 js 语言基础的人如果嫌啰嗦则可以去看我写的简单版的教程。

但是,教程和文档的区别在于,教程我可以写好几版,但是文档一般只有那一份,所以这里又回到了前两个问题一直在纠结的点——“度”的问题。我该把我的文档写得多详细?我的用户知不知道按下 Ctrl + c 是要在按下 c 的时候不松开 Ctrl?我该把我的文档写得多简略?我的用户会不会因为在浩如烟海的、他们已经熟知的内容中找不到他们所不知道、迫切想要了解得到内容而感到烦躁?

到底怎样的文档才能真正让不同水平的用户都能获得自己想要的信息?

使用 Hugo + Stack 主题构建