提高设计能力的有效手段

这个话题,在我心里运粮了好久,从携程离开后,总觉得重构设计是那么的刺耳,依稀记得开发主管多次在会议上批评我和另一名同事进行所谓的重构设计。可是没办法,作为有点节操的程序设计者,怎么可能放任着一团不知所云的DEMO片段,当作产品进行对外发布呢?最后,我们并没有像他所说的那样延误工期,我们当时也真的没有进行重构,我们只是把先前我们所写的用于实验性的DEMO重写了而已。或许,就像《重构改善既有代码的设计》的作者Martin Fowler所说的,我们不应该告诉他我们正在进行重构设计。

以上是题外话,扯得有点远,下面进入今天的正题。

程序设计,是一个很复杂的议题,面对各样的设计思想和模式,饱受争议,但无可厚非,一个优良的软件肯定是经过精心设计而来。所谓设计,必然和艺术会扯上关系,在我看来,程序设计师都是艺术家,不同的设计师,不同的思想,所设计出来的风格也会大不相同,这便是艺术的天性,允许你和别人不同,允许你天马行空。

在编程这个行业里,如果你不能很好的驾驭设计,不能找到属于自己的设计风格,那么,你注定只能一直作为最基层、最苦逼的码农。本篇文章,就如何提高自身设计能力,给出了我这些年总结出来的经验,以供大家作为参考。

追究完美的态度

态度决定一切,这句话放在任何行业都非空穴来风,在程序领域,如果你没有追求美好和优雅的态度,我都不会认可你是一个合格的程序员。倘若你的认知仅仅停留在让程序正常运转起来,并且你觉得这样就够了,那我劝你尽早脱离程序设计这个行业,或许做产品或测试更适合你。

程序设计绝非能够正常运转起来这么简单,或许在使用者或管理者眼里这样就够了,但想成为更好设计师的你,绝对不能这么简单的要求自己。一个合格的程序,必须要考虑它的健壮性扩展性、源码的可读性,这些都是需要以优良的设计作为支撑。如果一个程序永远都放在一个静态的环境中,那么设计就会显得多余,但事实是工作中的项目没有一个是做完就结束的,我们会面对需求的变更,不断的版本迭代,如果这时候面对的仅仅是能正常运转起来的程序,那势必会成为一种噩梦。

所以,面对程序设计,必须要有最求完美的态度,不断的修复,哪怕只是很小的瑕疵。当你拥有了这样的态度,甚至开始不自觉的有了些强迫症时,那么你自然就不会去随意命名一个函数、类或变量,不会随意的安放源代码的目录结构,不会让类与类之间的依赖纠缠不清;你会自觉的写好注释,会理所当然的构建完善的单元测试,会不断学习从前人的经验中找到解决自己觉得有坏气味的方法;随后你会深刻的理解设计模式的精妙,你会在各种模式最基础的模型下推算演化出属于自己的设计方式,最终,你淡忘了那些设计模式,你开始觉得自己的程序结构和那些知名的开源项目越来越贴近了,甚至你可以很轻松的站在设计者的角度理解他人的设计思想了,那么,这便是态度带给你的收获。

锻炼抽象能力

态度是使你朝一个正确的方向前进的前提,而在程序设计中,抽象能力起到至关重要的地位。目前行业里大多还是以面向对象设计的语言作为主流,这种形势的出现也是必然的,人类最容易理解的还是这个世界上存在的各种实物,面向对象的设计给予了我们去创造一个世界的能力,自然,我们可以大量的借鉴现实世界的实物去创造,这大大的降低了我们面对复杂算法和数据结构的必要性。上帝在创造这个世界时,绝对是没有使用算法和数据结构,他仅仅制订了各种法则,然后这个世界通过魔法便完美的运作了。我相信未来的程序设计,一定是朝着这样的方向发展的,而那时候我们或许更多的是去考虑法则约束的完整性。

虽然面向对象的设计降低了逻辑处理的复杂性,但也提高了对抽象能力的要求。所以,想要更好的进行面向对象的设计,我们必须要提高自身的抽象能力,这样我们才能设计出更合理的类。对于提高抽象能力,我建议从生活中去着手,当我们见到一个具体的物体时,我们可以试着从创建结构行为,这三个方面去分析它。这里举一个具体的实例,就比如我们看见一把椅子,我们该怎样对其分析?

首先从创建的角度来分析,我们要创建一把椅子,势必要有木头,当然钉子也是必不可少的,这样就够了么?在一般情况下也许就够了,但如果细分下去显然还是不足够的,我们还需要确定椅子的颜色、大小、样式等,这些都是创建的前提。创建对应的是程序设计时类的构造,也可以称为构建类的前置约束。

然后我们再从结构来分析,一把椅子拥有什么呢?对!它有靠背,有四条腿(当然我这里指的是那种比较普通的拥有四条腿椅子),还有什么呢?或许它还有扶手等其它构件。除了这些构件,颜色、高度、重量等,也应该纳入结构的范畴,所谓结构就是体现抽象物体的形态,而这些都体现了椅子的形态。结构对应程序设计中类的属性,也给我提供了细节抽象的参考,这个实例中我们可以把靠背、椅子腿抽象成其它类,这些都是我们在对椅子进行结构分析时,它提供给我们对细节抽象的参考。

