写了好几年代码,从开始懵懵懂懂不设计不写文档,到先搞一半再补文档,到先设计再编码调试。后来阅读各种好坏设计文档,看好的坏的代码,接触架构清晰的模块,也体验过在一团糟架构上新增规格解bug的艰难。最近看了一些大佬对于架构设计的思考见解,有很多感触,趁热打铁做博文记录多总结(大部分内容都来着 柱子哥的文章,链接见参考),后续也会持续更新,刚好有新项目需要自己重新设计架构,也尽力去运用总结。
一、写在前面的一些例子
看柱子哥blog中非常经典的几个例子,先阅读一下,对设计有一个感觉。
新房子的家具布置安排设计
比如你买了一间新房子,有5个房间,你的床放哪里?书柜放哪里?马桶放哪里?(允许我先假定哪里都有下水管道之类的设施)微波炉放哪里?如果按眼前的决策,刚进来的时候,哪里都能放。而且,这个判断最快了。但等你在床旁边安装了一个马桶。或者每个房间都安排了书柜,导致想放张大一点的床都放不下的时候,那就不由得感慨:早知道就……了。反之也不能过细 比如你提前决定每个家具摆放的位置,柜子宽高xx cm,和床间隔xx cm,离墙xx cm;如果某个家具不能到货,或者尺寸不准有偏差,你就处理不了,你考虑所有的可能组合呢,这个工作量你无法承担。真正的验证只有实施的时候才知道。你单考虑水、电、通讯、家具的时候,可能逻辑是通的。但把它们放在一起,就不一定了,每个独立的建模都有边际效应,这种边际效应会互相影响。
从深圳去北京的计划设计
你决定要从深圳去一趟北京,首先去的设计和你的目标强相关。比如是去旅游,去出差,去处理紧急事务都会影响你整个计划。比如是去参加一个今天到场的紧急会议 明天赶回公司,你其实没什么选择,也不用考虑带什么衣服,天气怎么样,赶紧看一下最近的飞机票带上必要文件走就完事儿了。如果是去旅游,去玩几天?坐飞机还是高铁?要带什么等等。这些就是设计。它是“需要”的,否则你在中间过程中肯定要出各种各样的 问题,比如到了机场没有航班,买了机票去机场误点,到机场已经是半夜没有公交,甚至 到了北京要见的客户去了深圳之类的。所以,“设计”是一种“必须”,不是可有可无的东西 。
下楼买包烟的设计
如果你只是下楼到对面小卖部买包烟 ,其实你就不需要思考,不需要什么设计,买就完事了。你可以穿个大裤衩,捧着半个西瓜,慢悠悠踱下去,一摸口袋,“我靠,忘了带手机了,老板,先佘一包红塔山,明天还你”。如果你写一个“设计”文档,说你怎么穿这个大裤衩,怎么捧这个西瓜,西瓜汁怎么流到你 的嘴角,这确实“正确”地形容了你买烟的过程,但你说这是“设计”,我是不认的。这样的文档,就叫“没有设计的成份”。简单地说,没有理智思考,我自己慢悠悠踱下去就 能搞定的事情,你还要把这个过程写一个“数字孪生”在纸面上,还自称“这有利于后面的人 更好地买烟”,我肯定觉得你神经病。有些人甚至把这个穿大裤衩的“优秀实践”写成规范要求所有买烟的人遵守,还振振有词说 :“不穿大裤衩难道裸体出门?你有没有羞耻心啊?”,或者“难道平时在家里也需要西装革履?这是不是太不人道了?”。甚至说,“你们自己说到底穿什么?你们摘一个,我也不是 不民主,但咱们得把规矩立起来不是?”这叫抓不住重点。
二、什么是架构设计,如何更好理解?
项目牛逼关键是需求驱动+你比别人好,大规模项目核心是控制逻辑
软件项目牛逼,不是因为 良好的架构、优秀的算法、优雅的代码组织,是因为它有用,并且把它的竞争对手都逼死了,它就牛逼了。研究项目是否有前途,第一研究它是否必须的,第二研究它如何保证它相对别人的优势。 有需求驱动,有人足够的人要用你,你比别人好你就没有办法不牛逼。接下来就是怎么控制你的设计比别人有竞争力。在架构设计上,保证竞争力很大程度上是如何对资源进行控制。 你的资源比别人高效那就是你的资源都被用到刀刃上了。 从一两个人开始慢慢扩展到几十到上百人的规模的项目,如果没有很好的控制逻辑,快速增加代码量会很轻松冲跨你当初简单的设计。
架构设计简单来说就是用一组较为粗糙或抽象的逻辑 证明目标可达成
架构设计的目标必须是一个商业目标,而不能是架构设计自己。 架构师用细节控制整体,在混沌中制造规律,使我们可以控制庞大复杂的系统。针对不同的目标有不同关注点,软件架构 通过定义一组较为粗糙或者说抽象的逻辑,让我们可以确认通过这组逻辑目标是可以达成的。它不是事无巨细,什么都设计,它更不是细节本身,只有实施才是细节本身。虽然它粗糙,但它的逻辑仍是连续的,有完整的逻辑链支持目标是可以达成的。它对执行具有限制作用,正是因为这个限制作用,才使我们在细节设计或者执行的时候简化了目标,不至于陷入无序的发展。
架构设计不是 “条条框框”“最佳实践”,而是针对具体问题建模过程
很多管理者希望把架构工作做成工具,或者把架构过程做成“条条框框”,“最佳实践”。但架构工作恰恰就是不能这样干的,因为架构本来就是发明跳跳框框的方法,是发现某个具体情形下,能否设定,如何设定条条框框的一个过程。 在架构设计的过程中,新的约束不断被发现和发明,主要矛盾会变成次要矛盾,架构提取的“名”也会随之不同,进而策略也要跟随变化,跟随变化就是架构这个工作存在的原因。如果你希望在架构设计之前就设定固定的方法,那么架构就无法发挥我们引入架构设计这个工作原本想起到的作用了。
架构设计本质是设计和管理一组约束,每个约束来源与一个两难选择
架构设计本身没有规律,它都是就事论事的,它本质是设计一组约束。每个架构选择都是一个“约束”,一个“两难”的选择,我们只是两相其害取其轻而已。 每个“成功”的架构选择背后,都种入一个“失败”的种子。架构设计是管理这个约束加入的过程,因为先引入的约束,会控制我们后面引入的约束。 一个约束,控制了其他约束的情形,在架构的语言中,就称为“关联”。关联就是说, 如果我修改了一个逻辑的“约束”,其他逻辑的约束也会跟着更改,那么,这两个逻辑,就 存在“关联”。否则我们就认为他们没有“关联”。
架构无法简单评价,好架构选择问题较少路线避免被拖垮
我们要避免不受控制的约束增加过程,导致后面我们怎么都无法加入剩下的逻辑,最后这个目标就达不到了。 架构无法被简单评价,因为每个架构的实施,都是独一无二的。架构也不是在选择不出任何问题的路线,架构只是在选择出问题比较少的路线。 所谓的成功,是每个选择都买到了一个足够的利益,而失败还没有积累到拖跨这个系统而已。没有值得欣赏的架构,因为“好”的架构是它所有的选择都没有做死做到把它自己拖垮而已。
理解架构设计需要良好的抽象能力
**抽象本质是提取共性减少属性。抽象的作用是把逻辑信息分层,让一部分程序强行看不到部分信息,那些信息更改程序仍可以成立。**抽象过程也是需要经验支持的,如果你缺乏这种经验,或者你会选择不好的抽象属性,或者你干脆就不知道两个模型其实是相同的。这种经验很难作为固定的知识传递出来,因为它抽象是提取被抽象对象的特征,这种特征无数,说出来本身就是抽象,说不出具体问题时的自由度,最终这个东西就很难进行“传授”,但也许你必须接受,抽象能力不是你学习一个概念就能掌握的东西。我们只能通过反复对一个个真实实例的对齐,才能让这种技巧在不同的人和团队之间传播。
逻辑闭包—在有限逻辑空间边界推演结论描述仍然在空间范围内
数学上闭包定义:In mathematics, closure describes the case when the results of a mathematical operation are always defined.简单说,我在一个集合中定义了一组成员和针对这组成员的操作,这些操作的结果,仍在这个集合之内。构架设计上我们做不到这么严格,因为架构设计是个逐步发现边界的过程,什么东西放在A模块中,或者什么东西放在层一,是逐步细化和维护中决定的。 注意不是发现的而是决定的,后面在不断发现新的信息和引入的新需求在强化这个边界。引入逻辑闭包这个概念,是要给出一个逻辑空间,这个空间引入的概念,进行逻辑推演后,结论的描述仍在这个逻辑空间的概念范围内,而不需要引入额外的信息去补充。 要做好逻辑闭包设计,必须学会严肃抽象概念的定义。第一,你必须给出明确的问题域,也就是你为什么要讨论这个问题。 你没有目的地说一个概念,这个概念没有正确与否可言。第二,你必须对它包含的具象有明确的理解。 你要穷举可能性,必然是用一个个的逻辑闭包分隔你的所有可能性,否则一个“差不多”的东西,它的结果没法用。你定义一个逻辑,只包含它一个子集,其他部分当看不见,我们讨论啥?这种“部分成立”的逻辑,就构不成抽象。
逻辑链—(基于逻辑事实共识)通过连续的因果链条推导实现目标
逻辑链定义:Chains of Logic to justify your plan When we apply for grants, we are telling the grant sponsor that our research plan will meeting their needs. We have already worked on creating explanations for how your research will meet their needs, But not every reviewer will necessarily agree with your plan as the best plan. Therefore, you mush make it very clear why your plan fits the solicitation needs.我们进行预判和推理,总是基于“因为-所以”,我把它简称“因果”。一对因果是一个单向矢量,包括“因”,“果”和“因果的有向关联”三个要素。如果我们把因果串起来,把一对因果的果作为另一对因果的因,组合成一个链条或者一棵树,我们就称这个链条或者这棵树构成一个逻辑链。 方案评审总共就三种结果:1)打破一个逻辑链,从而导出方案的不可行;2)无法打破一个逻辑链,从而“承认”方案可行;3)提出另外建立一个逻辑链,证明比本逻辑链更具优势。 基于此逻辑链补充延伸:1)打破逻辑链所证明的是方案不可行,不是目标不成立。 2)逻辑链讨论的底线是“逻辑事实”,如果某个逻辑事实讨论双方没有共识,整个逻辑链的讨论就失去价值了。需要更多的交流或者证据来实现这个共识。3)逻辑链可以基于逻辑事实无限分解,一对因果可以分解为子链,如果已经有了共识,细化下去并没有价值。 4)逻辑链可以被优化,如果链中除目标以外的某个事实删除逻辑链仍然成立就是一个更简逻辑链,还要注意到证明某个目标可能不止一个最简逻辑链。 很多设计方案,无目标的信息堆砌,构不成逻辑链,就算里面每个逻辑事实是正确的,这又解决什么问题呢?
旧架构分析不是要和对象一致,而是找到稳定的部分
很多工程师不能做好已经存在的软件的软件架构分析与建模,核心原因是求“正确”,他们希望他们的表述是没有错了,和被分析的对象完全一致。但架构分析不是这样的,架构是要找到那个软件“不变”的东西,宁愿和被分析对象不完全一致,也要保证首先突出“不变”的部分。 实际上软件任何一个地方都是“可变”的,我们其实不是追求“不变”的部分,而是“最难变”的部分。你没有用某种力量去推导它,它看起来就是一个平面,等你去推导分析它,它内在“骨架”才露出来了,你才可以在这个骨架上建你后续的逻辑,那些后续的逻辑才是稳固的。也要注意逻辑的稳固,本身是个动态的过程,顺着已有的逻辑在需求的作用下增强或者减弱,根本的驱动力是需求和竞争力。
讨论设计问题避免show me the code
很多人很喜欢“Talk is cheap, show me the code”来说事,这句话是对逻辑空间被定义得支离破碎后无奈的抨击,你各个名称空间的关系都连不起来,这里找一个上下文来说有理,那里找一个上下文来说这也有理,到处都依赖细节,那只能让你把所有的细节都拿上来了。到了“Show me the code”的地步,就没有架构了,该有的伤害,该破坏的逻辑关系都已经破坏了。
三、为什么要做架构设计,不做行不行?
如果逻辑简单或者你认为罩得住,可以不需架构设计
构架设计是要在逻辑量不足的情况下对未来进行预判和控制,类似绘制一幅复杂素描所打的草稿。如果你罩得住,可以不需要这个草稿。几千行的代码小程序你可以直接编码,但数十万、数千万行的大型程序没有设计支持很难最终达成目标。 很多时候当我们发现需求做错了,实现和设计不一致的时候,有时就会甩锅最初需求文档写得不够严谨,设计文档不接地气,这个判断方向基本上是缘木求鱼,你写了几年的程序,一直以来需求文档都不够严谨,设计文档一直不接地气,甚至从来没有改进过,你还不断相信,你有一天能写出“严谨”的文档?就好比一个领导者,他的工作本应是告诉大家想什么方向走,保证团队能走目的地。 你的工作照理说是研究情报,确认对整个团队影响最大的情报,在模凌两可的情况下强行选定方向等等。但这些事情他不去干,而跑去给团队做饭,或者参与编码和团队打成一片,看起来很亲民,但他自己的本职工作呢,等团队遭到重创的时候再去讲苦劳?
架构是针对未来的设计,能力经验不足强行考量造成浪费
架构是针对未来的设计,考量未来是有风险的,如果没有未来,这个根本就是浪费。 如果你做的是一次性软件,后续也不维护,只是验证一个模型就丢掉,没有必要花大力气设计,它本身没有未来。同样在团队能力不足,经验不足的时候,强行要求进行架构考量,这很容易造成浪费。有一些阶段的产品,架构师只能在前期进行最大流量,最大时延,客户群关键需求判定这些方面的推演,之后就只能退化为前面说的拿鞭子的项目经理,保证结果先出来,在出来一波后,才能谈软件方面的架构控制和长远发展。这种,是需要折腾一两个版本以后才能确定方向的。对于一个复杂系统,控制要素在不同的阶段可以控制的东西是不同的。对于连运行都没有运行过的新产品,软件架构控制很弱,重点是系统的可行性分析,等有一定的数据了系统的长远发展的问题就变得越来重要,这时架构控制的作用才会显现出来。 到开始上量卖出规模了,这时这些控制的作用就越来越弱了,但仍然可以也有控制,看在原来的基础上能活多久了。
架构师必然引入利益冲突,团队认为被动驱动就行 就不需架构师
做架构设计都是做未来,做未来天然就会和现在的利益冲突。 而且这个冲突,没有人会和你平衡,因为考察整个利益关系中,只有架构师这个职责是为未来负责的,不要指望实施团队会很喜欢你,你做的所有事情,都是为了未来让实施者“绕路”走。如果一个组织觉得自己一切正常,按部就班被需求驱动就挺好,管理也认为没面临什么问题 没有强敌需要时刻防范,那么这里就不需要架构师。 对团队而言架构师只是没事找事。换句话说,架构师是为你的现状做出改变的,你自己都认为没有必要改变,那我们就不要在这里浪费时间了。 冲突本身无法避免,当需要和实施人员对接过程,你可以用“个人魅力”去尽量缓和这种冲突,可以下去分担开发调试的压力,但你要知道如果实施团队很舒服,你的架构设计多半变成空话。不要为了实施团队的一般抱怨就去改变你的设计去迎合,否则产品失败的时候,就没有人跟你抱怨了。你应该多听实施团队的抱怨,但你要分清楚哪些是真正在反馈问题,哪些只是你战略实施的成本。 无论架构师的能力多强,如果你聚焦在你的代码上,你的架构设计基本上就毁了。设计方向错了所有的工作量都扔没有意义。架构师的工作是“限制”每个开发人员怎么工作,用工程师的脑子来为你为团队完成你个人无法完成的设计。总是聚焦哪个代码好关键,然后想自己来把这个设计做了,你强你的团队就弱。 高明的架构师只会让整个团队成功,让成员成名,而自己隐身其中,无人可见。
设计减熵原理,一个可控系统 要求它呈现层次逻辑简单
自然发展的东西总是混沌而没有规律的,出现增熵,而人造事物都需希望足够简单并且满足某种规律,这样人的脑子可以对它们有很简单的预期。简单是设计的目的,复杂是“专业 ”的无可奈何。我们觉得一个系统是可控的,要求是它对我们呈现的逻辑简单。 很多设计文档写了很多但怎么都不对,如缺乏逻辑链,缺乏目的,缺乏完整性,这背后都可以总结为对整个设计进行熵减控制的需要。 控制的始终要求使用简单,被控制一方(内部)无论有多复杂,但呈现给控制一方的逻辑是平坦的,可以重复的,这种东西好控制。我们调用一个库,为了调用这个库写的代码比自己直接实现库的功能的代码还多,那么就不是我们在调用一个库,而是那个库在调用你(的行为)。
没有具备竞争力架构支持,这团队就更易替换必然更弱势
操作系建立了高层逻辑链,为了操作系统可以维护进程,可以调度虚拟机,可以迁移应用,硬件必须实现某某功能,上面的软件必须这样用我,这个逻辑链必须被保证,否则维护进程,调度虚拟机这个目的就不会得到满足。硬件也有自己的高层逻辑链,比如投片费用是有限的,可用工艺选择有限,要保证能投片,要符合某某要求软件只能如此这般才能用我。在操作系统和逻辑之间的驱动团队就可能没有逻辑链了:操作系统要我这样,硬件接口只能这样,我看看细节上能不能做到。做高层判断的时候,这个团队的话是不需要听的,因为你都是就是论事的而已。 所以,一个设计团队要能持续发展,没有架构是不行的,而要有架构,就需要有自己的利益链,整个高层逻辑中,必须有你在保障的客户利益。而且你的保障逻辑链必须是在所有解决方案中是有竞争力的,否则你会被整体替换。 成为资源团队,是把自己的整体替换可能性放到最大。全局的控制者,也不会希望这样的团队成为组织主流,信息熵过高系统就不可控。
需求和竞争力表现是特性,没有放弃 要么没有发展 要么无法生存
需求和竞争力表现出来是一个个特性。 如果删除对feature2的需求,和它相关的逻辑约束就都可以删除。但如果feature1和feature2之间有复杂的绞连。用户被迫在“同时选择f1和f2”和“重建f1,整体放弃整个f1/f2的绞连”之间做出选择。太多的绞连不能放弃,是整个软件最终被抛弃的原因。每个设计,必然产生新的约束。 加一个状态机管理,为了保证每次跃迁在执行上是原子的,就要加上锁的使用约束。加上锁的使用约束,会对线程的使用加上约束,对线程的使用加上约束,就对对外接口的提供产生约束,对对外接口产生约束就会对应用的业务模型产生约束…… 看一个新特性在整个逻辑链中,已经建立的约束和带来的利益是什么,这才会看见其对架构影响。 每次设计新的特性,增加新的逻辑链,都希望少产生不产生新的约束,而是尽量复用已有的约束。把设计做独立目的就是希望当某个特性引入的约束太多的话可以整体放弃它,从而保护整体生存能力。没有放弃,要不没有发展,要不没有生存。
四、怎么做架构设计,关键点在何处?
架构设计的目标必须是一个商业目标,而不能是架构设计自己
架构设计的目标是为了达成那个商业目标,不是获得“这个架构设计不错”这样的评价。架构设计没有要求,架构设计的逻辑都是为架构设计的商业目标服务的。我们说“这个架构不符合架构我们的架构经验,因为关联太多了”,或者 架构不好,这个评价也是针对商业目标无法达成或者对其损坏来说的,不是针对架构设计必须有什么规矩这一点来说的。 所以,如果不能实事求是地看待架构设计工作,认为架构设计不是设计之外的一个设计,架构带来的是一个伤害。因为你在商业目标之外引入了额外约束,而我们架构设计的自身目的就是在达成商业目标的前提下减少约束。
架构设计充满了自由度的工作,不适合用过去成功直接指导当前问题
架构最不适合用过去的成功指导下一波策略设计的领域,无论你过去的领域和这个领域有多相像,你过去的成功经验都只能拿来参考,不适合用来拷贝。因为即使是完全相同的领域,时间已经不同了,行业生态,技术发展已经不同了,你面对的人不同了,业务的瓶颈已经不同了,生态的各个利益体的投资已经不同了,成熟的领域人员可能减少,当初的绩优股可能已经失败,你面对的是一个新的领域,我们做架构,永远不能被过去的“定义”,左右了我们的判断,所以,做新的架构设计,你可以参考过去的成功pattern,但你的分析,必须建立在现在的条件上,不能离开对现在问题的调查,直接打算使用过去的模式。
架构设计的典型输入:问题经验、高层逻辑建模、竞争对比、投资收益对比
架构设计的方法一般来自四个输入:1、架构师在这个问题上的经验。不存在跨行业的架构设计,没有实际经验,哲学再好也不能成为架构师;2、高层逻辑建模。设计师会在一个很高的层次上构造一些逻辑链,保证这个逻辑链是自恰的,先用这些逻辑链建立约束;3、竞争对比。“别人怎么做的”,特别是“成功者是怎么做的”;4、反复的投资收益对比。设计师可能想的是一件事怎么做,而架构师想的是这件事的成本是多少,是否有钱做,能否找别人做。架构师还要观察每波投资的时机,让每波投资进入系统的时候,都能成为架构目标的助力,所以成功的架构过程几乎不可能被复制,因为架构过程是和外部输入的时机紧密结合在一起的。
架构设计要以工程为准绳最后落地的版本,以可以实施为底线
架构设计必须以可以实施为底线,否则就沦为纸上谈兵了,没有独立于实施的架构设计。架构设计90%的工作是辅助实施团队实施架构战略和挑战架构战略。 把人分离出来是为了保证投入,不是为了让这个团队成为实施团队的竞争者。但反过来说,你不能说我们现在只有多少多少人,我们就做一个基于现在有多少人的方案,因为事情是变化的,在架构设计初期,给你很多的人你也用不了,但如果设计只是现在有多少人,那后面开始展开时也就根本就发展不了。在架构设计的最后一段,你要把你的所有设计落实版和项目。所有的人力管理,都是以项目为基础的,因为项目有确切的目标和人力资源投入,没有确切的资源分割,凡是长远的东西都会被忽略和放弃。
设计需要和调查结合起来的,更要勇敢进行“预判”
架构设计初期,我们有无数的“未知”:竞争对手的战略是什么?客户的期望是什么?研究机构有什么新的突破?市场份额的预测是什么?国家政策的走向是什么?等等。如果你要调查完这些东西才做决定,你就永远都不用做了。所以,进行架构设计,要勇敢进行“猜”,“预判”,哪怕错了你也要“猜”,因为这是架构设计工作的基础。你的决策要同时决策:使用猜这个结论和再调查一下,哪个投资收益比更低?然后就要去实施。架构设计强调“守弱”,其本质就是这个:架构本身就是一种猜,我们在猜的基础上执行。 在执行的时候收到当时猜错了的反馈,死要面子不肯调整那这个架构执行就失败了,做架构不能要面子,你眼中只能有产品的最后成功。
架构团队来自不同的领域,不要用“领域代表”看待自己
架构团队存在的目的,就是为了克服多个模块互相甩锅的Gap,所有模块和协同达到最优的效率。 不要说我只是做芯片设计,我只是做安全的,我只是做内核的,我只是做数据库的。大部分投资者都是不懂技术的,,这些人决策的方式就是多方确认,如果架构组自己都达不成共识,那怎么说服客户。架构组每个人都应该对整个架构策略都很熟悉,不要求做芯片的人就会写程序,但需要做芯片的人知道软件部分的构架要求,在解决方案中所处的地位。作为一个产品的架构团队,你也不能只管我的产品如何如何,你必须从整个产业生态上开始设计。 对于一个产品的架构师,必须知道整个生态链是怎么运作的,要为了整个生态的平衡,不怕把自己部分自己的业务让给其他产品,其他企业,也不怕自己背上别人不肯实施的业务,这样你才会有掌控生态的力量。
架构设计以简为美,以守为本,不为天下先,最少依赖解决必须解决问题
好的架构控制,是简单而无障碍,更多从“情理”而不是“当前事实”来看的,以简单为美,但这种简单的是以对未来的复杂度的良好容忍为前提的。 不少糟糕的架构设计,主要问题就是要解决的所有问题域,都在同一个或者同一层名称空间内考虑。比如实现一个系统库,用户用不用线程都不知道,就开始在数据架构上下加锁,美其名曰“线程安全”,这就是典型的把两个互相独立的逻辑空间混合了,以后增加功能的时候,一脚踩进去,会踩死多少个功能都完全无法预期了。任何时候要引入一个模块,无论这个模块埋得多深离用户多远,都要回到用户需求上,用这个需求来引导这个模块的一切变化。 一切都是被动的,而非主动的,设计都来自“可以卖钱”的需求。设计如果仅从“现在”会有什么问题上讨论,它不会有问题,但架构不是这样考虑问题的,抓住软件发展的初期这个机会,错过这个机会,所有的细节上来了,就不再有架构设计的余地了。**用最少的依赖解决必须解决的问题。**架构引入的约束就必须有非常坚挺的逻辑要素去支撑,最坚挺的是事物自身的属性,第二坚挺的是已经成熟的经验。所以,所谓“不为天下先”的策略,就是我总使用“有成功经验”的方案,这样我成功的机会就变大了。从这个角度上来说,开发的工作目标是“增加”,而架构设计的工作目标是“减少”。
架构设计依赖很粗但工作可以很细,找到优先设计点依赖功力
架构设计是个专业技能,不是你写几年程序就是“专家”的。架构设计的依赖确实很粗,但架构设计的工作本身可是很细的。架构设计是要找出最影响成败和工作量的逻辑出来优先设计。 比如某系项目目标涉及抽象全球用户的某种网络规律是很难的,但架构师仍有办法处理这种复杂度。他会假设,而且他有办法保证他的假设成立的可能性很高,建立假设基础搭建概念空间,然后针对性地进行调研论证,这才是功力。
遇到多方冲突没有明显方向如何选择,遵循有目标但守弱
有些选择本身没有明显的偏重因素。比如你需要一个链表,你是直接在你的数据结构中放next指针呢?还是建立一个list数据结构,抽象这个逻辑呢?这个时候,不往前走是错误的,乱往前走也是错误的。这种情况,你就发现,你习惯的那些“因为所以”的理由是完全无力的。这个时候能够采取的策略有目标,但守弱,先奔着直线去怎么简单怎么来,不添加这个力之外的其他的力,然后让现实去冲击你的策略,从而形成一条合理的路径。一旦从这个角度考虑这个问题,你就发现你的决策模型不再是因为-所以了。而是不断在过程中比较所有力量哪个对结果的作用力更大,不断进行观察,调整,和平衡。 而且这种判断仅在当时有效,过后就失效了。你不能因为项目开始的时候没有抽象一个模块,项目结束的时候抽象这个模块了,你就认为当时“决策失误”。
五、架构设计文档应该怎么写?评审注意事项
设计文档 追求解决问题,丈量和目标差距,而非追求完美
好的概要设计着眼在解决问题。很多时候只需要几页的文档就可以描述清楚(但工作量很可能不是不动脑写两三页文本那么大)。特别是很多特性一级的架构设计,你能搞清楚你的开发视图就够了。宁愿搞完这个高层逻辑,有时间马上投下去给细节关键设计一个逻辑,或者赶紧开始写用户手册,也别怕自己只写两三页显得不够高大上,而故意弄一大堆的细节出来。因为我们很多设计师因为这个问题而做了很多多余设计,写了很多无效文档,或者错过了关键的设计没有做,把设计的压力推到编码阶段。 文档目的清晰把控制目标给出来,然后不断丈量实现和目标之间的差距,而某些人所谓的“设计”,只是在自我欣赏自己无限的熵增,这就没有设计的成分在里面了。
设计文档 要正确挑选最优的逻辑事实,从逻辑链来证明目标可行性
你写10页的文档,不考虑逻辑链,30分钟写完,考虑逻辑链,可能至少3个小时,考虑清楚逻辑链,能让事情成,但不利于表“功”。如果你有志于工程师方向发展,就不要避开逻辑链推论。逻辑链是分层的,有些逻辑事实如果我们已经有共识了,在本层中就不需要细化的。所谓高层设计,是建立一个高层的逻辑链,证明“目标”是可以成立的,从而保证我们后续做的“细化”工作,是最终可以达成目标的。这就是为什么我们不能用代码代替文档。两者解决同一个问题,但两者的逻辑链是不同的。设计总是有外部依赖的,假设也可以成为“逻辑事实”中的一环,如果有人不同意这个假设,至少我们可以就这个假设来讨论。你要为你的逻辑链自洽负责,逻辑链是你的精神世界,别人可以挑剔你的逻辑事实,但没有人可以拒绝你把逻辑链建起来。 正确挑选最优的逻辑事实,基于逻辑事实建出无懈可击的逻辑链,这是写一个设计文档的基础,没有这个基础,我们什么都讨论不了。
设计文档 要提供有用有效的信息,而不是正确的废话
有些垃圾设计文档几十上百页,看完了才注意到一个有用的信息都没有。然后说“难道我说得不对吗?”我们不关心对不对,关心内容是否有用。文档应该体现你如何选择,从而达成需求上要求的目的。一般需要如下有效内容:1)、你所认可的目标是什么? 2)、你如何分解多个功能、角度和步骤,逐步实现所述的目的;3)、这些功能和步骤之间的关系细节,特别是逻辑关系无法在下一层设计中体现,只能成为“原则”的那些关系细节。4)设计文档 不要重复代码细节逻辑,未新增有效信息,增加维护成本。然后判断这些信息 是否违背事实,这个信息是否具有充要性,指向是否明确。 比如:本文描述XXXX设计方案,用于指导后续设计以及编码开发工作。XXXX可以支持PCIe的性能调优和维测,为系统维护人员提供强大的调试功能, 为增强YYYY设备的软件生态提供有力的支持等,看上去很美好实际没有任何用处。有用的信息都不需要好看和高大上,而是聚焦到你要干什么,比如我们的目标可能是这样:硬件团队预计在YYYY V2设备上实现了一个可以收集、统计、调整PCIe桥和RC/RP的的设备,称为PCIeTuner,本文讨论把该设备暴露为用户可见的功能的软件方案。 其中a接口仍在讨论中,未确定。b接口认为只有可能为两种方案。
概要设计怎么写,什么内容应该放到文档?
1、最前面确定好你要解决的问题,说明设计目标核心收益是什么,你的所有设计(包括你后面的妥协),必须用这个核心收益作为底线。你可以多放几个这样的核心收益,这样你妥协的机会就大很多,搞不定一个,可以看看能否搞定其他的,等你所有这些收益都“妥协”完了,这个架构其实也没必要存在了。 2、概要设计应该包含的典型内容:概念定义,以及概念之间的关系;概念和数据结构的对应关系;概念和模块的对应关系;关键状态机;关键流程(注意,不是代码,这是为了把模块关系串起来,不是为了描述代码关系);线程部署,锁的关系;关键Latency路径;关键动态对象(资源)的生命周期;接口函数组的集体Review,A模块对外提供了一组函数,有十几个,多了吗?少了吗? 关系可以整合吗?这要成组进行推演。外部接口,比如配置方法,API设计等不一定全部要写,取决于哪些代码本身无法表达。 概要设计,很大程度上是UML图,ER图,STP图,但它们不是代码的翻译,别拷贝代码上来。
关于4+1视图建模介绍,推演看约束和逻辑是否冲突
4+1视图是把最关键4个全局控制拿出来,然后一个功能,一个功能拿上去推演,看约束和 逻辑会不会冲突。 1)用例视图Use-Case View,不算一个完整的逻辑(因此"+1"),是一个持续补充的,不完整的“需求列表”。 2)逻辑视图Logical View,设计的对象模型,又称为概念空间建模,关注的是用户接口(如果你的逻辑抽象能力不那么强,就先别去学那么 多复杂的方法,你直接试试写用户手册)。 3)进程视图Process View,捕捉设计的并发和同步特征。 4)部署视图Deployment View,描述了软件到硬件的映射,反映了分布式特性。 5)实现视图Implementation View,描述了在开发环境中软件的静态组织结构。 4+1视图方法其实是一种学术上不那么严格的实用方法,但它却是我们进行一个 大型系统需要考虑问题的最基本约束考量。否则,想想你可以如何规整和逻辑完整地考量 你一个设计到底缺少了哪一环?怎么能确定你的系统可以持续用下去?对于每个具体的设 计,可以你会使用其中一部分视图,或者你会引入其他视图,比如可靠性设计,安全性设 计,这些都需要额外的视图来表达的。视图本质是一个独立的角度,保证我们最开始要解 决的问题:某些问题,一开始进入细节,我们必然会走偏,会留下巨大的破绽。补充一点:无论画什么图,我觉得如果你画出二三十个实体的,肯定是不指望人看懂的,只能体现“我很复杂,它么别碰我”。你画图的目的是抽象,要复杂你写代码去,让图比代码还复杂不如写代码。
接口定义是架构设计的最终结果,接口往往难一步到位
架构的外在表现常常表现为接口,接口定义是架构的最终结果。复杂系统的架构师,经常在这种问题上碰到困难:模块之间,都希望拿好做,容易出成绩的加工放自己一边;把坑多,不好出成绩的加工放到别人的模块里。不过这个算是小问题了,大部分时候就是个利益的问题,分配好利益就好了。更难的问题是这个接口怎么定?因为每个模块都在问你对我是什么要求,到底应该A模块定义还是B模块定义接口呢?双方都不想定义它,一方面工作量巨大出了错妥妥背锅,另一方面吃力不讨好的工作,定义接口是最难吹的,实现他们你还可以吹我搞的这个规格。但接口定义决定了整个系统的性能,它才是整个架构工作的核心。我们必须建立一个共识:接口定义不可能一次到位,先做一部分关键功能,尽量不建立太多约束,然后开始设计两边模块的内部,然后再升级调整,然后再进一步设计,然后在细化两边的模块设计。每次升级都考验定义时留余地的功夫,这样最终可能可以拿到一个较优接口。指望把责任推给任何一个模块或者团队,然后觉得自己没有错,花大量的时间希望接口一次成型,最终它就成不了型。
六、典型的烂设计举例
设计文档,如果你做了某个决策,但这个东西的控制要素不在你的设计逻辑链上,那么这个设计一定是错误的。
过高抽象无法制造直接约束,算不上是设计
比如,构架设计这样一组类似这样的Rules:修改要考虑前后兼容,不要出现旧代码不能兼容新硬件的情况;严格按照软硬件接口定义的范围获取所需的值;Driver不可以和芯片演进潜在的可能变化信息绑定;这些东西,作为一个高层“欲望”是可以的(但也不好,因为它们并不直接匹配有收益的欲望,不上不下的),但它算不上是设计,因为这东西不制造直接的约束。
概念空间混乱,没有明确指意
概念不稳定,在文档不同的地方有不同的内涵。 如文档中中device这个名字,有时指驱动,有时指模块,有时指设备的实例,除了 跟踪执行流程,你完全把握不了这个代码是如何组织的引用一个从来没有提过,而且不是通用概念的概念。 这种通常发生在“笔记式”文档中,作者没有指望别人能看懂,只是记录自己的思路。这样写文档也是有益的,我们写的第 一版本通常也会这样写。我们隐隐约约引入的模棱两可的概念,很可能包含了我们没有研究清楚的逻辑在其中,正经写设计文档时候躲这些概念,往往就是留下破绽的表现。
函数或数据安排混乱,所在层次和模块错误
函数安排得乱七八糟,该属于这个模块的函数或者函数实现,写到了另一个模块或不在应该所在的层次中。 数据结构混乱,指针满天飞,最后你只能认为所有的数据都是全局的。就算他们的索引在某个模块内,但在这些无所不在的指针的帮忙下,你总是可以从任何地方访问任何数 据。
状态机混乱,状态机本身不是自恰的
状态机混乱,状态机本身不是自恰的,而是附属于流程的。经常是在这个流程中缺个标 记,就加个状态,然后对应写个if,在另一个流程中又需要一个状态了,又写一个if。 好了,最后你想复盘这个状态机的时候,你发现状态空间奇大无比,你根本不知道系统 最后会陷在什么状态上。生生把一个可控的代码变成一个卷积神经网络。
锁关系混乱,交叉死锁
锁关系混乱,交叉死锁。链表的锁,链表成员的锁,类的锁,类实例的锁乱用,看见谁 用谁。或者关键流程上锁,把大部分CPU堵死。或者对非原子变量偷鸡,不上锁。所有 这些行为,都是把系统放入一个不可知状态,这种程序一旦成型,根本无法维护。
参考
柱子哥的软件构架设计blog:https://gitee.com/Kenneth-Lee-2012/MySummary/tree/master/%E8%BD%AF%E4%BB%B6%E6%9E%84%E6%9E%B6%E8%AE%BE%E8%AE%A1
|