first commit
This commit is contained in:
65
专栏/程序员进阶攻略/00开篇词程序行知:走在同样的路上,遇见自己的风景.md
Normal file
65
专栏/程序员进阶攻略/00开篇词程序行知:走在同样的路上,遇见自己的风景.md
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
00 开篇词 程序行知:走在同样的路上,遇见自己的风景
|
||||
你好,我是胡峰,从毕业到今天,在程序这条道路上已经走了十多年了,前期在金融、电信行业写写程序,最近七年在互联网行业从事电商应用相关系统的技术工作,也一路从程序员成长为了一名架构师。
|
||||
|
||||
今天这个时代,对于程序员来说是一个特别幸运的时代。每一个有追求的程序员都希望能获得快速的成长,但成长的道路没那么平坦和舒适,一路上充满了崎岖、障碍和迷雾。
|
||||
|
||||
同样,在我成长的道路上,我也走过很多弯路(更详细的故事,在后面的文章中会分享),这一路上我也有过迷茫,有过困惑。
|
||||
|
||||
如今,经历了这一路,当我回顾时,看见了一条渐渐清晰的成长路线。而幸运的是,在我行路的过程中,从七年前开始,我断断续续通过写作记录了这中间的所见、所感、所惑与所思。今年,我一直想找个时间去好好回顾并梳理下这条路线,而在一个适当的时机,遇到了 “极客时间”,所以这就给了我契机以专栏的形式来完成这件事。
|
||||
|
||||
在做这个专栏的过程中,我确信,一方面通过适时地驻足、回顾与梳理,它能帮助自己更好地认识到:我是如何从昨天走到今天的,并指导自己走向更好的明天。另一方面,程序(IT)行业还在高速发展,走在这条路上的人越来越多,而我对自身成长路径的反思与认知,想必也可以帮助到很多走在同样路上的人。
|
||||
|
||||
如今看,在这条路上我起步不算晚,但“永远有走在你前面的人”,当年他们留下的 “脚印” 和路径也给予了我很多的启发与指引。所以,这个专栏的意义在于,它提供了另一种可供参考的路标,正如题中所言:“走在同样的路上,遇见自己的风景。”
|
||||
|
||||
所以,这是一个关于程序员成长路径的专栏,它会围绕程序这个行业、程序员这个职业,画出一条清晰的成长路径。在这条路径上,有不同的成长阶段,会面临各种不同的问题与困惑。我会结合自身成长路径上面临的实际问题、设身处地去思索、分析、拆解这些问题,并给出可供参考的答案。
|
||||
|
||||
因为这是一个关于路径与行路的专栏,所以也就否定了另一面:它不会提供某一类具体的知识,并且由浅入深地去指导学习。
|
||||
|
||||
后面这一类知识,我称之为 “技能性知识”,需要你在日常的学习和工作中勤学苦练,练成之后你就会成为某一类问题的 “解答题高手”。
|
||||
|
||||
但前一类是关于路径选择和自我认知的知识,它能让你在成长的不同阶段重新认识自己,因为 “知” 从而改变你的 “行”。有时选择对了合适的路,比光顾着赶路要重要得多。
|
||||
|
||||
在这条成长的路径上,有期待、有坚持、有故事,也会有迷茫,以及最后穿越迷雾的曙光。而这个专栏的内容正是关于成长路径的,这条路径在我脑海里已形成了清晰的画面,现在我就把它画了出来,如下图:
|
||||
|
||||
|
||||
|
||||
这是一条成长线的表意图,有两个部分:图上左侧的路径,是匹配不同成长阶段,对应不同职业角色;右侧是一条由不同成长阶段组成的成长线,包括如下:
|
||||
|
||||
|
||||
征途:启程之初
|
||||
修炼:程序之术
|
||||
修行:由术入道
|
||||
徘徊:道中彷徨
|
||||
寻路:路在何方
|
||||
蜕变:破茧成蝶
|
||||
|
||||
|
||||
“启程之初”,是你刚踏上程序之路面临的一些问题和感悟。“程序之术”,是你工作早期的主要内容,以修炼编程技能为主。除了编程写代码,还有很多其他的内容,这是另外一个维度的修行之路,也即 “由术入道”。
|
||||
|
||||
工作数年,成长到一定阶段,你可能会面临一个成长平台期的困扰,在此就进入了 “道中彷徨” 的徘徊期。这些困扰和彷徨很多都关乎选择,这期间是你发出 “路在何方” 之问的寻路期。最后,你坚定了道路,继续前行,前面的路上还有一道 “断层”,突破之后你将会蜕变,最终 “破茧成蝶”。
|
||||
|
||||
而此次专栏正是围绕这6个不同的阶段所写(详细目录如下图所示),为你的成长排忧解难。同时,为了保证内容的连贯性,我会把之前的几篇旧文稍作整理之后作为福利放到这里,你可以免费阅读。
|
||||
|
||||
|
||||
|
||||
最后,你将会收获什么?我想会有如下一些方面:
|
||||
|
||||
|
||||
建立技术学习的体系框架与思维模型
|
||||
梳理清晰的成长与进阶路线
|
||||
扫清成长路上的迷茫与障碍
|
||||
形成明确的自我定位与认知
|
||||
|
||||
|
||||
它也许会是一扇观察的窗口,一张行路的地图,一瓶回血的苦药,一份认知的启发。其始于 “知”,需终于 “行”,在行走的道上,会有崎岖与气馁,希望能在这里,帮你找到未来的方向,给予指引;找到有效的方法,破除障碍;找到理想的自我,获得力量。
|
||||
|
||||
前路很长,而专栏的时间很短,希望我们有缘一起走上一程:走在同样成长的路上,遇见自己憧憬的风景。
|
||||
|
||||
|
||||
|
||||
|
89
专栏/程序员进阶攻略/01初心:为什么成为一名程序员?.md
Normal file
89
专栏/程序员进阶攻略/01初心:为什么成为一名程序员?.md
Normal file
@ -0,0 +1,89 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
01 初心:为什么成为一名程序员?
|
||||
在走上程序的道路前,你不妨先问问自己成为程序员的初心是什么。回首往昔,我似乎是阴差阳错地走上了这条路,正因初心未明,所以早期的路上就多了很多迟疑和曲折。
|
||||
|
||||
人生路漫漫,在专栏的第一个模块里,我会和你讲讲自己走上程序道路的故事,希望这些故事能够给迷茫或者奋进中的你以启发。在人生的不同阶段里,我都喜欢做“复盘”,一方面审视过去的自己,另外一方面思索未来的方向。现在看来,这些有节奏的复盘也给我自己留下了深深的印记,也让我在某些关键节点的决策更加坚决。
|
||||
|
||||
首次接触
|
||||
|
||||
说起我和程序的渊源,大概可以回溯到二十多年前。
|
||||
|
||||
那时,我还在读初中二年级,那是四川一所少数民族中学,硬件条件不是太好。那是1995年,国际友人赞助赠送了学校几台苹果 II 代电脑。作为学校成绩名列前茅的学生,在比较重视分数排名的背景下我还算有点 “小特权”。这点“小特权”就是可以接触这批电脑,所以在那时我为了搞懂怎么 “玩” 这些电脑去学了下 BASIC 语言,然后在上面编程并在单调的绿色屏幕上画出了一些几何图形。
|
||||
|
||||
当时还挺有成就感的,一度畅想将来要考清华的计算机专业。可能,那时觉得清华就是最好的学校,而计算机和编程是当时的最爱。然而,实际情况是上了高中以后学习压力大增,再也没时间去 “玩” 这些电脑了,光应对考试已然应接不暇,渐渐就忘了初中那时的想法。
|
||||
|
||||
现在回想起来第一次接触程序的时候,感觉它们还算是好 “玩” 的,有一种智力上的挑战,但当时也不太可能想到十年后我将会以此为生,走上程序之路。
|
||||
|
||||
彼时,初心未有。
|
||||
|
||||
选择专业
|
||||
|
||||
对我们80后这一代人,高考算是人生第一次重要的选择了吧。
|
||||
|
||||
我那时高考填志愿,都是在考试前进行。高中三年,我都再没接触过程序了,早已忘记当年的想法。高考前,当时觉得自己对物理最有兴趣就填报了南京大学的物理系,应该也考虑过清华什么的,但没什么信心。
|
||||
|
||||
关于兴趣有一个有趣的说法:“往往并不是有兴趣才能做好,而是做好了才有兴趣。”高中后可能觉得当时物理学得还不错,所以就有了兴趣,并在填报高考志愿时选择了物理专业。
|
||||
|
||||
后来高考的结果,一方面信心不是很足,另一方面单科数学发挥也失常。南大的物理系没能上成,落到了第二志愿东北大学,调剂成了机械工程专业。这是一个随机调剂的专业,着实让我无比苦闷,学了一年后,我非常清楚,我并不喜欢这个专业,也看不清未来的职业前景。
|
||||
|
||||
再回首时你总会发现,有些最重要的人生路径选择,就这么有点 “无厘头” 地完成了。在面临人生重要路径的选择时,当时只考虑了兴趣,如今看来也没那么靠谱。应该多听听众人的看法,参考前人的路径,最后再自己做出决定。人生路径还是需要自己来主动、有意识地掌舵的。
|
||||
|
||||
彼时,初心已有,但却是混乱的。
|
||||
|
||||
转换专业
|
||||
|
||||
机械专业煎熬了两年,迎来了第二次选择专业的机会。
|
||||
|
||||
在我读完大二的时候,国家开始兴办软件学院,新开软件工程专业。我当时在机械专业也会学一门编程课:C 语言。那时对 C 语言比较感兴趣,而对专业课机械制图则完全无感,所以当机会出现时就义无反顾去转了专业。
|
||||
|
||||
新专业面向所有非计算机的工程专业招生,但有一个门槛是:高学费。当时,机械专业一年学费四千多点,而软件工程本科一年一万六,学费读一年就抵得上别人读四年了,这对一个工薪家庭的压力不算小。
|
||||
|
||||
总之,我就是这么阴差阳错地又绕到了计算机专业这条路上。作为一门新开专业,软件工程相对计算机专业更偏应用,对接企业用人需求。可见,当时(2002 年)整个 IT 行业已经面临人才缺乏的问题,国家之所以新开软件工程专业,恐怕也是经济规律在发挥作用,平衡供需两端。
|
||||
|
||||
于我而言,转换专业算是时代给予的机遇,我在懵懂中做出了一次正确的选择。当时并不明了,但如今回顾却是如此清晰:面对新开的软件工程专业,其实表明了一个信息,这个行业发展很快,前景很好。
|
||||
|
||||
人生路很长,走了一段,也需要时不时重新审视当前的路径是否适合,是否无意错过了前途更好的岔路口。
|
||||
|
||||
我如今会感到庆幸没有错过这个路口,当时的确是没想过从机械专业换到软件工程会有更好的发展前景,但就是这样,我绕绕弯弯、曲曲折折地入了行,成为了一名程序员。
|
||||
|
||||
彼时,初心虽已不乱,但依然未明。
|
||||
|
||||
转换行业
|
||||
|
||||
人的一生面临很多重要选择,除了高考选专业,我想转行也是其中之一。
|
||||
|
||||
入行后,一路走来也碰到过很多从其他行业转行成为程序员的人。曾经在招聘面试时碰到过两个程序员,他们一个是毕业于中医药大学,在药房工作两年后转行并干了 3 年;另外一个主修环境工程专业,在该行业工作 9 年后才转行程序员,并在这行干了 5 年。
|
||||
|
||||
那时我就在想,为什么他们都要转行做一名程序员呢?也许,客观上来说,行业的景气度让程序员的薪酬水平水涨船高。需求的持续上涨,吸引着更多的人进入,这也是经济规律。但主观上来说,可能我们也没有想好为什么就要转行成为一名程序员。
|
||||
|
||||
我转换到软件工程专业,毕业后顺利进入程序这行。早期一开始就是为一些传统行业公司写企业应用程序,提供 IT 服务,完成一份合同。工作五年后,我才渐渐明白,同样写程序,但为不同的行业写的程序价值真是完全不同。因此,我选择了切换到电商互联网行业来写程序。
|
||||
|
||||
而这一次的选择我很确定的是,至少我模糊地看到了这条路的前景,并坚定地在众多选项中排除其他路径。转行,不同的跨度,代价或大或小。但不转变就没代价吗?不见得,因为有时不做选择的代价可能更大。
|
||||
|
||||
此时,初心才算渐渐明了。
|
||||
|
||||
心明行远
|
||||
|
||||
在成长的路上,我先后经历了换专业、换城市、换行业。
|
||||
|
||||
去年底(2017)我适时地驻足回顾了一下从进入大学到如今这些年的学习、工作和成长经历。其中有一些重要的时间事件节点,我把它们连接起来,就成了我们大多数人的成长线。下图,是我过去18年的成长线:
|
||||
|
||||
|
||||
|
||||
在这张图上,选专业、换专业、换城市、换行业,这几个重要的人生选择点,我都用红色字体标记了。把过往的18年浓缩到一张图上后,我就清晰地看出了趋势,在切换行业之前,初心未明,成长的路上起起伏伏,波动很大,也因为不成熟的选择带来过巨大的落差感。
|
||||
|
||||
在工作的前几年,图上也有一段快速的自然成长期。因为这时我们就像一张白纸,只要是在认真地做事儿,总是能成长。这段时期,心其实是乱的,但因为忙而充实,也获得了很多成长,但它的问题是:这样的自然成长期有多长取决于你所做事情的天花板,所以才有了后面的一次切换城市带来的落差。
|
||||
|
||||
切换了行业,一路走到现在,前路不尽,心已明,行将远。
|
||||
|
||||
为什么成为一名程序员,初心若何?有人有天赋,有人凭兴趣,有人看前景。也许,你上路之初还未曾明了,但在路上不时叩问内心,找到初心,会走得更坚定,更长远。
|
||||
|
||||
闭上眼睛,你可以试着问自己走上程序道路的初心是否已经明了呢?欢迎给我留言,我们一起分享和讨论。
|
||||
|
||||
|
||||
|
||||
|
91
专栏/程序员进阶攻略/02初惑:技术方向的选择.md
Normal file
91
专栏/程序员进阶攻略/02初惑:技术方向的选择.md
Normal file
@ -0,0 +1,91 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
02 初惑:技术方向的选择
|
||||
初入职场或还在校的同学想必都会有些共同的疑惑,比如:“到底我该选哪个技术方向?”“现在该学哪门语言?”“未来 Java 语言的发展趋势如何?”这些问题的本质其实都是技术的投资决策问题,也即现在我该把时间精力花在哪个方向上,未来的收益才可能最大。
|
||||
|
||||
这个问题并不好回答,因为这个问题的本质和 “我现在应该投资哪只股票” 一样。没有人能回答好这个问题,我觉得最好的做法就是:从投资的出发点而非终点来选择一条路径。
|
||||
|
||||
至于这样选择的路径是否能在未来获得很好的收益,这是没法预测的。但选择技术方向和选择股票不同的是,只要你在这条路径上持续努力、学习与进步,基本可以保证能和 “大盘” 持平而不至于有亏损,但是否能取得超过 “大盘” 的收益,其实是看运气的。
|
||||
|
||||
选择语言
|
||||
|
||||
选择技术方向,从某种意义上讲就是选择语言。
|
||||
|
||||
虽然有一些流传的说法,类似于:“语言并不重要,必要的时候可以在各种语言间自由切换。”但实际情况是,能做到自由切换的前提是你得对一门语言掌握到通透之后,再学习其他语言才可能触类旁通。
|
||||
|
||||
计算机程序语言虽然很多,但种类其实有限。2018 TIOBE 程序语言排行榜(见下图)上的前三位(Java、C、C++),本质上其实是一类语言。但大部分人只能选择去熟悉并通透其中一种,因为这些语言背后都有庞大的生态圈。
|
||||
|
||||
|
||||
|
||||
2018 TIOBE 程序语言排行榜
|
||||
|
||||
要做到通透,只熟悉语言本身是远远不够的,其实是要熟悉整个生态圈。而三门语言中最年轻的 Java 都有二十多年历史了,足够你耗费数年时光去熟悉其整个生态圈,而且目前其生态圈还处在不断扩张的状态,展现出一种蓬勃的生命力。
|
||||
|
||||
那么,要是我来选,我会如何选择语言呢?我会选择那些展现出蓬勃生命力的语言。
|
||||
|
||||
但其实十多年前我只是凑巧选择了 Java,它就像是被潮水推到我脚边的漂流瓶,顺手捡了起来。没想到居然蓬勃地发展了十多年,还没见衰退迹象。
|
||||
|
||||
那时的 Java 刚诞生不过七八年,和今天的 Go 语言很像。Go 语言在排行榜上的位置蹿升得很快,而且在云计算时代的基础设施上大放异彩,号称是:易用性要超越 PHP,而性能要超越 Java。
|
||||
|
||||
那么在 Java 之前我学的是什么?是 Visual Basic、ASP 和 Delphi / Object Pascal。我想今天不少年轻的程序员都未必听过这些语言了。但神奇的是,在 TIOBE 的排行榜上,VB 加了个 .NET 排名竟在世界最广泛的 Web 语言 PHP 和JavaScript 之上。而十五年前我用的 Delphi / Object Pascal 居然落后 JavaScript 也不远,且远高于 Go、Objective-C,力压 Swift。
|
||||
|
||||
这些老牌语言还值得学吗?当然不值得了。因为它们早已进入暮年,没了蓬勃的生命力。但为什么排名还这么高?也许是因为它们也曾有过蓬勃生命力的热血青春,留下了大量的软件系统和程序遗产,至今还没能退出历史的舞台吧。
|
||||
|
||||
美国作家纳西姆·塔勒布(《黑天鹅》《反脆弱》等书作者)曾说:
|
||||
|
||||
|
||||
信息或者想法的预期寿命,和它的现有寿命成正比。
|
||||
|
||||
|
||||
而编程语言以及由它编写的所有软件系统和程序,本质就是信息了。换句话说就是,如果你想预测一门语言还会存在多久,就看看它已经存在了多久。存活时间足够长的语言,可以预期,它未来也还可能存活这么长时间。当然这一论断并不绝对,但它更多想说明越是新的语言或技术,升级换代越快,也越容易被取代。
|
||||
|
||||
这一点在 Delphi 这门语言上已经得到了体现,进入二十一世纪后,这种编写 C/S 架构软件的语言,居然还存活了这么久。
|
||||
|
||||
选择回报
|
||||
|
||||
选择技术方向,选择语言,本质都是一种投资。
|
||||
|
||||
我们为此感到焦虑的原因在于,技术变化那么快,就怕自己选了一个方向,投了几年的时间、精力,最后却被技术迭代的浪潮拍在了沙滩上。
|
||||
|
||||
按上面塔勒布的说法,越年轻的语言和方向,风险越高。一个今年刚出现的新方向、新语言,你怎么知道它能在明年幸存下来?所以,考虑确定性的回报和更低的风险,你应该选择有一定历史的方向或语言,也许不能带来超额的回报,但最起码能带来稳定的回报,让你先在这个行业里立稳脚跟。在此基础上,再去关注新潮流、新方向或新技术,观察它们的可持续性。
|
||||
|
||||
有一句投资箴言:“高风险未必带来高回报。”在选择职业方向的路上,你甚至没法像分散投资一样来控制风险,所以选择确定性的回报,要比抱着赌一把的心态更可取。看看当前的市场需求是什么,最需要什么,以及长期需要什么。
|
||||
|
||||
比如,今天技术的热潮在人工智能、机器学习、区块链等上面,这是市场最需要的,而市场给的价格也是最高的。所以,你应该投入这里么?先别头脑发热,看看自己的基础,能否翻越门槛,及时上得了车吗?
|
||||
|
||||
世纪之初,互联网时代的到临,网络的爆发,你会写个 HTML 就能月薪上万。上万,似乎不多,但那时北京房价均价也才 5000 多啊。2010 年左右,移动互联网兴起,一年移动开发经验者的平均待遇达到了五到十年 Java 开发的水平。如今,你只会 HTML 基本找不到工作,你有五年移动开发经验和有五年 Java 开发经验的同学,薪资待遇也变得相差不多了。
|
||||
|
||||
关于技术,有一句流行的话:“技术总是短期被高估,但长期被低估。”今天,在人工智能领域获得超额回报的顶级专家,实际数十年前在其被低估时就进入了这个领域,数十年的持续投入,才在如今迎来了人工智能的 “牛市” ,有了所谓的超额回报。所以,不妨投入到一些可能在长期被低估的基础技术上,而不是被技术潮流的短期波动所左右。
|
||||
|
||||
技术的选择,都是赚取长期回报,短期的波动放在长期来看终将被抵消掉,成为时代的一朵小浪花。
|
||||
|
||||
选择行业
|
||||
|
||||
搞清楚了语言、技术方向和回报的关系后,最后做出选择的立足点通常会落在行业上。
|
||||
|
||||
当你问别人该选什么语言时,有人会告诉你,你应该学习 JavaScript,因为这是互联网 Web 时代的通用语言,到了移动互联网时代依然通用,而且现阶段生命力旺盛得就像再年轻十岁的 Java。也有人告诉你也许从 Python 开始更合适,语法简单,上手容易。还有人告诉你,现在学 Java 找工作最容易,平均工资也蛮高。这各种各样的说法充斥在你的耳边,让你犹豫不决,左右为难。
|
||||
|
||||
一个问题就像一把锁,开锁的钥匙肯定不会在锁上。否则这个问题也就不是问题了,太容易就解开了,不是吗?所以,选择什么语言通常不在于语言本身的特性。
|
||||
|
||||
选语言,就是选职业,而选职业首先选行业。
|
||||
|
||||
先想想自己想从事哪个行业的软件开发;然后,再看看:这个行业的现状如何?行业的平均增速如何?和其他行业相比如何?这个行业里最好的公司相比行业平均增速又如何?最后,再看看这些最好的公司都用些什么样的技术栈和语言。如果你想进入这样的公司,那就很简单了,就选择学这样的技术和语言。
|
||||
|
||||
这样选择是不是太功利了?选择不是应该看兴趣么?注意,这里选择的前提可不是发展什么业余爱好,而是为了获得安身立命的本领,获得竞争的相对优势。而兴趣,就是这件事里有些吸引你的东西,让你觉这是 “很好玩” 的事。但有个通常的说法是:“一旦把兴趣变成了职业也就失去了兴趣。”因为,职业里面还有很多 “不好玩” 的事。
|
||||
|
||||
兴趣能轻松驱动你做到前 50%,但按二八原则,要进入前 20%的高手领域,仅仅靠兴趣就不够了。兴趣给你的奖励是 “好玩”,但继续往前走就会遇到很多 “不好玩” 的事,这是一种前进的障碍,这时功利,也算是给予你越过障碍所经历痛苦的补偿吧。
|
||||
|
||||
以上,就是我关于技术方向选择的一些原则与方法。无论你当初是如何选择走上技术道路的,都可以再想想你为什么要选择学习一门编程语言,学习编程的一部分是学习语言的语法结构,但更大的一部分,同时也是耗时更久且更让你头痛的部分:学习如何像一个工程师一样解决问题。
|
||||
|
||||
有时这样的选择确实很难,因为我们缺乏足够的信息来做出最优选择。赫伯特·西蒙说:“当你无法获得决策所需的所有信息时,不要追求最优决策,而要追求满意决策。”定下自己的满意标准,找到一个符合满意标准的折中方案,就开始行动吧。
|
||||
|
||||
而停留在原地纠结,什么也不会改变。
|
||||
|
||||
最后,亲爱的读者朋友,你当初是如何选择技术道路的呢?欢迎留言分享。-
|
||||
|
||||
|
||||
|
||||
|
151
专栏/程序员进阶攻略/03初程:带上一份技能地图.md
Normal file
151
专栏/程序员进阶攻略/03初程:带上一份技能地图.md
Normal file
@ -0,0 +1,151 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
03 初程:带上一份技能地图
|
||||
程序世界是一片广阔的大地,相比我十多年前进入这个世界时,这片大地的边界又扩大了很多倍。初入程序世界难免迷茫,要在这个世界立足、生存,并得到很好的发展,应首要具备怎样的技能呢?未来的程序之路,先给自己准备一份基本的技能地图,先有图,再上路。
|
||||
|
||||
在程序的技能地图中,需要先开启和点亮哪些部分呢?回顾我过去的经历并结合现实的需要,可以从如下两个不同程度的维度来说明:
|
||||
|
||||
|
||||
掌握
|
||||
了解
|
||||
|
||||
|
||||
掌握,意味着是一开始就要求熟练掌握的硬技能,这是生存之本。而至于掌握的深度,是动态的,倒是可以在行进过程中不断去迭代加深。了解,相对掌握不是必需,但也需要达到知其然的程度,甚至知其所以然更好。
|
||||
|
||||
一、掌握
|
||||
|
||||
上路之初,需要掌握的核心生存技能有哪些呢?
|
||||
|
||||
1.开发平台
|
||||
|
||||
开发平台,它包括一种编程语言、附带的平台生态及相关的技术。在如今这个专业化分工越来越细的时代,开发平台决定了你会成为什么类型和方向的程序员。比如:服务端、客户端或前端开发等。其中进一步细分客户端还可以有 Windows、Mac、iOS 和 Android 等不同的平台。
|
||||
|
||||
编程语言
|
||||
|
||||
语言的选择基本决定了开发平台的性质,但有些语言可能例外,如:C++、JS、C# 等,这些语言都可以跨多个平台。但即使你选的是这些语言,基本也会归属到某一类平台上。好比你选了 C++,如果你去做了客户端开发,就很少可能再去用 C++ 写服务端程序了。
|
||||
|
||||
关于语言的选择,前面我已经写过了选择的逻辑,便不再多说。但选择了语言,我们不仅仅是熟悉语言自身的特性,还需要掌握支撑语言的平台库。Java 若仅从语言特性上来说,有其优点,但其瑕疵和缺陷也一直被吐槽,要是没有 JDK 强大的平台库支撑,想必也不会有今天的繁荣。
|
||||
|
||||
平台生态
|
||||
|
||||
与语言平台关联的还有其技术生态以及各种技术框架的繁荣程度。这些平台技术生态的存在让使用这门语言编程完成特定的任务变得容易和简单得多。Java 的生命力除了 JDK 的强大支撑,实际还有其平台生态的繁荣,也起了决定性的作用。
|
||||
|
||||
在选择了开发平台后,除了语言和平台库之外,其生态体系内主流的技术框架和解决方案也是必选的掌握内容。
|
||||
|
||||
2.常用算法
|
||||
|
||||
在学校学习的算法,基本是解决各种计算机科学问题的通用方法。
|
||||
|
||||
还记得在学校时看过一本算法经典书《算法导论》。刚又把这本书的目录翻了出来过了一遍,发现已经忘记了百分之七、八十的内容。因为忘记的这部分内容,在过去的十多年工作中我基本都没机会用上。那么掌握算法的目的是为了什么呢?
|
||||
|
||||
有时候你可能会觉得学校教科书上学习的经典算法,在实际工作中根本就用不上。我还记得考研的时候,专业考试课就是算法与数据结构,在考卷上随手写个排序、树遍历手到擒来。但到研究生毕业去参加腾讯校招面试时,让在白纸上手写一个快排算法,我被卡住了,自然也就没通过。因为好久已经没有进行这样的练习了,而在研究生阶段一年期的公司实习工作场景也没有这样的需求。
|
||||
|
||||
那么为什么还要学习这些经典算法?
|
||||
|
||||
算法,表达的是一个计算的动态过程,它引入了一个度量标准:时空复杂度。当我回思时,发现这个度量标准思维在工作十余年中一直在发挥作用。如今,几乎所有的经典算法都能在开发平台库里找到实现,不会再需要自己从头写。但结合工作实际的业务场景,我们需要去设计更贴合需求的算法,而只要是算法它都受到时空复杂度的约束,而我们只是在其中进行平衡与折衷。
|
||||
|
||||
学校教科书的经典算法,是剥离了业务场景的高度抽象,当时学来有种不知道用在哪里的感觉;如今回头结合真实的业务场景需求再看,会有一种恍然大悟之感。
|
||||
|
||||
3.数据结构
|
||||
|
||||
数据结构通常都和算法一起出现,但算法表达的是动态特性,而数据结构表达的是一种静态的结构特性。大部分开发平台库都提供了最基础和常用的数据结构实现,这些都是我们需要熟悉并掌握的,包括:
|
||||
|
||||
|
||||
数组 Array
|
||||
链表 Linked List
|
||||
队列 Queues
|
||||
堆栈 Stacks
|
||||
散列 Hashes
|
||||
集合 Sets
|
||||
|
||||
|
||||
另外,还有两种数据结构不属于基础结构,但在现实中有非常广泛的直接映射场景。
|
||||
|
||||
|
||||
树 Trees
|
||||
图 Graphs
|
||||
|
||||
|
||||
每种结构都有各种变体,适用于不同的场景,甚至很多时候你还需要会组合不同的结构去解决一些更复杂的问题。
|
||||
|
||||
二、了解
|
||||
|
||||
需要了解的内容比需要掌握的更广泛,但了解了这些方面会让你更高效地协作并解决问题。
|
||||
|
||||
1.数据存储
|
||||
|
||||
不管你写什么样的程序系统,估计都离不开数据存储。数据是一个业务系统的核心价值所在,所以怎么存储不同类型的生产数据,是你必须要了解的。如今广泛流行的数据存储系统有下面三类:
|
||||
|
||||
|
||||
SQL 关系型数据库(如:MySQL、Oracle)
|
||||
NoSQL 非关系型数据库(如:HBase、MongoDB)
|
||||
Cache 缓存(如:Redis、Memcached)
|
||||
|
||||
|
||||
每一种数据存储系统都有其特定的特性和应用场景。作为程序员,我们通常的需求就是最有效地用好各类数据存储,那么按了解的深度需要依次知道如下几点:
|
||||
|
||||
|
||||
如何用?在什么场景下,用什么数据存储的什么特性?
|
||||
它们是如何工作的?
|
||||
如何优化你的使用方式?
|
||||
它们的量化指标,并能够进行量化分析?
|
||||
|
||||
|
||||
这 4 点虽不要求一开始就能掌握到一定程度,但你最好一开始就有这个层次思维,在日后的工作中不断去迭代它的深度。
|
||||
|
||||
2.测试方法
|
||||
|
||||
为什么我们做开发还需要了解测试?
|
||||
|
||||
测试思维是一种与开发完全不同的思维模式。有一种流行的开发方法论叫 “测试驱动开发(TDD)”,它的流行不是没有道理的。在写代码的时候,用测试的思维与方式(提供单元测试)去审视和检测代码,也就是说明确要开发某个功能后,先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。
|
||||
|
||||
开发与测试这两种相反视角的切入维度,能真正长期地提高你写代码的效率和水平。
|
||||
|
||||
3.工程规范
|
||||
|
||||
每一种开发平台和语言,估计都有其相应约定俗成的一些工程规范要求。最基础的工程规范是代码规范,包括两个方面:
|
||||
|
||||
|
||||
代码结构
|
||||
代码风格
|
||||
|
||||
|
||||
像 Java 这么多年下来,逐渐形成了一种基于 Maven 的代码组织结构规范,这种约定俗成的代码结构规范省却了很多没必要的沟通。有时候,同样的内容,有更规范的结构,其可阅读性、理解性就能得到提升。
|
||||
|
||||
而至于代码风格,相对没那么标准化。但为了写出更清晰、易读的代码,我们至少要坚持自己写的代码具有某种一致性的风格。另外,除了风格问题,也可以借助静态代码检查工具来规避一些新手爱犯的低级错误,而老手也可以通过这些工具来找到自己的认知与习惯盲点。
|
||||
|
||||
4.开发流程
|
||||
|
||||
在开发流程方法论上,敏捷基本已经横扫天下,所以我们至少要了解下敏捷开发方法论。
|
||||
|
||||
虽然敏捷方法论定义了一些参考实践,但它依然是一组非常松散的概念。每个实践敏捷的开发团队,估计都会根据自己的理解和摸索建立一整套逐渐约定成型的开发流程规范。而为了和团队其他成员更好地协作,估计每个新加入团队的成员都需要了解团队演进形成的开发流程规范。
|
||||
|
||||
先了解,再优化。
|
||||
|
||||
5.源码管理
|
||||
|
||||
既然我们生产代码,自然也需要了解如何管理好代码。
|
||||
|
||||
在我的从业经历中,源码管理工具经历了从 CVS 到 SVN 再到 Git 的变迁。Git 诞生的背景是为 Linux 这样超大规模的开源项目准备的,自然决定了其能应对各种复杂场景的源码管理需求。所以,你至少要了解 Git,并用好它。
|
||||
|
||||
当工具变得越来越强大时,工具背后的思想其实更重要,对其的理解决定了我们应用工具的模式。而对源码进行管理的最基本诉求有以下三点:
|
||||
|
||||
|
||||
并行:以支持多特性,多人的并行开发
|
||||
协作:以协调多人对同一份代码的编写
|
||||
版本:以支持不同历史的代码版本切换
|
||||
|
||||
|
||||
最后,我把以上内容总结为如下一张图:红色区域相对更小而聚焦,是需要掌握的部分,要求深度;蓝色区域的部分更广而泛,需要广度。-
|
||||
|
||||
|
||||
程序员的基础技能图
|
||||
|
||||
以上就是我回顾走过的路径后,觉得需要具备的一份基础技能图。十多年后,这张图上的每一个分类都出现了新的技术迭代,有了新的框架、算法和产品等,但它们并不过时,依然可以为你的技能点亮之路提供方向指引。也许,你程序生涯的第一个一万小时你就会花在这张图上了。-
|
||||
|
||||
|
||||
|
||||
|
121
专栏/程序员进阶攻略/04初感:别了校园,入了江湖.md
Normal file
121
专栏/程序员进阶攻略/04初感:别了校园,入了江湖.md
Normal file
@ -0,0 +1,121 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
04 初感:别了校园,入了江湖
|
||||
每年七月,盛夏,一种火辣兼有闷润的热,但在这份热辣中也有一丝略显冰凉的愁绪。一批刚毕业的学生,将要告别校园,进入职场:一个新的江湖。
|
||||
|
||||
一到毕业季,就会经常看到一些转发给新入行程序员的“老司机”指南,老实说,这些指南不少都是金玉良言。当年我毕业新入行时可不像现在有这么多发蒙解惑的“老司机”指南,所以坑都没少踩,若说有什么坑没掉进去,那一定都是因为运气。
|
||||
|
||||
当毕业生们看到前路如此多坑时,其实也不必有太大的心理压力,毕竟成长之路不可能是轻松的。我也是这样一路走过来的。所以,这篇就不写关于坑的指南了,而是分享一些我的故事和感悟,给刚踏上征程的你或你们吧。
|
||||
|
||||
重剑无锋
|
||||
|
||||
作为一名新入职的程序员,首要之事就是配备一台电脑。
|
||||
|
||||
这个时代基本都是标配笔记本了,近年公司给配的电脑都很不错了,程序员全是高配大屏的 Macbook Pro 了。遥想我第一份工作,领到的是一个二手华硕笔记本,应该是上一个离职的前辈用了好几年的,这也是我的第一个笔记本电脑。
|
||||
|
||||
程序员就应该配笔记本电脑,为什么必须是笔记本电脑?不可以是台式机吗?笔记本电脑之于程序员,就像剑之于剑客。剑客的剑是不应该离开身边的,稍有风吹草动,听风辨器,拔剑出鞘(程序员一声不发就掏出笔记本开始写代码)。
|
||||
|
||||
当招程序员时,若来者不问公司配备什么笔记本,一般也就属于大多数的普通程序员啦,不太可能是那种 “不滞于物,草木竹石均可为剑” 的独孤求败级高手。
|
||||
|
||||
但也会有少数人问公司配什么笔记本电脑的,当对公司提供的笔记本感觉不满意时,就会要求是否可以自备笔记本电脑,而由公司给予补贴。
|
||||
|
||||
后一类同学,对环境、工具和效率是敏感的,我觉着就会比前面那类要强,当然我没有具体统计数据,也是纯凭感觉。
|
||||
|
||||
我毕业那年,腾讯来学校招聘,本科年薪六万,硕士八万,博士十万,都是税前。那时我心中最好的笔记本应该还是 IBM 的 ThinkPad T 系列,最差也得 10000+ 起的价格吧。但现在年薪十万在一线的北上广深算相当普遍了吧?而笔记本还是一万,所以能买到很不错的笔记本已经不是什么难事了。若现在的公司再在程序员的 “剑” 上琢磨省钱那绝对是得不偿失了。
|
||||
|
||||
我的第一个二手华硕笔记本相比现在的超薄轻快的笔记本,那绝对算是相反的厚重慢了。所以我把它称为 “重剑”,和独孤前辈不同的是,他老人家是先用轻剑,再用重剑,而我是先用重剑,然后越用越轻了。
|
||||
|
||||
但只是一年后我换了公司,这把 “重剑” 就还了回去。到了第二家公司,入职后才发现公司不配笔记本电脑,全是台式机。你看,当年我就是那种没问公司配什么电脑的普通程序员,考虑那时确实台式机占据主流,并且笔记本还属于一般程序员至少要两三个月工资才能买得起一台趁手的奢侈品范畴,我也就忍了。
|
||||
|
||||
新入职没多久,熟悉了公司环境和老同事交接过来的一个旧系统,公司派我出差去客户现场开发调试。我满心以为出差至少得配个笔记本吧,但组长过来说我们这边出差都带台式机的。然后我看看组长一脸正气不带开玩笑的表情,再看看桌上台式机硕大的机箱和 17 寸的 CRT 显示器,瞠目结舌。
|
||||
|
||||
“显示器也要带去?” 我问。-
|
||||
“噢,显示器可以让公司 IT 部给你寄过去,但主机箱要自带,因为快递很容易弄坏。” 组长说。-
|
||||
“好吧…”
|
||||
|
||||
对话结束,我立马奔去广州太平洋电脑城,花了之前一年工作攒下来的一万块中的七千,买了一台只有 ThinkPad T 系价位零头的 R 系笔记本,之后这把 “剑” 陪伴了我五年。
|
||||
|
||||
初入职场的同学,既然选择了某个公司,最大的因素除了薪酬外,想必每个人都还有这样或那样的在意点。当年我最不满意的就是笔记本这个问题,但从工作第二年开始我一直都是自备笔记本工作,持续多年,没花多少钱,但少了很多折腾和不便。
|
||||
|
||||
再后来,我挣得稍微多了些,就又自己换了大内存加固态硬盘(SSD)的 Mac。刚换上 Mac 就惊喜地发现,以前一个 Java 工程编译打包下要 50 多秒,在 Mac 下只需要 20 秒了。考虑写程序的这么多年,每天我要执行多少次编译打包命令,虽然每次节省的时间不多,但总体来看它大大提高了我的效率。
|
||||
|
||||
为什么我要单独谈谈笔记本这件小事?因为这是我们程序员每天使用最多的工具,如果你不在乎你的工具,可能你也就不会在乎你的时间和效率。
|
||||
|
||||
野蛮生长
|
||||
|
||||
现在的公司基本都会给新入职的同学配备一个老员工,俗称 “导师”。
|
||||
|
||||
导师制的主要目的是帮助新员工迅速熟悉公司环境和融入团队中。这个初衷自然是没问题的,只是因为导师制其实不算正式的规章制度,更多是一种文化上的强制介入,但很多时候又没有绑定任何利益机制,所以它的效果实际上和个体差异有很大关系。
|
||||
|
||||
我觉着新入行的同学,尽量不要去依赖此类导师制。我并不是否定这种机制,而是提醒你不要形成心理依赖,觉着面临问题时,总是有人可以询问和帮忙。职场的第一个台阶就是形成独立性:独立承担职责的能力。这和协作没有冲突,团队协作可以算是履行职责的一种途径和手段。
|
||||
|
||||
就以简单的问问题为例,如果只抱着获得答案搞定自己的事情为出发点,一次、两次也就罢了,长此以往是不可行的。因为通过你问出什么样的问题,就可以看出你对这个问题做出了怎样的探索与思考,以及让你困惑的矛盾点在哪里。有些人就喜欢不假思索地问些 Google 都能够轻易回答的问题,形成路径依赖,虽然最终搞定了问题,但换得的评价却不会高,特别要省之戒之。
|
||||
|
||||
当你能够问出 Google 都不容易回答的问题时,这可能才是你真正走上职业程序员的开端。
|
||||
|
||||
知乎上有个问题:“普通人到底跟职业运动员有多大差距?”里面有个回答,以篮球运动为例给出一个生动的评分体系。假如巅峰时的迈克尔·乔丹算满分 100,那么国内顶级的球员,巅峰时的易建联可能刚刚及格得分在 60~70 之间,而大部分业余选手基本就在 0.1~0.N 之间波动了。
|
||||
|
||||
幸运的是程序员这个职业不像运动员那么惨烈,借用上面的评分标准,假如把奠定计算机行业基础的那一批图灵奖得主级别的程序员定义在 90~100 分档,那么我们很多靠编码为生的程序员基本都在 1~10 分档间分布,而业余的只是写写 Demo 的程序员可能就是在 0.1~0.N 之间了。
|
||||
|
||||
所以,进入职场后,你需要先把得分从小数提高到整数,再持续努努力提升到两位数,搞不好就成了行业某个垂直细分领域小有名气的专家了。
|
||||
|
||||
都不需要及格的分数,程序员就能获得不错的成就感和回报。只需要是巅峰者的十分之一或二十分之一,基本就能超越身边绝大多数人了。望着遥远的巅峰不停地去靠近,翻越身前脚下的一座座小山包,然后不知不觉就超越了自己。
|
||||
|
||||
总之,应像野草一样野蛮而快速地生长起来。
|
||||
|
||||
青春有价
|
||||
|
||||
青春,到底是无价,还是有价?
|
||||
|
||||
电影《寒战》里面有个情节,劫匪劫持了一辆警方的冲锋车和五名警员,勒索赎金时让警方自己算一辆冲锋车及其装备外加五名警员的性命值多少钱。然后电影里一阵眼花缭乱的计算得出了最终价格,大约九千多万港币。
|
||||
|
||||
后来采访导演问是怎么个算法,导演如是说:
|
||||
|
||||
|
||||
五个警员,若不在事件中死去,由现在到退休期间的十多二十年任职的每月薪酬、房屋及子女医疗津贴、加上假设退休前的职位升迁,香港市民平均年龄以男方 79 岁,女方 85.9 岁的生存上限而计算的长俸,并加上冲锋车流动武器库内的价值、冲锋车本身的车价及保险等最后算出来的总值。
|
||||
|
||||
|
||||
按这么一算,其实一生值不了多少钱啊。
|
||||
|
||||
年轻时候嘴边爱唠叨一句话叫:青春无价。其实从我们挣到第一份工资开始,人生就是有价的了。而最黄金时段的青春就在毕业后的十年内,这是大部分人心理和心智走向成熟的阶段,但这十年的价值从市场价格衡量来看是十分有限的。
|
||||
|
||||
对于2018 年的毕业生, BAT 三家给出的年薪大约二十万左右,换算到月上每月接近两万了。而另外很大一部分进不了 BAT 三家的毕业生可能平均拿不到那么高,但估计在一线城市一万是差不多的。这样一算,未来十年你觉得能挣多少钱?
|
||||
|
||||
喜欢从静止的视角看问题的人一算大概一年十来万,十年也就一百多万,这个收入相对目前一线城市的房价,还能安居乐业吗?
|
||||
|
||||
另外思考一个问题:你能在十年后做到相比刚毕业时稳定收入增长十倍吗?也就是说现在月薪一万的人,十年后能月入十万吗?难,非常难。不信我们下面仔细算算。
|
||||
|
||||
我们回到用动态的视角看前面那个问题,你持续学习,努力工作,年年涨薪 20%(注意我说的是年年,这很不容易,也许你会说跳一次槽就可能翻倍,但你能年年跳槽翻倍么),十年后你年的收入将是十年前的 6.2 倍,离十倍还有距离,但换算为年薪也有七八十万了。所以要想靠加薪加到月入十万真的是一件极难的事情,而且即使做到了也离我们心中的无价青春,还差很远吧?
|
||||
|
||||
认清了这个现实,我们明白了这十年的青春是十分有价的。所以这时有人说了,要去创业,才有可能突破。前两年(2015)都在鼓励万众创业,但真实的现实是,你要给目前的万众创业者一个稳定的七八十万年薪,80%+ 的创业者表示就会放弃创业了,这数据是来自 TOMsInsight 深度观察文《互联网乱世之下,那些人才流动中的心酸和无奈》对 100 个创业者的抽样调查。-
|
||||
|
||||
|
||||
TOMsInsight 创业者放弃公司的薪水额度抽样调查
|
||||
|
||||
那么持续努力的学习还有意义吗?我只是说你很难做到每年加薪 20%,但是却可以做到每年比去年的自己多增长 20% 的知识、见识和能力。而关于知识、见识和能力的积累与相应价值的变现,理论与现实的对比可能如下图,纵坐标:年薪(单位万),横坐标:工作年限。-
|
||||
|
||||
|
||||
年薪与工作年限概念图
|
||||
|
||||
现实不太可能因为你的能力每增长 20% 就会立刻体现在你的收入上。现实有两种可能:一种存在一个拐点让你的积累获得相应的价格体现,另一种也可能不存在这个拐点,停留在某个水平位。其中拐点就是我们现实中常说的机遇吧。
|
||||
|
||||
无论怎样,要想获得拐点出现的机遇,可能你也只能持续努力地积累下去。
|
||||
|
||||
关于人生的选择,从来都是 All In,可没有股票那种分批建仓的办法,写到这里想起了曾经在网上记录下来的几句话,现分享给你:
|
||||
|
||||
|
||||
我不停的擦拭手中的利剑,不是因为我喜欢它,也不是因为它能带来安全,只是因为,每当下一次冲锋的号角响起时,我能够迅速拔出,纵横厮杀,直至战斗结束,不让自己倒下。-
|
||||
……-
|
||||
生活在这样的时代,与其被迫上场,心怀恐惧,不如主动征伐,加入时代的滚滚大潮当中,去见识一下时代的风采,写下自己的故事。
|
||||
|
||||
|
||||
这个江湖会有你的故事吗?
|
||||
|
||||
在这里我分享了一些我刚入江湖的故事,那你有怎样的精彩故事呢?欢迎你留言,和我一起分享。
|
||||
|
||||
|
||||
|
||||
|
144
专栏/程序员进阶攻略/05架构与实现:它们的连接与分界?.md
Normal file
144
专栏/程序员进阶攻略/05架构与实现:它们的连接与分界?.md
Normal file
@ -0,0 +1,144 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
05 架构与实现:它们的连接与分界?
|
||||
把一种想法、一个需求变成代码,这叫 “实现”,而在此之前,技术上有一个过程称为设计,设计中有个特别的阶段叫 “架构”。
|
||||
|
||||
程序员成长的很长一段路上,一直是在 “实现”,当有一天,需要承担起 “架构” 的责任时,可能会有一点搞不清两者的差异与界线。
|
||||
|
||||
是什么
|
||||
|
||||
架构是什么?众说纷纭。
|
||||
|
||||
架构(Architecture)一词最早源自建筑学术语,后来才被计算机科学领域借用。以下是其在维基百科(Wikipedia)中的定义:
|
||||
|
||||
|
||||
架构是规划、设计和构建建筑及其物理结构的过程与产物。在计算机工程中,架构是描述功能、组织和计算机系统实现的一组规则与方法。-
|
||||
Architecture is both the process and the product of planning, designing, and constructing buildings and other physical structures. In computer engineering, “computer architecture” is a set of rules and methods that describe the functionality, organization, and implementation of computer systems.
|
||||
|
||||
|
||||
在建筑学领域,有一组清晰的规则和方法来定义建筑架构。但可惜,到目前为止,在计算机软件工程领域并没有如此清晰的一组规则与方法来定义软件架构。
|
||||
|
||||
好在经过多年的实践,行业里逐渐形成了关于软件架构的共同认知:软件系统的结构与行为设计。而实现就是围绕这种已定义的宏观结构去开发程序的过程。
|
||||
|
||||
做什么
|
||||
|
||||
架构做什么?很多人会感觉糊里糊涂的。
|
||||
|
||||
我刚获得“架构师”称号时,也并不很明确架构到底在做什么,交付的是什么。后来不断在工作中去反思、实践和迭代,我才慢慢搞清楚架构工作和实现工作的差异与分界线。
|
||||
|
||||
从定义上,你已知道架构是一种结构设计,但它同时可能存在于不同的维度和层次上:
|
||||
|
||||
|
||||
高维度:指系统、子系统或服务之间的切分与交互结构。
|
||||
中维度:指系统、服务内部模块的切分与交互结构。
|
||||
低维度:指模块组成的代码结构、数据结构、库表结构等。
|
||||
|
||||
|
||||
在不同规模的团队中,存在不同维度的架构师,但不论工作在哪个维度的架构师,他们工作的共同点包括下面4个方面:
|
||||
|
||||
|
||||
确定边界:划定问题域、系统域的边界。
|
||||
切分协作:切分系统和服务,目的是建立分工与协作,并行以获得效率。
|
||||
连接交互:在切分的各部分之间建立连接交互的原则和机制。
|
||||
组装整合:把切分的各部分按预期定义的规则和方法组装整合为一体,完成系统目标。
|
||||
|
||||
|
||||
有时,你会认为架构师的职责是要交付 “一种架构”,而这“一种架构” 的载体通常又会以某种文档的形式体现。所以,很容易误解架构师的工作就是写文档。但实际上架构师的交付成果是一整套决策流,文档仅仅是交付载体,而且仅仅是过程交付产物,最终的技术决策流实际体现在线上系统的运行结构中。
|
||||
|
||||
而对于实现,你应该已经很清楚是在做什么了。但我在这里不妨更清晰地分解一下。实现的最终交付物是程序代码,但这个过程中会发生什么?一般会有下面6个方面的考虑:选型评估;程序设计;执行效率;稳定健壮;维护运维;集成部署。
|
||||
|
||||
下表为其对应的详细内容:-
|
||||
|
||||
|
||||
我以交付一个功能需求为例,讲述下这个过程。
|
||||
|
||||
实现一个功能,可能全部自己徒手做,也可能选择一些合适的库或框架,再从中找到需要的API。
|
||||
|
||||
确定了合适的选型后,需要从逻辑、控制与数据这三个方面进一步考虑程序设计:
|
||||
|
||||
|
||||
逻辑,即功能的业务逻辑,反映了真实业务场景流程与分支,包含大量业务领域知识。
|
||||
控制,即考虑业务逻辑的执行策略,哪些可以并行执行,哪些可以异步执行,哪些地方又必须同步等待结果并串行执行?
|
||||
数据,包括数据结构、数据状态变化和存取方式。
|
||||
|
||||
|
||||
开始编码实现时,你进一步要考虑代码的执行效率,需要运行多长时间?要求的最大等待响应时间能否满足?并发吞吐能力如何?运行的稳定性和各种边界条件、异常处理是否考虑到了?上线后,出现 Bug,相关的监控、日志能否帮助快速定位?是否有动态线上配置和变更能力,可以快速修复一些问题?新上线版本时,你的程序是否考虑了兼容老版本的问题等?
|
||||
|
||||
最后你开发的代码是以什么形态交付?如果是提供一个程序库,则需要考虑相关的依赖复杂度和使用便利性,以及未来的升级管理。如果是提供服务,就需要考虑服务调用的管理、服务使用的统计监控,以及相关的 SLA 服务保障承诺。
|
||||
|
||||
以上,就是我针对整个实现过程自己总结的一个思维框架。如果你每次写代码时,都能有一个完善的思维框架,应该就能写出更好的代码。这个思维框架是在过去多年的编程经验中逐步形成的,在过去每次写代码时如果漏掉了其中某个部分,后来都以某种线上 Bug 或问题的形式,让我付出了代价,做出了偿还。
|
||||
|
||||
“实现”作为一个过程,就是不断地在交付代码流。而完成的每一行代码,都包含了上面这些方面的考虑,而这些方面的所有判断也是一整套决策流,然后固化在了一块块的代码中。
|
||||
|
||||
因为实现是围绕架构来进行的,所以架构的决策流在先,一定程度上决定了实现决策流的方向与复杂度,而架构决策的失误,后续会成倍地放大实现的成本。
|
||||
|
||||
关注点
|
||||
|
||||
架构与实现过程中,有很多很多的点值得关注,若要选择一个核心点,会是什么?
|
||||
|
||||
架构的一个核心关注点,如果只能是一个点,我想有一个很适合的字可以表达: 熵。“熵”是一个物理学术语,在热力学中表达系统的混乱程度,最早是“信息论之父”克劳德·艾尔伍德·香农借用了这个词,并将其引入了信息科学领域,用以表达系统的混乱程度。
|
||||
|
||||
软件系统或架构,不像建筑物会因为时间的流逝而自然损耗腐坏,它只会因为变化而腐坏。一开始清晰整洁的架构与实现随着需求的变化而不断变得浑浊、混乱。这也就意味着系统的“熵”在不断增高。
|
||||
|
||||
这里我用一个图展示软件系统“熵”值的生命周期变化,如下:-
|
||||
|
||||
|
||||
系统只要是活跃的,“熵”值就会在生命周期中不断波动。需求的增加和改变,就是在不断增加“熵”值(系统的混乱程度)。但软件系统的“熵”有个临界值,当达到并超过临界值后,软件系统的生命也基本到头了。这时,你可能将迫不得已采取一种行动:重写或对系统做架构升级。
|
||||
|
||||
如果你不关注、也不管理系统的“熵”值,它最终的发展趋势就如图中的蓝线,一直升高,达到临界点,届时你就不得不付出巨大的代价来进行系统架构升级。
|
||||
|
||||
而实现中重构与优化的动作则是在不断进行减“熵”,作出平衡,让系统的“熵”值在安全的范围内波动。
|
||||
|
||||
那么,关于实现的核心关注点,也就呼之欲出了,我们也可以用一个字表达:简。
|
||||
|
||||
简,是简单、简洁、简明、简化,都是在做减法,但不是简陋。关于实现的全部智慧都浓缩在了这一个字里,它不仅减少代码量,也减少了开发时间,减少了测试时间,减少了潜在 Bug 的数量,甚至减少了未来的维护、理解与沟通成本。
|
||||
|
||||
架构关注复杂度的变化,自然就会带来简化,而实现则应当顺着把“简”做到极致。
|
||||
|
||||
断裂带
|
||||
|
||||
架构与实现之间,存在一条鸿沟,这是它们之间的断裂带。
|
||||
|
||||
断裂带出现在架构执行过程之中,落在文档上的架构决策实际上是静态的,但真正的架构执行过程却是动态的。架构师如何准确地传递架构决策?而开发实施的效果又如何能与架构决策保持一致?在这个过程中出现实施与决策的冲突,就又需要重新协调沟通讨论以取得新的一致。
|
||||
|
||||
当系统规模比较小时,有些架构师一个人就能把全部的设计决策在交付期限内开发完成,这就避免了很多沟通协调的问题。好些年前,我就曾这样做过一个小系统的架构升级改造,但后来的系统越来越大,慢慢就需要几十人的团队来分工协作。光是准确传递决策信息,并维持住大体的一致性,就是一件非常有挑战的工作了。
|
||||
|
||||
当系统规模足够大了,没有任何架构师能够把控住全部的细节。在实践中,我的做法是定期对系统的状态做快照,而非去把握每一次大大小小的变化,因为那样直接就会让我过载。在做快照的过程中我会发现很多的细节,也许和我当初想的完全不一样,会产生出一种“要是我来实现,绝对不会是这样”的感慨。
|
||||
|
||||
但在我发现和掌握的所有细节中,我需要做一个判断,哪些细节上的问题会是战略性的,而我有限的时间和注意力,必须放在这样的战略性细节上。而其他大量的实现细节也许和我想的不同,但只要没有越出顶层宏观结构定义的边界即可。系统是活的,控制演化的方向是可行的,而妄图掌控演化过程的每一步是不现实的。
|
||||
|
||||
关注与把控边界,这就比掌控整个领地的范围小了很多,再确认领地中的战略要地,那么掌控的能力也就有了支撑。架构与实现的鸿沟会始终存在,在这条鸿沟上选择合适的地方建设桥梁,建设桥梁的地方必是战略要地。
|
||||
|
||||
等效性
|
||||
|
||||
架构升级中,经常被问到一个问题:“这个架构能实现么?”
|
||||
|
||||
其实,这根本不是一个值得疑惑的问题。相对于建筑架构,软件架构过程其实更像是城市的规划与演变过程。有一定历史的城市,慢慢都会演变出所谓的旧城和新城。而新城相对于旧城,就是一次架构升级的过程。
|
||||
|
||||
城市规划师会对城市的分区、功能划分进行重新定位与规划。一个旧城所拥有的所有功能,如:社区、学校、医院、商业中心,难道新城会没有,或者说 “实现” 不了吗?
|
||||
|
||||
任何架构的可实现性,是完全等效的,但实现本身却不是等效的,对不同的人或不同的团队可实现性的可能、成本、效率是绝对不等效的。
|
||||
|
||||
近些年,微服务架构火了,很多人都在从曾经的单体应用架构升级到微服务架构。以前能实现的功能,换成微服务架构肯定也可以实现,只是编写代码的方式不同,信息交互的方式也不同。
|
||||
|
||||
架构升级,仅仅是一次系统的重新布局与规划,成本和效率的重新计算与设计,“熵”的重新分布与管理。
|
||||
|
||||
最后我归纳下:架构是关注系统结构与行为的决策流,而实现是围绕架构的程序开发过程;架构核心关注系统的“熵”,而实现则顺应“简”;架构注重把控系统的边界与 “要塞”,而实现则去建立 “领地”;所有架构的可实现性都是等效的,但实现的成本、效率绝不会相同。
|
||||
|
||||
文中提到,架构和实现之间有一条断裂带,而让架构与实现分道扬镳的原因有:
|
||||
|
||||
|
||||
沟通问题:如信息传递障碍。
|
||||
水平问题:如技术能力不足。
|
||||
态度问题:如偷懒走捷径。
|
||||
现实问题:如无法变更的截止日期(Deadline)。
|
||||
|
||||
|
||||
以上都是架构执行中需要面对的问题,你还能想到哪些?欢迎给我留言,和我一起探讨。
|
||||
|
||||
|
||||
|
||||
|
97
专栏/程序员进阶攻略/06模式与框架:它们的关系与误区?.md
Normal file
97
专栏/程序员进阶攻略/06模式与框架:它们的关系与误区?.md
Normal file
@ -0,0 +1,97 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
06 模式与框架:它们的关系与误区?
|
||||
在学习程序设计的路上,你一定会碰到“设计模式”,它或者给你启发,或者让你疑惑,并且你还会发现在不同的阶段遇到它,感受是不同的。而“开发框架”呢?似乎已是现在写程序的必备品。那么框架和模式又有何不同?它们有什么关系?在程序设计中又各自扮演什么角色呢?
|
||||
|
||||
设计模式
|
||||
|
||||
设计模式,最早源自 GoF 那本已成经典的《设计模式:可复用面向对象软件的基础》一书。该书自诞生以来,在程序设计领域已被捧为“圣经”。
|
||||
|
||||
软件设计模式也是参考了建筑学领域的经验,早在建筑大师克里斯托弗·亚历山大(Christopher Alexander)的著作《建筑的永恒之道》中,已给出了关于“模式”的定义:
|
||||
|
||||
|
||||
每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心,通过这种方式,我们可以无数次地重用那些已有的成功的解决方案,无须再重复相同的工作。
|
||||
|
||||
|
||||
而《设计模式》一书借鉴了建筑领域的定义和形式,原书中是这么说的:
|
||||
|
||||
|
||||
本书中涉及的设计模式并不描述新的或未经证实的设计,我们只收录那些在不同系统中多次使用过的成功设计;尽管这些设计不包括新的思路,但我们用一种新的、便于理解的方式将其展现给读者。
|
||||
|
||||
|
||||
虽然该书采用了清晰且分门别类的方式讲述各种设计模式,但我相信很多新入门的程序员在看完该书后还是会像我当年一样有困扰,无法真正理解也不知道这东西到底有啥用。
|
||||
|
||||
早年我刚开始学习 Java 和面向对象编程,并编写 JSP 程序。当我把一个 JSP 文件写到一万行代码时,自己终于受不了了,然后上网大量搜索到底怎样写 JSP 才是对的。之后,我就碰到了《设计模式》一书,读完了,感觉若有所悟,但再去写程序时,反而更加困扰了。
|
||||
|
||||
因为学 “设计模式” 之前,写程序是无所顾忌,属于拿剑就刺,虽无章法却还算迅捷。但学了一大堆 “招式” 后反而变得有点瞻前顾后,每次出剑都在考虑招式用对没,挥剑反倒滞涩不少。有人说:“设计模式,对于初窥门径的程序员,带来的麻烦简直不逊于它所解决的问题。”回顾往昔,我表示深有同感。
|
||||
|
||||
后来回想,那个阶段我把《设计模式》用成了一本 “菜谱” 配方书。现实是,没做过什么菜只是看菜谱,也只能是照猫画虎,缺少好厨师的那种能力——火候。初窥门径的程序员其实缺乏的就是这样的“火候”能力,所以在看《设计模式》时必然遭遇困惑。而这种“火候”能力则源自大量的编程设计实践,在具体的实践中抽象出模式的思维。
|
||||
|
||||
“设计模式” 是在描述一些抽象的概念,甚至还给它们起了一些专有名字,这又增加了一道弯儿、一层抽象。初窥门径的程序员,具体的实践太少,面临抽象的模式描述时难免困惑。但实践中,经验积累到一定程度的程序员,哪怕之前就没看过《设计模式》,他们却可能已经基于经验直觉地用起了某种模式。
|
||||
|
||||
前面我说过我刚学习编程时看过一遍《设计模式》,看完后反而带来更多的干扰,不过后来倒也慢慢就忘了。好些年后,我又重读了一遍,竟然豁然开朗起来,因为其中一些模式我已经在过往的编程中使用过很多次,另一些模式虽未碰到,但理解起来已不见困惑。到了这个阶段,其实我已经熟练掌握了从具体到抽象之间切换的思维模式,设计模式的 “招数” 看来就亲切了很多。
|
||||
|
||||
在我看来,模式是前人解决某类问题方式的总结,是一种解决问题域的优化路径。但引入模式也是有代价的。设计模式描述了抽象的概念,也就在代码层面引入了抽象,它会导致代码量和复杂度的增加。而衡量应用设计模式付出的代价和带来的益处是否值得,这也是程序员 “火候” 能力另一层面的体现。
|
||||
|
||||
有人说,设计模式是招数;也有人说,设计模式是内功。我想用一种大家耳熟能详的武功来类比:降龙十八掌。以其中一掌“飞龙在天”为例,看其描述:
|
||||
|
||||
|
||||
气走督脉,行手阳明大肠经商阳…此式跃起凌空,居高下击,以一飞冲天之式上跃,双膝微曲,提气丹田,急发掌劲取敌首、肩、胸上三路。
|
||||
|
||||
|
||||
以上,前半句是关于内功的抽象描述,后半部分是具体招数的描述,而设计模式的描述表达就与此有异曲同工之妙。所以,设计模式是内功和招数并重、相辅相成的 “武功”。
|
||||
|
||||
当你解决了一个前人从没有解决的问题,并把解决套路抽象成模式,你就创造了一招新的 “武功”,后来的追随者也许会给它起个新名字叫:某某模式。
|
||||
|
||||
开发框架
|
||||
|
||||
不知从何时起,写程序就越来越离不开框架了。
|
||||
|
||||
记得我还在学校时,刚学习 Java 不久,那时 Java 的重点是 J2EE(现在叫 Java EE 了),而 J2EE 的核心是 EJB。当我终于用“JSP + EJB + WebLogic(EJB 容器)+ Oracle数据库”搭起一个 Web 系统时,感觉终于掌握了 Java 的核心。
|
||||
|
||||
后来不久,我去到一家公司实习,去了以后发现那里的前辈们都在谈论什么 DI(依赖注入)和 IoC(控制反转)等新概念。他们正在把老一套的 OA 系统从基于 EJB 的架构升级到一套全新的框架上,而那套框架包含了一堆我完全没听过的新名词。
|
||||
|
||||
然后有前辈给我推荐了一本书叫 _J2EE Development Without EJB_,看完后让我十分沮丧,因为我刚刚掌握的 Java 核心技术 EJB 还没机会出手就已过时了。
|
||||
|
||||
从那时起,我开始知道了框架(Framework)这个词,然后学习了一整套的基于开源框架的程序开发方式,知道了为什么 EJB 是重量级的,而框架是轻量级的。当时 EJB 已步入暮年,而框架的春天才刚开始来临,彼时最有名的框架正好也叫 Spring。如今框架已经枝繁叶茂,遍地开花。
|
||||
|
||||
现在的编程活动中,已是大量应用框架,而框架就像是给程序员定制的开发脚手架。一个框架是一个可复用的设计组件,它统一定义了高层设计和接口,使得从框架构建应用程序变得非常容易。因此,框架可以算是打开“快速开发”与“代码复用”这两扇门的钥匙。
|
||||
|
||||
在如今这个框架遍地开花的时代,正因为框架过于好用、易于复用,所以也可能被过度利用。
|
||||
|
||||
在 Java 中,框架很多时候就是由一个或一些 jar 包组成的。早在前几年(2012 年的样子)接触到一个 Web 应用系统,当时我尝试去拷贝一份工程目录时,意外发现居然有接近 500M 大小,再去看依赖的 jar 包多达 117 个,着实吓了一跳。在 500M 工程目录拷贝进度条缓慢移动中,我在想:“如今的程序开发是不是患上了框架过度依赖症?”
|
||||
|
||||
我想那时应该没有人能解释清楚为什么这个系统需要依赖 117 个 jar 包之多,也许只是为了完成一个功能,引入了一个开源框架,而这个框架又依赖了其他 20 个 jar 包。
|
||||
|
||||
有时候,框架确实帮我们解决了大部分的脏活累活,如果运气好,这些框架的质量很高或系统的调用量不大,那么它们可能也就从来没引发过什么问题,我们也就不需要了解它们是怎么去解决那些脏活、累活的。但若不巧,哪天某个框架在某些情况下出现了问题,在搞不懂框架原理的情况下,就总会有人惊慌失措。
|
||||
|
||||
如今,框架带来的束缚在于,同一个问题,会有很多不同框架可供选择。如何了解、评估、选择与取舍框架,成了新的束缚。
|
||||
|
||||
一些知名框架都是从解决一个特定领域问题的微小代码集合开始发展到提供解决方案、绑定概念、限定编程模式,并尝试不断通用化来扩大适用范围。
|
||||
|
||||
这样的框架自然不断变得庞大、复杂、高抽象度。
|
||||
|
||||
我一直不太喜欢通用型的框架,因为通用则意味着至少要适用于大于两种或以上的场景,场景越多我们的选择和取舍成本越高。另外,通用意味着抽象度更高,而现实是越高的抽象度,越不容易被理解。例如,人生活在三维世界,理解三维空间是直观的,完全没有抽象,理解四维空间稍微困难点,那五维或以上理解起来就很困难了。
|
||||
|
||||
框架,既是钥匙,也是枷锁,既解放了我们,也束缚着我们。
|
||||
|
||||
两者关系
|
||||
|
||||
分析了模式,解读了框架,那么框架和模式有什么关系呢?
|
||||
|
||||
框架和模式的共同点在于,它们都提供了一种问题的重用解决方案。其中,框架是代码复用,模式是设计复用。
|
||||
|
||||
软件开发是一种知识与智力的活动,知识的积累很关键。框架采用了一种结构化的方式来对特定的编程领域进行了规范化,在框架中直接就会包含很多模式的应用、模式的设计概念、领域的优化实践等,都被固化在了框架之中。框架是程序代码,而模式是关于这些程序代码的知识。
|
||||
|
||||
比如像 Spring 这样的综合性框架的使用与最佳实践,就隐含了大量设计模式的套路,即使是不懂设计模式的初学者,也可以按照这些固定的编程框架写出符合规范模式的程序。但写出代码完成功能是一回事,理解真正的程序设计又是另外一回事了。
|
||||
|
||||
小时候,看过一部漫画叫《圣斗士》。程序员就像是圣斗士,框架是“圣衣”,模式是“流星拳“,但最重要的还是自身的“小宇宙”啊。
|
||||
|
||||
我相信在编程学习与实践的路上,你对设计模式与开发框架也有过自己的思考。欢迎给我留言,说说你有过怎样的认识变化和体会,我们一起讨论。
|
||||
|
||||
|
||||
|
||||
|
110
专栏/程序员进阶攻略/07多维与视图:系统设计的思考维度与展现视图.md
Normal file
110
专栏/程序员进阶攻略/07多维与视图:系统设计的思考维度与展现视图.md
Normal file
@ -0,0 +1,110 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
07 多维与视图:系统设计的思考维度与展现视图
|
||||
大学上机械设计课程时学习了 “三视图” 。三视图是观测者从三个不同位置观察同一个空间几何体所画出的图形,是正确反映物体长宽高尺寸正投影的工程图,在工程设计领域十分有用。三视图也是精确的,任何现实世界中的立体物都必然能被 “三视图” 投影到二维的平面,有了这张图就能准确制作出相应的机械零部件。
|
||||
|
||||
但在软件设计领域,则有较大的不同,软件系统是抽象的,而且维度更多。20世纪90年代,软件行业诞生了 UML(Unified Modeling Language): 统一建模语言,一种涵盖软件设计开发所有阶段的模型化与可视化支持的建模语言。
|
||||
|
||||
从 UML 的出现中就可以知道,软件先驱们一直在不懈地努力,使软件系统设计从不可直观感受触摸的抽象思维空间向现实空间进行投影。
|
||||
|
||||
UML 是一种类似于传统工程设计领域 “三视图” 的尝试,但却又远没有达到 “三视图” 的精准。虽然 UML 没能在工程实施领域内广泛流行起来,但其提供的建模思想给了我启发。让我一直在思考应该需要有哪些维度的视图,才能很好地表达一个软件系统的设计。
|
||||
|
||||
而在多年的工程实践中,我逐渐得到了一些维度的视图,下面就以我近些年一直在持续维护、设计、演进的系统(京东咚咚)为例来简单说明下。
|
||||
|
||||
一、组成视图
|
||||
|
||||
组成视图,表达了系统由哪些子系统、服务、组件部分构成。
|
||||
|
||||
2015 年,我写过一篇关于咚咚的文章:《京东咚咚架构演进》。当时我们团队对系统进行了一次微服务化的架构升级,而微服务的第一步就是拆分服务,并表达清楚拆分后整个系统到底由哪些服务构成,所以有了下面这张系统服务组成图。
|
||||
|
||||
如下图示例,它对服务进行大类划分,图中用了不同的颜色来表达这种分类:
|
||||
|
||||
|
||||
|
||||
组成视图示例
|
||||
|
||||
每一类服务提供逻辑概念上比较相关的功能,而每一个微服务又按照如下两大原则进行了更细的划分:
|
||||
|
||||
|
||||
单一化:每个服务提供单一内聚的功能集。
|
||||
正交化:任何一个功能仅由一个服务提供,无提供多个类似功能的服务。
|
||||
|
||||
|
||||
如上,就是我们系统的服务组成视图,用于帮助团队理解整体系统的宏观组成,以及个人的具体工作内容在整个系统中的位置。
|
||||
|
||||
了解了服务的组成,进一步自然就需要了解服务之间的关系与交互。
|
||||
|
||||
二、交互视图
|
||||
|
||||
交互视图,表达了系统或服务与外部系统或服务的协作关系,也即:依赖与被依赖。
|
||||
|
||||
由于咚咚系统的业务场景繁多,拆分出来的服务种类也比较多,交互关系复杂。所以可以像地图一样通过不同倍率的缩放视角来表达和观察服务之间的交互关系。
|
||||
|
||||
如下图,是一张宏观大倍率的整体交互视图示例。它隐藏了内部众多服务的交互细节,强调了终端和服务端,以及服务端内部交互的主要过程。这里依然以地图作类比,它体现了整体系统主干道场景的运动过程。而每一个服务本身,在整体的交互图中,都会有其位置,有些在主干道上,而有些则在支线上。
|
||||
|
||||
|
||||
|
||||
交互视图示例
|
||||
|
||||
如果我们把目光聚焦在一个服务上,以其为中心的表达方式,就体现了该服务的依赖协作关系。所以,可以从不同服务为中心点出发,得到关注点和细节更明确的局部交互细节图,而这样的细节图一般掌握在每个服务开发者的脑中。当我们需要写关于某个服务的设计文档时,这样的局部细节交互图也应该是必不可少的。
|
||||
|
||||
在逻辑的层面了解了服务间的协作与交互后,则需要更进一步了解这些服务的部署环境与物理结构。
|
||||
|
||||
三、部署视图
|
||||
|
||||
部署视图,表达系统的部署结构与环境。
|
||||
|
||||
部署视图,从不同的人员角色出发,关注点其实不一样,不过从应用开发和架构的角度来看,会更关注应用服务实际部署的主机环境、网络结构和其他一些环境元素依赖。下面是一张强调服务部署的机房结构、网络和依赖元素的部署图示例。
|
||||
|
||||
|
||||
|
||||
部署视图示例
|
||||
|
||||
部署视图本身也可以从不同的视角来画,这取决于你想强调什么元素。上面这张示例图,强调的是应用部署的 IDC 及其之间的网络关系,和一些关键的网络通讯延时指标。因为这些内容可能影响系统的架构设计和开发实现方式。
|
||||
|
||||
至此,组成、交互和部署图更多是表达系统的宏观视图:关注系统组合、协作和依存的关系。但还缺乏关于系统设计或实现本身的表达,这就引出了流程和状态两类视图。
|
||||
|
||||
四、流程视图
|
||||
|
||||
流程视图,表达系统内部实现的功能和控制逻辑流程。
|
||||
|
||||
可能有人喜欢用常见的流程图来表达系统设计与实现的流程,但我更偏好使用 UML 的序列图,个人感觉更清晰些。
|
||||
|
||||
下图是咚咚消息投递的一个功能逻辑流程表达,看起来就像是 UML 的序列图,但并没有完全遵循 UML 的图例语法(主要是我习惯的画图工具不支持)。而且,我想更多人即使是程序员也并不一定会清楚地了解和记得住 UML 的各种图例语法,所以都用文字做了补充说明,也就没必要一定要遵循其语法了,重点还是在于要把逻辑表达清楚。
|
||||
|
||||
|
||||
|
||||
流程视图示例
|
||||
|
||||
逻辑流程一般分两种:业务与控制。有些系统业务逻辑很复杂,而有些系统业务逻辑不复杂但请求并发很高,导致对性能、安全与稳定的要求高,所以控制逻辑就复杂了。这两类复杂的逻辑处理流程都需要表达清楚,而上图就是对业务功能逻辑的表达示例。
|
||||
|
||||
除了逻辑流程的复杂性,系统维持的状态变迁很可能也是另一个复杂性之源。
|
||||
|
||||
五、状态视图
|
||||
|
||||
状态视图,表达系统内部管理了哪些状态以及状态的变迁转移路径。
|
||||
|
||||
像咚咚这样的 IM 消息系统,就自带一个复杂的状态管理场景:消息的已读/未读状态。它的复杂性体现在,它本身就处在一个不可控的分布式场景下,在用户的多个终端和服务端之间,需要保持尽可能的最终一致性。
|
||||
|
||||
为什么没法满足绝对严格的最终一致性?如下图所示,IM 的 “已读/未读” 状态需要在用户的多个终端和服务端之间进行分布式的同步。按照分布式 CAP 原理,IM 的业务场景限定了 AP 是必须满足的,所以 C 自然就是受限的了。
|
||||
|
||||
|
||||
|
||||
状态视图示例
|
||||
|
||||
所有的业务系统都一定会有状态,因为那就是业务的核心价值,并且这个系统只要有用户使用,用户就会产生行为,行为导致系统状态的变迁。比如,IM 中用户发出的消息,用户的上下线等等都是行为引发的状态变化。
|
||||
|
||||
但无状态服务相比有状态的服务和系统要简单很多,一个系统中不是所有的服务都有状态,只会有部分服务需要状态,我们的设计仅仅是围绕在,如何尽可能地把状态限制在系统的有限范围内,控制其复杂性的区域边界。
|
||||
|
||||
至此,关于软件系统设计,我感觉通用的维度与视图就这些,但每个具体的系统可能也还有其独特的维度,也会有自己独有的视图。
|
||||
|
||||
用更系统化的视图去观察和思考,想必也会让你得到更成体系化的系统设计。
|
||||
|
||||
以上就是我关于系统设计的一些通用维度与视图的思考,那么你平时都用怎样的方式来表达程序系统设计呢?
|
||||
|
||||
|
||||
|
||||
|
91
专栏/程序员进阶攻略/08代码与分类:工业级编程的代码分类与特征.md
Normal file
91
专栏/程序员进阶攻略/08代码与分类:工业级编程的代码分类与特征.md
Normal file
@ -0,0 +1,91 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
08 代码与分类:工业级编程的代码分类与特征
|
||||
编程,就是写代码,那么在真实的行业项目中你编写的这些代码可以如何分类呢?回顾我曾经写过的各种系统代码,按代码的作用,大概都可以分为如下三类:
|
||||
|
||||
|
||||
功能
|
||||
控制
|
||||
运维
|
||||
|
||||
|
||||
如果你想提高编程水平,写出优雅的代码,那么就必须要清晰地认识清楚这三类代码。
|
||||
|
||||
一、功能
|
||||
|
||||
功能代码,是实现需求的业务逻辑代码,反映真实业务场景,包含大量领域知识。
|
||||
|
||||
一个程序软件系统,拥有完备的功能性代码仅是基本要求。因为业务逻辑的复杂度决定了功能性代码的复杂度,所以要把功能代码写好,最难的不是编码本身,而是搞清楚功能背后的需求并得到正确的理解。之后的编码活动,就仅是一个“翻译”工作了:把需求“翻译”为代码。
|
||||
|
||||
当然,“翻译” 也有自己独有的技术和积累,并不简单。而且 “翻译” 的第一步要求是 “忠于原文”,也即真正地理解并满足用户的原始需求。可这个第一步的要求实现起来就很困难。
|
||||
|
||||
为什么搞清楚用户需求很困难?因为从用户心里想要的,到他最后得到的之间有一条长长的链条,如下所示:
|
||||
|
||||
|
||||
用户心理诉求 -> 用户表达需求 -> 产品定义需求 -> 开发实现 -> 测试验证 -> 上线发布 -> 用户验收
|
||||
|
||||
|
||||
需求信息源自用户的内心,然后通过表达显性地在这个链条上传递,最终固化成了代码,以程序系统的形态反馈给了用户。
|
||||
|
||||
但信息在这个链条中的每个环节都可能会出现偏差与丢失,即使最终整个链条上的各个角色都貌似达成了一致,完成了系统开发、测试和发布,但最终也可能发现用户的心理诉求要么表达错了,要么被理解错了。
|
||||
|
||||
因为我近些年一直在做即时通讯产品(IM),所以在这儿我就以微信这样一个国民级的大家都熟悉的即时通讯产品为样本,举个例子。
|
||||
|
||||
微信里有个功能叫:消息删除。你该如何理解这个功能背后的用户心理诉求呢?用户进行删除操作的期待和反馈又是什么呢?从用户发消息的角度,我理解其删除消息可能的诉求有如下几种:
|
||||
|
||||
|
||||
消息发错了,不想对方收到。
|
||||
消息发了后,不想留下发过的痕迹,但期望对方收到。
|
||||
消息已发了,对于已经收到的用户就算了,未收到的最好就别收到了,控制其传播范围。
|
||||
|
||||
|
||||
对于第一点,微信提供了两分钟内撤回的功能;而第二点,微信提供的删除功能正好满足;第三点,微信并没有满足。我觉着第三点其实是一个伪需求,它其实是第一点不能被满足情况下用户的一种妥协。
|
||||
|
||||
用户经常会把他们的需要,表达成对你的行为的要求,也就是说不真正告诉你要什么,而是告诉你要做什么。所以你才需要对被要求开发的功能进行更深入的思考。有时,即使是日常高频使用的产品背后的需求,你也未必能很好地理解清楚,而更多的业务系统其实离你的生活更远,努力去理解业务及其背后用户的真实需求,才是写好功能代码的基本能力。
|
||||
|
||||
程序存在的意义就在于实现功能,满足需求。而一直以来我们习惯于把完成客户需求作为程序开发的主要任务,当功能实现了便感觉已经完成了开发,但这仅仅是第一步。
|
||||
|
||||
二、控制
|
||||
|
||||
控制代码,是控制业务功能逻辑代码执行的代码,即业务逻辑的执行策略。
|
||||
|
||||
编程领域熟悉的各类设计模式,都是在讲关于控制代码的逻辑。而如今,很多这些常用的设计模式基本都被各类开源框架固化了进去。比如,在 Java 中,Spring 框架提供的控制反转(IoC)、依赖注入(DI)就固化了工厂模式。
|
||||
|
||||
通用控制型代码由各种开源框架来提供,程序员就被解放出来专注写好功能业务逻辑。而现今分布式领域流行的微服务架构,各种架构模式和最佳实践也开始出现在各类开源组件中。比如微服务架构模式下关注的控制领域,包括:通信、负载、限流、隔离、熔断、异步、并行、重试、降级。
|
||||
|
||||
以上每个领域都有相应的开源组件代码解决方案,而进一步将控制和功能分离的 “服务网格(Service Mesh)” 架构模式则做到了极致,控制和功能代码甚至运行在了不同的进程中。
|
||||
|
||||
控制代码,都是与业务功能逻辑不直接相关的,但它们和程序运行的性能、稳定性、可用性直接相关。提供一项服务,功能代码满足了服务的功能需求,而控制代码则保障了服务的稳定可靠。
|
||||
|
||||
有了控制和功能代码,程序系统终于能正常且稳定可靠地运行了,但难保不出现异常,这时最后一类 “运维” 型代码便要登场了。
|
||||
|
||||
三、运维
|
||||
|
||||
运维代码,就是方便程序检测、诊断和运行时处理的代码。它们的存在,才让系统具备了真正工业级的可运维性。
|
||||
|
||||
最常见的检测诊断性代码,应该就是日志了,打日志太过简单,因此我们通常也就疏于考虑。其实即使是打日志也需要有意识的设计,评估到底应该输出多少日志,在什么位置输出日志,以及输出什么级别的日志。
|
||||
|
||||
检测诊断代码有一个终极目标,就是让程序系统完成运行时的自检诊断。这是完美的理想状态,却很难在现实中完全做到。
|
||||
|
||||
因为它不仅仅受限于技术实现水平,也与实现的成本和效益比有关。所以,我们可以退而求其次,至少在系统异常时可以具备主动运行状态汇报能力,由开发和运维人员来完成诊断分析,这也是我们常见的各类系统或终端软件提供的机制。
|
||||
|
||||
在现实中,检测诊断类代码经常不是一开始就主动设计的。但生产环境上的程序系统可能会偶然出现异常或故障,而因为一开始缺乏检测诊断代码输出,所以很难找到真实的故障原因。现实就这样一步一步逼着你去找到真实原因,于是检测诊断代码就这么被一次又一次地追问为什么而逐渐完善起来了。
|
||||
|
||||
但如果一开始你就进行有意识地检测诊断设计,后面就会得到更优雅的实现。有一种编程模式:面向切面编程(AOP),通过早期的有意设计,可以把相当范围的检测诊断代码放入切面之中,和功能、控制代码分离,保持优雅的边界与距离。
|
||||
|
||||
而对于特定的编程语言平台,比如 Java 平台,有字节码增强相关的技术,可以完全干净地把这类检测诊断代码和功能、控制代码彻底分离。
|
||||
|
||||
运维类代码的另一种类,是方便在运行时,对系统行为进行改变的代码。通常这一类代码提供方便运维操作的 API 服务,甚至还会有专门针对运维提供的服务和应用,例如:备份与恢复数据、实时流量调度等。
|
||||
|
||||
功能、控制、运维,三类代码,在现实的开发场景中优先级这样依次排序。有时你可能仅仅完成了第一类功能代码就迫于各种压力上线发布了,但你要在内心谨记,少了后两类代码,将来都会是负债,甚至是灾难。而一个满足工业级强度的程序系统,这三类代码,一个也不能少。
|
||||
|
||||
而对三类代码的设计和实现,越是优雅的程序,这三类代码在程序实现中就越是能看出明显的边界。为什么需要边界?因为,“码以类聚,人以群分”。功能代码易变化,控制代码固复杂,运维代码偏繁琐,这三类不同的代码,不仅特征不同,而且编写它们的人(程序员)也可能分属不同群组,有足够的边界与距离才能避免耦合与混乱。
|
||||
|
||||
而在程序这个理性世界中,优雅有时就是边界与距离。
|
||||
|
||||
|
||||
|
||||
|
96
专栏/程序员进阶攻略/09粗放与精益:编程的两种思路与方式.md
Normal file
96
专栏/程序员进阶攻略/09粗放与精益:编程的两种思路与方式.md
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
09 粗放与精益:编程的两种思路与方式
|
||||
几年前,我给团队负责的整个系统写过一些公共库,有一次同事发现这个库里存在一个Bug,并告诉了我出错的现象。然后我便去修复这个Bug,最终只修改了一行代码,但发现一上午就这么过去了。
|
||||
|
||||
一上午只修复了一个Bug,而且只改了一行代码,到底发生了什么?时间都去哪里了?以前觉得自己写代码很快,怎么后来越来越慢了?我认真地思考了这个问题,开始认识到我的编程方式和习惯在那几年已经慢慢发生了变化,形成了明显的两个阶段的转变。这两个阶段是:
|
||||
|
||||
|
||||
写得粗放,写得多
|
||||
写得精益,写得好
|
||||
|
||||
|
||||
多与粗放
|
||||
|
||||
粗放,在软件开发这个年轻的行业里其实没有确切的定义,但在传统行业中确实存在相近的关于 “粗放经营” 的概念可类比。引用其百科词条定义如下:
|
||||
|
||||
|
||||
粗放经营(Extensive Management),泛指技术和管理水平不高,生产要素利用效率低,产品粗制滥造,物质和劳动消耗高的生产经营方式。
|
||||
|
||||
|
||||
若把上面这段话里面的 “经营” 二字改成 “编程”,就很明确地道出了我想表达的粗放式编程的含义。
|
||||
|
||||
一个典型的粗放式编程场景大概是这样的:需求到开发手上后,开始编码,编码完成,人肉测试,没问题后快速发布到线上,然后进入下一个迭代。
|
||||
|
||||
我早期参与的大量项目过程都与此类似,不停地重复接需求,快速开发,发布上线。在这个过程中,我只是在不停地堆砌功能代码,每天产出的代码量不算少,但感觉都很类似,也很粗糙。这样的过程持续了挺长一个阶段,一度让我怀疑:这样大量而粗放地写代码到底有什么作用和意义?
|
||||
|
||||
后来读到一个故事,我逐渐明白这个阶段是必要的,它因人、因环境而异,或长或短。而那个给我启发的故事,是这样的。
|
||||
|
||||
有一个陶艺老师在第一堂课上说,他会把班上学生分成两组,一组的成绩将会以最终完成的陶器作品数量来评定;而另一组,则会以最终完成的陶器品质来评定。
|
||||
|
||||
在交作业的时候,一个很有趣的现象出现了:“数量” 组如预期一般拿出了很多作品,但出乎意料的是质量最好的作品也全部是由 “数量” 组制作出来的。
|
||||
|
||||
按 “数量” 组的评定标准,他们似乎应该忙于粗制滥造大量的陶器呀。但实际情况是他们每做出一个垃圾作品,都会吸取上一次制作的错误教训,然后在做下一个作品时得到改进。
|
||||
|
||||
而 “品质” 组一开始就追求完美的作品,他们花费了大量的时间从理论上不断论证如何才能做出一个完美的作品,而到了最后拿出来的东西,似乎只是一堆建立在宏大理论上的陶土。
|
||||
|
||||
读完这个故事,我陷入了沉思,感觉故事里的制作陶器和编程提升之路是如此类似。很显然,“品质” 组的同学一开始就在追求理想上的 “好与精益” ,而 “数量” 组同学的完成方式则似我早期堆砌代码时的“多与粗放”,但他们正是通过做得多,不断尝试,快速迭代 ,最后取得到了更好的结果。
|
||||
|
||||
庆幸的是,我在初学编程时,就是在不断通过编程训练来解答一个又一个书本上得来的困惑;后来工作时,则是在不断写程序来解决一个又一个工作中遇到的问题。看到书上探讨各种优雅的代码之道、编程的艺术哲学,那时的我也完全不知道该如何通往这座编程的 “圣杯”,只能看着自己写出的蹩脚代码,然后继续不断重复去制作下一个丑陋的 “陶器”,不断尝试,不断精进和进阶。
|
||||
|
||||
《黑客与画家》书里说:“编程和画画近乎异曲同工。”所以,你看那些成名画家的作品,如果按时间顺序来排列展示,你会发现每幅画所用的技巧,都是建立在上一幅作品学到的东西之上;如果某幅作品特别出众,你往往也能在更早期的作品中找到类似的版本。而编程的精进过程也是类似的。
|
||||
|
||||
总之,这些故事和经历都印证了一个道理:在通往 “更好” 的路上,总会经过 “更多” 这条路。
|
||||
|
||||
好与精益
|
||||
|
||||
精益,也是借鉴自传统行业里的一个类比:精益生产。
|
||||
|
||||
|
||||
精益生产(Lean Production),简言之,就是一种以满足用户需求为目标、力求降低成本、提高产品的质量、不断创新的资源节约型生产方式。
|
||||
|
||||
|
||||
若将定义中的 “生产” 二字换成 “编程”,也就道出了精益编程的内涵。它有几个关键点:质量、成本与效率。但要注意:在编程路上,如果一开始就像 “品质” 组同学那样去追求完美,也许你就会被定义 “完美” 的品质所绊住,而忽视了制作的成本与效率。
|
||||
|
||||
因为编程的难点是,无论你在开始动手编程时看过多少有关编程理论、方法、哲学与艺术的书,一开始你还是无法领悟到什么是编程的正确方法,以及什么是“完美” 的程序。毕竟纸上得来终觉浅,绝知此事要躬行。
|
||||
|
||||
曾经,还在学校学习编程时,有一次老师布置了一个期中课程设计,我很快完成了这个课程设计中的编程作业。而另一位同学,刚刚看完了那本经典的《设计模式》书。
|
||||
|
||||
他尝试用书里学到的新概念来设计这个编程作业,并且又用 UML 画了一大堆交互和类图,去推导设计的完美与优雅。然后兴致勃勃向我(因为我刚好坐在他旁边)讲解他的完美设计,我若有所悟,觉得里面确实有值得我借鉴的地方,就准备吸收一些我能听明白的东西,重构一遍已经写好的作业程序。
|
||||
|
||||
后来,这位同学在动手实现他的完美设计时,发现程序越写越复杂,交作业的时间已经不够了,只好借用我的不完美的第一版代码改改凑合交了。而我在这第一版代码基础上,又按领悟到的正确思路重构了一次、改进了一番后交了作业。
|
||||
|
||||
所以,别被所谓 “完美“ 的程序所困扰,只管先去盯住你要用编程解决的问题,把问题解决,把任务完成。
|
||||
|
||||
编程,其实一开始哪有什么完美,只有不断变得更好。
|
||||
|
||||
工作后,我做了大量的项目,发现这些项目都有很多类似之处。每次,即使项目上线后,我也必然重构项目代码,提取其中可复用的代码,然后在下一个项目中使用。循环往复,一直干了七八年。每次提炼重构,都是一次从 “更多” 走向 “更好” 的过程。我想,很多程序员都有类似的经历吧?
|
||||
|
||||
回到开头修改Bug 的例子,我用半天的时间改一个Bug,感觉效率不算高,这符合精益编程的思路吗?先来回顾下这半天改这个Bug 的过程。
|
||||
|
||||
由于出问题的那个公共库是我接到Bug 时的半年前开发的,所以发现那个Bug 后,我花了一些时间来回忆整个公共库的代码结构设计。然后我研究了一下,发现其出现的场景比较罕见,要不不至于线上运行了很久也没人发现,属于重要但不紧急。
|
||||
|
||||
因此,我没有立刻着手去修改代码,而是先在公共库的单元测试集中新写了一组单元测试案例。单元测试构建了该Bug的重现场景,并顺利让单元测试运行失败了,之后我再开始去修改代码,并找到了出问题的那一行,修改后重新运行了单元测试集,并顺利看见了测试通过的绿色进度条。
|
||||
|
||||
而作为一个公共库,修改完成后我还要为本次修改更新发布版本,编写对应的文档,并上传到 Maven 仓库中,才算完成。回想这一系列的步骤,我发现时间主要花在了构建重现Bug 的测试案例场景中,有时为了构建一个测试场景编写代码的难度可能比开发功能本身更困难。
|
||||
|
||||
为修改一个Bug 付出的额外单元测试时间成本,算一种浪费吗?虽说这确实提高了代码的修复成本,但也带来了程序质量的提升。按前面精益的定义,这似乎是矛盾的,但其实更是一种权衡与取舍。
|
||||
|
||||
就是在这样的过程与反复中,我渐渐形成了属于自己的编程价值观:世上没有完美的解决方案,任何方案总是有这样或那样一些因子可以优化。一些方案可能面临的权衡取舍会少些,而另一些方案则会更纠结一些,但最终都要做取舍。
|
||||
|
||||
以上,也说明了一个道理:好不是完美,好是一个过程,一个不断精益化的过程。
|
||||
|
||||
编程,当写得足够多了,也足够好了,你才可能自如地在 “多” 与 “好” 之间做出平衡。
|
||||
|
||||
编程的背后是交付程序系统,交付关心的是三点:功能多少,质量好坏,效率快慢。真实的编程环境下, 你需要在三者间取得平衡,哪些部分可能是多而粗放的交付,哪些部分是好而精益的完成,同时还要考虑效率快慢(时间)的需求。
|
||||
|
||||
编程路上,“粗放的多” 是 “精益的好和快” 的前提,而好和快则是你的取舍:是追求好的极致,还是快的极致,或者二者的平衡?
|
||||
|
||||
在多而粗放和好而精益之间,现在你处在哪个阶段了?欢迎留言谈谈你的看法。
|
||||
|
||||
|
||||
|
||||
|
71
专栏/程序员进阶攻略/10炫技与克制:代码的两种味道与态度.md
Normal file
71
专栏/程序员进阶攻略/10炫技与克制:代码的两种味道与态度.md
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
10 炫技与克制:代码的两种味道与态度
|
||||
虽然你代码可能已经写得不少了,但要真正提高代码水平,其实还需要多读代码。就像写作,写得再多,不多读书,思维和认知水平其实是很难提高的。
|
||||
|
||||
代码读得多了,慢慢就会感受到好代码中有一种味道和品质:克制。但也会发现另一种代码,它也会散发出一种味道:炫技。
|
||||
|
||||
炫技
|
||||
|
||||
什么是炫技的代码?
|
||||
|
||||
我先从一个读代码的故事说起。几年前我因为工作需要,去研究一个开源项目的源代码。这是一个国外知名互联网公司开源的工具项目,据说已在内部孵化了 6 年之久,这才开源出来。从其设计文档与代码结构来看,它高层设计的一致性还是比较好的,但到了源代码实现就显得凌乱了些,而且发现了一些炫技的痕迹。
|
||||
|
||||
代码中炫技的地方,具体来说就是关于状态机的使用。状态机程序本是不符合线性逻辑思维的,有点类似goto语句,程序执行会突然发生跳转,所以理解状态机程序的代码要比一般程序困难些。除此之外,它的状态机程序实现又是通过自定义的内存消息机制来驱动,这又额外添加了一层抽象复杂度。
|
||||
|
||||
而在我看来,状态机程序最适合的场景是一种真实领域状态变迁的映射。那什么叫真实领域状态呢?比如,红绿灯就表达了真实交通领域中的三种状态。而另一种场景,是网络编程领域,广泛应用在网络协议解析上,表达解析器当前的运行状态。
|
||||
|
||||
而但凡使用状态机来表达程序设计实现中引入的 “伪” 状态,往往都添加了不必要的复杂性,这就有点炫技的感觉了。但是我还是能常常在一些开源项目中看到一些过度设计和实现的复杂性,而这些项目往往还都是一些行业内头部大公司开源的。
|
||||
|
||||
在程序员的成长路径上,攀登公司的晋升阶梯时,通常会采用同行评审制度,而作为技术人就容易倾向性地关注项目或工程中的技术含量与难点。
|
||||
|
||||
这样的制度倾向性,有可能导致人为制造技术含量,也就是炫技了。就像体操运动中,你完成一个高难度动作,能加的分数有限,而一旦搞砸了,付出的代价则要惨重很多。所以,在比赛中高难度动作都是在关键的合适时刻才会选择。同样,项目中的炫技,未必能加分,还有可能导致减分,比如其维护与理解成本变高了。
|
||||
|
||||
而除了增加不必要的复杂性外,炫技的代码,也可能更容易出 Bug。
|
||||
|
||||
刚工作的头一年,我在广东省中国银行写过一个小程序,就是给所有广东省中国银行的信用卡客户发邮件账单。由于当时广东中行信用卡刚起步,第一个月只有不到 10 万客户,所以算是小程序。
|
||||
|
||||
这个小程序就是个单机程序,为了方便业务人员操作,我写了个 GUI 界面。这是我第一次用 Java Swing 库来写 GUI,为了展示发送进度,后台线程每发送成功一封邮件,就通知页面线程更新进度条。
|
||||
|
||||
为什么这么设计呢?因为那时我正在学习 Java 线程编程,感觉这个技术很高端,而当时的 Java JDK 都还没标配线程 concurrent 包。所以,我选择线程间通信的方案来让后台发送线程和前端界面刷新线程通信,这就有了一股浓浓的炫技味道。
|
||||
|
||||
之后,就出现了界面动不动就卡住等一系列问题,因为各种线程提前通知、遗漏通知等情况没考虑到,代码也越改越难懂。其实后来想想,用个共享状态,定时轮询即可满足需要,而且代码实现会简单很多(前面《架构与实现》一文中,关于实现的核心我总结了一个字:简。这都是血泪教训啊),出 Bug 的概率也小了很多。
|
||||
|
||||
回头想想,成长的路上不免见猎心喜,手上拿个锤子看到哪里都是钉子。
|
||||
|
||||
炫技是因为你想表达得不一样,就像平常说话,你要故意说得引经据典去彰显自己有文化,但其实效果不一定佳,因为我们更需要的是平实、易懂的表达。
|
||||
|
||||
克制
|
||||
|
||||
在说克制之前,先说说什么叫不克制,写代码的不克制。
|
||||
|
||||
刚工作的第二年,我接手了一个比较大的项目中的一个主要子系统。在熟悉了整个系统后,我开始往里面增加功能时,有点受不了原本系统设计分层中的 DAO(Data Access Object, 数据访问对象)层,那是基于原生的 JDBC 封装的。每次新增一个 DAO 对象都需要复制粘贴一串看起来很类似的代码,难免生出厌烦的感觉。
|
||||
|
||||
当时开源框架 Hibernate 刚兴起,我觉得它的设计理念优雅,代码写出来也简洁,所以就决定用 Hibernate 的方式来取代原本的实现。原来的旧系统里,说多不多,说少也不少,好几百个 DAO 类,而重新实现整个 DAO 层,让我连续加了一周的班。
|
||||
|
||||
这个替换过程,是个纯粹的搬砖体力活,弄完了还没松口气就又有了新问题:Hibernate 在某些场景下出现了性能问题。陆陆续续把这些新问题处理好,着实让我累了一阵子。后来反思这个决策感觉确实不太妥当,替换带来的好处仅仅是每次新增一个 DAO 类时少写几行代码,却带来很多当时未知的风险。
|
||||
|
||||
那时年轻,有激情啊,对新技术充满好奇与冲动。其实对于新技术,即使从我知道、我了解到我熟悉、我深谙,这时也还需要克制,要等待合适的时机。这让我想起了电影《勇敢的心》中的一个场景,是战场上华莱士看着对方冲过来,高喊:“Hold!Hold!”新技术的应用,也需要等待一个合适的出击时刻,也许是应用在新的服务上,也许是下一次架构升级。
|
||||
|
||||
不克制的一种形态是容易做出臆想的、通用化的假设,而且我们还会给这种假设安一个非常正当的理由:扩展性。不可否认,扩展性很重要,但扩展性也应当来自真实的需求,而非假设将来的某天可能需要扩展,因为扩展性的反面就是带来设计抽象的复杂性以及代码量的增加。
|
||||
|
||||
那么,如何才是克制的编程方式?我想可能有这样一些方面:
|
||||
|
||||
|
||||
克制的编码,是每次写完代码,需要去反思和提炼它,代码应当是直观的,可读的,高效的。
|
||||
克制的代码,是即使站在远远的地方去看屏幕上的代码,甚至看不清代码的具体内容时,也能感受到它的结构是干净整齐的,而非 “意大利面条” 似的混乱无序。
|
||||
克制的重构,是每次看到 “坏” 代码不是立刻就动手去改,而是先标记圈定它,然后通读代码,掌握全局,重新设计,最后再等待一个合适的时机,来一气呵成地完成重构。
|
||||
|
||||
|
||||
总之,克制是不要留下多余的想象,是不炫技、不追新,且恰到好处地满足需要,是一种平实、清晰、易懂的表达。
|
||||
|
||||
克制与炫技,匹配与适度,代码的技术深度未必体现在技巧上。有句话是这么说的:“看山是山,看水是水;看山不是山,看水不是水;看山还是山,看水还是水。”转了一圈回来,机锋尽敛,大巧若拙,深在深处,浅在浅处。
|
||||
|
||||
最后,亲爱的读者朋友,在你的编码成长过程中,有过想要炫技而不克制的时候吗?欢迎你留言。
|
||||
|
||||
|
||||
|
||||
|
186
专栏/程序员进阶攻略/11三阶段进化:调试,编写与运行代码.md
Normal file
186
专栏/程序员进阶攻略/11三阶段进化:调试,编写与运行代码.md
Normal file
@ -0,0 +1,186 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
11 三阶段进化:调试,编写与运行代码
|
||||
刚开始学编程写代码,总会碰到一些困惑。比如,曾经就有刚入行的同学问我:“写程序是想到哪写到哪,边写边改边验证好,还是先整体梳理出思路,有步骤、有计划地分析后,再写更好?”
|
||||
|
||||
老实说,我刚入行时走的是前一条路,因为没有什么人或方法论来指导我,都是自己瞎摸索。一路走来十多年后,再回溯编程之路的经历,总结编程的进化过程,大概会经历下面三个阶段。
|
||||
|
||||
阶段一:调试代码 Debugging
|
||||
|
||||
编程,是把用自然语言描述的现实问题,转变为用程序语言来描述并解决问题的过程;翻译,也是把一种语言的文字转变为另一种语言的文字,所以我想编程和翻译应该是有相通之处的。
|
||||
|
||||
好些年前,我曾偶然读到一篇关于性能的英文文章,读完不禁拍案叫绝,就忍不住想翻译过来。那是我第一次尝试翻译长篇英文,老实说翻得很痛苦,断断续续花了好几周的业余时间。那时的我,之于翻译,就是一个刚入门的初学者。
|
||||
|
||||
初次翻译,免不了遇到不少不熟悉的单词或词组,一路磕磕碰碰地查词典或 Google。一些似乎能理解含义的句子,却感觉无法很好地用中文来表达,如果直白地译出来感觉又不像正常的中文句子表达方式。
|
||||
|
||||
如是种种的磕碰之处,难道不像你刚学编程时候的情形吗?刚开始写代码,对语法掌握得不熟,对各种库和 API 不知道,不了解,也不熟悉。一路写代码,翻翻书,查查 Google,搜搜 API 文档,好不容易写完一段代码,却又不知道能否执行,执行能否正确等等。
|
||||
|
||||
小心翼翼地点击 Debug 按钮开始了单步调试之旅,一步步验证所有的变量或执行结果是否符合预期。如果出错了,是在哪一步开始或哪个变量出错的?一段不到一屏的代码,足足单步走了半小时,反复改了好几次,终于顺利执行完毕,按预期输出了执行结果。
|
||||
|
||||
如果不是自己写全新的代码,而是一来就接手了别人的代码,没有文档,前辈稍微给你介绍两句,你就很快又开始了 Debug 的单步调试之旅,一步步搞清代码运行的所有步骤和内部逻辑。根据你接手代码的规模,这个阶段可能持续数天到数周不等。
|
||||
|
||||
这就是我感觉可以划为编程第一阶段的 “调试代码 Debugging” 时期。这个时期或长或短,也许你曾经为各种编程工具或 IDE 提供的高级 Debug 功能激动不已,但如果你不逐渐降低使用Debug 功能的频率,那么你可能很难走入第二阶段。
|
||||
|
||||
阶段二:编写代码 Coding
|
||||
|
||||
翻译讲究 “信、达、雅”,编码亦如此。
|
||||
|
||||
那么何谓 “信、达、雅” ?它是由我国清末新兴启蒙思想家严复提出的,他在《天演论》中的 “译例言” 讲到:
|
||||
|
||||
|
||||
译事三难:信、达、雅。求其信已大难矣,顾信矣,不达,虽译犹不译也,则达尚焉。
|
||||
|
||||
|
||||
信,指不违背原文,不偏离原文,不篡改,不增不减,要求准确可信地表达原文描述的事实。
|
||||
|
||||
这条应用在编程上就是:程序员需要深刻地理解用户的原始需求。虽然需求很多时候来自于需求(产品)文档,但需求(产品)文档上写的并不一定真正体现了用户的原始需求。关于用户需求的“提炼”,早已有流传甚广的“福特之问”。
|
||||
|
||||
|
||||
福特:您需要一个什么样的更好的交通工具?-
|
||||
用户:我要一匹更快的马。
|
||||
|
||||
|
||||
用户说需要一匹更快的马,你就跑去 “养” 只更壮、更快的马;后来用户需求又变了,说要让马能在天上飞,你可能就傻眼了,只能拒绝用户说:“这需求不合理,技术上实现不了。”可见,用户所说的也不可 “信” 矣。只有真正挖掘并理解了用户的原始需求,最后通过编程实现的程序系统才是符合 “信” 的标准的。
|
||||
|
||||
但在这一条的修行上几乎没有止境,因为要做到 “信” 的标准,编写行业软件程序的程序员需要在一个行业长期沉淀,才能慢慢搞明白用户的真实需求。
|
||||
|
||||
达,指不拘泥于原文的形式,表达通顺明白,让读者对所述内容明达。
|
||||
|
||||
这条应用在编程上就是在说程序的可读性、可理解性和可维护性。
|
||||
|
||||
按严复的标准,只满足 “信” 一条的翻译,还不如不译,至少还需要满足 “达” 这条才算尚可。
|
||||
|
||||
同样,只满足 “信” 这一条的程序虽然能准确地满足用户的需要,但没有 “达” 则很难维护下去。因为程序固然是写给机器去执行的,但其实也是给人看的。
|
||||
|
||||
所有关于代码规范和风格的编程约束都是在约定 “达” 的标准。个人可以通过编程实践用时间来积累经验,逐渐达到 “达” 的标准。但一个团队中程序员们的代码风格差异如何解决?这就像如果一本书由一群人来翻译,你会发现每章的文字风格都有差异,所以我是不太喜欢读由一群人一起翻译的书。
|
||||
|
||||
一些流行建议的解决方案是:多沟通,深入理解别人的代码思路和风格,不要轻易盲目地修改。但这些年实践下来,这个方法在现实中走得并不顺畅。
|
||||
|
||||
随着微服务架构的流行,倒是提供了另一种解决方案:每个服务对应一个唯一的负责人(Owner)。长期由一个人来维护的代码,就不会那么容易腐烂,因为一个人不存在沟通问题。而一个人所能 “达” 到的层次,完全由个人的经验水平和追求来决定。
|
||||
|
||||
雅,指选用的词语要得体,追求文章本身的古雅,简明优雅。
|
||||
|
||||
雅的标准,应用在编程上已经从技艺上升到了艺术的追求,这当然是很高的要求与自我追求了,难以强求。而只有先满足于 “信” 和 “达” 的要求,你才有余力来追求 “雅” 。
|
||||
|
||||
举个例子来说明下从 “达” 到 “雅” 的追求与差异。
|
||||
|
||||
下面是一段程序片段,同一个方法,实现完全一样的功能,都符合 “信” 的要求;而方法很短小,命名也完全符合规范,可理解性和维护性都没问题,符合 “达” 的要求;差别就在对 “雅” 的追求上。
|
||||
|
||||
private String generateKey(String service, String method) {
|
||||
String head = "DBO$";
|
||||
String key = "";
|
||||
|
||||
int len = head.length() + service.length() + method.length();
|
||||
if (len <= 50) {
|
||||
key = head + service + method;
|
||||
} else {
|
||||
service = service.substring(service.lastIndexOf(".") + 1);
|
||||
len = head.length() + service.length() + method.length();
|
||||
key = head + service + method;
|
||||
if (len > 50) {
|
||||
key = head + method;
|
||||
if (key.length() > 50) {
|
||||
key = key.substring(0, 48) + ".~";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
该方法的目标是生成一个字符串 key 值,传入两个参数:服务名和方法名,然后返回 key 值,key 的长度受外部条件约束不能超过 50 个字符。方法实现不复杂,很短,看起来也还不错,分析下其中的逻辑:
|
||||
|
||||
|
||||
先 key 由固定的头(head)+ service(全类名)+ method(方法)组成,若小于 50 字符,直接返回。
|
||||
若超过 50 字符限制,则去掉包名,保留类名,再判断一次,若此时小于 50 字符则返回。
|
||||
若还是超过 50 字符限制,则连类名一起去掉,保留头和方法再判断一次,若小于 50 字符则返回。
|
||||
最后如果有个变态长的方法名(46+ 个字符),没办法,只好暴力截断到 50 字符返回。
|
||||
|
||||
|
||||
这个实现最大限度地在生成的 key 中保留全部有用的信息,对超过限制的情况依次按信息重要程度的不同进行丢弃。这里只有一个问题,这个业务规则只有 4 个判断,实现进行了三次 if 语句嵌套,还好这个方法比较短,可读性还不成问题。
|
||||
|
||||
而现实中很多业务规则比这复杂得多,以前看过一些实现的 if 嵌套多达 10 层的,方法也长得要命。当然一开始没有嵌套那么多层,只是后来随着时间的演变,业务规则发生了变化,慢慢增加了。之后接手的程序员就按照这种方式继续嵌套下去,慢慢演变至此,到我看到的时候就有 10 层了。
|
||||
|
||||
程序员有一种编程的惯性,特别是进行维护性编程时。一开始接手一个别人做的系统,不可能一下能了解和掌控全局。当要增加新功能时,在原有代码上添加逻辑,很容易保持原来程序的写法惯性,因为这样写也更安全。
|
||||
|
||||
所以一个 10 层嵌套 if 的业务逻辑方法实现,第一个程序员也许只写了 3 次嵌套,感觉还不错,也不失简洁。后来写 4、5、6 层的程序员则是懒惰不愿再改,到了写第 8、9、10 层的程序员时,基本很可能就是不敢再乱动了。
|
||||
|
||||
那么如何让这个小程序在未来的生命周期内,更优雅地演变下去?下面是另一个版本的实现:
|
||||
|
||||
private String generateKey(String service, String method) {
|
||||
String head = "DBO$";
|
||||
String key = head + service + method;
|
||||
|
||||
// head + service(with package) + method
|
||||
if (key.length() <= 50) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// head + service(without package) + method
|
||||
service = service.substring(service.lastIndexOf(".") + 1);
|
||||
key = head + service + method;
|
||||
if (key.length() <= 50) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// head + method
|
||||
key = head + method;
|
||||
if (key.length() <= 50) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// last, we cut the string to 50 characters limit.
|
||||
key = key.substring(0, 48) + ".~";
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
从嵌套变成了顺序逻辑,这样可以为未来的程序员留下更优雅地编程惯性方向。
|
||||
|
||||
阶段三:运行代码 Running
|
||||
|
||||
编程相对翻译,其超越 “信、达、雅” 的部分在于:翻译出来的文字能让人读懂,读爽就够了;但代码写出来还需要运行,才能产生最终的价值。
|
||||
|
||||
写程序我们追求 “又快又好”,并且写出来的代码要符合 “信、达、雅” 的标准,但清晰定义 “多快多好” 则是指运行时的效率和效果。为准确评估代码的运行效率和效果,每个程序员可能都需要深刻记住并理解下面这张关于程序延迟数字的图:
|
||||
|
||||
|
||||
|
||||
每个程序员都应该知道的延迟数字
|
||||
|
||||
只有深刻记住并理解了程序运行各环节的效率数据,你才有可能接近准确地评估程序运行的最终效果。当然,上面这张图只是最基础的程序运行效率数据,实际的生产运行环节会需要更多的基准效率数据才可能做出更准确的预估。
|
||||
|
||||
说一个例子,曾经我所在团队的一个高级程序员和我讨论要在所有的微服务中引入一个限流开源工具。这对于他和我们团队都是一个新东西,如何进行引入后线上运行效果的评估呢?
|
||||
|
||||
第一步,他去阅读资料和代码搞懂该工具的实现原理与机制并能清晰地描述出来。第二步,去对该工具进行效果测试,又称功能可用性验证。第三步,进行基准性能测试,或者又叫基准效率测试(Benchmark),以确定符合预期的标准。
|
||||
|
||||
做完上述三步,他拿出一个该工具的原理性描述说明文档,一份样例使用代码和一份基准效率测试结果,如下:
|
||||
|
||||
|
||||
|
||||
上图中有个红色字体部分,当阀值设置为 100 万而请求数超过 100 万时,发生了很大偏差。这是一个很奇怪的测试结果,但如果心里对各种基准效率数据有谱的话,会知道这实际绝不会影响线上服务的运行。
|
||||
|
||||
因为我们的服务主要由两部分组成:RPC 和业务逻辑。而 RPC 又由网络通信加上编解码序列化组成。服务都是 Java 实现的,而目前 Java 中最高效且吞吐最大的网络通信方式是基于 NIO 的方式,而我们服务使用的 RPC 框架正是基于 Netty(一个基于 Java NIO 的开源网络通信框架)的。
|
||||
|
||||
我曾经单独在一组 4 核的物理主机上测试过 Java 原生 NIO 与 Netty v3 和 v4 两个版本的基准性能对比,经过 Netty 封装后,大约有 10% 的性能损耗。在 1K 大小报文时,原生的 Java NIO 在当时的测试环境所能达到 TPS(每秒事务数) 的极限大约 5 万出头(极限,就是继续加压,但 TPS 不再上升,CPU 也消耗不上去,延时却在增加),而 Netty 在 4.5 万附近。增加了 RPC 的编解码后,TPS 极限下降至 1.3 万左右。
|
||||
|
||||
所以,实际一个服务在类似基准测试的环境下单实例所能承载的 TPS 极限不可能超过 RPC 的上限,因为 RPC 是没有包含业务逻辑的部分。加上不算简单的业务逻辑,我能预期的单实例真实 TPS 也许只有 1千 ~2 千。
|
||||
|
||||
因此,上面 100 万的阀值偏差是绝对影响不到单实例的服务的。当然最后我们也搞明白了,100 万的阀值偏差来自于时间精度的大小,那个限流工具采用了微秒作为最小时间精度,所以只能在百万级的范围内保证准确。
|
||||
|
||||
讲完上述例子,就是想说明一个程序员要想精确评估程序的运行效率和效果,就得自己动手做大量的基准测试。
|
||||
|
||||
基准测试和测试人员做的性能测试不同。测试人员做的性能测试都是针对真实业务综合场景的模拟,测试的是整体系统的运行;而基准测试是开发人员自己做来帮助准确理解程序运行效率和效果的方式,当测试人员在性能测试发现了系统的性能问题时,开发人员才可能一步步拆解根据基准测试的标尺效果找到真正的瓶颈点,否则大部分的性能优化都是在靠猜测。
|
||||
|
||||
到了这个阶段,一段代码写出来,基本就该在你头脑中跑过一遍了。等上线进入真实生产环境跑起来,你就可以拿真实的运行数据和头脑中的预期做出对比,如果差距较大,那可能就掩藏着问题,值得你去分析和思考。
|
||||
|
||||
最后,文章开头那个问题有答案了吗?在第一阶段,你是想到哪就写到哪;而到了第三阶段,写到哪,一段鲜活的代码就成为了你想的那样。
|
||||
|
||||
你现在处在编程的哪个阶段?有怎样的感悟?欢迎你留言分享。
|
||||
|
||||
|
||||
|
||||
|
84
专栏/程序员进阶攻略/12Bug的空间属性:环境依赖与过敏反应.md
Normal file
84
专栏/程序员进阶攻略/12Bug的空间属性:环境依赖与过敏反应.md
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
12 Bug的空间属性:环境依赖与过敏反应
|
||||
从今天开始,咱们专栏进入 “程序之术” 中关于写代码的一个你可能非常熟悉,却也常苦恼的小主题:Bug。
|
||||
|
||||
写程序的路上,会有一个长期伴随你的 “同伴”:Bug,它就像程序里的寄生虫。不过,Bug 最早真的是一只虫子。
|
||||
|
||||
1947年,哈佛大学的计算机哈佛二代(Harvard Mark II)突然停止了运行,程序员在电路板编号为 70 的中继器触点旁发现了一只飞蛾。然后把飞蛾贴在了计算机维护日志上,并写下了首个发现 Bug 的实际案例。程序错误从此被称作 Bug。
|
||||
|
||||
这只飞蛾也就成了人类历史上的第一个程序 Bug。
|
||||
|
||||
回想下,在编程路上你遇到得最多的 Bug 是哪类?我的个人感受是,经常被测试或产品经理要求修改和返工的 Bug。这类 Bug 都来自于对需求理解的误差,其实属于沟通理解问题,我并不将其归类为真正的技术性 Bug。
|
||||
|
||||
技术性 Bug 可以从很多维度分类,而我则习惯于从 Bug 出现的 “时空” 特征角度来分类。可划为如下两类:
|
||||
|
||||
|
||||
空间:环境过敏
|
||||
时间:周期规律
|
||||
|
||||
|
||||
我们就先看看 Bug 的空间维度特征。
|
||||
|
||||
环境过敏
|
||||
|
||||
环境,即程序运行时的空间与依赖。
|
||||
|
||||
程序运行的依赖环境是很复杂的,而且一般没那么可靠,总是可能出现这样或那样的问题。曾经我经历过一次因为运行环境导致的故障案例:一开始系统异常表现出来的现象是,有个功能出现时不时的不可用;不久之后,系统开始报警,不停地接到系统的报警短信。
|
||||
|
||||
这是一个大规模部署的线上分布式系统,从一开始能感知到的个别系统功能异常到逐渐演变成大面积的报警和业务异常,这让我们陷入了一个困境:到底异常根源在哪里?为了迅速恢复系统功能的可用性,我们先把线上流量切到备用集群后,开始紧急地动员全体团队成员各自排查其负责的子系统和服务,终于找到了原因。
|
||||
|
||||
只是因为有个别服务器容器的磁盘故障,导致写日志阻塞,进程挂起,然后引发调用链路处理上的连锁雪崩效应,其影响效果就是整个链路上的系统都在报警。
|
||||
|
||||
互联网企业多采用普通的 PC Server 作为服务器,而这类服务器的可靠性大约在 99.9%,换言之就是出故障的概率是千分之一。而实际在服务器上,出问题概率最高的可能就是其机械硬盘。
|
||||
|
||||
Backblaze 2014 年发布的硬盘统计报告指出,根据对其数据中心 38000 块硬盘(共存储 100PB 数据)的统计,消费级硬盘头三年出故障的几率是 15%。而在一个足够大规模的分布式集群部署上,比如 Google 这种百万级服务器规模的部署级别上,几乎每时每刻都有硬盘故障发生。
|
||||
|
||||
我们的部署规模自是没有 Google 那么大,但也不算小了,运气不好,正好赶上我们的系统碰上磁盘故障,而程序的编写又并未考虑硬盘 I/O 阻塞导致的挂起异常问题,引发了连锁效应。
|
||||
|
||||
这就是当时程序编写缺乏对环境问题的考虑,引发了故障。人有时换了环境,会产生一些从生理到心理的过敏反应,程序亦然。运行环境发生变化,程序就出现异常的现象,我称其为 “程序过敏反应”。
|
||||
|
||||
以前看过一部美剧《豪斯医生》,有一集是这样的:一个手上出现红色疱疹的病人来到豪斯医生的医院,豪斯医生根据病症现象初步诊断为对某种肥皂产生了过敏,然后开了片抗过敏药,吃过后疱疹症状就减轻了。但一会儿后,病人开始出现呼吸困难兼并发哮喘,豪斯医生立刻给病人注射了 1cc 肾上腺素,之后病人呼吸开始变得平稳。但不久后病人又出现心动过速,而且很快心跳便停止了,经过一番抢救后,最终又回到原点,病人手上的红色疱疹开始在全身出现。
|
||||
|
||||
这个剧情中表现了在治疗病人时发生的身体过敏反应,然后引发了连锁效应的问题,这和我之前描述的例子有相通之处:都是局部的小问题,引发程序过敏反应,再到连锁效应。
|
||||
|
||||
过敏在医学上的解释是:“有机体将正常无害的物质误认为是有害的东西。”而我对 “程序过敏反应” 的定义是:“程序将存在问题的环境当作正常处理,从而产生的异常。”而潜在的环境问题通常就成了程序的 “过敏原”。
|
||||
|
||||
该如何应对这样的环境过敏引发的 Bug 呢?
|
||||
|
||||
应对之道
|
||||
|
||||
应对环境过敏,自然要先从了解环境开始。
|
||||
|
||||
不同的程序部署和运行的环境千差万别,有的受控,有的不受控。比如,服务端运行的环境,一般都在数据中心(IDC)机房内网中,相对受控;而客户端运行的环境是在用户的设备上,存在不同的品牌、不同的操作系统、不同的浏览器等等,多种多样,不可控。
|
||||
|
||||
环境那么复杂,你需要了解到何种程度呢?我觉得你至少必须关心与程序运行直接相关联的那一层环境。怎么理解呢?以后端 Java 程序的运行为例,Java 是运行在 JVM 中,那么 JVM 提供的运行时配置和特性就是你必须要关心的一层环境了。而 JVM 可能是运行在 Linux 操作系统或者是像 Docker 这样的虚拟化容器中,那么 Linux 或 Docker 这一层,理论上你的关心程度就没太多要求,当然,学有余力去了解到这一层次,自是更好的。
|
||||
|
||||
那么前文案例中的磁盘故障,已经到了硬件的层面,这个环境层次比操作系统还更低一层,这也属于我们该关心的?虽说故障的根源是磁盘故障,但直接连接程序运行的那一层,其实是日志库依赖的 I/O 特性,这才是我们团队应该关心、但实际却被忽略掉的部分。
|
||||
|
||||
同理,现今从互联网到移动互联网时代,几乎所有的程序系统都和网络有关,所以网络环境也必须是你关心的。但网络本身也有很多层次,而对于在网络上面开发应用程序的你我来说,可以把网络模糊抽象为一个层次,只用关心网络距离延时,以及应用程序依赖的具体平台相关网络库的 I/O 特性。
|
||||
|
||||
当然,如果能对网络的具体层次有更深刻的理解,自然也是更好的。事实上,如果你和一个对网络具体层次缺乏理解的人调试两端的网络程序,碰到问题时,经常会发现沟通不在一个层面上,产生理解困难。(这里推荐下隔壁的“趣谈网络协议”专栏)
|
||||
|
||||
了解了环境,也难免不出 Bug。因为我们对环境的理解是渐进式的,不可能一下子就完整掌握,全方位,无死角。当出现了因为环境产生的过敏反应时,收集足够多相关的信息才能帮助快速定位和解决问题,这就是前面《代码与分类》文章中 “运维” 类代码需要提供的服务。
|
||||
|
||||
收集信息,不仅仅局限于相关直接依赖环境的配置和参数,也包括用户输入的一些数据。真实场景确实大量存在这样一种情况:同样的环境只针对个别用户发生异常过敏反应。
|
||||
|
||||
有一种药叫抗过敏药,那么也可以有一种代码叫 “抗过敏代码”。在收集了足够的信息后,你才能编写这样的代码,因为现实中,程序最终会运行在一些一开始你可能没考虑到的环境中。收集到了这样的环境信息,你才能写出针对这种环境的 “抗过敏代码”。
|
||||
|
||||
这样的场景针对客户端编程特别常见,比如客户端针对运行环境进行的自检测和自适应代码。检测和适应范围包括:CPU、网络、存储、屏幕、操作系统、权限、安全等各方面,这些都属于环境抗过敏类代码。
|
||||
|
||||
而服务端相对环境一致性更好,可控,但面临的环境复杂性更多体现在 “三高” 要求,即:高可用、高性能、高扩展。针对 “三高” 的要求,服务端程序生产运行环境的可靠性并不如你想象的高,虽然平时的开发、调试中你可能很难遇到这些环境故障,但大规模的分布式程序系统,面向失败设计和编码(Design For Failure)则是服务端的 “抗过敏代码” 了。
|
||||
|
||||
整体简单总结一下就是:空间即环境,包括了程序的运行和依赖环境;环境是多维度、多层次的,你对环境的理解越全面、越深入,那么出现空间类 Bug 的几率也就越低;对环境的掌控有广度和深度两个方向,更有效的方法是先广度全面了解,再同步与程序直接相连的一层去深度理解,最后逐层深入,“各个击破”。
|
||||
|
||||
文章开头的第一只飞蛾 Bug,按我的分类就应该属于空间类 Bug 了,空间类 Bug 感觉麻烦,但若单独出现时,相对有形(异常现场容易捕捉);如果加上时间的属性,就变得微妙多了。
|
||||
|
||||
下一篇,我将继续为你分解 Bug 的时间维度特征。
|
||||
|
||||
|
||||
|
||||
|
101
专栏/程序员进阶攻略/13Bug的时间属性:周期特点与非规律性.md
Normal file
101
专栏/程序员进阶攻略/13Bug的时间属性:周期特点与非规律性.md
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
13 Bug的时间属性:周期特点与非规律性
|
||||
在上一篇文章中,我说明了“技术性 Bug 可以从很多维度分类,而我则习惯于从 Bug 出现的 ‘时空’ 特征角度来分类”。并且我也已讲解了Bug 的空间维度特征:程序对运行环境的依赖、反应及应对。
|
||||
|
||||
接下来我再继续分解 Bug 的时间维度特征。
|
||||
|
||||
Bug 有了时间属性,Bug 的出现就是一个概率性问题了,它体现出如下特征。
|
||||
|
||||
周期特点
|
||||
|
||||
周期特点,是一定频率出现的 Bug 的特征。
|
||||
|
||||
这类 Bug 因为会周期性地复现,相对还是容易捕捉和解决。比较典型的呈现此类特征的 Bug 一般是资源泄漏问题。比如,Java 程序员都不陌生的 OutOfMemory 错误,就属于内存泄漏问题,而且一定会周期性地出现。
|
||||
|
||||
好多年前,我才刚参加工作不久,就碰到这么一个周期性出现的 Bug。但它的特殊之处在于,出现 Bug 的程序已经稳定运行了十多年了,突然某天开始就崩溃(进程 Crash)了。而程序的原作者,早已不知去向,十多年下来想必也已换了好几代程序员来维护了。
|
||||
|
||||
一开始项目组内经验老到的高工认为也许这只是一个意外事件,毕竟这个程序已经稳定运行了十来年了,而且检查了一遍程序编译后的二进制文件,更新时间都还停留在那遥远的十多年前。所以,我们先把程序重启起来让业务恢复,重启后的程序又恢复了平稳运行,但只是安稳了这么一天,第二天上班没多久,进程又莫名地崩溃了,我们再次重启,但没多久后就又崩溃了。这下没人再怀疑这是意外了,肯定有 Bug。
|
||||
|
||||
当时想想能找出一个隐藏了这么多年的 Bug,还挺让人兴奋的,就好像发现了埋藏在地下久远的宝藏。
|
||||
|
||||
寻找这个 Bug 的过程有点像《盗墓笔记》中描述的盗墓过程:项目经理(三叔)带着两个高级工程师(小哥和胖子)连续奋战了好几天,而我则是个新手,主要负责 “看门”,在他们潜入跟踪分析探索的过程中,我就盯着那个随时有可能崩溃的进程,一崩掉就重启。他们“埋伏”在那里,系统崩溃后抓住现场,定位到对应的源代码处,最后终于找到了原因并顺利修复。
|
||||
|
||||
依稀记得,最后定位到的原因与网络连接数有关,也是属于资源泄漏的一种,只是因为过去十来年交易量一直不大且稳定,所以没有显现出来。但在我参加工作那年(2006年),中国股市悄然引来一场有史以来最大的牛市,这个处理银行和证券公司之间资金进出的程序的“工作量”突然出现了爆发性增长,从而引发了该 Bug。
|
||||
|
||||
我可以理解上世纪九十年代初那个编写该服务进程的程序员,他可能也难以预料到当初写的用者寥寥的程序,最终在十多年后的一天会服务于成百上千万的用户。
|
||||
|
||||
周期性的 Bug,虽然乍一看很难解决的样子,但它总会重复出现,就像可以重新倒带的 “案发现场”,找到真凶也就简单了。案例中这个 Bug 隐藏的时间很长,但它所暴露出的周期特点很明显,解决起来也就没那么困难。
|
||||
|
||||
其实主要麻烦的是那种这次出现了,但不知道下次会在什么时候出现的 Bug。
|
||||
|
||||
非规律性
|
||||
|
||||
没有规律性的 Bug,才是让人抓狂的。
|
||||
|
||||
曾经我接手过一个系统,是一个典型的生产者、消费者模型系统。系统接过来就发现一个比较明显的性能瓶颈问题,生产者的数据源来自数据库,生产者按规则提取数据,经过系统产生一系列的转换渲染后发送到多个外部系统。这里的瓶颈就在数据库上,生产能力不足,从而导致消费者饥饿。
|
||||
|
||||
问题比较明显,我们先优化 SQL,但效果不佳,遂改造设计实现,在数据库和系统之间增加一个内存缓冲区从而缓解了数据库的负载压力。缓冲区的效果,类似大河之上的堤坝,旱时积水,涝时泄洪。引入缓冲区后,生产者的生产能力得到了有效保障,生产能力高效且稳定。
|
||||
|
||||
本以为至此解决了该系统的瓶颈问题,但在生产环境运行了一段时间后,系统表现为速度时快时慢,这时真正的 Bug 才显形了。
|
||||
|
||||
这个系统有个特点,就是 I/O 密集型。消费者要与多达 30 个外部系统并发通信,所以猜测极有可能导致系统性能不稳定的 Bug 就在此,于是我把目光锁定在了消费者与外部系统的 I/O 通信上。既然锁定了怀疑区域,接下来就该用证据来证明,并给出合理的解释原因了。一开始假设在某些情况下触碰到了阈值极限,当达到临界点时程序性能则急剧下降,不过这还停留在怀疑假设阶段,接下来必须量化验证这个推测。
|
||||
|
||||
那时的生产环境不太方便直接验证测试,我便在测试环境模拟。用一台主机模拟外部系统,一台主机模拟消费者。模拟主机上的线程池配置等参数完全保持和生产环境一致,以模仿一致的并发数。通过不断改变通信数据包的大小,发现在数据包接近 100k 大小时,两台主机之间直连的千兆网络 I/O 达到满负载。
|
||||
|
||||
于是,再回头去观察生产环境的运行状况,当一出现性能突然急剧下降的情况时,立刻分析了生产者的数据来源。其中果然有不少大报文数据,有些甚至高达 200k,至此基本确定了与外部系统的 I/O 通信瓶颈。解决办法是增加了数据压缩功能,以牺牲 CPU 换取 I/O。
|
||||
|
||||
增加了压缩功能重新上线后,问题却依然存在,系统性能仍然时不时地急剧降低,而且这个时不时很没有时间规律,但关联上了一个 “嫌疑犯”:它的出现和大报文数据有关,这样复现起来就容易多了。I/O 瓶颈的怀疑被证伪后,只好对程序执行路径增加了大量跟踪调试诊断代码,包含了每个步骤的时间度量。
|
||||
|
||||
在完整的程序执行路径中,每个步骤的代码块的执行时间独立求和结果仅有几十毫秒,最高也就在一百毫秒左右,但多线程执行该路径的汇总平均时间达到了 4.5 秒,这比我预期值整整高了两个量级。通过这两个时间度量的巨大差异,我意识到线程执行该代码路径的时间其实并不长,但花在等待 CPU 调度的时间似乎很长。
|
||||
|
||||
那么是 CPU 达到了瓶颈么?通过观察服务器的 CPU 消耗,平均负载却不高。只好再次分析代码实现机制,终于在数据转换渲染子程序中找到了一段可疑的代码实现。为了验证疑点,再次做了一下实验测试:用 150k 的线上数据报文作为该程序输入,单线程运行了下,发现耗时居然接近 50 毫秒,我意识到这可能是整个代码路径中最耗时的一个代码片段。
|
||||
|
||||
由于这个子程序来自上上代程序员的遗留代码,包含一些稀奇古怪且复杂的渲染逻辑判断和业务规则,很久没人动过了。仔细分析了其中实现,基本就是大量的文本匹配和替换,还包含一些加密、Hash 操作,这明显是一个 CPU 密集型的函数啊。那么在多线程环境下,运行这个函数大概平均每个线程需要多少时间呢?
|
||||
|
||||
先从理论上来分析下,我们的服务器是 4 核,设置了 64 个线程,那么理想情况下同一时间可以运行 4 个线程,而每个线程执行该函数约为 50 毫秒。这里我们假设 CPU 50 毫秒才进行线程上下文切换,那么这个调度模型就被简化了。第一组 4 个线程会立刻执行,第二组 4 个线程会等待 50 毫秒,第三组会等待 100 毫秒,依此类推,第 16 组线程执行时会等待 750 毫秒。平均下来,每组线程执行前的平均等待时间应该是在 300 到 350 毫秒之间。这只是一个理论值,实际运行测试结果,平均每个线程花费了 2.6 秒左右。
|
||||
|
||||
实际值比理论值慢一个量级,这是为什么呢?因为上面理论的调度模型简化了 CPU 的调度机制,在线程执行过程的 50 毫秒中,CPU 将发生非常多次的线程上下文切换。50 毫秒对于 CPU 的时间分片来说,实在是太长了,因为线程上下文的多次切换和 CPU 争夺带来了额外的开销,导致在生产环境上,实际的监测值达到了 4.5 秒,因为整个代码路径中除了这个非常耗时的子程序函数,还有额外的线程同步、通知和 I/O 等操作。
|
||||
|
||||
分析清楚后,通过简单优化该子程序的渲染算法,从近 50 毫秒降低到 3、4 毫秒后,整个代码路径的线程平均执行时间下降到 100 毫秒左右。收益是明显的,该子程序函数性能得到了 10 倍的提高,而整体执行时间从 4.5 秒降低为 100 毫秒,性能提高了 45 倍。
|
||||
|
||||
至此,这个非规律性的 Bug 得到了解决。
|
||||
|
||||
虽然案例中最终解决了 Bug,但用的方法却非正道,更多依靠的是一些经验性的怀疑与猜测,再去反过来求证。这样的方法局限性非常明显,完全依赖程序员的经验,然后就是运气了。如今再来反思,一方面由于是刚接手的项目,所以我对整体代码库掌握还不够熟悉;另一方面也说明当时对程序性能的分析工具了解有限。
|
||||
|
||||
而更好的办法就应该是采用工具,直接引入代码 Profiler 等性能剖析工具,就可以准确地找到有性能问题的代码段,从而避免了看似有理却无效的猜测。
|
||||
|
||||
面对非规律性的 Bug,最困难的是不知道它的出现时机,但一旦找到它重现的条件,解决起来也没那么困难了。
|
||||
|
||||
神出鬼没
|
||||
|
||||
能称得上神出鬼没的 Bug 只有一种:海森堡 Bug(Heisenbug)。
|
||||
|
||||
这个 Bug 的名字来自量子物理学的 “海森堡不确定性原理”,其认为观测者观测粒子的行为会最终影响观测结果。所以,我们借用这个效应来指代那些无法进行观测的 Bug,也就是在生产环境下不经意出现,费尽心力却无法重现的 Bug。
|
||||
|
||||
海森堡 Bug 的出现场景通常都是和分布式的并发编程有关。我曾经在写一个网络服务端程序时就碰到过一次海森堡 Bug。这个程序在稳定性负载测试时,连续跑了十多个小时才出现了一次异常,然后在之后的数天内就再也不出现了。
|
||||
|
||||
第一次出现时捕捉到的现场信息太少,然后增加了更多诊断日志后,怎么测都不出现了。最后是怎么定位到的?还好那个程序的代码量不大,就天天反复盯着那些代码,好几天过去还真就灵光一现发现了一个逻辑漏洞,而且从逻辑推导,这个漏洞如果出现的话,其场景和当时测试发现的情况是吻合的。
|
||||
|
||||
究其根源,该 Bug 复现的场景与网络协议包的线程执行时序有关。所以,一方面比较难复现,另一方面通过常用的调试和诊断手段,诸如插入日志语句或是挂接调试器,往往会修改程序代码,或是更改变量的内存地址,或是改变其执行时序。这都影响了程序的行为,如果正好影响到了 Bug,就可能诞生了一个海森堡 Bug。
|
||||
|
||||
关于海森堡 Bug,一方面很少有机会碰到,另一方面随着你编程经验的增加,掌握了很多编码的优化实践方法,也会大大降低撞上海森堡 Bug 的几率。
|
||||
|
||||
综上所述,每一个 Bug 都是具体的,每一个具体的 Bug 都有具体的解法。但所有 Bug 的解决之道只有两类:事后和事前。
|
||||
|
||||
事后,就是指 Bug 出现后容易捕捉现场并定位解决的,比如第一类周期特点的 Bug。但对于没有明显重现规律,甚至神出鬼没的海森堡 Bug,靠抓现场重现的事后方法就比较困难了。针对这类 Bug,更通用和有效的方法就是在事前预防与埋伏。
|
||||
|
||||
之前在讲编程时说过一类代码:运维代码,它们提供的一种能力就像人体血液中的白细胞,可以帮助发现、诊断、甚至抵御 Bug 的 “入侵”。
|
||||
|
||||
而为了得到一个更健康、更健壮的程序,运维类代码需要写到何种程度,这又是编程的 “智慧” 领域了,充满了权衡选择。
|
||||
|
||||
程序员不断地和 Bug 对抗,正如医生不断和病菌对抗。不过Bug 的存在意味着这是一段活着的、有价值的代码,而死掉的代码也就无所谓 Bug 了。
|
||||
|
||||
在你的程序员职业生涯中,有碰到过哪些有意思的 Bug呢?欢迎你给我留言分享讨论。
|
||||
|
||||
|
||||
|
||||
|
150
专栏/程序员进阶攻略/14Bug的反复出现:重蹈覆辙与吸取教训.md
Normal file
150
专栏/程序员进阶攻略/14Bug的反复出现:重蹈覆辙与吸取教训.md
Normal file
@ -0,0 +1,150 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
14 Bug的反复出现:重蹈覆辙与吸取教训
|
||||
Bug 除了时间和空间两种属性,还有一个特点是和程序员直接相关的。在编程的路上,想必你也曾犯过一些形态各异、但本质重复的错误,导致一些 Bug 总是以不同的形态反复出现。在你捶胸顿足懊恼之时,不妨试着反思一下:为什么你总会写出有 Bug 的程序,而且有些同类型的 Bug 还会反复出现?
|
||||
|
||||
|
||||
重蹈覆辙
|
||||
——–
|
||||
|
||||
|
||||
重蹈覆辙的错误,老实说曾经我经历过不止一次。
|
||||
|
||||
也许每次具体的形态可能有些差异,但仔细究其本质却是类似的。想要写出没有 Bug 的程序是不可能的,因为所有的程序员都受到自身能力水平的局限。而我所经历的重蹈覆辙型错误,总结下来大概都可以归为以下三类原因。
|
||||
|
||||
1.1 粗心大意
|
||||
|
||||
人人都会犯粗心大意的错误,因为这就是 “人” 这个系统的普遍固有缺陷(Bug)之一。所以,作为人的程序员一定会犯一些非常低级的、因为粗心大意而导致的 Bug。
|
||||
|
||||
这就好比写文章、写书都会有错别字,即使经历过三审三校后正式出版的书籍,都无法完全避免错别字的存在。
|
||||
|
||||
而程序中也有这类 “错别字” 类型的低级错误,比如:条件if 后面没有大括号导致的语义变化,==、= 和 === 的数量差别,++ 或-- 的位置,甚至 ;的有无在某些编程语言中带来的语义差别。即使通过反复检查也可能有遗漏,而自己检查自己的代码会更难发现这些缺陷,这和自己不容易发现自己的错别字是一个道理。
|
||||
|
||||
心理学家汤姆·斯塔福德(Tom Stafford)曾在英国谢菲尔德大学研究拼写错误,他说:“当你在书写的时候,你试图传达想法,这是非常高级的任务。而在做高级任务时,大脑将简单、零碎的部分(拼词和造句)概化,这样就可以更专注于更复杂的任务,比如将句子变成复杂的观点。”
|
||||
|
||||
而在阅读时,他解释说:“我们不会抓住每个细节,相反,我们吸收感官信息,将感觉和期望融合,并且从中提炼意思。”这样,如果我们读的是他人的作品,就能帮助我们用更少的脑力更快地理解含义。
|
||||
|
||||
但当我们验证自己的文章时,我们知道想表达的东西是什么。因为我们预期这些含义都存在,所以很容易忽略掉某些感官(视觉)表达上的缺失。我们眼睛看到的,在与我们脑子里的印象交战。这,便是我们对自己的错误视而不见的原因。
|
||||
|
||||
写程序时,我们是在进行一项高级的复杂任务:将复杂的需求或产品逻辑翻译为程序逻辑,并且还要补充上程序固有的非业务类控制逻辑。因而,一旦我们完成了程序,再来复审写好的代码,这时我们预期的逻辑含义都预先存在于脑中,同样也就容易忽略掉某些视觉感官表达上的问题。
|
||||
|
||||
从进化角度看,粗心写错别字,还看不出来,不是因为我们太笨,而恰恰还是进化上的权衡优化选择。
|
||||
|
||||
1.2 认知偏差
|
||||
|
||||
认知偏差,是重蹈覆辙类错误的最大来源。
|
||||
|
||||
曾经,我就对 Java 类库中的线程 API 产生过认知偏差,导致反复出现问题。Java 自带线程池有三个重要参数:核心线程数(core)、最大线程数(max)和队列长度(queues)。我曾想当然地以为当核心线程数(core)不够了,就会继续创建线程达到最大线程数(max),此时如果还有任务需要处理但已经没有线程了就会放进队列等待。
|
||||
|
||||
但实际却不是这样工作的,类库的实现是核心线程(core)满了就会进队列(queues)等待,直到队列也满了再创建新线程直至达到最大线程数(max)的限制。这类认知偏差曾带来线上系统的偶然性异常故障,然后还怎么都找不到原因。因为这进入了我的认知盲区,我以为的和真正的现象之间的差异一度让我困惑不解。
|
||||
|
||||
还有一个来自生活中的小例子,虽然不是关于程序的,但本质是一个性质。
|
||||
|
||||
有时互联网上,朋友圈中小道消息满天飞,与此类现象有关的一个成语叫 “空穴来风”,现在很多媒体文章有好多是像下面这样用这个成语的:
|
||||
|
||||
|
||||
他俩要离婚了?看来空穴来风,事出有因啊!-
|
||||
物价上涨的传闻恐怕不是空穴来风。
|
||||
|
||||
|
||||
第一句是用的成语原意:指有根据、有来由,“空”发三声读 kǒng,意同 “孔”。第二句是表达:没有根据和由来,“空”发一声读kōnɡ。第二种的新意很多名作者和普通大众沿用已久,约定俗成,所以又有辞书与时俱进增加了这个新的义项,允许这两种完全相反的解释并存,自然发展,这在语义学史上也不多见。
|
||||
|
||||
而关于程序上有些 API 的定义和实现也犯过 “空穴来风” 的问题,一个 API 可以表达两种完全相反的含义和行为。不过这样的 API 就很容易引发认知偏差导致的 Bug,所以在设计和实现 API 时我们就要避免这种情况的出现,而是要提供单一原子化的设计。
|
||||
|
||||
1.3 熵增问题
|
||||
|
||||
熵增,是借用了物理热力学的比喻,表达更复杂混乱的现象;程序规模变大,复杂度变高之后,再去修改程序或添加功能就更容易引发未知的 Bug。
|
||||
|
||||
腾讯曾经分享过 QQ 的架构演进变化,到了 3.5 版本 QQ 的用户在线规模进入亿时代,此时在原有架构下去新增一些功能,比如:
|
||||
|
||||
|
||||
“昵称” 长度增加一半,需要两个月;
|
||||
|
||||
增加 “故乡” 字段,需要两个月;
|
||||
|
||||
最大好友数从 500 变成 1000,需要三个月。
|
||||
|
||||
|
||||
后端系统的高度复杂性和耦合作用导致即使增加一些小功能特性,也可能带来巨大的牵连影响,所以一个小改动才需要数月时间。
|
||||
|
||||
我们不断进行架构升级的本质,就在于随着业务和场景功能的增加,去控制住程序系统整体 “熵” 的增加。而复杂且耦合度高(熵很高)的系统,正是容易滋生 Bug 的温床。
|
||||
|
||||
|
||||
吸取教训
|
||||
——–
|
||||
|
||||
|
||||
为了避免重蹈覆辙,我们有什么办法来吸取曾经犯错的教训么?
|
||||
|
||||
2.1 优化方法
|
||||
|
||||
粗心大意,可以通过开发规范、代码风格、流程约束,代码评审和工具检查等工程手段来加以避免。甚至相对写错别字,代码更进一步,通过补充单元测试在运行时做一个正确性后验,反过来去发现这类我们视而不见的低级错误。
|
||||
|
||||
认知偏差,一般没什么太好的自我发现机制,但可以依赖团队和技术手段来纠偏。每次掉坑里爬出来后的经验教训总结和团队内部分享,另外就是像一些静态代码扫描工具也提供了内置的优化实践,通过它们的提示来发现与你的认知产生碰撞纠偏。
|
||||
|
||||
熵增问题,业界不断迭代更新流行的架构模式就是在解决这个问题。比如,微服务架构相对曾经的单体应用架构模式,就是通过增加开发协作,部署测试和运维上的复杂度来换取系统开发的敏捷性。在协作方式、部署运维等方面付出的代价都可以通过提升自动化水平来降低成本,但只有编程活动是没法自动化的,依赖程序员来完成,而每个程序员对复杂度的驾驭能力是有不同上限的。
|
||||
|
||||
所以,微服务本质上就是将一个大系统的熵增问题,局部化在一个又一个的小服务中。而每个微服务都有一个熵增的极限值,而这个极限值一般是要低于该服务负责人的驾驭能力上限的。对于一个熵增接近极限附近的微服务,服务负责人就需要及时重构优化,降低熵的水平。而高水平和低水平程序员负责的服务本质差别在于熵的大小。
|
||||
|
||||
而熵增问题若不及时重构优化,最后可能会付出巨大的代价。
|
||||
|
||||
丰田曾陷入的 “刹车门” 事件,就是因为其汽车动力控制系统软件存在缺陷。而为追查其原因,在十八个月中,有 12 位嵌入式系统专家受原告诉讼团所托,被关在马里兰州一间高度保安的房间内对丰田动力控制系统软件(主要是 2005 年的凯美瑞)源代码进行深度审查。最后得到的结论把丰田的软件缺陷分为三类:
|
||||
|
||||
|
||||
非常业余的结构设计
|
||||
不符合软件开发规范
|
||||
对关键变量缺乏保护
|
||||
|
||||
|
||||
第一类属于熵增问题,导致系统规模不断变大、变复杂,结果驾驭不了而失控;第二类属于开发过程的认知与管理问题;第三类才是程序员实现上的水平与粗心大意问题。
|
||||
|
||||
2.2 塑造环境
|
||||
|
||||
为了修正真正的错误,而不是头痛医头、脚痛医脚,我们需要更深刻地认识问题的本质,再来开出 “处方单”。
|
||||
|
||||
在亚马逊(Amazon),严重的故障需要写一个 COE(Correction of Errors)的文档,这是一种帮助去总结经验教训,加深印象避免再犯的形式。其目的也是为了帮助认识问题的本质,修正真正的错误。
|
||||
|
||||
但一旦这个东西和 KPI 之类的挂上钩,引起的负面作用是 COE 的数量会变少,但真正的问题并没有减少,只是被隐藏了。而其正面的效应像总结经验、吸取教训、找出真正问题等,就会被大大削弱。
|
||||
|
||||
关于如何构造一个鼓励修正错误的环境,我们可以看看来自《异类》一书讲述的大韩航空的例子,大韩航空曾一度困扰于它的飞机损失率:
|
||||
|
||||
|
||||
美国联合航空 1988 年到 1998 年的飞机损失率为百万分之 0.27,也就是说联合航空每飞行 400 万次,会在一次事故中损失一架飞机;而大韩航空同期的飞机损失率为百万分之 4.79,是前者的 17 倍之多。
|
||||
|
||||
|
||||
事实上大韩航空的飞机也是买自美国,和联合航空并无多大差别。它的飞行员们的飞行时长,经验和训练水平从统计数据看也差别不大,那为什么飞机损失率会如此地高于其他航空公司的平均水平呢?在《异类》这本书中,作者以此为案例做了详细分析,我这里直接引用结论。
|
||||
|
||||
|
||||
现代商业客机,就目前发展水平而言,跟家用烤面包机一样可靠。空难很多时候是一系列人为的小失误、机械的小故障累加的结果,一个典型空难通常包括 7 个人为的错误。
|
||||
|
||||
|
||||
一个飞机上有正副两个机长,副机长的作用是帮助发现、提醒和纠正机长在飞行过程中可能发生的一些人为小错误。大韩航空的问题正在于副机长是否敢于以及如何提醒纠正机长的错误。其背后的理论依据源自荷兰心理学家吉尔特·霍夫斯泰德(Geert Hofstede)对不同族裔之间文化差异的研究,就是今天被社会广泛接受的跨文化心理学经典理论框架:霍夫斯泰德文化纬度(Hofstede’s Dimensions)。
|
||||
|
||||
|
||||
在霍夫斯泰德的几个文化维度中,最引人注目的大概就是 “权力距离指数(Power Distance Index)”。权力距离是指人们对待比自己更高等级阶层的态度,特别是指对权威的重视和尊重程度。
|
||||
|
||||
而霍夫斯泰德的研究也提出了一个航空界专家从未想到过的问题:让副机长在机长面前维护自己的意见,必须帮助他们克服所处文化的权力距离。
|
||||
|
||||
|
||||
想想我们看过的韩国电影或电视剧中,职场上后辈对前辈、下级对上级的态度,就能感知到韩国文化相比美国所崇尚的自由精神所表现出来的权力距离是特别远的。因而造成了大韩航空未被纠正的人为小错误比例更高,最终的影响是空难率也更高,而空难就是航空界的终极系统故障,而且结果不可挽回。
|
||||
|
||||
吸取大韩航空的教训应用到软件系统开发和维护上,就是:需要建立和维护有利于程序员及时暴露并修正错误,挑战权威和主动改善系统的低权力距离文化氛围,这其实就是推崇扁平化管理和 “工程师文化” 的关键所在。
|
||||
|
||||
一旦系统出了故障非技术背景的管理者通常喜欢用流程、制度甚至价值观来应对问题,而技术背景的管理者则喜欢从技术本身的角度去解决当下的问题。我觉着两者需要结合,站在更高的维度去考虑问题:规则、流程或评价体系的制定所造成的文化氛围,对于错误是否以及何时被暴露,如何被修正有着决定性的影响。
|
||||
|
||||
我们常与错误相伴,查理·芒格说:
|
||||
|
||||
|
||||
世界上不存在不犯错误的学习或行事方式,只是我们可以通过学习,比其他人少犯一些错误,也能够在犯了错误之后,更快地纠正错误。但既要过上富足的生活又不犯很多错误是不可能的。实际上,生活之所以如此,是为了让你们能够处理错误。
|
||||
|
||||
|
||||
人固有缺陷,程序固有 Bug;吸取教训避免重蹈覆辙,除了不断提升方法,也要创造环境。你觉得呢?欢迎你留言和我分享。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
94
专栏/程序员进阶攻略/15根源:计划的愿景——仰望星空.md
Normal file
94
专栏/程序员进阶攻略/15根源:计划的愿景——仰望星空.md
Normal file
@ -0,0 +1,94 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
15 根源:计划的愿景——仰望星空
|
||||
在前面第 2 章节 “程序之术” 中,我已把对“设计”“编程”和“Bug”的思考与理解都分享给你了。今天开始进入第 3 章节,是关于成长修行中 “由术入道” 的部分,而“道”的维度众多,我就先从和个人成长最直接相关的 “计划体系” 讲起。它会有助于你一步一步走向你“理想的自己”,所以可别小看它的重要性。
|
||||
|
||||
我想你肯定做过计划,我也不例外。一般在开始一件中长期的活动前,我都会做计划,但更重要的是反问为什么要做这个计划,因为计划是抵达愿望的途径。如果不能清晰地看见计划之路前方的愿景,计划半途而废的概率就很大了。
|
||||
|
||||
古希腊哲学家苏格拉底有一句名言:“未经检视的人生不值得活。”那么我们为什么要检视自己的人生呢?正是因为我们有成长的愿望,那么愿望的根源又到底是什么呢?
|
||||
|
||||
需求模型
|
||||
|
||||
上世纪四十年代(1943 年)美国心理学家亚伯拉罕·马斯洛在《人类激励理论》中提出了需求层次理论模型,它是行为科学的理论之一。
|
||||
|
||||
该理论认为个体成长发展的内在力量是动机,而动机是由多种不同性质的需要所组成,各种需要之间,有先后顺序与高低层次之分,每一层次的需要与满足,将决定个体人格发展的境界或程度。 其层次模型的经典金字塔图示如下:
|
||||
|
||||
|
||||
|
||||
马斯洛的经典金字塔图:需求层次模型
|
||||
|
||||
在人生的不同阶段,会产生不同层次的目标需求。
|
||||
|
||||
在人生的早期,我们努力学习,考一个好大学,拥有一技之长,找一份好工作,带来更高薪的收入,这很大程度都是为了满足图中最底层的生存需求,让生活变得更舒适美好。
|
||||
|
||||
成长拼搏数年,事业小成,工作稳定,有房,有车,有娃后,第二层次,也就是安全的需求开始凸显。有人在这阶段开始给自己、父母、老婆、孩子都买人寿保险,开始考虑理财,投资甚至强身健体。然而处在这个阶段时,我却有一种强烈的不安全感,这也许和长年的程序员职业经历养成的习惯也有关系。
|
||||
|
||||
我们做系统应用服务时总是需要考虑各种意外和异常事件发生,一般至少提供主备方案。于人生而言,保持持续学习,与时俱进,追求成长,这其实也是一种主备方案:主,指当前支撑生活的工作;备,是通过持续学习,同步成长,保持核心能力的不断积累与时间的付出来获得一份备份保障,以避免 “主” 出现意外时,“备” 的能力已被时代淘汰。
|
||||
|
||||
需求金字塔底部两层属于物质层次的 “经济基础”,而再往上则进入了更高精神层次的 “上层建筑”。就个体而言,高层次需求要比低层次需求具有更大的价值。在 “生存” 和 “安全” 基本满足的保障基础之上,我们才会更从容地向内求,更多地探求内心,进而向外索,对外去探索、发现和建立不同的圈层关系,以满足上层的社交 “归属”、获得 “尊重” 与 “自我实现” 的需求。
|
||||
|
||||
马斯洛把底层的四类需求:生存、安全、归属、尊重归类为 “缺失性” 需求,它们的满足需要从外部环境去获得。而最顶层的“自我实现” 则属于 “成长性” 需求。成长就是自我实现的过程,成长的动机也来自于 “自我实现” 的吸引。就像很多植物具有天生的向阳性,而对于人,我感觉也有天生的 “自我实现” 趋向性。
|
||||
|
||||
人生最激荡人心的时刻,就在于自我实现的创造性过程中,产生出的一种 “高峰体验” 感。正因为人所固有的需求层次模型,我们才有了愿望,愿望产生目标,目标则引发计划。
|
||||
|
||||
生涯发展
|
||||
|
||||
在攀登需求金字塔的过程中,我们创造了关于人生的 “生涯”。而 “生涯” 一词最早来自庄子语:
|
||||
|
||||
|
||||
吾生也有涯,而知也无涯。以有涯随无涯,殆已。
|
||||
|
||||
|
||||
“涯” 字的原意是水边,隐喻人生道路的尽头,尽头已经没了路,是终点,是边界。正因如此,人生有限,才需要计划。著名生涯规划师古典有一篇文章《你的生命有什么可能?》对生涯提出了四个维度:高度、宽度、深度和温度。这里就借他山之玉,来谈谈我的理解。
|
||||
|
||||
|
||||
高度:背后的价值观是影响与权力。代表性关键词有:追逐竞争、改变世界。
|
||||
深度:背后的价值观是卓越与智慧。代表性关键词有:专业主义、工匠精神。
|
||||
宽度:背后的价值观是博爱与和谐。代表性关键词有:多种角色、丰富平衡。
|
||||
温度:背后的价值观是自由与快乐。代表性关键词有:自我认同、精彩程度。
|
||||
|
||||
|
||||
每个人的人生发展路线都会有这四个维度,只是不同人的偏好、愿望和阶段不同导致了在四个维度分布重心的差异。在不同维度的选择,都代表了不一样的 “生涯”,每一种 “生涯” 都需要一定程度的计划与努力。
|
||||
|
||||
虽有四种维度,四个方向,但不代表只能选其一。虽然我们不太可能同时去追求这四个维度,但可以在特定的人生不同阶段,在其中一个维度上,给自己一个去尝试和探索的周期。所以,这就有了选择,有了计划。而计划会有开始,也会有结束,我们需要计划在人生的不同阶段,重点开始哪个维度的追求,以及大概需要持续的周期。
|
||||
|
||||
人生本是多维的,你会有多努力、多投入来设计并实现自己的生涯规划呢?不计划和努力一下,也许你永远无法知道自己的边界和所能达到的程度。
|
||||
|
||||
上世纪七十年代初,一个文学专业成绩很一般的学生毕业了。他虽然喜欢读文学作品却没写出过什么东西,毕业后就结了婚,和老婆开了个酒吧,生意不错,生活无忧。到了七十年代末,他似乎感受到某种 “召唤”,觉得应该写点什么东西了,于是每天酒吧打烊后,他就在餐桌上写两小时的小说,这一写就写了三十多年。熟悉的人想必已经知道他是谁了?对,就是村上春树。
|
||||
|
||||
所以,总要开始计划做点啥,你才能知道自己的 “涯” 到底有多远;而计划就是在系统地探索生涯,甚至人生的无限可能性。
|
||||
|
||||
回首无悔
|
||||
|
||||
关于后悔,有研究说:“我们最后悔的是没做什么,而不是做过什么。”回味一下,这个结论也确实符合我们的感觉。
|
||||
|
||||
万维钢写过一篇文章《决策理性批判》,里面引用了一个最新(2018)的关于后悔的研究,这个研究从 “理想的自己” 与 “义务的自己” 两个角度来说明:
|
||||
|
||||
|
||||
“理想的自己” 就是你想要成为什么人。
|
||||
|
||||
“义务的自己” 就是你应该干什么。
|
||||
|
||||
|
||||
若放到前面马斯洛需求金字塔中,“理想的自己” 就是站在顶端 “自我实现” 位置的那个自己;而 “义务的自己” 正在金字塔下面四层,挣扎于现实的处境。如果你从来没有去向 “理想的自己” 望上一眼,走上一步,将来终究会后悔的。事实上,研究结论也证明了这点:70% 以上的人都会后悔没有成为 “理想的自己”。
|
||||
|
||||
当我把自己进入大学以后的这十八年分作几个阶段来回顾时,有那么一段的好多时间我就是那样浑浑噩噩地混过去了,以至于现在回忆那段日子发现记忆是如此的粘连与模糊。后悔么?当然。
|
||||
|
||||
如果我能好好计划一下那段日子,也许会得到一个更 “理想的自己”。而在最近的这一段,我也感谢好些年前 “曾经的我”,幸运兼有意地做了一些计划。虽然一路走来,有些辛苦,但感觉会充实很多,而且如今再去回首,就没有太多后悔没做的事了。
|
||||
|
||||
计划,就是做选择,你在为未来的你做出选择,你在选择未来变成 “谁”。如果你还在为今天的自己而后悔,那就该为明天的自己做出计划了。
|
||||
|
||||
人生的征程中,先是恐惧驱动,地狱震颤了你,想要逃离黑暗深渊;后来才是愿望驱动,星空吸引了你,想要征服星辰大海。
|
||||
|
||||
逃离与征服的路,是一条计划的路,也是一条更困难的路,而 “你内心肯定有着某种火焰,能把你和其他人区别开来” 才让你选择了它。
|
||||
|
||||
最后,你想去到哪片星空?你为它点燃了内心的火焰了吗?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
122
专栏/程序员进阶攻略/16方式:计划的方法——脚踏实地.md
Normal file
122
专栏/程序员进阶攻略/16方式:计划的方法——脚踏实地.md
Normal file
@ -0,0 +1,122 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
16 方式:计划的方法——脚踏实地
|
||||
当你内心成长的火焰被点燃,有成长的愿望,也形成了清晰的成长愿景,但却可能苦恼于不知道如何确定目标、制定计划,以达成愿景。
|
||||
|
||||
就拿我来说,每年结束我都会做一次全年总结,然后再做好新一年的计划,一开始这个过程确实挺艰难且漫长的,因为毕竟要想清楚一年的计划还是挺难的。但慢慢的,我开始摸索和学习到了一套制定富有成效计划的方法,成为了我成长的捷径。现借此机会我将其总结、分享给你。
|
||||
|
||||
目标
|
||||
|
||||
富有成效的计划的第一步,便是确定目标。
|
||||
|
||||
在设定目标这个领域,国外一位研究者马克·墨菲(Mark Murphy)曾提出过一种 HARD 方法。HARD 是 4 个英文词的首字母缩写:
|
||||
|
||||
|
||||
Heartfelt 衷心的,源自内心的
|
||||
Animated 活生生,有画面感的
|
||||
Required 必须的,需求明确的
|
||||
Difficult 困难的,有难度的
|
||||
|
||||
|
||||
如其解释,这是一种强调内心愿望驱动的方法。按这个标准,一种源自内心的强烈需求在你头脑中形成很具体的画面感,其难度和挑战会让你感到既颤栗又激动,那么这也许就是一个好目标。
|
||||
|
||||
应用到个人身上,HARD 目标中的 H 体现了你的兴趣、偏好与心灵深处的内核。就拿写作这个事情来说吧,于我而言,兴趣只是驱动它的一种燃料,而另一种燃料是内心深处的表达欲望。写作本身不是目标,通过写作去完成一部作品才是目标,就像通过写代码去实现一个系统,它们都是作品,其驱动内核就是一种 “创造者之心”。
|
||||
|
||||
而 A 是你对这个目标形成的愿景是否足够清晰,在头脑中是否直接就能视觉化、具象化。就拿我个人来说,我非常喜欢读书,常在夜深人静的时候,默默潜读,掩卷而思,和作者产生一种无声的交流。这样一种画面,慢慢烙在脑海中,渐渐就激发起了想要拥有一部作品的目标。
|
||||
|
||||
R 则是由上一篇文章中的马斯洛需求模型层次决定的。写作一方面本是自带属于第三层次的社交属性,但另一方面更多是一种成长性的自我实现需求在激发。完成一部作品,需要明确一个主题,持续地写作,一开始我从每月写,到每周写,再到写这个专栏,作品也就渐渐成型。
|
||||
|
||||
而最后的 D 是其难度,决定了目标的挑战门槛。太容易的目标不值得设定,太难或离你现实太远的目标也不合适。基于现实的边界,选择舒适圈外的一两步,可能就是合适的目标。于我,从写代码到写作,其实也真就只有那么一两步的距离。
|
||||
|
||||
以 HARD 目标法为指导,我回顾了我工作以来的成长发展阶段,根据目标的清晰度,大概可以划分为如下三个阶段:
|
||||
|
||||
|
||||
目标缺乏,随波逐流
|
||||
目标模糊,走走停停
|
||||
目标清晰,步履坚定
|
||||
|
||||
|
||||
第一个阶段,属于工作的前三、四年,虽然每天都很忙,感觉也充实,一直在低头做事。但突然某一天一抬头,就迷茫了,发现不知道自己要去向哪里,原来在过去的几年里,虽然充实,但却没有形成自己明确的目标,一直在随波逐流。
|
||||
|
||||
在那时,人生的浪花把我推到了彼时彼地,我停在岸边,花了半年的时间重新开始思考方向。当然这样的思考依然逃不脱现实的引力,它顶多是我当时工作与生活的延伸,我知道我还会继续走在程序这条路上,但我开始问自己想要成为一个怎样的程序员,想要在什么行业,什么公司,写怎样的程序。就这样,渐渐确立了一个模糊的目标。
|
||||
|
||||
重新上路,比之前好了不少,虽然当时定的目标不够清晰,但至少有了大致方向,一路也越走越清晰。从模糊到清晰的过程中,难免走走停停,但停下迷茫与徘徊的时间相对以前要少了很多,模糊的目标就像一张绘画的草图,逐渐变得清晰、丰富、立体起来。当目标变得越来越清晰时,步履自然也就变得越发坚定。
|
||||
|
||||
回顾目标在我身上形成的经历,我在想即使当时我想一开始就要去定一个目标,想必也不可能和如今的想法完全一致。毕竟当时受限于眼界和视野,思维与认知也颇多局限,所立的目标可能也高明不到哪里去;但有了目标,就有了方向去迭代与进化,让我更快地摆脱了一些人生路上的漩涡。
|
||||
|
||||
假如,你觉得现状不好,无法基于现状延伸出目标。那么也许可以试试这样想:假如我不做现在的事情,那么你最想做的是什么?通常你当前最想做的可能并不能解决你的谋生问题,那么在这两者之间的鸿沟,如何去搭建一条桥梁,可能就是一个值得考虑的目标。
|
||||
|
||||
我们为什么要立 HARD 目标?有一句话是这么说的:
|
||||
|
||||
|
||||
Easy choices, hard life. Hard choices, easy life.
|
||||
|
||||
容易的选择,艰难的生活;艰难的选择,轻松的生活。
|
||||
|
||||
|
||||
方法
|
||||
|
||||
目标是愿望层面的,计划是执行层面的,而计划的方式也有不同的认识维度。
|
||||
|
||||
从时间维度,可以拟定 “短、中、长” 三阶段的计划:
|
||||
|
||||
|
||||
短期:拟定一年内的几个主要事项、行动周期和检查标准。
|
||||
中期:近 2~3 年内的规划,对一年内不足以取得最终成果的事项,可以分成每年的阶段性结果。
|
||||
长期:我的长期一般也就在 5~7 年周期,属于我的 “一辈子” 的概念范围了,而 “一辈子” 当有一个愿景。
|
||||
|
||||
|
||||
短期一年可以完成几件事或任务,中期两三年可以掌握精熟一门技能,长期的 “一辈子” 达成一个愿景,实现一个成长的里程碑。
|
||||
|
||||
从路径维度,订计划可以用一种 SMART 方法,该方法是百年老店通用电气创造的。在 20 世纪 40 年代的时候,通用电气就要求每一个员工把自己的年度目标、实现方法及标准写信告诉自己的上级。上级也会根据这个年度目标来考核员工。这种方法进化到了 20 世纪 80 年代,就成了著名的SMART原则。
|
||||
|
||||
SMART 也是 5 个英文词的首字母缩写:
|
||||
|
||||
|
||||
Specific 具体的
|
||||
Measurable 可衡量的
|
||||
Achievable 可实现的
|
||||
Relevant 相关的
|
||||
Time-bound 有时限的
|
||||
|
||||
|
||||
今天 SMART 已经非常流行和常见,我就不解释其具体含义了,而是讲讲我如何通过 SMART 来跟踪个人年度计划执行的。按 SMART 方式定义的计划执行起来都是可以量化跟踪的,我通常用如下格式的一张表来跟踪:
|
||||
|
||||
|
||||
|
||||
计划跟踪表示意图
|
||||
|
||||
其实,一年值得放进这张表的就那么几件事,每件事又可以分解为具体的几个可量化的任务,再分解到一年 50 周,就可以很明显地看出理想计划和现实路径的曲线对比。如下,是我 2017 年的一张计划与实际执行的对比曲线图:
|
||||
|
||||
|
||||
|
||||
计划与实际执行对比示意图
|
||||
|
||||
按 SMART 原则方法使用计划跟踪表的优点是:简单、直接、清晰。但缺点也明显:即使百分百完成了所有的计划,也都是预期内的,会缺乏一些惊喜感。而因为制定目标和计划会有意识地选择有一定难度的来挑战,所以实际还很难达成百分百。
|
||||
|
||||
说到目标的难度与挑战,使用 SMART 方法最值得注意的点就是关于目标的设定和方法的选择。鉴于人性和现实的因素,制定计划时很可能是这样一种情况:基于现实掌握的方法,考虑计划的可达性。这样制定出来的计划看起来靠谱,但却失去了真正挑战与创新的可能。
|
||||
|
||||
通用电气传奇 CEO 杰克·韦尔奇执掌时期,有一个飞机引擎工厂制定了一个减少 25% 产品缺陷的目标。韦尔奇当时就觉得这个 SMART 目标很普通,没什么挑战,但工厂负责人却觉得已经很有难度了。韦尔奇执意坚持,把目标提高到了减少 70% 的缺陷,工厂负责人一开始很焦虑,认为这根本不可能完成。
|
||||
|
||||
没办法,标准是韦尔奇定的,改不了。工厂负责人知道按以前的方法根本达不成,只好去寻找新方法。在寻找的过程中,他们发现,要想如此大幅度地减少缺陷,不能只靠质检人员,而是必须让每名员工都有质检意识。
|
||||
|
||||
于是,工厂开始大规模进行培训;同时,工厂开始有意识招聘综合素质更高的技术工人。为了吸引并留住这些工人,工厂必须改变以前的管理方式,给他们更多的自主权,因为这些工人普遍受过很好的教育,而且很容易找到工作。最后,一个拔高的目标计划改变了整个工厂的培训、招聘和运行方式。
|
||||
|
||||
SMART 计划,正如其名,需要聪明且智慧地设定并使用它。
|
||||
|
||||
有时你可能会觉得计划没有变化快,或者计划好的人生,过起来好机械,没劲。其实计划是准备,变化才是永恒,而计划就是为了应对变化。为此,我经常会把计划表按优先级排得满满的,但我永远只做那些计划表顶部最让自己感到 HARD 的事情。
|
||||
|
||||
变化来了,就把它装进计划表中,看这样的变化会排在哪个位置,和之前计划表前列的事情相比又如何。如果变化的事总能排在顶上,那么说明你的人生实际就在不断变得更精彩,做的事情也会让你更激动。而如果变化老是那些并不重要却还总是紧急的事情,老打断当下的计划,那么也许你就要重新审视下你当前的环境和自身的问题了。
|
||||
|
||||
这样,计划表就成了变化表,人生无法机械执行,只有准备应对。
|
||||
|
||||
最后,找到属于你的 HARD 目标,开始有计划且 SMART 的每一天;这样的每一天,走的每一步也许会更重些、累些,但留下的脚印却很深、很长。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
87
专栏/程序员进阶攻略/17检视:计划的可行——时间与承诺.md
Normal file
87
专栏/程序员进阶攻略/17检视:计划的可行——时间与承诺.md
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
17 检视:计划的可行——时间与承诺
|
||||
有了愿景,也有了具体的计划,但经常还是一年过去,发现实际和计划相比,总是有差距。是的,这是普遍现象,你可能并不孤独和例外:统计数字表明,在年初制定了计划的人中,只有8% 实现了这些计划。
|
||||
|
||||
老实说,我回顾了近几年的个人年度计划完成情况,也只完成了约 70% 的样子。但我个人把这70% 的比例算作“完成”,毕竟一年中谁还没个变化呢?于是,我把另外的30%留给变化,毕竟一成不变地按计划来的人生,感觉太过枯燥,有 30% 的变化还可能会碰到 “惊喜”;而如果 70% 都是变化,那可能就是 “惊吓”了。
|
||||
|
||||
程序员啊,有一个特点就是偏乐观,所以对于计划的估计总是过于乐观,乐观地期待 “惊喜”,然后又“惊吓”地接受现实。那如何才能让计划更具可行性呢?又可以从哪些方面来检视呢?
|
||||
|
||||
时间与周期
|
||||
|
||||
计划的第一个影响因素是和时间有关。
|
||||
|
||||
在过去的人类社会生活中,人们已经习惯了以年为单位来进行时间分界,所以我们都会习惯于做年度计划。在个人的时间感觉中,一年,似乎也算是挺长一段时间了,但在过去这么些年的计划与实践中,我学到的经验是:做计划不能靠模糊的感觉,而是需要精确理性的计算。
|
||||
|
||||
先来计算下,一年,我们到底有多少时间?一个正常参与社会工作的人,时间大约会被平均分成三份。
|
||||
|
||||
其中的 1/3(约 8 小时)会被睡过去了,这里假设一个正常人的生理睡眠需求大约 8 小时。虽然有一些讲述成功人士关于睡眠的说法,比如:“你见过凌晨四点钟的…”,似乎在暗示他们之所以成功,是因为每天都很努力只睡四个小时。但这个说法并没有提每天几点入睡,只是说四点起床而已。而我写这篇文字也是在周末的早晨五点开始的,但前一晚十点之前便睡了过去,至少我对睡眠时间的要求是没法长期低于 8 小时的。
|
||||
|
||||
另一个 1⁄3 你会贡献到和你的工作有关的各种事项中,虽然国家法律规定了每周只用上 5 天班,每天 8 小时,似乎用不了 1⁄3 的时间。但如果你的工作不是那种 “混日子” 的清闲工作的话,实际占用的时间基本总会多于法律规定的,至少程序员这份工作肯定是这样了。不过值得庆幸的是程序员的工作是可以随着时间积累起相应的知识、技能和经验,那么这份时间投入就是很有价值的了,随着时间积累,慢慢你就会成为工作领域内的行家。
|
||||
|
||||
最后的 1⁄3 就是我们常说的决定人生的业余 8 小时。可能有人会说我根本就没有业余 8 小时,天天都在加班。实际上工作和业余的 8 小时有时不太那么具有明显的分界线。程序员的工作,是一份知识性工作,很可能工作时间你在学习,也有很多情况是你在业余时间处理工作的事务。对于严格区分工作和业余时间的思维,我碰到过一种人:上厕所都要忍着,到了公司利用工作时间再去,以达成变相在工作时间偷懒的感觉。但,其实时间总是自己的。
|
||||
|
||||
一年 52 周,会有一些法定的长假和个人的休假安排,我们先扣除两周用于休假。那么一天业余 8 小时,一年算 350 天,那么一年总共有 2800 小时的业余时间。但实际这 2800 小时里还包括了你全部的周末和一些零星的假期,再预扣除每周 8 小时用于休闲娱乐、处理各种社会关系事务等等,那么你还剩下 2400 小时。
|
||||
|
||||
这2400 小时就是你可以比较自由地用来安排的全部业余时间了,这就是理性计算的结果。这样看来,一年实际能用来计划的时间并不多,需要仔细挑选合理的事项,放进计划表,并真正地执行。而实际,一年中你还需要把时间合理地分配在 “短、中、长” 三种不同周期的计划上。
|
||||
|
||||
|
||||
短期:完成事项,获取结果,得到即时反馈与成就感(比如:写这个专栏)。
|
||||
中期:学习技能,实践经验,积累能力(比如:学一门语言)。
|
||||
长期:建立信念,达成愿景(比如:成长为一名架构师)。
|
||||
|
||||
|
||||
你可以从时间的维度,看看你计划的时间安排是否合理分配在了不同周期的计划事项上。如果计划的事项和周期匹配错了,计划的执行就容易产生挫败感从而导致半途而废,曾经的我就犯过这样的错误。
|
||||
|
||||
这个错误就是在学习英语的计划上。两年多以前,工作十年后的我又重启了英语提升计划,希望能通过每天 3 ~ 4 小时的英语学习,一年内使自己的英语听读都能达到接近汉语的水平。但实际情况是,我用了两年(接近 1500 小时吧)才勉强比刚从学校毕业时上了一个台阶,离母语水平,我不知道前面还有多少个台阶。
|
||||
|
||||
英语提升计划,我搞错了周期,一度颇受打击。英语技能,实际就是一个 10000 小时技能,虽然我是从初中开始学习,然后至大学毕业拿到六级证,差不多有十年时间。但实际真正有效的学习时间有多少呢?假如每天一节课算 1 小时,一周 6 小时,每年 50 周,十年上课下来也就 3000 小时,再考虑为了考试自己的主动复习时间,再加 2000 小时,那么过去在学校总共投入了 5000 小时。
|
||||
|
||||
但从学校毕业后的十年,实际工作环境中,除了技术英语阅读,我几乎很少再接触英语了。而语言基本就是用进废退的技能,所以再重启学习提升计划时,我对此计划的周期完全估算错误,最后得到的效果也远低于我的预期。其实这应该是一个长期的计划,定一个合理的愿景,循序渐进成为一名熟练的英语使用者。
|
||||
|
||||
要让计划可行,就是选择合适的事项,匹配正确的周期,建立合理的预期,得到不断进步的反馈。
|
||||
|
||||
兴趣与承诺
|
||||
|
||||
既然时间有限,那该如何选择有限的事项,才可能更有效地被执行下去呢?
|
||||
|
||||
其中有一个很重要的因素:兴趣。有的人兴趣可能广泛些,有的人兴趣可能少一些,但每个人多多少少都会有些个人的兴趣爱好。对于兴趣广泛的人来说,这就有个选择取舍问题,若不取舍,都由着兴趣来驱动,计划个十几、二十件事,每样都浅尝辄止。实际从理性上来说价值不大,从感性上来说只能算是丰富了个人生活吧。
|
||||
|
||||
彼得·蒂尔在《从 0 到 1 》这本书里批判了现在的一个观点:过程胜于实效。他解释说:“当人们缺乏一些具体的计划去执行时,就会用很正式的规则来建立一些可做的事情选项的组合。就像今天美国中学里一样,鼓励学生参与各种各样的课外活动,表现的似乎全面发展。到了大学,再准备好一份看似非常多元化的简历来应对完全不确定的将来。言外之意,不管将来如何变化,都在这个组合内能找到可以应对的准备。但实际情况是,他们在任何一个特定方面都没有准备好。”
|
||||
|
||||
因此,在有限的学校生涯中,你就得做出选择。就好像我大学那时,学校开了几十门(记得大概有 45 门)各类专业课,这就是一个组合。但其中真正重要的课程实际只有个位数,重心应该放在少数课程上,其他的只起到一个开阔眼界和凑够学分的作用。
|
||||
|
||||
几十门课是学校给的选项,你可以从中做出选择。那应该选择哪些事项放进计划表呢?我建议你可以从兴趣作为出发点,因为这样更容易启动;而对于中期目标,像学习提升一项技能,只靠兴趣是不足以驱动去有效执行的,甚至达不到预期效果。关于此,吴军有一个观点:
|
||||
|
||||
|
||||
凡事从 0 分做到 50 分,靠的是直觉和经验;从 50 分到 90 分,就要靠技艺了。
|
||||
|
||||
|
||||
凭借兴趣驱动的尝试,结合直觉和经验就能达成 50 分的效果,而要到 90 分就需要靠技艺了。而技艺的习得是靠刻意练习的,而刻意练习通常来说都不太有趣。要坚持长期的刻意练习,唯一可靠的办法就是对其做出郑重的承诺。
|
||||
|
||||
通过兴趣来启动,但要靠承诺才能有效地执行下去。感兴趣和做承诺的差别在于,只是感兴趣的事,到了执行的时候,总可以给自己找出各种各样的原因、借口或外部因素的影响去延期执行;而承诺就是这件事是每天的最高优先级,除非不可抗力的因素,都应该优先执行。
|
||||
|
||||
比如,写作本是我的兴趣,但接下 “极客时间” 的专栏后,这就是承诺了,所以为此我就只能放弃很多可以用于休闲、娱乐的时间。
|
||||
|
||||
兴趣让计划更容易启动,而承诺让计划得以完成。
|
||||
|
||||
而在现实生活中,让计划不可行或半途而废的常见错误有:
|
||||
|
||||
|
||||
以为一年之内自己有足够多的自由支配时间;
|
||||
对计划的事情误判了其开发与成长的周期;
|
||||
兴趣很多,一直在尝试,却不见有结果。
|
||||
|
||||
|
||||
放进计划表的事项是你精心识别、选择并做出的承诺,而承诺也是一种负担,若承诺太多,负担可能就太重,会让你感觉自己不堪重负,最后就可能放弃了,到头来又是一场空。其实,一年下来,重要的不是开启了多少计划,而是完成了几个计划。
|
||||
|
||||
所以,可行的计划应该是:有限的时间,适合的周期,兴趣的选择,郑重的承诺。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
114
专栏/程序员进阶攻略/18评估:计划的收获——成本与收益.md
Normal file
114
专栏/程序员进阶攻略/18评估:计划的收获——成本与收益.md
Normal file
@ -0,0 +1,114 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
18 评估:计划的收获——成本与收益
|
||||
做计划自是为了有收获,实现愿景也好,获得成长也罢,每一份计划背后都有付出与收获的关系。如果计划的收益不能高于执行它付出的成本,那么其实这种的计划就几乎没有执行价值。
|
||||
|
||||
执行计划的成本通常是你付出的时间或金钱,但收益则没那么明确,这就需要你去仔细评估和取舍。
|
||||
|
||||
而有些计划本身从成本和收益的角度看就不是一个好计划,比如,我见过一些计划是:今年计划读20本书。读书本是好事,但读书的数量并不是关键点,关键是计划今年读哪些书。因为只有明确了读哪些书,才能评估是否值得和适合在这阶段去读。
|
||||
|
||||
值得与否,就是关于成本与收益的评估,而为了更好制定有价值的计划,你就需要去仔细权衡这种关系。
|
||||
|
||||
成本与机会
|
||||
|
||||
计划即选择,而但凡选择就有成本。
|
||||
|
||||
从经济学思维的角度,做计划就是做选择,选择了某些事情;而选择了这些事情,就意味着放弃了另外可能做的事情,这里面的成本就是机会成本。机会成本是放弃的代价,选择这些事情从而放弃的其他可能选项中拥有最高价值的事情。
|
||||
|
||||
就好像同样一个晚上,有人选择了用来玩网络游戏,你以为的成本是几小时的点卡钱,但实际你放弃的是用来学习、看书等其他事项的潜在价值与收益。青少年时代谁还没玩过游戏,我也玩过十多年的游戏,虽不能简单地认为游戏毫无意义,但十年前,我明白了机会成本的概念后,就做出了选择。
|
||||
|
||||
我的长期计划中有一项是写作。从我 2011 年开始写下第一篇博客放在网上到现在,已经过去了七年。那写作的成本和收益又是怎样的呢?
|
||||
|
||||
一开始总有一些人愿意免费写一些优质内容放在网上,从读者的角度来看,他们总是希望作者能长期免费地创造优质内容。但从花费的时间成本来看,这是不太现实的,也很难长久持续下去。
|
||||
|
||||
从作者的角度,时间成本其实是越来越高,而且很刚性。比如,七年前我写一篇文章的时间其实和现在差不太多,时间成本按说是增加的(因为单位成本随时间增加了);但是写作会持续创造价值,我可以在持续写作中不断总结获得成长,而成长的价值都会通过职业生涯发展获得收益,这是间接收益。而一些成功的作者,可能还可以通过写作获得直接收益,比如目前蒸蒸日上的各类知识付费专栏。
|
||||
|
||||
在中国互联网快速发展的这十多年间,我的学习路径也发生了转变。前期,我都是从网上去扒各种免费的电子书,看免费的博客,读开源的代码;但今天我几乎不会再去网上找免费的学习材料了,而是直接付费购买。
|
||||
|
||||
而且你应该也发现了现在知识和内容付费的趋势在扩大,这是为什么?因为大家都意识到了时间的成本,是选择花费自己的时间去搜索、甄别和筛选内容,还是付出一点点费用得到更成体系的优质内容?大家已经做出了选择。
|
||||
|
||||
学习计划是个人成长计划中的一部分,而成长计划中,最大的成本依然是时间。在你早期的学习阶段,虽然时间没那么值钱,但把钱和时间都花在加速成长上,其实是“成本有限,潜在收益巨大”的选择。
|
||||
|
||||
而计划,就是对你的时间做分配。时间在不同的阶段,价值不同,那么成本也就不同。你要敏感地去感知自己时间的成本,去提升时间的价值,根据时间的价值再去调整自己的计划和行动。成长过程中,早期的成本低而选项多,后期的成本高且选项少。
|
||||
|
||||
文艺复兴时期法国作家蒙田曾说过:
|
||||
|
||||
|
||||
真正的自由,是在所有时候都能控制自己。
|
||||
|
||||
|
||||
如蒙田所说,计划才能给你真正的自由,你对计划的控制力越强,离自由也就更近了。
|
||||
|
||||
结果与收益
|
||||
|
||||
计划得到了执行,产生了预期的结果,才会有期望的收益。
|
||||
|
||||
但据抽样统计,制定了年度计划的人里面,仅有 8% 的人能完成他们的年度计划。年度计划通常都是一份从未向任何人公布的计划,从某种意义上来说,除了你自己自律,并没有任何约束可言。这个世界的外部环境变化那么快,你很容易找到一个理由说服自己:计划赶不上变化。
|
||||
|
||||
变化之后的计划,只是一份更契合实际的计划,而非不再存在。很多外部因素是你无法预测和控制的,总会来干扰你的计划,所以这给了你足够的客观原因。但无论有多少客观原因,你做计划的初衷是:一点点尝试去控制自己的生活,然后得到自己想要的结果。
|
||||
|
||||
在获得结果的路上,这个世界上似乎有两类人:
|
||||
|
||||
|
||||
第一类人,自己给自己施加约束,保持自律并建立期望;
|
||||
第二类人,需要外部环境给予其约束和期望。
|
||||
|
||||
|
||||
在我读高中时,现实中就有一种巨大的社会期望和约束施加己身,那就是高考。在这种巨大的社会外部约束和期望下,第二类人可以表现得非常好,好到可以考出状元的分数。但进入大学后,这样的外部约束和期望会瞬间下降,最后可能也就泯然众人之间了。
|
||||
|
||||
心理学上有个皮格马利翁效应:
|
||||
|
||||
|
||||
人们基于对某种情境的知觉而形成的期望或预言,会使该情境产生适应这一期望或预言的效应。
|
||||
|
||||
|
||||
通俗点说就是,如果有人(可以是别人或自己)对你的期望很高,你会不自觉地行动去满足并符合这种期望;若周围没有这样的期望,最终你可能就是一个符合周围人群平均期望的人。而所谓的自驱力,就是你对自己的期望所形成的推动力量。
|
||||
|
||||
要获得好的结果,你就要做第一类人,需要对自己有更高的期望,需要有自驱力。
|
||||
|
||||
进入大学或工作以后,周围环境对你的期望已经降到很低。于我而言,来自父辈的那一代人,也就是上世纪四五十年代那一代,经历了饥荒甚至战争,他们的期望通常代表一代人,都是平平安安、健健康康,有个稳定的工作就够了。
|
||||
|
||||
这样的期望对于大部分读了大学、有个工作的人来说都不足以形成驱动力了,更何况我们大多数人每日工作忙里忙外,不外乎忧心柴米油盐,困于当下。少了外部足够强大的期望推动,多数第二类人的内心驱动从此也就熄火了,但还是有少数的第一类人在 “仰望星空”,比如科幻小说《三体》的作者大刘(刘慈欣)。
|
||||
|
||||
我是 1999 年在四川成都的一本科幻杂志《科幻世界》(现已停刊)上读到他的首部短篇小说的。实际他 85 年毕业,在电厂任工程师,89 年开始写科幻小说,直到 99 年才见到他的第一部作品公开发表。从 89 年到 99 年这十年间基本就是独自“仰望星空”来完成了写作这门技艺的打磨过程,并留下了自己的第一部作品,再之后到写完《三体》,这又是另一个十年了。
|
||||
|
||||
而于我,除了写作,还有另一项长期计划:学好英语。快三年前了,我重启了英语提升计划,付出的成本是每天至少一到数小时不等的学习和听读文章的时间成本,那么收益呢?学好英语是能产生直接收益的,比如通过翻译就能赚钱,但这就落入了一种狭隘的思维。
|
||||
|
||||
一方面,翻译的时间单价市场行情是非常低的,目前英译中的普通文章,恐怕不到 100 元每千字,相比一个初中级程序员的市场价,时间成本是很不划算的。所以,学好英语从我的角度来说,赚取的不是直接的经济收益,而是间接的结构性收益,增强直接收益结构价值。
|
||||
|
||||
那么如何理解收益结构?以我现阶段的状态来说,已有三个直接收益结构:
|
||||
|
||||
|
||||
专业
|
||||
写作
|
||||
理财
|
||||
|
||||
|
||||
专业,自然是指程序专业技能,通过出售自己的时间和人力资源来获取一份相对稳定的工资收入来源。写作,到今天这个专栏出品后,终于可以通过作品的形式产生直接收益,它只需一次性投入时间来完成作品。而理财属于资产性收益,就是任何等价于钱的家庭动产或不动产,能产生利息、分红或租金的收入,它需要长期的收入结余积累。
|
||||
|
||||
而英语技能的提升对这三个直接收益结构,都能产生增益作用。程序行业自不必多说,行业里最好的文章、书籍或专业论文材料等可能都是英文的,只有少部分被翻译了过来,但翻译总是有损失、有偏差、有歧义,能直接高效地阅读英语对提升你的专业技能和能力帮助巨大。
|
||||
|
||||
而写作,英语给我提供了另外一个更广阔世界的写作素材和看待世界的角度。所以,我在时间分配上不仅看中文文章,也看一些英文媒体文章和书籍。
|
||||
|
||||
至于理财,英语让我更直接高效地接收中文世界以外的信息,从某种角度来说,具备了更多元化的视角和思维结构。而思维和视角是投资理财的核心能力,在这个领域全是选择题,只有做对选择的概率高于做错的概率,才可能获得正收益。
|
||||
|
||||
这就是我选择一项长期计划时关于结果与收益的思考,而成长计划的收益,从经济价值来说,都是远期收益,是为了变得更值钱。也许期望的结果达成,目标实现,真的会变得更值钱,就像上面例子里的大刘。但也可能没能实现目标,那么你还能收获什么?也许有来自过程的体验,这也是选择目标时,源自内心和兴趣是如此重要的原因。
|
||||
|
||||
在考虑付出与收获时,后来读到一句话,大意如下:
|
||||
|
||||
|
||||
生活也许不会像计划那样发生,但对待生活的态度可以是:期待伟大的事情发生,同时也要保持快乐和幸福,即使它没能发生。
|
||||
|
||||
|
||||
如此,面对真实的生活,也当释然了。
|
||||
|
||||
最后,留个思考题:关于计划你感觉是束缚了你的生活,还是让你更自由了?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
95
专栏/程序员进阶攻略/19障碍:从计划到坚持,再到坚持不下去的时候.md
Normal file
95
专栏/程序员进阶攻略/19障碍:从计划到坚持,再到坚持不下去的时候.md
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
19 障碍:从计划到坚持,再到坚持不下去的时候
|
||||
设定一个计划并不困难,真正的困难在于执行计划。若你能够坚持把计划执行下去,想必就能超越绝大部分人,因为大部分人的计划最终都半途而废了。
|
||||
|
||||
为什么那么多计划都半途而废了?在执行计划时,你会碰到怎样的障碍?我想从计划生命周期的各个阶段来分析下。
|
||||
|
||||
酝酿
|
||||
|
||||
酝酿期,是计划的早期雏形阶段;这阶段最大的障碍来自内心:理性与感性的冲突。
|
||||
|
||||
计划的目标是源自内心的,但也是有难度的,若是轻而易举的事情,也就不用计划了。这些需要坚持的事情,通常都 “不好玩”,而人是有惰性的,内心里其实并不愿意去做,这是我们感性的部分。但理性告诉我们,去完成这些计划,对自己是有长远好处的。这,就是冲突的地方。
|
||||
|
||||
就以我自己写作的例子来看,我不是一开始就写作的,我是工作了 5 年后,碰到了平台期,撞上了天花板,感觉颇为迷茫。于是就跑到网上到处看看有没有人分享些经验,找找道路。然后,看到了一些 “大神” 们写的博客,分享了他们一路走过的经历,在我迷茫与灰暗的那个阶段的航行中,就像一盏灯塔指引着前进方向。
|
||||
|
||||
于是我在想,也许我也可以开始写写东西。那时,内心里出现了两个声音,一个声音说:“你现在能写什么呢?有什么值得写的吗?有人看吗?”而另一个声音反驳说:“写,好过不写,写作是一件正确的事,就算没人看,也是对自己一个时期的思考和总结。”
|
||||
|
||||
最终,理性占了上风,开启了写作计划,然后注册了一个博客,想了一句签名:“写下、记下、留下”。
|
||||
|
||||
启动
|
||||
|
||||
启动期,是计划从静止到运动的早期阶段;这阶段的最大障碍是所谓的“最大静摩擦力”。
|
||||
|
||||
我们都学过初中物理,知道 “最大静摩擦力” 是大于 “滑动摩擦力” 的,也就是说要让一个物体动起来所需要的推力,比它开始运动后要大一些。这个现象,放在启动一个计划上时,也有类似的感觉,所以才有一句俗语叫:“万事开头难”。
|
||||
|
||||
还是回到我开始写作那个例子,我的第 1 篇博客的写作过程,至今还记得很清楚:一个周六的下午,在租的小房间里整整写了一下午。写得很艰苦,总感觉写得不好,不满意。最后一看天都黑了,肚子也饿了,就勉勉强强把它发了出去。
|
||||
|
||||
发出去后的前两天,我也会经常去刷新,看看阅读量有多少,有没有人评论啊。让人失望的是,前一个声音的说法变成了事实:的确没什么人看。两天的点击量不到一百,一条评论也没有,而且这一百的阅读计数里,搞不好还有些是搜索引擎的爬虫抓取留下的。
|
||||
|
||||
但是,写完了第一篇,我终于克服了写作的 “最大静摩擦力” 开始动了起来,一直写到了今天,这已经过去了 7 年。
|
||||
|
||||
执行
|
||||
|
||||
执行期,是计划实现过程中最漫长的阶段;这阶段的最大障碍就是容易困倦与乏味。
|
||||
|
||||
漫长的坚持过程期,大部分时候都是很无聊、乏味的,因为真实的人生就是这样,并没有那么多戏剧性的故事。所以,我在想这也许就是为什么那么多人爱看小说、电视剧和电影的原因吧,戏中的人物经历,总是更有戏剧性。
|
||||
|
||||
美国当代著名作家库尔特·冯内古特在一次谈话中谈及人生,他用了一组形象的类比来描述人生。我翻译过来并演绎了一下,如下面系列图示:
|
||||
|
||||
其中,纵坐标表示生活的幸福程度。越往上,代表幸福指数越高;越往下,代表幸福指数越低。中间的横线表示普通大众的平凡人生。
|
||||
|
||||
|
||||
|
||||
那么先来看一个大家都很熟悉的从 “丑小鸭” 变 “白天鹅”的故事:灰姑娘 。
|
||||
|
||||
|
||||
|
||||
我们从小就听过这个故事,人们喜欢这样的故事。同样的故事内核,被用在不同的故事里书写了上千次,传诵了上千年。这是一个皆大欢喜的故事,而下面则是一个稍微悲伤点的故事。
|
||||
|
||||
|
||||
|
||||
故事虽以悲剧开始,但好在以喜剧结束。人们也喜欢这样的故事,生活不就该这样吗?问题是,真实的生活可能是下面这样的。
|
||||
|
||||
|
||||
|
||||
没有那么多大起大落,我们大部分人的生活只是在经历一些平平凡凡的琐事。也许其中有些会让你感到高兴与兴奋,有些又让你感到烦躁与郁闷。但这些琐事都不会沉淀进历史中,被人们传诵上千年,它仅仅对你自己有意义。
|
||||
|
||||
所以呢,你明白为什么你感觉你的坚持那么无聊、单调与乏味了吧,大多数时候它都缺乏像 “灰姑娘” 故事曲线的戏剧性。而对抗这种过程的无聊,恰恰需要的就是故事。你看人类的历史上为什么要创造这么多戏剧性的故事,让这些戏剧性的故事包围了我们的生活,让人们想象生活充满了戏剧性,这种想象是治疗乏味的良药,也成为了创造更美好生活的动力。
|
||||
|
||||
万维钢的一篇文章《坚持坚持再坚持》里也提到:
|
||||
|
||||
|
||||
故事的价值不在于真实准确,而在于提供人生的意义。
|
||||
|
||||
|
||||
坚持,特别是长期的坚持,是需要动力的,而动力来自目标和意义。而获得目标与意义的最好方式是讲好一个故事。你看,成功的企业家会把未来的愿景包进一个美好的故事里,让自己深信不疑;然后再把这个故事传播出去,把所有相信这个故事的人聚在一起去追寻这个故事;最后,这个关于未来的故事就这样在现实中发生了。
|
||||
|
||||
漫长的人生,你需要为自己讲好一个故事。
|
||||
|
||||
挫败
|
||||
|
||||
挫败,不是一个阶段,而是坚持路上的一些点;正是在这些点上你遭遇了巨大的挫败感。
|
||||
|
||||
为什么会产生挫败感?可能的原因有,一开始你就不知道这件事有多难,直到走了一段后才发现这太难了。一开始就评估清楚一个计划的难度,需要投入大量的时间、经历和金钱,甚或有更高的技能与能力要求,这本身就是一件不容易的事。
|
||||
|
||||
而如果你计划的是一件从来没做过的事情,这就更难准确评估了。在路上,行至中途遭遇 “低估” 的挫败感就再正常不过了,而不少人,因为挫败过一两次后,就会放弃了计划。有时,遭遇挫败,选择了放弃,这个未必就是不合适的,但这要看这个放弃的决策是在什么情况下做出的。
|
||||
|
||||
遭遇挫败,你会进入一种心情与情绪的低谷,这个时候有很高的概率做出放弃的决策。而我的经验是,不要在挫败的情绪低谷期进行任何的选择与决策。可以暂时放下这件事,等待情绪回归到正常,再重新理性地评估计划还是否该坚持。
|
||||
|
||||
每经历一次挫败之后,你还选择坚持,那么就已经收获了成长。
|
||||
|
||||
最后总结来说,就是:你为了做成一件事,定一个计划,在执行计划的过程中,在 “酝酿”“启动” 和 “执行” 的不同阶段都会碰到各种障碍,可能都会让你产生一种快坚持不下去了的感觉。每到此时,你都要想想清楚,哪些是真正客观的障碍?哪些是主观的退却?
|
||||
|
||||
从坚持到持续,就是试图让现实的生活进入童话的过程,而后童话又变成了现实。
|
||||
|
||||
本文分析了计划的执行障碍,最后我也想问问你,在你成长的路上,遭遇过哪些障碍?是什么原因让你坚持不下去了的?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
103
专栏/程序员进阶攻略/20执行:从坚持到持续,再到形成自己的节奏.md
Normal file
103
专栏/程序员进阶攻略/20执行:从坚持到持续,再到形成自己的节奏.md
Normal file
@ -0,0 +1,103 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
20 执行:从坚持到持续,再到形成自己的节奏
|
||||
有了一个目标后,我们通常会做好全方位的计划,并满心期待启动它,本想着朝着既定目标“一骑红尘飞奔而去”。但计划赶不上变化,很多时候,执行了一段时间后,我们可能会觉得比较累,有种快坚持不下去了的感觉,然后就半途而废了。这种场景我们每个人应该都不陌生。
|
||||
|
||||
其实,在执行过程中,容易半途而废的一个很可能的原因在于节奏出了问题。
|
||||
|
||||
计划的节奏
|
||||
|
||||
一个计划被制定出来后,我们通常会根据它的周期设定一个执行的节奏。
|
||||
|
||||
长期,就像长跑,跑五千米是长跑,跑马拉松(四万多米)也是长跑,但我们知道跑五千米和跑拉松肯定是用不同的节奏在跑。
|
||||
|
||||
一个长期的目标可以是五年,也可以是十年,因目标而异。要精熟一门技能领域,比如编程,确切地说应该是编程中的某一分支领域,对于一般人来说,可能就需要三五年不等了。而像精通一门外语,可能需要的时间更长,我是从初中开始学习英语的,如今二十多年过去了,别说精,可能连熟都谈不上。
|
||||
|
||||
于我而言,可能因为编程技能是要解决吃饭温饱的需要,刚需比较强烈;而英语这么多年,都是考试的需要,刚需太弱,故二者的学习和练习节奏完全不同,最后学习掌握的能力也相差甚远。
|
||||
|
||||
一个中期的目标,也许是一年。比如,计划用一年时间写一本书,假如一本书 20 万字,那每周大约需要完成 4000 字,再细化到每天就是800 字左右。这就是我们做一年计划的方式,计划成型后,相应做出分解,分解到周这个级别后,基本的计划节奏就出来了。
|
||||
|
||||
一个短期的目标,可能是几个月。比如,我这个 “极客时间” 专栏,计划就是几个月内完成的事情。它上面已经形成了每周三篇更新的节奏,这样的写作节奏对于我来说基本已经算是全力冲刺了,所以时间就不能拉得太长。
|
||||
|
||||
不同周期的计划,都会有一个共同的问题:计划总是过于乐观了,现实的执行很难完全符合计划。
|
||||
|
||||
你可能也遇到过,计划的节奏总是会被现实的“意外”打断,每次计划的节奏被打断后,都会陷入一种内疚的挫败感中;然后就强迫自己去完成每日计划列表中的每一项,否则不休息,最终也许是获得了数量,但失去了质量。在这样的挫败中纠结了几次后,你慢慢就会发现,现实总是比计划中的理想情况复杂多变。
|
||||
|
||||
不过,这才是真实的人生。偶尔错过计划没什么大不了的,如果人生都是按计划来实现,那岂不也有些无聊。
|
||||
|
||||
万维钢有篇文章叫《喜欢 = 熟悉 + 意外》,这篇文章下有位读者留言说:
|
||||
|
||||
|
||||
贾宝玉第一次见到林黛玉说的第一句话就是 “这个妹妹好像在哪儿见过似的”。有点熟悉,也有点意外,这就是喜欢了。
|
||||
|
||||
|
||||
所以,当“意外”出现时你不必感到太过闹心,试着换个角度来看,这偶尔出现的“意外”也许会反而让你更喜欢这样的人生呢。
|
||||
|
||||
计划更多是给予预期和方向,去锚定现实的走向,但在行进的过程中,“意外” 难免会出现。所以,你要从心理上接受它,并从行为上合理地应对它。
|
||||
|
||||
下面我就来说说我是怎么应对这些“意外”的。
|
||||
|
||||
按程序员的思考方式,我会为所有计划中的事情创建了一个优先级队列,每次都只取一件最高优先级的事情来做。而现实总会有临时更高优先级的 “意外” 紧急事件插入,处理完临时的紧急事件,队列中经常还满满地排着很多本来计划当天要做的事情。
|
||||
|
||||
以前,我总是尝试去清空队列,不清空不休息,但实际上这很容易让人产生精疲力竭的感觉。如今,我对每个计划内的事情对应了一个大致的时间段,如果被现实干扰,错过了这个时间段,没能做成这件计划内的事情,就跳过了,一天下来到点就休息,也不再内疚了。
|
||||
|
||||
举例来说,我计划今晚会看看书或写篇文章,但如果这天加班了,或者被其他活动耽误了,这件计划中的事情也就不做了。但第二天,这件事依然会进入队列中,并不会因为中断过就放弃了。只要在队列里,没有其他事情干扰,到了对应的时间段就会去执行。
|
||||
|
||||
计划的节奏,就像中学物理课上假设的理想的无摩擦力环境,而现实中,摩擦力则总是难以避免的,所以你要学会慢慢习惯并适应这真实而有点“意外”的节奏。
|
||||
|
||||
他人的节奏
|
||||
|
||||
跑马拉松的时候,一大群人一起出发,最后到达终点时却是稀稀拉拉。这说明每个人的节奏是不同的,即便同一人在不同阶段的节奏也是不一样。
|
||||
|
||||
同理,就拿我的写作节奏来说,在七年中也慢慢从每月一篇提升到了每周一篇。当然,有些微信公众号的作者写作速度一直都很快,可能是每天一篇。但如果我要用他们的节奏去写作,可能一开始坚持不了多久就会放弃写作这件事了。
|
||||
|
||||
所以,从写作这件长期的事情中,我收获的关于节奏的体会是:每个人都会有自己不同的节奏,这需要自己去摸索、练习,并慢慢提升。如果开始的节奏太快,可能很快就会疲惫、倦怠,很容易放弃;但如果一直节奏都很慢,则会达不到练习与提升的效果,变成了浪费时间。
|
||||
|
||||
执行长期计划,就如同跑马拉松,本来是一群人一起出发,慢慢地大家拉开了距离,再之后你甚至前后都看不到人了。是的,正如《那些匀速奔跑的人你永远都追不上》那篇文章所说:
|
||||
|
||||
|
||||
匀速奔跑的人是那些可以耐住寂寞的人,试想当你按照自己的节奏持之以恒默默努力地去做一件事情时,是极少会有伙伴同行的,因为大家的节奏各不一样,即便偶尔会有也只是陪你走过一段。
|
||||
|
||||
|
||||
但有时,我们看见别人跑得太快没了踪影,心里会很是焦急。我们身边有太多这样的人,把一切都当成是任务,必须要在某个确定的时间做完它,必须要在一个规定的时间内取得它应有的效益。
|
||||
|
||||
的确,我们的世界变化太快了,快到我们都怕浪费一分一秒,快到我们被这个世界的节奏所裹挟,所以就逼迫自己去努力,去完成,去改变,但却完全失去了自己的节奏,直到我们决定随它去吧,和大家随波逐流就好。
|
||||
|
||||
有时太急迫地“追赶”,最后反而阻挡了你稳步前进的步伐和节奏。
|
||||
|
||||
自己的节奏
|
||||
|
||||
找到并控制好自己的节奏,才能长期匀速地奔跑,才能更高效地利用好自己的时间和注意力。
|
||||
|
||||
对于每日计划的执行节奏,我自己的经验是:把自己的时间安排成一段一段的,高度集中和高度分心交叉分布。
|
||||
|
||||
假如某段时间需要高度集中注意力,就可以处理或思考一些比较难的事情。比如,50 ~ 60 分钟,集中注意力处理工作事务,远离手机信息推送及其他各种环境的打扰;然后休息一会儿,10 ~ 15 分钟左右,回复一些聊天或邮件这类其实不需要那么高注意力的事情。
|
||||
|
||||
有时,当你想去处理一件复杂困难的事情,比如写作,这是一种短时间内需要高度集中注意力的活动,但这时脑中总是在同时想着其他很多事情或者被动地接收一些环境信息(周围的谈话声之类的),还控制不住,很难集中注意力。这种情况下,就不用勉强开始,我通常会通过切换环境,从外部去排除一些干扰。
|
||||
|
||||
另外,如果感觉是比较疲惫,则更不能马上开始了,这种状态下,一般我都是立刻去小憩片刻或者闭目养神一段时间(20 ~ 30 分钟),进入一种浅睡眠状态再恢复过来,精力的恢复感会比较好。
|
||||
|
||||
恢复精力,我的感觉是浅睡优于深度睡眠,一是因为进入深度睡眠需要更长的时间,二是因为从中恢复过来也需要更长时间。所以,一旦进入深度睡眠,中途被人打断叫醒,会感觉非常困倦,我想很多人都有过这种感觉,俗称:睡过头了。
|
||||
|
||||
而另外一种中长期目标的执行节奏,控制起来可能要更困难一些。
|
||||
|
||||
比如,我们大部分人人生中第一阶段的奔跑目标:高考。为了奔向高考这个目标,我们有十二年时间进入学校,按照固定的节奏学习。一开始轻松些,跑得随意些;慢慢长大后,学业的压力开始明显起来,竞争的味道开始浓厚起来。特别是进入高中后,所有的同学都开始加速奔跑,以这样一种被设计好的节奏奔向目标。
|
||||
|
||||
这高考之前的学习节奏,更多是被整个社会和教育体系设计好的。我们只是在适应这个节奏,适应得很好的学生,高考一般都会取得不错的成绩;当然也有适应不了的同学,甚至有到不了参加高考就已经离开了赛道的。
|
||||
|
||||
在这个过程中,外界会给予我们一些期望的节奏压力,但要取得最好的效果,我们还是要找到自己的节奏。节奏是我们能长期持续奔跑的很重要的因素。还好高考结束后,再没有一个固定的时间点,也没有那么强大的外部环境去制约甚至强迫改变我们的节奏。
|
||||
|
||||
有时,只需要有一个目标,制一个计划,然后持续按自己的节奏跑下去。
|
||||
|
||||
找到自己的节奏,就是在每天略感挑战的状态下,形成不断加速前行,直到一个最终接近匀速的状态。匀速是我们能长期坚持的临界点,它能让我们跑得更久,跑得更远。
|
||||
|
||||
至此,关于计划一节的内容就全部结束了,我在文中分享了一些我的长期计划。那你有怎样的计划呢?是在用怎样的节奏去执行并完成它呢?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user