最后,我们再从行为上进行分析,椅子拥有什么行为呢?它能被人坐,这是最直观、最容易被想到的,但这真的是椅子的行为么?椅子的行为,当然应该以椅子为行为的实施者,被人坐显然是人的行为,人可以坐椅子。行为的划分必须要明确谁是行为的实施者,这里有“人”和“椅子”两个抽象,“人”是坐在椅子上这个行为的实施者,自然我们应该把这个行为归结到“人”这个抽象上。那么椅子到底拥有什么行为呢?它可以折叠(折叠椅),可以移动(带滑轮的椅子),这些都是以椅子为行为的实施者,如果椅子不能折叠,那么“人”是不可能去折叠它的,所以“人”没有拥有折叠椅子的行为,自然,移动也是类似。行为对应的便是程序设计中类的方法,这也是类拥有的行为。

通过上面的分析,我们可以通过伪代码构建出这样一个椅子来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Chair {

property ChairLeg legs[4];

property Backrest backrest;

property Color color;

property Float Weight;

Chair(Wood wood, Nail nail) {
...
}

func fold() {
...
}

func move() {
...
}

}

这样的抽象锻炼,还是很有意思的,久而久之,你会很容易的去逆向的进行抽象,所谓逆向抽象,就是当你面对具体的业务逻辑时,你提取了这个业务逻辑所涉及的创建、结构和行为,然后进行归类和抽象设计。逆向的思想都是反人类的,只有当我们在正向的思想上足够成熟了,才能更好的去逆向思考。

思想还是技巧

很多时候,当我们进行程序设计时,发现一些实现细节,必须要使用设计语言平台相关的深层次技术,这就是所谓的技巧,或称之为一些个黑魔法

思想是什么呢?思想便是解决问题的思路,任何时候,我们都应该抱着越简单越好的态度去解决特定问题,思想的正确性会起到至关重要的地位。

目前圈子里会存在两种人,一种以各种技巧而夸夸其谈,一种以各种思想而坑蒙拐骗,我可能属于后者。在我看来,软件工程和建筑工程有很多相似之处,设计软件就好像盖一所房子,编程语言和平台框架就好比水泥和砖块。主张技巧的,是那些懂得如何更好的搅拌水泥和烧制砖块的构件生产者;主张思想的,是那些懂得如何设计出结构优雅、空间利用度高的建筑设计师。这二者都缺一不可,但,如果一个设计师开始主张技巧,那我觉得就不应该了(这种油漆刷在这种水泥上会更加亮泽哦)。

