读《A Philosophy of Software Design》(01-07)
🤔☕️🤔☕️🤔
读《A Philosophy of Software Design》——(1)Introduction
📖:计算机科学里,最根本性的问题是拆解,即如何把复杂问题拆解到能够独立解决的子问题。
🤔:说到软件开发,第一感觉是我会写代码,有啥问题丢过来,我来写代码,把问题给解决。可是,当没有问题过来的时候,就只会看着别人的代码干瞪眼,愣是不知道自己可以写点啥代码。现在想来,当一个可以直接用代码来实现的问题出现时,实际上它已经被拆解过,它已经跟问题的本来样子不一样。而只有把一个真实的问题,拆解到能够用程序解决的子问题,才是让自己摆脱无码可写的窘境的办法。这就好像学习了写作的技术,等着别人出题目的文员一样,自己并不知道要写什么才好。相比于作家,他们正是知道要去表达自己的想法,要去解决社会的某个问题,才用写作这个技术去实现他的目标。所以说,等活来写代码的开发,就像会写作的文员,而解决现实问题的开发,才像拿写作当工具的作家。前面的开发,顶多算个码员,后面的开发,就可以称为码家。
🤔:基于这个点,就很容易解释,为何看到大量代码,那也就是大量代码,没有其它更多的感觉,其根本原因在于,当看到代码时,仅能看出这些代码的功能,这些代码能够解决的一个局部问题,但是并不能看出来这局部代码在解决一个怎样的全局问题。在不理解全局问题的情况下,其实没有办法理解局部代码存在的真正价值。这就显得很有趣,也很迷人,有人心里有个大问题,拆解成小问题后,有人看着小问题,在局部解决,局部拼合起来,涌现出大问题被解决的效果。
📖:CS-190:先写出首稿代码,进行评审和问题识别,再来修改或重写。就像英文写作课一般。
🤔:写代码,总有最好一次写完,懒得再修改的情况,重写似乎更是很负面的情形。仔细想想,似乎跟自负不无关系,总以为自己写的就是最好,至少也是足够好,没有改进的必要。非得让我改就很抵触,面子有点挂不住,自尊心都受到伤害。可是翻一下写作相关的故事,就发现到处都是写了撕毁、写了烧掉、写了重写的戏码。为啥在写程序的世界里,没有追求写了修改、写了重构、写了重写的情况发生。如果我想表达什么,那我就会反复去琢磨,是否表达到位,是否还有改进的余地。现在要我做到什么,那我就会在刚过接受条件后立刻停止,再也无心思考更多改进相关的方面。因为无论如何改进,都不会影响验收结果。这么说来,影响点就在这验收标准。在工程领域,当下问题解决受关注,只要解决即可,那么自然就出现代码先解决当下问题,至少它已经是及格的代码。如果考虑到后续改动,后续解决新问题,那么才会对当下及格代码,提出更多要求,才有可能出现对当下的代码,带着未来的视角,产生更多方面的关注,也产生更多改进的建议。
—— By 术子米德 @2022.03.19
🤔☕️🤔☕️🤔
读《A Philosophy of Software Design》——(2)The Nature of Complexity
📖:识别复杂度是软件设计的关键能力。
🤔:复杂度,知道它现在很复杂,知道它会变得越来越复杂,更要在开始的地方,就把复杂这头怪兽约束在笼子里。一般人,能准确识别出当下面临的复杂度,当然如果识别不出来,仅仅是感到复杂,那只能算不上一般人。所谓的高手,能控制住未来的复杂度发展趋势和方向,至少不会让复杂度任意蔓延。时刻思考复杂度的来源,持续优化降低和控制复杂度的方案,直到找出复杂度的根因。再所谓的高人,必定经历过一般人到高手之路,知道怎么控制复杂度,更理解哪些复杂度来自基因型因素。所以,当全新机遇时,那种天赐良机时,就在一开始种下复杂度自控型基因。这样的人极难得、这样的机会极难得、这样的结果只有懂的人会欣赏,而且会发自内心的欣赏、这样的开始更需要信任为基石。既然这么难得的机会,刻意打造这样的练习场景,架设这样的研发模式,就有机会训练、识别并筛选出识别复杂度的一般人、高手和高人。好像是这个道理,忽然间明白,某些公司的产品,每一代都把核心部分推倒重来,这是能冒出多少高手和高人,才明白如此非同凡响的研发模式。
📖:【定义】复杂度,此处指软件系统的复杂度,是任何导致该系统难以理解和难以修改的因素(🤔:当我说某个系统很复杂,要么我没看懂,要么我怎么改都不对,才好说这个系统好复杂,说白了就是我驾驭不住这个系统。可是,真的我不理解,真的我改不对嘛。不见的。很有可能是我水平差,很有可能是我熟悉度不够。这么说来,复杂度更是个相对的概念,对我复杂,不等于对别人复杂。对现在的我复杂,不等于一直对我都复杂)。换个角度,复杂度某种程度上,实际就是指投入和收益,而且主要针对维护方或使用方而言(🤔:这个角度很务实,如果很稳定,不存在维护量,就无所谓复杂不复杂。如果使用界面简单,其实现内部无论有多复杂,都不会感受到复杂度)。复杂度的三个症状:
更改蔓延(Change amplification):原本以为一个小地方改动就行,结果是到处都要修改(🤔:这是一种很抓狂的过程,别人问我好了没,答曰就好。别人又问我好了没,答曰就好。别人都不好意思来问,我也更不好意思答曰)
认知负担(Cognitive load):得做多少准备,才可能完成某个任务(🤔:看完 1K 行代码,就修改几个点,特别没有成就感,然后脱口而出,这玩意儿太复杂)
不知不知道(Unknown unknowns):要完成某个任务,搞不明白到底要修改哪些方面(🤔:改一点,试一点,发会儿呆,抓起头发,连蒙带猜弄出个东西,第一遍对,后面再也不对,还不知道方向在哪里,除了改回原来的样子。终于知道发际线的秘密)
📖:好设计的最重要目标,就是让系统显而易见。看到后直觉式的猜测,就是系统的实际样子。
🤔:真的能设计出这样的系统嘛,其实我有点怀疑。不过随着自己翻过的错误越来越多,看到别人系统显而易见的缺陷,这个倒不假。
—— By 术子米德 @2022.03.20
🤔☕️🤔☕️🤔
读《A Philosophy of Software Design》——(3)Working Code Isn’t Enough
📖:战术法,盯住能工作,战略法,先思考是否会增加复杂度,再盯住能工作。前者往往体现得很快,后者可能显得左思右想。复杂度就是逐步累积起来,而战术法会在每次不经意间增加点复杂度进去,超过阈值后就会出现难以控制的复杂性。战略法,会消除掉不该出现的随意复杂,无法根除复杂本身,只要持续增加的跟原来有差别,那就是在持续增加系统的复杂度。
🤔:说那个法子好,显然缺乏真实开发经验。最起码就是要知道两种方法的优劣势,尤其要明白什么样的团队,在什么样的阶段,相对而言适合哪种方法。或者出现问题时,可以分析出来,问题的原因是否跟当下采取的方法有关系。一个团队里,支持战术法和战略法的人都有,而且互相理解,仅仅是立场不同,那将是一个适应性很强的团队。为了生存,战术法先让团队活下来。为了生活,战略法可以让团队获得好起来。
—— By 术子米德 @2022.03.21
🤔☕️🤔☕️🤔
读《A Philosophy of Software Design》——(4)Modules Should Be Deep
📖:模块化设计,使得开发在某个时间段内,仅面临整个系统的一小部分复杂。
🤔:偶尔问自己,为何要模块化设计,可能会说上一车理由,但是可能没有把模块与复杂的关系,放在比较高的地位,也更不能一语道破模块在解决何种复杂,复杂是如何被模块化解,仅仅能说的就是,模块化能解决复杂度,一句正确的废话而已。可是如果模块化后,仅仅是做了逻辑切分,或者只是功能切分,验证模块的正确性时,一堆模块得一起跑起来,某个特性的修改,都要牵扯出一堆模块的改动,改完又得一起做整体验证,很显然这样的模块设计,非但未解决复杂性问题,实际带来更多复杂性问题。所以,得把模块设计是否能降低复杂性,至少没有增加复杂性,这个问题要放到模块设计的自检项,发起模块设计评审前,就该做好这个问题的解答准备。
📖:模块思维(Module) = 接口思维(Interface) + 实现思维(Implementation)。
🤔:模块是概念化、整体化的思维模型,它的关注点是 WHY,即为何有这样的模块。接口是界面化、契约化的思维模型,它的关注点是 WHAT,即它到底是咋样的模块。实现是过程化、状态化的思维模型,它的关注点是 HOW,即它如何才能实现接口的契约。好模块的典型特征是名字好记,生态位明晰,一说就记住它的名字,一看结构就知道没它不行。好接口的典型特征是简单,符合生活中的行为直觉,一猜就知道会有这类接口,一用就发现它的行为符合直觉。好实现的典型特征是易改,看到不爽就能下手修改一番,一改就知道有没有改错,一测就知道是否影响其它地方。
—— By 术子米德 @2022.03.22
🤔☕️🤔☕️🤔
读《A Philosophy of Software Design》——(5)Information Hiding and Leakage
📖:信息隐藏(information hiding)的基本出发点就是,模块得把设计决策相关的知识,以如何实现的形式封装在模块内部。
🤔:为何会有这样的出发点?模块的接口,站在模块外面看,最期望看到与使用方正在做的事情强相关的对象和方法,以接口参数和接口种类的方式呈现出来。使用方在外部仅想知道,也只想知道这些必不可少的对象和方法,其它任何方面的内容都不要出现。任何多余的东西,都会增加使用方的认知负担,从而产生复杂感。从这个视角看,模块的确要做到信息隐藏,原因就在于降低使用方的复杂感。至于把设计决策相关的知识隐藏到模块内部,是在这个原因之下的结果,似乎更符合逻辑。
📖:信息泄露(information leakage)出现在设计决策跨越多个模块的情景里。
🤔:这句话,第一眼看好奇怪,设计决策跨多个模块是什么意思?一个数据对象,存储它有专用的数据结构,有模块生成它,称为生成方,有模块处理它,称为处理方,有模块保存它,成为保存方。这时候生成方、处理方、保存方都操作相同的数据对象,只要修改这个数据对象,那么三方都得跟着修改。这样的情况就很满足信息泄露的定义。可是,我经常在设计这样的东西,还冠以统一数据对象的模型,有助于模块输入输出的接口一致性,也有助于模块的管道化架构。这就有点让我为难。如果要让接口处理一致的数据对象,势必定义公共的数据对象,而这又触犯到信息泄露的边界。
📖:时间维度拆分(temporal decomposition)容易引起信息泄露。
🤔:面向过程,不可避免会出现信息泄露。面向对象,围绕着这个对象的状态和方法,相比于过程,的确更容易让信息都在对象内部,不跑到外面来。不过,过程更符合直觉式的思维,毕竟任何事情,在纸上画的时候,首先会画一条带箭头的线条,表示这是时间,然后在时间轴上如何如何发生。换成状态机,事件发生,状态转移,不符合直觉,但是更符合信息封装型的建模。如此看过程式和对象式编程模式,的确有显著差异。
—— By 术子米德 @2022.03.23
🤔☕️🤔☕️🤔
读《A Philosophy of Software Design》——(6)General-Purpose Modules are Deeper
📖:有点通用(somewhat general-purpose)意味着模块所需功能满足的当下,它的接口不仅足够通用,更能支持多种用途。
🤔:有点通用,这多少算是有点够了呢?通用到打开、读写控、关闭算够嘛?对于老兵,恨不得都这样,因为已经熟练在业务过程和技术接口之间转换,这么来回倒腾无数回,已经自来熟,而且这的确就是最通用的实际标准。对于新手,总是在摸虾和摸鱼之间措手不及,只有摸上来被刺一下,已经握住却又溜走后,才会有切实体验,原来是这么回事情。这就是看到有点通用,刚刚好通用这样的描述,老兵和新手拿捏的角度和力道,完全不一样。可是,作为曾经的新手和现在所谓的老兵,如果要我给出建议,那么先给我来具体的接口,能满足当下的功能所需,先跑通一次,把经验赚到后,如果再来新的需求要改动,才会发现原来不够通用到底是意味着什么。否则的话,在没有能真切感受过通用意味着什么之前,刻意去追求,或者说刻意去模仿通用,很容易陷入似乎这么个意思,但实际却回答不了为何是这个意思,除了不可言说的无脑模仿后似是而非的尴尬,其它什么都没有。实践中,还会面临通用和效率的迎面撞击,谁能战胜谁,不见的技术说了算,更不可能哲学说了算,而是为了更低成本,必须在约束条件下把效率全部发掘出来,任何设计都会跪给效率的神秘眼神
—— By 术子米德 @2022.03.24
🤔☕️🤔☕️🤔
读《A Philosophy of Software Design》——(7)Different Layer,Different Abstraction
📖:软件系统由多层组成,良好的设计下,每层都有存在的必要性。
🤔:软件分层真是个绝妙的存在,典型的三明治结构,上层接近业务、中层展现特性、下层封装约束,合起来就能一口吃准业务、一口磨平约束,还口吐白莲特性。所谓的不同层、不同抽象,最佳代表都满足这个三明治结构。再多一层显多余,若少一层就残缺。
🤔:为何非得要分层?这个问题跟为何非得要编程,似乎有相同的道理。我要做一件事情,完成这件事情依赖某项技术或工具,做这件事情跟依赖的技术之间,没有完全匹配,甚至存在错配,通过层次就能解决掉因错配引起的问题。所以说,层次的本质就是在匹配问题和技术,层次同时能带来让匹配更协调的新润滑。编程不也就是在解决技术和问题之间的匹配度嘛?如果技术直接解决问题,那无需编程。只有技术和待解决的问题之间还存在缝隙,通过编程就是把这个缝隙给补上,就是编程存在的根本原因。如果问题是上层、技术是下层,那么编程就是三明治的中间层。
—— By 术子米德 @2022.03.25
版权声明: 本文为 InfoQ 作者【术子米德】的原创文章。
原文链接:【http://xie.infoq.cn/article/d2cf8b5ffa538d5cf7ee6537b】。文章转载请联系作者。
评论