漫谈技术_需要注意的软件开发模式

在知乎上看到一篇文章,从中看到了自己的很大不足,转到自己的博客上,勉励自己,希望激励自身,提高自己的知识水平!

作者:覃超
链接:https://zhuanlan.zhihu.com/p/20941886
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 各位,这个周末小魔王介绍一篇很不错的文章 — 关于程序开发的模式总结。作者是我的好朋友 — 陈天。
 陈天同学和我在知乎上相识,后来线下交流过几次。我们俩背景很相似,都是技术出身,也都喜欢写blog来讨论技术和职业发展。天哥先拿了创新工场的投资开始创业做在线旅游,而我则刚刚加入Facebook进行例行工作。而后创业结束的他,回到Juniper并且随之transfer到美国,并且直接通过高级人才引进的渠道直接拿到绿卡,非常厉害。天哥的这篇“软件开发模式”我看了之后非常喜欢,所以这里推荐给大家,同时最后附带陈天的公众号,欢迎订阅 :D

—– START —–

 做软件开发十数年,见识了形形色色的开发者,和各种各样的奇葩软件开发模式。本文跟你侃侃这些软件开发模式及其特点。

IDD(IDE-Driven Development)

大巧在所不为,大智在所不虑。

– 荀子 天论

 IDD,也就是 IDE 驱动开发,几乎是初学者步入软件开发殿堂的必经之路。IDE 为开发者屏蔽了很多细节,并且几乎不用配置(相对于 vim / emacs / sublime)就可以使用代码自动补全,代码跳转,搜索,以及签入签出等软件开发中将会使用到的几乎所有工作。

 然而,它带来的危害也是显而易见的,陷入 IDD 开发模式太深,开发者离开了 IDE 便像无头苍蝇一样,几乎不知道怎么读代码,甚至不太会写代码,不懂怎么编译,不会自动化完成常见的任务(比如 android 项目从编译到打包到 apk 分析),甚至连基本的 git 操作都无法完成。而这些被屏蔽的细节都是软件工程师的基本功,就像弹钢琴的基本指法一样,是必须修炼好的。试想一下,如果你只会在 IDE 里读代码,离了 IDE 的跳转就不知道怎么追溯代码的脉络,那么有的代码你只能通过 browser 阅读怎么办?不读了?如果你只会在 IDE 里写代码,有天你只能 ssh 到服务器上写代码(表笑,有很多公司这么做,也有很多场合需要这么做),你难道就不写了?

 此外,IDD 开发者如果不能及时从对 IDE 的极度依赖中抽身而出,很容易转化成下一类开发者。

适用人群:初学者
舒适指数:五星
危害指数:四星
解决之道:学会任意一个 shell 下的编辑器

DDD(Debugger-Driven Development)

合格的程序员的代码是一行行写出来的;不合格的程序员的代码是一行行调出来的。

– 某子 程序员的自我修养

 DDD,面向调试器开发,是 IDD 依赖到一定程度的必然反应。这种开发模式的典型表现为:写出来的代码不知道对不对,从头到尾设置无数个断点,然后进入到调试模式,一个断点一个断点跟踪。发现一个问题,解决一个问题(也许引入一个新的问题),直到所有断点走数遍,所有遇到的问题被消灭,抹一抹头上的汗,心里骂上一句:妈的,这段代码老子(娘)终于调通了!

 DDD 是非常浪费程序员生命的一种开发方式,它让我们把有限的时间精力都浪费在无穷地瞎忙活之中。因为断点过于唾手可得,程序员会变得懒于思考,懒于设计,甚至写完了代码都懒得自己看一眼 —— 大手一挥,无数个红色的断点就设置好了,反正出了问题我调就行了呗。

 其实很多 concurrent 的问题,靠调试器是无法复现和解决的,更要命的是,DDD 还容易使程序员陷入 tunnel vision —— 我们太过于关注眼前的那一点点状态,而忽视了全局。

适用人群:入门者
舒适指数:四星
危害指数:五星
解决之道:多花时间思考和设计,使用 TDD(Test Driven Development),如果非要追踪状态,合理地使用日志(log)而非断点

PDD(Print-Driven Development)

王好战,请以战喻。填然鼓之,兵刃既接,弃甲曳兵而走。或百步而后止,或五十步而后止。以五十步笑百步,则何如?

– 孟子 梁惠王上

 看到 DDD,做嵌入式开发的 C 程序员笑了,我们没有那臭毛病 —— 大部分嵌入式开发的场景,要么设断点太麻烦(需要 remote debugger),要么无法设置(比如说很多内核态的代码),所以我们的代码都是写出来的。不过,这部分程序员大多自发聚集起来,立起一个山头:PDD,也就是面向打印开发。其开发逻辑有如下面的代码:

1
2
3
4
5
6
7
8
// ...some awesome logic...
printf("haha, hit 1");
// ...some more logic
printf("!!!!!! hit 2");