平常我们的程序设计,基本都不会去进行平台语言设计(编译器,词法规则等),也就是说,我们基本上不会去搅拌水泥和烧制砖块,特定平台语言(如Java、C++、C#等),对高层设计暴露了它底层实现的细节,基本都是为了弥补自身设计时的缺陷。所以,在进行程序架构设计时,我觉得应该尽量去避免使用技巧,每当你开始想使用技巧时,你应该要好好考虑下,是不是你的思想上出了问题,以至于你不得不使用这样的黑魔法。所以,在进行设计之初,我们应该处于什么都不懂的状态,也就是在设计时不要过多的依赖细节实现;而在实现设计的时候,我们应该处于什么都懂的状态,这样我们便可以撰写出健壮的代码,以此来巩固设计。

学习、阅读开源项目

一个人的力量终究是有限的,就好比以前的闭关锁国,世界这么大,你怎能不出去看看?要想提高自身的设计能力,学习是必不可少,也是非常重要的一个元素。

学习的方式很多,但我推荐不要一开始就去看书,因为书上会有太多不必要的文字。最好多去看看一些圈内知名大牛的博客,博客上的文字基本都会比较精炼,这样可以快速的掌握大部分知识。当需要了解更多细节时,可以选择性的从书本上进行阅读,按照这样的方法可以节省很多时间,时间是宝贵的,我们不可以把它浪费在太多没有意义的事情上。

阅读开源项目也是汲取设计思想非常有效的手段,一般大型的开源项目,贡献者可能多达几千,而这么多人的结晶里,自然有很多值得你去效仿的地方。思想一定要不断的去开阔,看得越多,你知道的也就越多,不怕做不到,就怕你想不到。

不断尝试设计,找到自我

没有任何一个设计,从一诞生就是完美的,大多都是通过设计师们不断的调整,不断的重构,甚至为了解决最初设计时的弊端而重写。所以,当我们在进行设计时,不要一开始就追求极致的完美,最初的设计,或称之为核心原型,我们一定要保证它表述意义上的稳定性,不会有任何歧义,什么叫表述意义?一个优良的设计,应该是一个故事,它会向它的阅读者阐述设计者定下来的剧情,表述意义便是一个设计的约束点、扩展点、风格。通过一个设计,我们甚至可以推断出设计者的性格和当时的心情,软件设计是一门艺术,我一开始就这样说过了。

要想找寻到属于自己的设计风格,便是不断的失败,失败到你可能都开始认为自己并不适合做软件设计,坚持下去,解决所有问题,总结失败的经验。当突破了这样一个瓶颈期后,你会对解决特定问题有了自己的看法,你从失败中总结出了需要避免的设计方式,你甚至可以用你觉得可以的方式防范于未然,如此,这便是你的设计风格。每个人对问题的思考方式都不一样,所谓设计风格其实很简单,就是你面对问题时的思考方式,想要更好的进行设计,你就不得不先去失败。

曾经有一个项目,是通过串口去读取某种设备的数据,然后实时的绘制出曲线图表。当时这个项目就我一个人全部囊括,我一共出了三个大版本,每一个大版本的更新,我都几乎是把所有功能重写了一遍,当要出第四个版本时,我辞职了。

为什么我要辞职?因为我觉得我终于可以安心的将它交接给别人了。

越过语言和平台障碍

语言和平台,是设计的基石,但设计不应该太依赖于特定语言和平台,自然,设计中肯定会考虑平台相关特性,这样可以减少设计的复杂度,而,我们设计的基础模型里,是不应该包含语言和平台特性的。

举个例子,在Java8(JDK8)中的Comparator实例建立方式,无论是nullsFirst(naturalOrder())naturalOrder().reversed(),它的基础模型都是Decorator模式;链式构建API基础模型基本都是Builder模式;像是stream.map(ele -> ele.length()).filter(length -> length > 10).findFirst()这样的惰性求值,基础模型基本都是Iterator模式。

所以,当我们的设计中,大量采用平台给予的这些便利时,我们要很清楚我们设计的基础模型,这一点在进行跨平台设计合作时非常重要。曾经,我和一位Java设计师一同进行一款APP的架构设计,他负责Android端的设计实现,我负责iOS端的设计实现。当我们进行设计沟通时,都是使用设计的基础模型,而实现时,都会采用相应的平台便利来进行实现。比如,Observer模式,他会使用Java的监听器(Interface)方式实现,而我会采用Objective-C的代理(protocol)方式实现;将任务封装成闭包,他会使用Java的匿名内部类,我则使用Objective-C的block块;任务调度,他使用Java的队列类自己实现了一套,我则直接采用了Objective-C中的GCD。这些都是实现细节上的差别,还有一些是结构上也需要变动的差别,比如对算法的封装上,他可以使用Java的泛型,而我只能调整实现的结构来做到相同的效果。

无论如何,我们设计都是围绕着一个相同的基础模型而展开的,这样的模型是可以让大多平台和语言来进行实现的。我也越来越觉得,越是和平台无关性的设计,越能体现出设计的本质,可能,向别人展示时,别人更希望看到的也是这样的设计,而实现上,我们应当毅然的使用平台便利,减少不必要的啰嗦。

优秀的设计师,自然需要拥有驾驭这样设计的能力,而如何拥有这样的能力,其实方法很简单,就是去学习各种语言和平台的特性。而我和那位Java设计者,之所以能够从容的设计沟通,都是因为我们对对方的平台有足够多的了解,不会陷入自己的平台特性而不顾忌对方。语言和平台特性的学习,应该还是很简单的,难点依旧是在思想上,当你能够站在设计者的角度去考虑语言和平台提供给我们的基础设施时,你便能更好的利用它们去简单的完成更多复杂的事情。

领悟设计的本质:没有设计

当你在程序设计领域里经历了漫长岁月的洗礼,当你不断的被人赞同和否认后,当你面对新事物感叹它为什么不早点出现时,你似乎已经开始慢慢领悟到设计的本质了。那时的你,不再对设计模式夸夸而谈,甚至对它有点反感;你会一眼就看出对方的刻意设计,和不合理的架构方式,心里淡淡的忧伤;你开始害怕面试,因为面试官肯定会问你,代理模式和适配模式的区别,以及适用场景,而你却早已忘了。

为什么会有这么多的设计模式和架构风格,它们不过是前人将正确解决问题的方式进行了命名而已,当这些方式已经和你的思想融为一体时,无论如何你都做不到刻意了。所以,你忘了,因为这些理所当然,这样做是那么的自然。

“非大有不可以大无”,这句话富含的哲理是值得慢慢深思的,程序设计的本质亦是如此,完美的设计是从无到有,再从有到无的过程。从无到有相对比较简单,不过是迭代构建的过程,而从有到无则会比较困难,要做到这样,必须让设计的每一个细节深入人心。

没有设计便是最好的设计,很多武侠小说中将功夫的最高层次也定义的如此类似,而我更喜欢从生活中总结这样的思想:最有效的表达,便是懂得沉默;最美好的拥有,便是那些失去;最珍贵的东西,往往一文不值;最能让你有成就感的,往往是类似下面这的段代码:

1
2
3
4
int main(int argc, char * argv[]) {
printf("Hello World\n");
return 0;
}

你,赞同么?