CSAPP这本书基本看完了,还有一个自己不太想看的章,稍微翻翻就算结束。手上还有不少经典书籍,光放着吃灰就浪费了,还是想按照原来这种一章章的读书方式去读,下一阶段要读的书是:《敏捷软件开发 原则,模式与实践》这本书在豆瓣打分为9.0分,是一本非常优秀的设计书籍,为什么要阅读这些书,因为这些基本都是通用的知识,无论你用什么编程语言,无论是做哪类行业软件,这些知识都可以用到,相当于内功了。
建筑和软件有相似点,也有非常大的区别,软件是不断变化的,而建筑是建好了之后很少变化的,所以这也就造就了软件编写完成后,要容易修改。建筑可以不用考虑好修改的问题。
软件最大的特点是需要不断演化的,如果一个软件开发完成如建筑一样,无法修改,那其生命周期肯定不会长的。什么样的软件是无法变化的那,根上来说软件属于虚拟的东西,都是可以变化的。这里说的不能变化,只变化的代价很大,比如软件是如下的模块组成,之间的关系是由模块间的连线表示:这样情况下,我们修改扔一个模块,其他模块都受到影响了,成本太高,以至于我们把它看作无法变化的。这样的软件设计就是僵化的。
对于这种情况,我们尽量要减少模块之间的关系,减少了模块的耦合性,让变化隔离起来,假如可以按照下面的方式来设计,这样软件之间的联系由12个,减少到4个,降低了耦合,让软件结构更加灵活了。
正如上面所说,软件和建筑的最大区别,就是建筑建成后基本无需做大的改动,而软件的变化是持续其整个生命周期的。如果建筑也是随时可以改变的,那么改变而不影响建筑的稳定是最重要的考量因素了。这就要求,整个架构上,改动起来关联性一定要小,就是说不能因为改动一点而导致其他地方也受到影响,总不能拿掉一块砖,整个房子就塌了吧。这对应软件来说,就是软件的脆弱性,脆弱的软件好像盖了个危楼,这座楼随时可能因为一个无关紧要的变化而倒掉。
要对付这种脆弱性,那我们将整个建筑划分成不同部分组成,每一个部分和其他部分有尽可能少的联系,这样我们修改一部分的话,其他部分的影响尽可能的小,或者只要我们不改动连接的地方(即软件的接口层),整个软件还可以保持相当的稳健。
所以软件架构是由基础层搭建的一个架子,这个架子是稳定的,中间的填充部分可以随时变化,而对整个软件的影响甚小,抽象出这个架子是最重要的工作,如果这个架子未考虑到未来可能的变化,未来的功能可能会导致整个架子的变化,即架构跟不上需求了,这变化就要大了。当然我们也无法预测所有变化,只能尽可能地让未来的变化在自己的框架之中,并在新功能开发的过程中随时调整。
正如上图所示,我们通过稳定的抽象层构成整体架子,具体的模块都依赖于抽象,这样各个模块可以随意变化,而对整体机构没有大的影响。
对建筑里面也有复用,比如我们常用的楼板,我们可以在不同的建筑物都可以使用。软件更是这样,每个软件开发都要耗费大量的人力,如果软件中的模块,可以抽出来,用在其他软件上。不仅可以减少开发这个模块的精力,而且由于这个模块是已经经过实践检验的,所以稳定性更好些。
如果一个软件里面的每个模块的联系都很紧密,抽离模块就很困难,这就相当于乐高积木有非常多的接口和周围的积木都有联系,这样的模块就比较难拼了,拿到其他地方,也难以复用了。感觉和上面还是一样,要把软件设计尽量设计简单的模块化的接口,而且是模块之间的调用是通过接口的,少直接用侵入的。
不可复用不可抽离,就是说软件牢固性比较强,或粘连性比较强,导致想复用的组件无法抽离。这让我想到平时自己封装函数或库的时候,尽量不要打印日志,特别这个日志模块是外部引入的,这样就限制了通用性,如果别人想用你的函数或库就必须引入不必要的日志模块,影响了可用性。
软件模块复用的越高,软件的边界成本就越低,整体软件费用降低,而且开发速度会越来越快。
正如好的法律设计,让正常做事很容易,让做违法的事情很难一样。好的软件设计,让在添加功能的时候,做正确的事情很容易,做影响软件架构或不符合架构的事情比较难。人均有惰性,如果做正确的事情很麻烦,那么在时间紧的情况下,很多人会倾向于走捷径,这就导致架构的腐烂,后续再来应对变化的时候就会越来越吃力。这里面所谓的正就是容易的,顺手的;所谓的奇就是不顺手的,复杂的。在设计软件的时候,让增加功能变成容易的事情,靠正,而不是走那种取巧的方式,不顺手的方式来实现需求。
这还体现在整体设计上,设计尽可能地简单和易于理解,不要做晦涩的设计,这样沟通成本高,后续实现成本也高。
软件设计为了扩展性,都要预留些接口,目的是将现有的功能和未来可能添加的功能结合在一起,抽象出来一个接口。这样的好处是,未来的变化如果是被预测准了,那就皆大欢喜,如果预测不准那,就需要做伤筋动骨的改变,得不偿失。
这里不是说不要为未来留有接口,而是不要过分注重扩展性,这样会加大复杂性。那如果需求来了怎么办,那就在增加功能的同时进行代码的重构,把这个功能恰如其分地放入到软件合适的位置。
这有点像我们向数据库中插入数据,要保障ID的唯一性,如果我们每次插入之前都判断下此ID是否存在,这种方法是可行,但是却很影响性能。怎么办,就当ID不存在地去处理,如果存在了,在ID唯一约束的情况下,肯定会报错,我们再进一步处理这个错误即可。
浣溪沙·从石楼石壁往来邓尉山中
[清代] 郑文焯
一半黄梅杂雨晴,虚岚浮翠带湖明,闲云高鸟共身轻。
山果打头休论价,野花盈手不知名,烟峦直是画中行。