if (awesome_check(awesome_state)) {
printf("####### condition captured")
}

 后者 iOS 里面的 NSLog,或者 Android 里面的 println,或者 console.log (懂的!)

 PDD 和 DDD 相比,是旗鼓相当,半斤八两。PDD 开发者加入的代码,和 DDD 开发者设置的断点一样,头疼医头,脚疼医脚,哪里的状态不对了,或者和预想的流程不一致,先不考虑设计上是否有缺陷,为什么会出现这样的结果,而是不管三七二十一,加个打印(设个断点)看看状态是什么。如果没抓对位置,那么就继续细化,直到很被动地找到原因为止。

适用人群:入门者
舒适指数:三星
危害指数:四星
解决之道:多花时间思考和设计,合理地使用深思熟虑过的日志(log)而非用完即扔的打印信息

BDD(Bug-Driven Development)

以管窥天,以蠡测海,以莛撞钟,岂能通其条贯,考其文理,发其音声哉。

– 东方朔 答客难

 看到 BDD,也就是问题单驱动开发,相信大家都相视一笑。本来这里我想用 TDD(Ticket-Driven Development),更接近我的原意,为了不和 Test-Driven Development 混淆,故而只好改成 BDD。这可能是我们最熟悉的开发模式了 —— 在一个业务稳定的软件公司(甭管规模大小),勉力维护现有的代码,小心地添加新功能是多数程序员的主要职责。在这些公司里,与其说我们是工程师,不如说我们是补锅匠。看不懂代码?没关系,只要你会读日志(出错信息);解决不了问题?不打紧,能找到 workaround 把问题绕过去也可以,更有甚者,遇到神问题,看不懂,想不明,解不了,还没有 workaround,大笔一挥:not reproducible,就把问题关了,几个月半年后,说不定自己已经去补别的锅了。

 BDD 开发者修的都是防御之道,一手华丽的 defensive coding skill,堪比仙女座的星云锁链,把系统的每寸肌肤防得滴水不漏。如果你看到一段代码,函数 A(a, b, c) 调了函数 B(c),即便参数 c 在 A 的入口进行了详细的检查,在 B 中也还会再度详细检查(哪怕 B 只有 A 调用),那么这代码一定是 BDD 开发者开发的。

 BDD 开发者的视野往往很窄,所学所用皆局限于已有的系统,由于系统并非自己所写,阅读代码又是就着问题去追根溯源,所以对系统的理解会比较狭窄。这并非程序员能力不足,相反,能做 BDD 开发往往都是很有经验的程序员。然而,他们被公司的需求所局限,整日被 ticket 追逐,疲于奔命,在工作中无法有效提升,渐渐迷失在一个又一个犯罪现场,眼界变得越来越狭窄。

适用人群:业务稳定的公司里的『高级程序员』
舒适指数:二星
危害指数:四星
解决之道:自己主导一个项目的开发,或者,跳槽

RDD(Rat-race-game-Driven Development)

From day to day, we programmers dreamed of being an expert inside the team/company; however, when that day really comes we trapped ourselves.

– 程序君 Programmer’s dilemma

These walls are kind of funny. First you hate ’em, then you get used to ’em. Enough time passes, gets so you depend on them. That’s institutionalized. They send you here for life, that’s exactly what they take. The part that counts, anyways.

– Red, The Shawshank Redemption

 RDD,老鼠赛跑驱动的开发,是指那些整个职业生涯都在原地打转的开发模式。Rat race game 是『富爸爸穷爸爸』中的经典例子 —— 老鼠在环形的笼子里拼命地奔跑。

 每个程序员都在努力地成为他所在领域的专家。成为专家的代价是巨大的,我们需要付出经年累月的努力,才能爬到峰顶,带上「专家」的帽子。然而,成为「专家」往往意味着一条路走到黑 —— 一来之前的累计的惯性实在太大,掉头的机会成本太大;二来你的雇主因此而付你更高薪水,所以你只能在这个方向上不断前进。

 我们知道,软件是个工程的活儿,并非科学。科学的路上走得越远,打出的装备越稀缺;而在同一家公司或者同一个行业里搞软件的,走得越远,就有越多的路是重复再走。你可能精于 chip verification,于是从第一份工作到第 n 份工作,干的都是 verification 的活,直到有天惊闻这个职位被砍;你可能是web 桌面化的好手,extjs 玩得风生水起,公司依赖你的技能开发产品,直到有一天,这个产品没人用了,你到市场上一看,你的 extjs 九段的功力,比不上玩 react 才刚刚两段的水平受欢迎。

 RDD 就像 Red 口中所述的那些高墙。当你沉浸在 RDD 中不能自拔时,悄悄地,你曾经引以为豪赖以生存的能力成了你生存下去的最大障碍。

适用人群:大公司里的『专家』
舒适指数:五星
危害指数:四星
解决之道:在公司里换不同的团队,或者,跳槽去更有挑战的公司
以上写了这么多,总有一款符合你。你有没有找到自己心仪的开发模式?如果没有,恭喜你;如果找到了,别慌,有则改之,无则加勉即可。

这里小魔王受此启发,再自己补充几个(持续更新中):(@刘凯 在评论里的提示)

