每个学过软件工程的人都不应该对这句话感到陌生;但真正能理解它、实践它的人并不多。
甚至于,还有很多人把它理解成“大公司的繁文缛节,属于大公司病的一种”——他们是这样理解的,也是这样做的。
我就亲眼见过很多这样翻车的案例。
当我质问其中一些人,为什么初始设计那么大的漏洞就没人看见时,他们理直气壮的答复说“这不就是个过场嘛”“谁能一开始就想到会有这样的问题”——不是,如果没有能力从一开始就预见到问题的话,你凭什么在总设计师这个职位上占住茅坑不拉屎?
先设计,后编码,它的意义在于,我们首先要从整体上搞明白这个项目可不可行、都有哪些方案、这些方案都有哪些优劣势,以及不同方案可能带来哪些风险、如何应对……
一场大仗、硬仗,你没个plan B,那怎么可能啃下来。
哪怕小打小闹的一个很小的项目,事先规划一下也比信马由缰好无数倍。
事实上,业界公认,需求/设计阶段应该占软件开发总时长的60%;但这60%使得后面的40%没了悬念,最终整个项目反而能更快、更轻松的上线,且上线后极少有棘手bug。
当然,也有不少人没那个水平,这60%的时间完全是空耗。但那是人的问题,也是公司管理的问题。
打个比方的话,这就好像开车去北京一样:先设计,后编码要求你先看看地图,大概总结一下去北京有几条路线、路上补给容不容易、消费高不高、容不容易被气候影响,等等。
弄清了大方向、搞明白了优劣势,然后拿语言准确描述出来:西安去北京,都是京昆高速起始,然后有三条路线:京昆高速就能直达,转京港澳高速货车多,转沧榆高速就绕远了……
心里有了谱,那么当埋头开发时才不会被细节弄花眼:
你看大家都走右边了,我们是不是也转过去啊?为什么?不知道啊,但大家都这样走,我们不跟着……不好吧?
——人家是货车!
哎哎哎,听说前面车祸了,堵车!我们得绕道!
绕哪?
绕洛阳!小马说了,他洛阳人,那边他熟!
——醒醒!我们要去北京,你咋不绕海南呢!
坏了……迷路了……
不是,去个北京你都能迷路?不就一条道嘛?
那不是……半道我听一司机说,去北京可以走XX环城路转YY省道,不收费……
嗯,知道。然后呢?
然后去了太原……
再然后呢?
逛了青岛……
那现在呢?
我们在哈尔滨。雾霾太大,挡风玻璃糊了,这真没法……
等等,我们在哪?
哈尔滨。
——合着你不绕海南就绕东北啊。
别笑。实践中,比这更大更惨烈的笑话多了。比如我吐槽过的某个“五百人开发两年多投资五千万又追加五千万,结果预计支持2000万用户的项目才放了两万用户进去盘点一次就要六小时”的奇葩项目——没错,一个多亿打水漂了。
打水漂的原因,是因为这个项目的盘点逻辑复杂度是O(N^3)!
了解复杂度是什么的,自然明白为什么我会吐槽“这个项目需要我们出兵全世界,把所有已经造出的电脑全都抢回来;再把电脑工厂都占领了,为我们免费生产100年的电脑,这才能……咳咳,2000万用户就别想了,支持不了的。我说的是,这才能让我们的高管们顺利搪塞到退休!”
当然,作为初学者,你可能没做过这么大的项目。
但哪怕是你为女朋友写的“生日提醒程序”,你都应该先设计、后编码。
先站在设计的高度,这才能一下子看透问题,解决“电脑关机怎么办”“提示时我刚好不在电脑旁怎么办”“提示时我正在lol团战,结果它跳出来闹的我团战失败、生了一肚子闷气,结果把什么都忘了,这该怎么办”等等疑难。
相反,你要兴冲冲直接动手——生日提醒啊?简单!我就不停的读系统时钟,时间到了就弹窗!啊坏了,关机怎么办?我放到自动运行里!
写完,一试,CPU占用100%,一秒内一口气弹了8万个窗口,只能拔电源。
拔完电源,重启,桌面还没显示利索,另外8万个提示窗又来了……
怎么办怎么办?让我写个程序自动关闭它!
真好。现在你开个记事本也会自动秒关了——为了女朋友,这台机器……已经成了爆米花机!
没有事先规划,想哪写哪;结果被层出不穷的状况(还美其名曰需求变更)牵着鼻子、面多了加水水多了加面:这就是绝大多数软件开发时间冗长、状况不断、无疾而终的根本原因。
这句话实际上是接着上一条的。
很简单,除非你真的做了设计,否则你的代码当然不会有什么“设计感”——信马由缰淌出来的,怎么可能有“整齐的设计感”。
你必须先用心设计,找出所有可以到达目标的路径中、最短最快消耗最低的那一条。
只有先做到这一点,你才能把代码写简单,才能让人一眼看出意图、明显没有缺陷。
把很简单的程序搞复杂、搞的没人能懂,这很简单——不妨搜一下“C语言混乱代码大赛”。
但把很复杂的功能写简单、写的让人一眼看懂、而且很确定“这里明显没有错误”,反而需要极深的功底。
尤其一项技术/一个领域发展成熟之前,第一个把它写成大白话的,哪个都是业界大拿。
这句话又承接了上一条。
学最激进的技术,是因为“最激进的技术”往往关联着“最天才的思路”。
我们当然应该及时跟进、融会贯通——然后化用到我们自己的项目里。
但为了展示,“最激进的技术”往往会极尽炫技之能事——要的就是惊爆一地眼球的效果:这居然也能行!这特么也能行!
当你乍一看云山雾罩,细一看“……这特么也能行”时,你还可能断定“这代码明显没有错误”吗?
尤其是,激进的技术都是不成熟的。盲目跟进,后果很严重哦。
举例来说,C++的模板就是一项非常先进、非常好用的技术;但它也是非常激进的——激进到哪怕现在都难说完善的程度。
包括C++ stl库——C++基本库啊!基石级的存在!——那可都是……奇计迭出。
比如——尤其是早年——当你希望它帮你为某个容器里的数据时,你可能得到超过4k的、满是<><<>><>><><<>>>>>的错误信息;原因仅仅是:排序算法需要随机访问数据,而你给它的容器不支持“随即迭代器”。所以后来才有了concept。
因此,我们应该学习最激进的东西;但只用那些能把工程变得更简单的技术。
注意,我可没说“学最激进的东西,但千万不要用它”或者“学最激进的东西,然后一定要用它”哦。
我说的是,第一,最激进的东西要了解;第二,只用足够稳定的东西;第三,也是最重要的一点,技术无所谓先进落后,能最简单平易解决问题的,就是最好的技术。
——因此,我会在大部分公司视STL为洪水猛兽时,自己在工程中实现模板算法,因为只有模板才能最简洁直观的完成我当时面对的问题;但哪怕到了C++20年代,该查表时我也会简单直白的敲出查表算法:只要它在解决问题时更简洁、更直白、效率更高。
特意指出这点,是因为这是很多“追新”追到“忘本”的人的通病——病到明目张胆的犯蠢。
这方面的典型就是面向对象大师们——没错,包括你们崇拜的很多面向对象专家/大师,那都是明目张胆的蠢。
这些人的特征是,他们特别见不得代码中的if,一定要用层峦叠嶂的类继承层次把它包装起来,美其名曰“应对变化”。
恰恰相反。他们这样子不仅无法应对需求变更,反而造成了代码和数据的深度耦合——过去,我们处理文档数据用word,处理报表数据用excel,两个软件应对一切;面向对象大师一来,坏了,我写的文档要用自word继承的invalid_word类;我写的程序类文档要再继承一层,用invalid_program_doc类;而读后感类则要invalid_textreading_doc类……
光我一个人都能把你累死,何况知乎上亿答主……
这实在太蠢了。
请记住,不要把配置参数固化在代码里——尤其不能固化在类继承结构里:这特么就是个但凡长了个脚趾头都不应该犯的低级错误!
相反,如果真的有需求,请使用配置参数来动态改变类的行为——更严苛更复杂的需求,不妨实现成DSL(领域特定语言 domain-specific language)。
但无论如何,不要把配置写死到类继承结构里——只有最白痴最脑残的外行才会这么做。
哦,对了,被他们误解的“开闭原则”说的其实就是,程序代码写出来就不用再动了,这叫对修改封闭;但随便你需要什么样的功能,都可以借助既有的代码完成,这叫“对扩展开放”。
你看,当你搞出个DSL时,是不是整个领域的一切都能借助它来完成?这是不是最彻底的“对扩展开放”?
同时,用户都被你隔离到DSL那一侧了,你的基本库是不是用就行了、再也用不着也不应该修改?这是不是最彻底的“对扩展开放”?
总之,设计程序,请一定要记住:我们只写固定不变的东西;经常会变的东西,我们只写hold住它变化的那一点精华(此所谓meta编程),所有可能变化的东西都应该隔离到数据中去。
只有这样写,程序才会简单,工作才会轻松,才能做到一出手就是0bug,才能“虽然我只写了一个很小的程序,然而任你需求万般变化,我只一样应付”。
尤其是,那些复杂的、处理元规则(meta)的逻辑是难以排错、难以更改的,所以更应该“meta化”——把这些写的简单,把对它们的调用隔离到外层配置、数据乃至DSL中去,程序自然好写、好测。
嗯,你想一想,假如你写一个C语言版本的hello world,要调试它居然要看编译器源代码,这玩意儿你还有能力调试吗?还有能力debug吗?
反之,当你把“元规则”隔离进编译器、只留给外界一个干干净净纯纯粹粹的C/C++语言说明文档、使得他们哪怕写操作系统都无需碰你的编译器时,这玩意儿是不是才好学、好用、好改?
还是那句话,记住了:对修改封闭不是你拍桌子砸凳子,撒泼打滚骂高层“这段程序是我的,不改!打死也不改!打死也不给你们改!”
错了。
对修改封闭是,不用你说,你的上司,你的同事,就会在别人犯错时这样说:“不,这只是你不会用而已。这套库我们用了很多年了,质量非常好,基本不可能出bug。请检查你自己的程序,不要遇到点捉摸不定的就去怀疑编译器!”“什么新需求?不不不,你要明白,我们这套库足够应付一切需求了,因为我们可以证明它的对外接口以及功能组是完备的。对,它不能改,也不需要改。当然,如果你们面对的场景很普遍,那么我们也可以在库中增加支持——但仅仅是添加,原有的接口是不需要动的”。
你看,真正做到了“对修改封闭”,表现是“不需要动”“不要去怀疑”“可以增加功能,但没必要触动已有代码”……
而“把数据的拓扑结构”都通过“设计模式”固定到代码里、尤其是拿类继承/多态来取代if的那种蠢做法呢,实质上是“把代码组织结构和数据耦合在一起”——面对多变的需求,这种蠢透了的东西不仅不能灵活应变、反而通过额外的、“继承的拓扑结构”这层维度,把自己绑的更死了。
所以,我才要专门强调一下:请一定要区分开“业务模型”和“业务数据”;甚至于,请特别警惕,不要被用户自己描述的“业务模型”带歪了:具体的某个公司的组织结构也只是数据,他们这些外行哪能给你合理的总体设计!
你得自己琢磨对方的组织结构和信息流程,思考下“如果业务部不再从属于客户部,而是独立出来、改名为‘服务部’,和客户部并列,那我的程序该怎么办”……
你看,这才叫“需求变更”——那些半吊子那种“从一开始就没弄明白、搞了两年了才发现一开始弄错了得改”,那叫“需求事故”,可不是什么需求变更。
所谓happy path,就是完全不考虑出错、不考虑数据竞争、不考虑操作提交时条件不满足、假定世界是完美的、按顺序来一切都能解决的这么一个执行流。
嗯,如你所见,很多人happy path都还设计不好呢……
但,如果你有所追求的话,就别只盯着happy path。
请从你的小的、玩具级别的项目开始,训练自己同时考虑happy path和异常退出时的执行流——甚至是异常恢复流。
做大一些的、需要长时间运行的项目,不会处理异常是不可能的。
当然,happy path也是必须先找出来的。
先把happy path找出来,再一点点添加——这里可能出现意外,出现意外怎么办……逐渐添加、丰富下去,正确的设计稿就出来了。
这一条看似和上一条重复了;但实际上是上一条的深化。
比如知乎上就有人问过:
很多人,一听说要搞“异常处理”就觉得头大如斗——这怎么搞啊?还不是catch掉打一条日志继续……
没想到,日志越打越多;搞着搞着到处都是异常,于是不得不到处catch到处吞异常到处打日志——写了100行代码,容错代码倒写了五百行;容错代码的容错代码又写了1000行;容错代码的容错代码的容错代码又……
你看,无组织,无纪律,遇到风吹草动都得记日志……
啊,倒也简单。也就是每个接口附近都长个肿瘤一样的“容错代码层”嘛。
于是,整个项目代码量暴增,1万变10万10万变百万……然后就再也没人敢碰了。
不要这样。
异常处理是需要在设计阶段就考虑好的。
首先,要把异常分为几类。
比如,用户输入数据本身的错误、网络错误、磁盘数据被破坏、宇宙射线造成位反转,等等。
然后,对确定来源的异常应设计正常处理流程——它是正常流程的一部分,是设计之初就应该考虑好的,可不是什么异常。
比如,用户输入数据过不了检测,那就打回去让用户重填。
网络连接出错呢,要给用户提示;如果用户提交了数据,那么可以考虑把这份数据先存在本地(注意数据安全),或者提示他等网络好了重做。
特别的,有些时候,用户的一连串动作整体构成一个“事务”,比如加购物车、下单、付款、送货、确认收货等。这一系列动作,其中的每一个可以做成“原子”的,比如付款要么成功、要么不成功;但整个事务要允许暂停、要记住每一步的状态——然后,无论用户手机重启还是我们服务器宕机,其中的每一步都要记录在案,绝不能有丝毫差错。
之后,对未知来路的奇怪错误,不要姑息——见到了,就让程序崩掉。
这是因为,此时,我们可能是遇到了宇宙射线,也可能被人缓冲区溢出攻击——或者我们自己错误覆写了某个数据结构。
此时程序的状态是不可控的,它犯任何错误都有可能。
我们不应该寄希望奇迹发生、程序突然自己就好了——它好不了。甚至,它真突然“表现正常”了,那才可怕呢:说明我们最最不想见到的情况出现了(比如,黑客成功控制了它,抹去了一切异常痕迹……)
因此,正确的做法就是:让程序立即崩溃!
最终,如果我们的程序的确有极高可靠性要求的话,我们需要设计一个机制,及早发现程序崩溃并自动拉起新的实例。
举例来说,apache就非常“激进”:默认的,每个php解释器实例在接受过50次页面请求后,哪怕没有任何异常,apache都会强行杀死它、重新拉起一个实例来。
这是因为,很多时候,程序的异常状态需要存在很久、持续扩散、到了某个“病入膏肓”的状态,才能被迟钝的我们感知。
因此,当安全特别重要时,我们不妨假定“一个响应过50次请求的解释器已经出错了、甚至被黑客攻陷了”——立即杀死它并启动新的实例,对于Linux这种起一个进程消耗极低(仅需一个fork而已)的OS来说,几乎没有可观测的额外负担,但却可以最大限度的保障系统的可靠性。
所以说,不要把“程序崩溃”看作洪水猛兽。我们hold得住它。
不仅如此,及早让出现了位置状况的程序崩溃,我们也能更容易的找到问题根源——趁着犯罪现场尚未被破坏及早立案侦察,这才能确保罪犯(bug)无处可逃。
如此反复,最终就是:一出错就马上抛异常崩溃掉的程序,出错的机率越来越低、渐至于怎么折腾都不会崩溃、甚至单实例都能7x24小时可靠运行;而使劲容错、绝不崩溃的程序,它几乎每时每刻都在出错、逼得用户不得不“重启下说不定就好了”“这破系统用十分钟就得重启,不然丢数据……不是丢新数据,旧数据都会被破坏……”
经常见到一些新手程序员,写了个新程序。给人一用,崩了……
——哎呀,你别乱点!要先这个,再这个,然后这个……
——你要乱点,程序还没初始化呢,那肯定崩……
——不行不行,必须ABC的顺序依次点下来。你要ABBC的点,会怎么样?天知道……
这样是不行的。
用户不是专业人员;就是专业人员,人家又不知道你的程序的内部逻辑。
如果你需要保证顺序,请安排逻辑,确保用户点C前必须点B、点了B就得点C。
如果你要支持回退,那就理清逻辑,把栈管好。
甚至,我自己遇到的,某个设备设计来就是监督用户(巡道工)、防止他们偷懒的——他们恨不得把机器用坏、省的带个“随身间谍”打自己小报告呢。
不仅如此,联网的程序还几乎必然会遭遇黑客攻击……
因此,请就把用户看成“专门来捣乱的”,甚至看成“无孔不入的黑客”——而你,要写一个程序,经受他们的“严刑拷打”却依然可靠运行。
这听起来很难,做起来……嗯,也没那么容易。
但只要你愿意往这方面努力,却也没那么高不可攀——绝对不是什么“黑客无可匹敌,我们只能俯首称臣”……没那回事。
黑客说白了,也就是个“偏安全侧的白盒测试工程师”——你会怕测试工程师吗?
那你干嘛怕黑客。
前面关于软件工程的讨论可能吓到你了:妈耶,写一个可以应对需求变更的东西好难!
其实一点也不难:只做一件事,把一件事做好,这玩意儿就天然是方便复用的。
这很容易理解。
玩过积木吧?
什么样的积木摆什么造型都用得上?
方块,对吧。
为什么方块这么容易复用?
因为它最简单。
你看,门拱、门柱之类,只能摆屋顶、摆房门,对吧:
而方块呢,摆哪都可以:
软件也一样。
你往里面添加了越多的“高级功能”“自动化机制”,它就越发的只能为它的设计目标服务了——稍微改一点?重写吧。
反之,你把功能简化到简无可简、不让它保存什么在状态,而是用户给什么数据它提供什么服务……那么,很自然的,用户拿它来做什么都可以。
Linux的grep、awk等犀利的文本工具,其实就是这么来的。
这实际上也是我前面说过的:把可变的东西隔离到数据中,程序只提供一组元规则!
你看,grep是万能的。因为它压根不理睬你的文本是什么、从哪里来;它只是提供了一组文本模式匹配工具而已。
把具体事务相关的东西隔离出去、只写程序处理“共性”……越是这样,你的程序越简洁。
程序越简洁,就越是可以随意的拼起来、拼出千变万化五彩缤纷的大千世界。
当然了,还是那句话,把程序写复杂、写的功能单一死板、一点点“需求变更”就得劳民伤财,这很容易;但想要学会抽共性、写简单、把用户相关的东西尽量往外放、甚至最终只体现于配置文件/DSL,这很难。
尤其玩到DSL,那就进入编译器领域了。
当然,也别为DSL而DSL。一定要找到技术性-简洁度曲线的最低点,这才能最高效率最低成本的完成项目——同时保留无与伦比的扩展性。
前面“藐视”了一把黑客,恐怕不少不学无术者未免要“友邦惊诧”了。
但实际上,这个并不难,也是有专业的、现成的体系的——而且有现成的课本,拿来学就是了。
比如,前面我提到“要把用户当敌人”,恐怕很多人就想不通了:不是,你是不知道,用户能有多奇葩……
还会有人想起这个笑话:
一个测试工程师走进一家酒吧,要了一杯啤酒;
一个测试工程师走进一家酒吧,要了一杯咖啡;
一个测试工程师走进一家酒吧,要了0.7杯啤酒;
一个测试工程师走进一家酒吧,要了-1杯啤酒;
一个测试工程师走进一家酒吧,要了232杯啤酒;
一个测试工程师走进一家酒吧,要了一杯洗脚水;
一个测试工程师走进一家酒吧,要了一杯蜥蜴;
一个测试工程师走进一家酒吧,要了一份asdfQwer@24dg!&*(@;
一个测试工程师走进一家酒吧,什么也没要;
一个测试工程师走进一家酒吧,又走出去又从窗户进来又从后门出去从下水道钻进来;
一个测试工程师走进一家酒吧,又走出去又进来又出去又进来又出去,最后在外面把老板打了一顿;
一个测试工程师走进一家酒吧,要了一杯烫烫烫的锟斤拷;
一个测试工程师走进一家酒吧,要了NaN杯Null;
一个测试工程师冲进一家酒吧,要了500T啤酒咖啡洗脚水野猫狼牙棒奶茶;
一个测试工程师把酒吧拆了;
一个测试工程师化装成老板走进一家酒吧,要了500杯啤酒并且不付钱;
一万个测试工程师在酒吧门外呼啸而过;
一个测试工程师走进一家酒吧,要了一杯啤酒';DROP TABLE 酒吧;
测试工程师们满意地离开了酒吧。
然后一名顾客点了一份炒饭,酒吧炸了。
嗯,不可否认,的确“总会有我们意想不到的状况”;但绝大多数情况,我们是可以预想到的。
不仅可以预想到,甚至测试理论还帮我们压缩了负担。
比如说,一个人机界面(UI),用户的输入有无穷多种,这怎么测?
简单,划分等价类。
什么叫等价类?
就是把数据分为若干类。合法数据程序必须给出正确响应;非法数据程序必须明确拒绝。
比如,我们这个程序负责收钱,金额上限100,下限为0,精确到小数点后两位小数。
那么,[100, 0]就是合法数据。
(0, -∞)和(100, +∞)就是非法数据。
abc也是非法数据
我们不需要测试所有这些数字——那不可能做到;但我们可以从每一类里选择几个进行测试。
因为程序也是针对集合进行判断的,因此,每类数据选择一两个代表就行了,足以涵盖所有情况。
然后,一般人经常犯错的,刚刚好介于合法和非法之间的数据,这叫“临界值”,比如-0.1、0、0.1、99.99、100、100.01都是边界值——注意边界值和合法/非法数据并不是同一层面的东西——我们最好也都找出来,测一测。
不仅如此。
有些数据,虽然接口上看都属于合法数据;但内部处理呢,可能大于30块钱的走转账、小于30块钱走快捷支付——你看,其实这里也要分成两个等价类,这才能覆盖所有流程,对吧。
同样的,这里也会有新的边界值,比如29.99、30、30.01之类。
你看,快刀斩乱麻,是不是一下子就清晰了?
其实很多很多东西都这样。你自己得知道背后的原理,知道了,自然觉得一切井井有条,轻松应付;但如果不知道……祝你好运。
这在过去是老生常谈;但现在嘛……
由于国内根深蒂固的等级制思想,测试嘛,他们要求比我低,水平没我高,写不来程序才当了测试……
所以,测试当然是来伺候大爷我的。我写完程序,丢给他,他负责找出所有错误——漏测了,那是他的责任!
爽吧?
然而很遗憾,这只是一时爽。长久来说对你是不利的。
事实上,请把程序员看作“公司任务的承包商”;而测试工程师呢,则是“公司派来的验收员”——他的职责是给你的软件质量下一个评估,并不是替你找bug来了。
早年一些公司的方法论里面,还建议“当发现软件bug过多时,不要提交全部bug,要让程序员自测,然后验证那些未提交的bug是否也被修复了,以确保大部分bug已被修正”。
当然,这些年来,有些公司干脆撤销了测试工程师,让程序员自己测——思路上是一脉相承的,只是把“最终软件质量”踢给了用户。
那么,为什么业界如此看重程序员自测呢?为什么我说“学会测试”对我们程序员自己的长远发展更有利呢?
很简单,最熟悉软件的就是程序员自己。
就国内这绝大多数测试工程师看不懂程序的水平,他们能覆盖接口等价类就不错了;至于内部判断/执行流程,他们看不懂,也写不出用例、做不了覆盖。
哪怕他们能做到,写程序这件事也是程序员在干,轮不到他们。
那么,怎样才能把一个程序写的简洁、好测、可以“明显没有问题”呢?
你猜你写完程序二郎腿一翘,等测试奴忙活出结果你再和他扯皮几天……你能学会吗?
事实上,很多人不光程序越写越糟烂、甚至反而学会隐藏错误了!
然后,代码质量就……
相反,当你自己对代码质量就有一个很清晰的认识、对于暴露缺陷的理论方法了如指掌时,你还可能写那些“一看就不大对”的代码吗?
换句话说,测试测出来、没有bug的代码,那叫“没有明显的缺陷”;而程序员写出来就没有bug的代码,那才叫“明显没有缺陷”。
想要做到“写明显没有错误的代码”,想要走到这个层次,你就得训练自己,让自己对缺陷敏感、能够持续产出“测试友好”的代码,甚至一出手就是成品,就没人能找到bug。
不知道往正确方向努力,那就永远不会有进步。
这个又是国内普遍的、不学无术环境下养成的严重错误认知。
典型的瀑布式开发,高级工程师要从需求到详设全部包揽,只留一些空函数给程序员填(当年日本人就喜欢这么搞)。
这种模式对高级工程师要求很高,但对写代码的工程师要求很低,会填空即可。缺点是过于僵死,项目周期长;而且容易搞出庞大、累赘的方案。太容易走进“重分解、轻复用”的误区。
敏捷其实也是瀑布式开发,也需要从需求到总体设计到模块设计走上一遭;但它同时又汲取了“自底向上”模式和“快速原型法”的长处。
它要求程序员写出可复用的、库函数水平的代码,这样才可以根据需要随意组合(这是自底向上开发模式的长项;但这个开发模式底层接口简洁优美,高层经常一团乱);它也要求程序员做出可复用的设计、做好模块分解,这样需求变更才不会对程序造成过大冲击(这是瀑布模型的风格,高层设计优美简洁,越往下越不能看);最后,它先做基本功能,让客户看到样子,再一点点改(这又是快速原型法的优点,只是原型法写出来的代码是要扔的,而敏捷方法不扔,留下来继续用)。
换句话说,敏捷的“先写用例后写代码”,实际上是以单元测试代替详细设计文档、以高质量的面向对象的类定义代替模块设计、总体设计文档,从而把写文档的时间直接拿来写代码——然后借助面向对象框架直接导出文档就行了。
显然,敏捷模式对工程师的要求非常高。因此,最初搞敏捷的那些人甚至提倡“结对编程”,就是让两个工程师用一台电脑,一个写一个看。据说效率和软件质量比起单人分别开发都更好一些。
但这样太理想化。因为它要求两个工程师水平得差不多,不然就成了一个人写另一个干看,甚至看都看不懂。
不仅如此,它还要求这两个工程师都得有总体设计能力、可以一步到位的做出优秀的模块分解设计——这个要求一般公司根本做不到:大多数公司里,能做总体设计/模块设计的工程师可能只有一个,甚至连一个都没有。
但没有这个能力、没有足够的质量,程序就会越改越乱、越改越烂,绝不会越改越符合客户需要。也就是敏捷不起来,搞成了行为艺术。
再后来,就不再搞结对编程了;而是改成“先写测试,后写代码”——说白了,详细设计文档没人想写,写文档的功夫就把代码写出来了;而且经常写完了文档,才发现实际实现时遇到了问题,还得回头改文档……
先写测试,其实就是定内部功能划分和接口设计。不然没法写测试用例。写完测试再写代码,写出来就能马上跑一下测试,确保接口正确——倘若发现了问题,那就改测试用例(也就是改接口):反正总是要测的,改一下很自然,不多费工。
换句话说,现在只要求普通程序员有详细设计的能力,那就足够玩敏捷了——至于模块设计、总体设计、需求等等,那当然还是得专人来做。
相比之前,这显然就实际多了。
但我们现在大部分人、大部分公司玩的所谓“敏捷”呢?
简单说就是我也不会总体设计我也不懂详细设计,爱做啥样做啥样,做完了给客户碰运气,碰过去了喝啤酒,碰不过去加班再改——反正客户有花不完的钱!
1. 规范化自己的代码,少点个人风格,多点通用规矩,并学会使用CheckStyle工具。
其实任何东西我们都希望它能够“自动化”,随着编程经验的提升,大部分编程规范你已经了然于心,但是实际操作的时候,又总是忘这忘那,我们希望一个工具来帮我们自动检测我们的程序是否是符合规范,结构良好的。
事实上,任何语言都是有自己的编程规范的,编程规范的制定,十分有利于代码的阅读和潜在Bug风险的降低,比如在Java中,有严格的命名规范:
对于类(Class)的命名,有这样的规范:
Class names should be nouns, in mixed case with the first letter of each internal word capitalized. Try to keep your class names simple and descriptive. Use whole words-avoid acronyms and abbreviations (unless the abbreviation is much more widely used than the long form, such as URL or HTML).
类的名字必须是名词,每个单词的第一个字母需大写。尽可能让你的类名称简洁又能传递清楚含义。尽量使用单词全拼,避免同义词或缩写(除非缩写使用更广泛,比如URL, HTML等)。
比如在Java中,有严格的文档规范:
/** * Returns an Image object that can then be painted on the screen. * The url argument must specify an absolute {@link URL}. The name * argument is a specifier that is relative to the url argument. * <p> * This method always returns immediately, whether or not the * image exists. When this applet attempts to draw the image on * the screen, the data will be loaded. The graphics primitives * that draw the image will incrementally paint on the screen. * * @param url an absolute URL giving the base location of the image * @param name the location of the image, relative to the url argument * @return the image at the specified URL * @see Image */ public Image getImage(URL url, String name) { try { return getImage(new URL(url, name)); } catch (MalformedURLException e) { return null; } }
文档是用HTML语言写成的,前半部分是关于当前方法或类的描述,后面需要有参数标签@param和返回标签@return,还可以加一些别的标签,比如@see,只有这样,当别人试图引用你的程序时,才能马上明白你的某段程序是用来干嘛的,参数传递,返回等一目了然,要知道,实际工作中,大量的协作就意味着代码需要高度的重用性,你必须把你的程序封装完美,并且让别人仅仅看你的文档,就知道你的这个API怎么用。
上面说的仅仅是编程规范的冰山一角了,问题是,你有时会忘掉或用错一些规范,即便你知道它。
所以我们需要使用checkstyle插件去自动检测我们的程序是否符合规范。
对于Java而言,详情请见
Github地址:
在各大JavaIDE中,可以直接在Eclipse Marketplace中下载:
其他的语言应该也有自己的插件,可以自行谷歌了解。
2. 宁可变量名长,也不要让变量名短得让人无法推测其含义。
3. 在电脑里安装两套输入法,编程的时候,将中文输入法彻底关掉,确保任何快捷键都不会将其转换成中文输入法,防止中文类似符号引起混淆,比如:
中文 :(); English: () ;
一点点小错误,就有可能让你多花一两个小时在没有意义的事上,人生苦短,尽量避免低级错误。
4. 尽可能杜绝重复代码,凡是需要用至少两次的代码,给它单独做一个类或函数。
5. 避免类与类之间的内部调用(Cycle Reference),其实也就是降低函数模块的耦合程度。类与类之间的调用只允许通过接口,保证更改某个类的时候,其他的仍然能工作。
6. 多读别人的优秀代码,拿别人的优秀代码和自己的代码进行对比,学习别人的长处,吸收经验。
7. 尝试着做内容的生产者,尝试着写一些教程或笔记,分享给社区,不要只做社区内容的吸收者,还要不断地生产内容,回馈社区给你的帮助,比如在StackOverFlow上回答别人的问题等。
8. 既要脚踏实地,也要多看看社区发生了什么新闻,有什么新的技术和软件的发布,这些技术和软件将怎样影响你的开发工作,现在使用的IDE或Editor是否有更好的替代产品等等。
9. 没有任务的时候,也不要闲下来,去开发点你喜欢的东西,从中挑战自己,增长经验。
10. 不要过分依赖教程,要学会看官方文档。凡是能被做成教程的东西,往往已经过时了,最新的技术,最新发布的标准,往往没有现成的教程,你需要去认真阅读官方文档,那里的东西才是最权威的。
11. 不要参与语言好坏的争论,人们往往偏向于喜欢自己用得熟练,用得多的那个语言,语言好坏之争,就和争谁的女朋友漂亮一样,我当然觉得自己的女朋友(虽然是null)最漂亮,但是别人并不这么觉得。
12. 当你有什么需求的时候,往往别人也有这个需求,而且往往也有了相应的工具去解决你这个需求,比如,你想将函数的调用关系可视化,弄成树状图那样,这样的工具已经有了,比如SourceInsight(付费),Source Navigator(免费)等。
13. 少在国内的XX软件园里下载各种破解软件,盗版软件等,这些软件园为了盈利,会在你安装的过程中,悄无声息地给你安装上一堆其他的流氓软件,360首当其冲,这些垃圾软件,删的越干净越好。
14. 你的开发电脑,CPU可以差些,但内存最好大些,推荐至少要8G,甚至推荐10G往上走,你常常需要同时打开一堆浏览器页面和一个IDE甚至还有别的一堆工具,如果你做过安卓开发,AndroidStudio动辄就调用你电脑2-3G的内存,一般的4G电脑肯定是吃不消的,严重降低开发体验,但也并不是让你换电脑,内存条了解一下。
15. 保持一个健康,干净的电脑状态,硬盘里的文件存储要有调理,容易寻找指定文件,降低文件丢失概率,加快文件寻找速度。
16. C盘快满了的话,可以通过Disk Manager将别的磁盘的空间送给C盘。
17. 用NetWork NotePad画网络图表示函数调用关系(当然你可以用别的来画),像这样:
这是前两天编一个FlappyBird时草草画的图,虽然简陋,但有用。
18. 可以考虑用一个电脑架子,防止乌龟颈,保护颈椎。
19. 下载一个护眼宝,保护视力。
20. 少刷知乎,但不要做一个不点赞的冷漠青年。
说了这么多,如果你是编程新手的话,建议先从简单的编程语言入手,并且最好前期不要涉及太底层的东西,先培养兴趣,这里给大家推荐一个特别适合新手入门的编程学习网站,叫「夜曲编程」,是和「百词斩」同一个公司出品,这个课程的特点就是极易上手,特别适合早期关于编程的信心和兴趣的培养,甚至还能带你养成一些好的习惯,比如不要盲目的复制代码,合理的使用注释等等:
尤其是对计算机零基础的童鞋,这个课程尤其推荐,感兴趣的可以去他家公众号看看~,回复『免费教程』还有免费的体验课 ,感觉内容量还是挺足的,包括对学生和教育者在9月也有一些优惠活动。
哈,这真是一个有意思的问题。
一些朋友说的比如尽早习惯做版本管理,一开始就形成良好的代码风格,我很赞同,另外一些朋友提到DRY原则,大括号对齐,甚至还有free(p);p=NULL;这样的建议,都是很容易误导人的,这里希望刚入门或者入门不久的朋友们学会独立思考,凡事打个问号,不要盲目遵循。
我个人觉得最重要的一个习惯其实有不止一个人说了,就是想清楚之后再动手。这里我稍微展开说一下,因为“想清楚”其实是一个很模糊的概念。怎样才算想清楚了呢?
我常常有这样的经历,对一个难题,经过了一番思考之后觉得自己想到了一个比其他人好得多的方法,结果去实现的时候,发现原来是想的时候疏漏的一个细节,方法不可行,感到很挫败,不得不回头过去重新审视问题,浪费了很多时间。
怎样才能想清楚呢?
Leslie Lamport在斯坦福做了一个讲座(底下有链接,推荐)。里面引用了一句话: “Writing is nature‘s way of let you know how sloppy your thinking is” 我深有同感。怎么才能知道自己是否想清楚了呢?最自然的方式就是写下来。怎么写呢?这个因人而异,比如我在编码之前,会在如下两个问题里面迭代几次。
0. 做什么? (需求: 白话)
1. 怎么做? (方法: 伪码)
关于做什么,其实就是分析需求,这里跟那个“需求分析”过程有些区别。怎么舒服怎么来,大白话,不拘一格,理清楚问题就行。我通常的做法就是以自言自语自我审问的方式把整个过程理出来,为了不至于特别无聊,这部分通常会写得十分口语化。等这部分弄清楚之后,基本上伪代码的框架在心里面就有眉目了。接下来会尝试着写伪代码,写伪代码的途中通常会不断的进行重构或者跟第一步进行迭代,直到伪代码比较精简,逻辑上没有冗余的时候,就去喝杯咖啡,小个便,回来开始实现。实现的过程中难免会有需要回到第一个步或者第二步进行迭代的时候,随着经验的提升,迭代的次数会变得越来越少。
实际上0,1两步我都是在代码的头部大块注释里面完成的,这个部分可以直接成为十分容易理解的文档。实际中除了这块注释,我几乎不在代码里面写注释。除了少数实现的trick外。
另外刚才提到写伪代码,引出了一个潜在的问题: 怎么写伪代码? 建议不要尝试着用算法导论或者一些论文里面的方法。那些数学符号或者不容易敲打的符号会严重的影响写伪代码的快感。我觉得《The algorithm design manual》里面的伪代码格式就不错,但是因为实际中的伪代码可能比写一个算法复杂一些,所以还需要添加一些其他元素,比如我自己的伪代码格式其实有点像是Python代码。写伪代码的主要目的是弄清楚实现的逻辑。
在第0步,其实不是每个人一开始都能写得清晰的,需要通过长期写作来锻炼。所以无论是写日记,写博客还是写情书,坚持下去,都能对日后写代码的能力有所帮助。
时间不早了,最后再附送两个程序员生活小贴士:
0. 做笔记,OneNote或者EverNote,把遇到的bug,看到过的好trick,好文章,好照片都记录下来。
1. Eat your own dog food. 那些自己写过的可以抽离出来重用的代码或者脚本整理好同步到github或者bitbucket上去,不断增加和改善自己的dog food。
Update 2015-07-22:
评论里几个人提到free(p);p=NULL;是一个合理的建议。我想支撑这一点的理由是“悬空指针被复用的话很危险”。但是在你的程序里面每一个p都存在被复用的可能吗?并不是。所以每一次写下free(p)的时候就应该想这个问题。然后再考虑是否写p=NULL;因为害怕而写下的这些冗余代码会极度影响代码的美观,这也是我认为从free(p)到p=NULL不应该成为一个顺手的习惯的原因。
---
https://www. youtube.com/watch? v=6QsTfL-uXd8重视模块化,重视抽象但不滥用
我刚接触编程的时候,在网上看到许多大牛写程序都十分注重模块化,因此我就下意识的模仿他们;后来看SICP,知道了抽象的好处,因此在写程序的时候会仔细思考抽象的问题。这些对我都有着非常大的帮助。
在一篇讲述程序员代码行数瓶颈的博客中(
程序员的成长和代码行数的关系)提到,程序员在2k行、20k行、200k行等若干程序规模时会遇到瓶颈,如果不用更科学有效的方法,超过了这个行数代码就会混乱到难以维护。但我第一次写很大的程序时(8k+)并没有感觉到文中提到的瓶颈;我目前接手的项目有近900k行,我自己写的部分也已经快上10k,但我仍然没遇到文中提到的瓶颈。
针对这一现象,我做过一些实验。我在很不认真的写一些小程序时,也总是写的混乱不堪,我发现,这种情况下,程序行数超过200行我就觉的很难受了,在需要进行一点小的修改时,我往往需要花很长时间去寻找到底该改哪里,十分吃力——这种吃力感是我在那些精心思考的大项目里从未感受过的。这说明了,我并没有过人的天赋能在混乱中轻易找出清晰的脉络,那就是说,之前的如鱼得水,是因为好的习惯。
后来,我进行了深入的思考。在模块划分合理、抽象合理的程序里,我可以简单的把一个个功能抽象为一个简单的黑盒,我不需要知道他们内部发生了什么复杂的反应,我只需要知道他们对什么样的输入会做出什么样的输出。这种抽象极大的减轻了大脑的负担,让我可以把精力更多的投入到真正需要考虑的地方。而那些混乱的程序里,我需要理清每一句话之间的关系,这无疑会极大的消耗脑力。这种情况下,200行就浑身难受就可以理解了——因为我用于维护项目关系所消耗的脑力已经远远大于了那些好程序里的消耗。
这个习惯,真的让人十分受益,请一定坚持。刚开始的时候,你或许觉得花很长时间去思考程序的模块划分、抽象层级是十分浪费时间的无用功;但久了以后,你就会感受到这种习惯带来的好处:它会在无声无息之间帮你消除掉许多瓶颈。而且还有额外的好处:当你习惯用模块化组织你的思维时,思维能力也会有一定的增强。
写Unit Test!
很难坚持,但是一旦这么做了就会觉得当初的决定太正确了。
(有空再补充)
不请自来,关于Mac的使用,一周进步的编辑部一直以来都有自己的心得。很多人似乎都对MacOS存在误解,尤其是刚从Windows过来的朋友会极度不适应,但其实MacOS也有许多便人之处,只要你学会有正确的方式去使用它。
回答目录
1.系统辅助:Haptic Touch Bar、Alfred、manico、Mounty、NTFS、Dr.cleaner
2.效率办公:幕布、eagle、Paralles Desktop、WPS、XMindZEN、钉钉、Folx
3.影音播放:VLC、Camtasia、ScreenFlow、暴雪客户端、Mach Desktop
1.1.Haptic Touch Bar
Haptic Touch Bar是MacOS下一款能调节Touch Bar的声音、振动反馈幅度的软件,让你在使用Touch Bar的时候体验感更强,有一种好像在使用实体按键的错觉。(Tips:请在2016款或以上带有TouchBar功能的MacBook使用)
1.2.Alfred
几乎是Mac必装的软件之一。它可以帮助你快速查找电脑文件、启动软件,你还可以用Alfred设置一些常用的Workflow,从而快速完成许多复杂的工作。比如:百度搜索、Google搜索、转换链接为二维码等。
1.3.manico
众所周知Mac打开多窗口后切换十分不方便,一个个点选缩小效率非常低。使用manico后可以获得像Windows一样的Tab栏,只需要使用快捷键commod+tab就可以在不同窗口间快速切换,速度非常快。
1.4.Mounty
很多刚接触Mac的用户经常都会有这样的疑惑:为什么我的硬盘无法读取?这是因为一般出厂默认的硬盘是NTFS格式,而Mac不支持这种格式,你需要安装相应的第三方软件,比如:Mounty、NTFS For Mac后,才能正常读取硬盘或者U盘。
除此外,还有一种操作是在Windows电脑中将硬盘格式化,然后修改格式为exfat,这样再在Mac中就能正常使用了。
1.5.Dr.cleaner
使用Mac几乎不需要杀毒软件,但同样需要一款清理软件。Dr.Cleaner就是一款免费但却非常好用清理工具,还能实时监测电脑的运行情况,非常方便。一般来说,免费版提供的功能就已足够使用,如果有深度清理的需求可以购买Pro版。
2.1.幕布
幕布是一款非常好用的思维导图工具。但它却和普通的思维导图工具不同,可以采用大纲的模式去记录笔记,同时可以转换成思维导图,而且还拥有Windows、Mac、微信公众号、网页端等多平台,同步速度也非常快。办公利器,值得推荐。
2.2.XMindZEN
说起思维导图自然少不了Xmind,但与传统的Xmind系列不同,新推出的ZEN系列非常轻便、好用。界面UI等也非常赏心悦目,使用起来有一种沉浸感,非常爽。
2.3.Paralles Desktop
有的时候我们难免逃脱不了需要使用Windows电脑进行办公,很多朋友也会选择在Mac里安装一个虚拟机以备不时之需。安装虚拟机推荐使用Parplles Desktop,安装起来非常简单,使用也非常舒适。
2.4.eagle
eagle是一款图片管理软件,如果你是一名设计师经常管理大量图片,强烈推荐使用。使用eagle你可以对Mac上的所有图片进行统一的管理,直接拖拽到PS等也非常方便,而且eagle也有Windows客户端。
2.5.WPS
WPS For Mac其实也是非常不错的工具,Office For Mac时常会出现许多bug或者卡顿的现象出现,非常影响效率。如果你的需求只是阅读和查看Office文档,可以考虑安装WPS。
2.6.钉钉
钉钉现在是企业办公市场第一,很多公司都会采用钉钉才进行办公协作,但其实作为个人用户同样可以使用。与QQ不同,钉钉不会限制登录的设备及数量,所以你可以在你任意的设备上登录钉钉,传文件起来就非常方便。
2.7.Folx
迅雷在目前来说,还是最方便的下载工具之一。但它的问题在于许多热门资源很容易失效或者限制下载,这时候往往我们替换一个磁力链接下载工具, 比如:Flox就可以了。
3.1.VLC
Mac上的播放器有很多,一般情况下quicktime就已经足够使用,但还是会遇到一些格式的文件无法打开,这时强烈推荐VLC,支持格式非常广泛而且还支持字幕导入。
3.2.Camtasia
Camtasia是一款非常好用的录屏软件,在Windows下同样也有,不过在Mac没有汉化。之所以说它强大是因为它不仅能完成视频录制,还能做后期剪辑、音频美化、字幕导入等功能,相当于简易的Premiere了。
3.3.Screen Flow
Mac下最为人广泛使用的录屏软件之一,相比Camtasia它对Mac的优化更好,使用起来也更人性化。
3.4.Mach Desktop
可以用高清Gif或者视频来作为电脑的背景,相当于Windows中Steam的wallpaper engine,只要找到高清的资源后,你的桌面就会非常炫酷。但同样这个软件非常消耗内存和电量,酌情使用。
3.5.暴雪游戏客户端
如果你想在Mac里玩游戏...那暴雪游戏客户端值得推荐,目前暴雪除了守望先锋外,所有游戏都有Mac版本。当然Steam也有很多适合Mac的游戏,也非常不错。
最后,说起MacOS常用的软件,大名鼎鼎的Adobe全家桶怎么能少呢?
关于MacOS软件就回答到这里,想了解更多关于办公、职场、效率的知识就关注我们吧。
更多阅读
Mac上有哪些冷门但逆天软件? (推荐)
还用Mac装win?你就out了。 (推荐)
一周进步〡年轻人快速突破自己的学习入口
微信公众号:关注「一周进步」(weekweekup)
青年兴趣课堂,每周一场训练营,欢迎关注,让我们一起进步
更多干货欢迎阅读:一周进步文章精选