作为24k高纯度理工技术男,从大量和女性朋友交流的经历中,我发现一个有意思的现象: 男性在聊天的时候,一般会习惯就某一个话题持续深入的探讨,而女性总是在无意识当中频繁的切换话题 。如果将话题的切换比作线程的context switch,男人的并发执行能力和女人相比,简直弱爆了。在经历无数的挫败之后,我也终于发现了和女性朋友愉快聊天的关键法则: 不要尝试去记住10分钟之前的聊天内容 。
男人的大脑虽然不太擅长context switch,计算机却是这方面的行家。现在的手机系统无论Android,还是iOS,同时在100个线程之间进行context switch也不会down机。可惜的是计算机在聊天方面缺乏“情趣”,又或者我们工科男在女神眼里并不比一台冷冰冰的电脑多少高明多少,女神和Siri聊天可能会更开心点你信不信?
不管我们愿不愿意承认,我们人类的大脑其实“不太好用”。“脑子转的太慢”,“记性不太好了”,“事情太多,我处理不过来了”等等,都是现实低于预期的表现。虽然存在个别大脑性能爆表的例子,比如28岁就憋出了[相对论]的爱因斯坦“大叔”,绝大部分人的大脑都只能用平庸来形容。我保守一点评价,依据“二八法则”,80%人的大脑瓶颈都是”3“。3是什么鬼?3不是你的智商值绝对值,3是人脑并发的一道坎。
我还可以举出更多的例子,这些例子有一个共同点,都在尝试挑战人脑的并发能力。
自诩为计算机世界魔法师的程序员们也摆脱不了魔法数字3的限制。虽然有少部分程序员天赋异禀,领悟力创造力惊人,写代码自成一格,但我们所写的代码最终还是要能被大部分人所理解。计算机行业越发达,个人英雄也会越来越少,团队合作是大趋势。尝试去遵循,敬畏人脑的局限性能让我们写出更优质的代码,以下列出一些小tip,皆来自个人这么些年的摸爬滚打,希望能对初入行的朋友们有些帮助。
当我们定义一个新方法的时候,参数的个数越少越好,如果业务逻辑复杂,也不要让参数的个数超过三个。比如下面这个方法定义(以下例子说明都使用Objective-C语法):
- (void)initUserWithName:(NSString*)name gender:(int)gender age:(int)age { self.name = name; self.gender = gender; self.age = age; }
上面这段代码尝试初始化一个User的信息,包括名字,性别和年龄,简单好理解。方法的使用者一眼就记住怎么使用。如果我再加一个参数呢:
- (void)initUserWithName:(NSString*)name gender:(int)gender age:(int)age height:(int)height { self.name = name; self.gender = gender; self.age = age; self.height = height; }
多了一个年龄的参数,方法的使用者在初始化User的时候要记得同时传入4个信息。大家可以尝试下感受这其中的差别,现实当中我们很有可能要面对比User更复杂的业务model,要传入更陌生的业务参数。方法调用者记住3个参数的概率要远高于记住4个,一旦调用者忘记第四个参数是什么,就必须jump到方法的declaration,同时必须暂停当前的思考,进行一次大脑的context switch,切换到方法是怎么定义的理解上去,记住第四个参数之后,再切回之前的工作场景。这其中两次的switch,看似微乎其微,积少成多,对工作效率和心情的影响不言而喻。自己写的方法会记忆深刻一些,但程序员职业生涯中大部分时候都是在读别人的代码,而且读自己四五年前写的代码,可能和读别人代码并没太大差别,写方法的时候参数少一点,是对别人负责,也是对自己负责。
当然有些业务会复杂,需要的参数就有5个甚至6个,遇到这种情况,可以尝试将方法抽象成一个对象,构建对象之后再设置具体的任务属性,或者使用java当中经常使用的builder pattern来构建。
对于套嵌的if层级,和方法的调用层级类似,不要超过3层。比如下面这段代码:
if (a >= 0) { //mess up with a if (b >= 0) { //mess up with b if (c >= 0) { //mess up with c } } }
有些程序员习惯写很深的if套嵌,每一个if里面逻辑可能还很长(即使作者当时写的简短,总有一天也会被别人写得又臭又长)。这种套嵌的if读起来真得很费劲,调试起来也麻烦,经常需要人眼去对齐大括号,来判断if的包含范围。如果这几个if当中再放几个return,debug的滋味就更酸爽了。
尽量避免if套嵌是个好习惯,当然有些逻辑用套嵌的if表达起来会更顺畅一些,这种情况下,请不要让套嵌的层级超过3个,同时还要避免单个if当中的代码量尽量简短。
我们在调试别人代码的时候,经常会从某一个方法入手作为debug的起点,从一个方法jump到另一个方法,在查看完所以变量之后,再继续往更深的函数调用排查问题。每一次的方法调用切换都是一次context switch,对于debug来说,你往往需要同时记住之前所有的context才能作出完整准确的推理,所以一旦方法的调用层级超过3个,大脑的压力就会骤增。
所以在我们新写某个业务流程的时候,要格外注意把握方法的粒度和调用深度,一旦方法的定义粒度过细,就会带来过多的方法切换,层级越深,就要记住越多的方法context。
这个法则很容易被打破,因为业务总是朝着复杂化的方向发展,一旦业务膨胀,我们不可避免的就要增加方法的个数和加深调用的层数,再怎么骂产品经理是狗,他提的需求你还是得做。所以这条法则需要靠程序员自己的领悟和经验去把握,去尽量遵循,其目的无非是让你在下次debug的时候少一些context switch,用更短的路径去完成调试任务。
继承(Inheritance)虽然名列OOP编程的三大特性之一,但老司机一般都会告诉你尽量避免使用继承,在尽可能多的场景下用组合(Composition)代替继承。并非继承本身存在设计问题,而是继承比大部分人预料中的要难以掌握。在不得不使用继承的场景下,要反复用Liskov substitution principle去验证父类和子类设计的合理性,任何一个property是归属到子类合适还是父类更好,都需要仔细的斟酌。
继承的层级要少,越少越好。两层的继承大部分人都能简单的应付,三层的继承需要合理的设计,请不要超过三层。在设计iOS Controller相关类的时候,三层的类设计类似这样:
@interface CCViewController : UIViewController @end @interface CCAnimatedViewController : CCViewController @end @interface MyDetailedViewController : CCAnimatedViewController @end
CCViewController是你定义的第一层基类,处理所有Controller公共业务逻辑。你的App有非常酷炫的动画交互,所以你决定在做一层CCAnimatedViewController来处理所有动画的逻辑(实际上,这一层是否需要值得斟酌),最后一层MyDetailedViewController是具体实现业务的Controller。这已经是三层的继承设计,试想下如果再加一层基类,这种继承关系的维护难度会变得如何。
从传统的PC软件行业开始,架构师们就已经懂得如何使用分层来设计软件的架构。分层的设计可以将业务和基础服务做到合理的解耦,降低软件维护的成本。分层在降低耦合的同时,也带来了维护层与层之间通信代码的开销,层级越深,逻辑的链路就越长,数据传递的环节也就越多,显然这样会消耗掉开发人员更多的开发调试时间。
现阶段经典的移动端架构大致分为三层,分别为Application Layer,Service Layer, Data Access Layer。这种分法经验过业界大量产品的验证,能在解耦和额外代码开销之间达到很好的平衡。不要轻易尝试将 单个 项目架构的层级超过3层,当然业务会膨胀,项目会进一步细分,与之对应的代码量也会增加,代码的增加可以分为横行和纵向,横行代码的增加限制在某一层之内,纵向的增加即增加架构的层数,我们应该尽可能的去横向拓展代码量,也就是增加某一层当中模块的数量。
以上,全是一家之言,欢迎各位同行共同探讨。