DDD2:(Deadline Driven Development)

 简而言之就是:平时不做,等到deadline到来的时候狂做。这个模式可以远远回溯到学生时期,作业开始布置下来后,先放在一边,干其他喜欢的事情;到了最后要交之前狂写一通。在程序开发上也如此,小魔王和我认识的旁边的人多多少少也经历过如此的编程模式 — 毕竟每个人都或多或少有拖延症或者趋于懒惰吧。

 Deadline Driven Development 最刺激的就是死线快到的时候感受到的焦虑和不适;于是马上摆正心态,摒弃掉平时的各种爱好或者坏习惯,开始狂写程序。这时的效率之高,连自己都被自己惊讶到。最后大概20%的概率赶上了deadline,另外80%机率是错过,然后给PM或者老大说,不好意思要再延后一段时间。这就是之前在行业里一直提到的问题 “为何软件的开发周期如此难以预测” ?

 DDD2危害很大,让人无法很有效率地利用自己的时间;同时在deadline到来的时候,很多开发者选择熬夜,对身体很不好。但是最后冲刺开发时候的高效总是让开发者觉得自己的效率和编程能力极强,进而对DDD2有瘾,甚至觉得只有最后deadline到来的时候才能激发出自己的强大潜能。O(∩_∩)O~ 偷笑。

好的,与大家共勉,戒掉。

CDD:(Comment Driven Development)

 Comment Driven Development:注释驱动式编程。简而言之就是“不写注释不舒服斯基”。

 这里说个比较有意思的地方,在中午里所有的注释都统一称之为注释,但是在计算机里的官方语言英语里,其实注释分为两种: Documentation 和 Comment。

 Documentation指的是在 文件类,Class前面,以及所有 public method 上面的注释。这部分在很多公司(比如TopCoder、Google、Facebook)几乎都是必须要的,很多规范放到了 Linter(一般在code review的时候自动触发)里面,强制执行。

 Comment指的是在 逻辑代码 里面的注释,一般是 // 写出来的。有趣的是,在CMU的java课上和在Facebook的 Code Review 里是尽量杜绝写这样的注释,因为这种注释要不就是多余,要不就是你的代码逻辑很混乱,需要用注释再来解释一番。所以,很多时候,我们说 comment 是有害的,而 documentation 是鼓励的。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Extracts and calculates the attachment information from the message.
*
* @param message the message with the attachment
* @return the attachment info
*/

public MessageAttachmentInfo getAttachmentInfo(Message message) {
// Validate the input param
Preconditions.checkNotNull(message);
MessageUtil.checkPermission(mViewContext, message);

// Initialize the size, counter, fbid
long totalAttachmentSize = 0;
int attachmentCountPhoto = 0;
int attachmentCountAudio = 0;
int attachmentFbid = 0;

try {
// Loop the pending send attachments
for (MediaResource attachment : message.pendingSendAttachments) {
if (attachment.getType() == MediaResource.Type.AUDIO) {
// For audio attachment, calculate the audio attachment count and total size.
totalAttachmentSize += new File(attachment.getUri().getPath()).length();
attachmentCountAudio ++;
} else if (attachment.getType() == MediaResource.Type.PHOTO) {
// For photo attachment, calculate the resized photo count and total size.
totalAttachmentSize += new File(attachment.getFileName()).length();
attachmentCountPhoto ++;
}
}

// Right now this is either 0 or 1.
if (!Strings.isNullOrEmpty(message.pendingAttachmentFbid)) {
attachmentFbid++;
}
} catch (Exception e) {
// Ignore any error that could occur due to file IO and set error values
return INVALID_INFO;
}

return new MessageAttachmentInfo(
totalAttachmentSize,
attachmentCountPhoto,
attachmentCountAudio,
attachmentFbid);
}

 这一段代码是典型的CDD风格。除了最前面的method doc之外,函数里面的 comment 显得冗余且幼稚,全部都应该去掉。同时代码里有一处隐患,就是 if - else if 之后对于 else 没有任何处理。这是典型的埋坑行为,如果此后支持其他更多的 attachment,那么这里的代码很容易漏处理一些情况。建议写成:

1
2
3
4
5
6
7
8
9
10
11
for (MediaResource attachment : message.pendingSendAttachments) {
if (attachment.getType() == MediaResource.Type.AUDIO) {
totalAttachmentSize += new File(attachment.getUri().getPath()).length();
attachmentCountAudio ++;
} else if (attachment.getType() == MediaResource.Type.PHOTO) {
totalAttachmentSize += new File(attachment.getFileName()).length();
attachmentCountPhoto ++;
} else {
Preconditions.checkFail("Unsupported attachment type.");
}
}

 这样后续如果加上其他attachment type,在这里可以直接暴露出来。

 所以,去掉所有代码里的comemnts,用简洁明了的代码来”自解释”您的代码逻辑。有朋友可能会问:如果当代码的逻辑非常复杂,很不容易看懂的时候怎么办? — 答案就是:Refactor your code:将复杂的逻辑拆分成一个一个的子操作,每个操作extract成private method,然后用 method name 和 method doc 来表达你这个操作的含义。如果实在一些地方的代码逻辑上有空或者很奇葩的处理,这时候没办法,写coment吧(但是不要随意地将近)。