36 能力维度一:如何提升结构化设计的能力?
你好, 我是郭东白。
上节课我们定义了架构师这个角色,也了解了架构师的五个成长阶段,分别是程序员、兼职架构师、跨域架构师、总架构师和CTO。以及与这五个阶段分别对应的核心能力,即:结构化设计能力、解决横向问题的能力、解决跨域冲突的能力、正确技术决策和创造生存优势的能力。
从这节课开始,我们就来探讨一下从程序员到CTO的能力跃迁问题。在分析的过程中,我们会着重分析两个角色间的能力差异、面临的工作挑战,从现有能力到下一个能力所必须跨越的障碍,以及对应的跨越方法,最终帮助你规划自己的职业发展。
我们先来研究一下从程序员成长为架构师所必需的核心能力,也就是结构化设计的能力。
什么是结构化设计能力?
我跟很多资深的研发管理者、CTO交流过,我们的一个共同观察是:一个缺乏结构化设计能力的程序员,永远都成不了好的架构师。
我先给出软件架构能力的定义,从中可以看出结构化设计能力的关键作用。软件架构能力指的是为相对复杂的场景定义并引导实施结构化软件方案的能力,其中结构化,代表这个软件在其设计范围内的设计理念、代码结构和实现方式上是同质的。
我用了“同质(Homogenous)”一词来形容结构化,指的是在设计范围内处处一致。与结构化(Structured)相反的是缺乏一致性,也就是说结构是混乱的(Chaotic)。
在这个定义中,我并没有锁定适用的人群范围,适用范围可大可小。也就是说,一个刚入行的程序员,即使没有做过任何架构规划,也完全可以培养自己的软件架构能力。
举个例子。他可以在自己的决策范围内,也就是自己写的代码里面,寻找并给出结构化的解决方案。这种结构化的实现往往不是产品需求的一部分,也没有人考核,更不会为此发放直接的奖金激励。但是,你会发现身边一些程序员对代码有一种几乎偏执的洁癖。在我看来,这就是对于结构性(Structuredness)的追求。
这种追求,在一个程序员的职业发展中是具有连贯作用的。从程序员到CTO,刚开始可能是对代码结构性的追求。随着职业的发展,这种结构性会扩展到更大的范围和更广的维度中,甚至延伸到公司软件研发的各个方面。最终,代码洁癖就会演变成对于软件系统甚至研发组织结构性的执着,而这种执着也会固化成对软件结构性的识别、改进和设计能力,贯穿整个职业生涯。
我观察很多资深的软件工程师和管理者。他们的职业成长过程,其实就是把结构性的能力从一个领域迁移到另一个领域的过程。一开始是在这个领域写代码,然后就到了数据模型,接着是功能模块、团队定位和组织结构,最终就到了商业模块。
可以说,对于一个以架构师为职业追求方向的程序员,结构化设计能力是基础能力。那么,该如何提升程序员的结构化设计能力呢?
如何提升结构化设计能力?
提升结构化设计能力的起点,其实就是代码的结构性。不过在结构性之前,还有个更朴素的起点,就是代码的整洁性。
在互联网时代,代码整洁主要来自编程规范的掌握和运用、设计抽象和常见设计模式的采用。对于编程规范,各大软件公司一般都有要求,网上可以检索到。这里就不再赘述。
我简单讲一下代码抽象,尤其是面向对象编程(OOP)的设计思想。OOP其实是结构化思维的一个重要范式,要求我们认真思考什么是类,什么是对象,什么是对象所具备的属性,什么是对象可能发生的行为,这个行为的作用对象是谁,等等。通过这种对模型本质的思考来定义软件结构,就是提升软件结构性设计能力的有效方法。
介绍OOP的国内外书籍有很多,我相信每个人都看过几本,不少人甚至能对OOP的原理倒背如流。但是我发现大家在写代码时,却很少习惯性地思考OOP方面的问题。我认为这种频繁的深度思考习惯,恰恰是架构师成长的必需。
举个关于扑克牌游戏的简单例子。如果去GitHub里翻看一下这类游戏的代码,会发现很多程序员都是把每张扑克牌设计成一个普通对象。如果再认真思考一下这种设计,会发现在普通的扑克牌游戏场景中其实是不太准确的。
一般来说,当一张扑克牌出现在电子游戏这个场景下的时候,除非游戏允许出老千,每张牌都可以有猫腻,否则每张扑克牌都不能是一个对象,而是全局状态统一的单例(Singleton)。对比之下,扑克游戏里面的每一手牌是一个普通对象,每一个玩家也是一个普通对象。
对象和Singleton是两种完全不同的设计,它们的运行成本和代码可读性也完全不同。其实任何一本OOP书籍和入门的设计模式书籍,都有类似的单例更优的场景。我相信如果稍稍思考一下,也都会做出正确的选择。但多数程序员缺乏这个思考的习惯。我认为正是这个习惯,将一般程序员和职业软件工程师区分开来了。
不过这个思考还没有完成。要想达到优秀的软件工程师的层次,还要再深度思考一层。如果把一张牌设计成单例之后,会不会带来某些局限呢?所以这个时候就要问问自己,在哪些场景中需要把这张牌设计成一个对象呢?
为了回答这个问题,这里我举个稍微阴暗一点儿的例子。假设你的外包公司在给一个外国赌场做网上德州扑克的游戏。这个国家的赌徒很迷信,认为每天晚上每张牌的运气都不一样。比如有的牌在今天晚上就会给自己带来好运气。
本来一个赌徒手里拿了一张黑桃3和红桃8,应该主动弃牌。但是你在红桃8上加了一行小字注释:
刚才真武太郎赢牌的那一手葫芦,就是这张红桃8加另外一对儿8和一个小对儿,那次他赢了3000个金币。而前面那个帮你赢了510个金币的All-in的同花里,也有这张红桃8!
一旦有了这个注释,赌场每天交易额就会增长30%。在这种商业模式下,虽然你还在设计扑克牌游戏,但每一张牌都有自己的生命周期。因为每张牌会因为经历过某个重大事件,而引起了各自生命周期状态的变化,所以就不再是单例了。至少每场赌局中的每一张牌,都是一个独立的对象。
作为一个优秀的软件工程师,这时候就要作出判断了。我的公司处在哪个阶段?需要这么具有前瞻性的设计吗?如果现在不需要,但是未来需要,我应该采取什么样的设计才能让未来的过渡最容易呢?
如果日常就在做这种深度思考,那么恭喜,你已经养成一个架构师应该具备的思考习惯了。日积月累,这种思想实验就会成为你的核心竞争力。想想看,别人写代码就是依照产品需求文档,做个从自然语言到计算机程序的转义。而你出手就不一样了,是一个基于对问题本质、商业价值和软件结构性思考的长远设计!
另一个对代码整洁性帮助很大的就是设计模式。设计模式的主要价值包括三个方面。
一是沉淀经验,用一句话来简单描述就是:如果遇到这种场景,就应该采用这种设计模式。可以说,这是个系统化且高效借鉴他人成功经验的过程。
二是传递设计原理,也就是对代码背后思考的一种标准化注释。代码所覆盖的领域可能是全新的,比如是元宇宙中的一个新物种,甚至在现实世界中都不一定有对应的存在。但如果用_Factory_或者_Visitor_这个词,别人马上就会知道这个设计背后的含义,比注释更深远也更准确。可以说,你通过规范化的设计语言表达了自己的思想。
三是降低理解和维护代码的门槛。如果实现中经常采用常见的设计模式,就会大幅降低其他人理解和维护你的代码的成本。在代码中,这些设计模式就好比旅游景点里供游人歇息的座椅一样,让另一个在你的代码高山里艰难攀行的路人,突然间找到了一个熟悉的小憩之地。
设计模式与OOP思维范式不同。设计模式强调趋同性,用广泛传播的知识来标准化代码的实现,从而降低实现手段和命名的多样性,也就是提升代码的一致性。当然也有人非常反对设计模式,认为设计模式多数时间都被用烂了。很多人为了引用设计模式而得出了错误的设计,这种现象的确存在。但这不是设计模式的问题,而是使用者应用不当的问题。
在设计模式的基础之上,一旦开始模块设计,就需要对世界做建模。这时候还需要学习问题域建模,也就是通过领域模型帮助我们与所有需求方在某个问题上达成共识。这个过程,模块二中已经有了详细解释,这里就不重复了。
确定了问题之后,接下来就可以设计服务API了。也就是如何以简洁明了的方式包装要解决的问题,让使用者能够轻松且长期使用你的服务,而不需要耗费大量的时间去理解、学习、集成、测试、维护和升级系统。
结构化设计过程中的具体关注点
那么在日常工作中,想做好结构化设计,应该具体关注什么呢?我认为主要是以下三点。
第一,设计理念。也就是说,整体设计需要与公司或部门的理念保持一致。
比如整个公司都采用分层架构,那么你的设计也要尽量采用分层架构。整个公司都采用微服务的设计理念,那么你的设计也要采用微服务的设计理念。否则写出来的代码既难维护又难理解,也难以被别人复用。
第二,API的结构性。也就是说,软件模块的对外界面要有比较明显的语义结构。
暴露给其他调用方的API,要有条理、表达准确,且易于理解。否则,API在被使用的环境中,会让调用方的代码变得晦涩难懂、结构混乱。
具体而言,API的结构性体现在三个方面。
首先是语义表达的结构性。如果把API理解成一段文字,那么这段文字需要有内在的顺序和结构,也就是我们强调的语义结构。这种定义具有一定的内在含义和逻辑顺序,因此也就具有宏观的结构性。
比如说,一个实体的API要反映出这个实体状态和行为。因为这些状态和行为会随着实体的生命周期而发生变化,所以API就要围绕实体生命周期来组织。而围绕一个流程定义的API,则要根据流程的前后顺序来组织API。甚至在API命名上的语言结构性,也会大幅提升API的可读性和可维护性。
其次是功能组织上的结构性。举个例子,一个API可能为多个用户角色服务,每个用户角色又有多个场景,每个场景还可能有多个功能。那么这些功能就应该组织成三层的结构:角色、场景和功能。不同的用户角色和不同的使用场景,都应该有不同的设计粒度。
比如订单服务可能为用户、商家、运营和审计这四个角色服务,那么面向用户的订单查询与面向商家的订单服务的粒度和内容就完全不同。
最后是数据模型的结构性。API会暴露数据给调用方,那么暴露出来的数据就要有清晰的结构,遵守一定的规范。
假设你在使用JSON传递数据,那么JSON就要做到可读且合理。所谓可读,就是只看JSON,很快就能明白这些数据的含义,及其内部对象之间的关系。所谓合理,就是包含在JSON中的数据结构符合最小必要的原则。
做到这三点很不容易。哪怕是所有程序员都熟悉的GitHub,它们的API和数据模型中也能挑出很多毛病来。
第三,模块内部的结构性,也就是程序的结构性。这种结构性其实是我们前面提到的程序员设计能力的具体体现。
举个例子,如果你现在使用的是Java, 那么对package hierarchy、package、Interface、Class和function实现的设计,就体现了你的设计实力。其中:
- Package Hierarchy和Package的设计,体现了你对领域的理解和对领域的封装。
- Interface的设计,体现了你对一个领域对外服务的理解。
- Class的设计,体现了你对实体、状态和数据模型的理解。
- Function的设计,则体现了你对计算封装的思考。
你可能说,我是个职场新人,怎么一上来就能想出很完美的设计呢?事实上,我们日常工作涉及的很多领域都有现成的国际标准,不需要再去发明创造。除了我们前面提到的设计模式之外,使用常见的SprintBoot等服务框架,依赖主流而不是小众的技术,遵守W3C发布的最佳实践等,都是提升代码结构性的好办法。
不过这里有一个常见的问题:如果队友水平不高,是不是要降低我的标准来与他们对齐?我的观点如下:
- 在模块内部的结构性上,完全不需要这样做。因为这是你的私域,更好的结构性不仅可以帮助自己,还能帮助未来接替自己维护模块的人。
- 在API的结构性上,可以尽量追求API的结构性,前提是不能与整个研发团队现有的设计规范相冲突。哪怕你认为现有规范不合理,也应该建议调整规范,从而引入更先进、更可读和更容易维护的标准,而不是直接越过规范。尤其在API的命名、JSON schema的定义上,如果与团队的主流选择相冲突,那么你的设计会渗透到其他人的代码里,影响他人代码的一致性和可读性。
- 在整体设计的理念上,你没有权利做任何的发明创造,否则将会增加公司架构的混乱程度。这个时候,更好的选择是想办法去影响决策者。
小结
我们这节课简单聊了程序员的结构化设计能力。说句实在的,我刚做程序员时,这项能力很一般。一方面是自己当时的能力不够,另一方面是自己的主动性意愿也不强。不过后来有了转变,所以现在就把我的转变经过分享出来,希望对你有帮助。
我第一份全职工作是在甲骨文,刚开始时主要写算法代码,不需要和其他人合作太多。我后来发起了一个内部创业项目,做得比较成功,成了Oracle数据库的一个主要功能。由于商业上比较成功,所以有很多人参与了进来。这时候我才发现自己的设计有很多瑕疵,同时也收到了很多来自内外部的批评。
对于这些批评,我起初很抵触。直到最初的代码被重构两三次之后,才终于意识到有些设计失误其实完全可以避免。于是我开始重新阅读消化软件架构方面的书籍,开启习惯性的架构思考,我的能力也因此有了非常大的提升。因为我发现自己并不是一个邋遢的人,只是懒得照镜子而已。
是的,在我看来,习惯性地对自己所设计的软件结构性进行反思,这个过程就像是照镜子。首先要看到设计中的那些污垢,然后再去清除。
能做到这一点,结构化设计的能力自然就会提升。
思考题
三个课后作业,可以选择其中一道作答。跟之前一样,不是做得多,而是做得深。
- 这节课我们简单提到了“美”,并且隐含地把“架构之美”与“结构性”画上了等号。你认可这种说法吗?如果不认同, 你的观点是什么?
- API的延续性,是API结构性中非常头疼的问题。为了保证现有用户的习惯,新的系统必须要提供与过去兼容的接口和设计。你可以举几个例子吗?你碰到过类似的场景吗?是怎么平衡的呢?
- 在你的程序员职业生涯中,追求结构化设计的最大阻力是什么?是怎么克服的?有什么经验可以分享一下吗?
欢迎把你的思考和想法分享在留言区,我会和你交流。也欢迎把课程分享给你的朋友或同时,我们一起进步。我也请你关注我的抖音号郭东白。下节课再见!
- 术子米德 👍(8) 💬(1)
🤔☕️🤔☕️🤔 * 📖:结构化设计,如何提升? * 🤔:设计,只要不给别人看,自己总是无比满意,尤其是刚完成的时候,得意得不行。过几天来看,这都是啥,怎么这么奇怪。这时候,就会问自己,怎么没点结构感,都不知道怎么看起。如果拿给别人看,大概率对方很雾水一头,即使解释再三,还不如自己拿起马克笔,在白板上画起来,效果略好,但是依然会越画越乱,只能擦掉重来,这次先主要的,再针对某个点细化,先取对象名字,不能这个那个乱点兵。 * 🤔:回头想想,为何现场画的效果好一点,原因是有个时间顺序,那为何自己画图的时候,不提前加上时间轴的思考维度,包括在图上标注出序号,提示阅读人关注的顺序。结构化的时间维度,我就是这么实践并反思出来。 * 🤔:再想想看,为啥要整体再局部,才能好理解,为啥非得取个名字,才能讲得顺,这不就是在空间里,指着什么喊出名字,然后远看怎么样,走进看又如何嘛。结构化的空间维度,我也反思中顿悟。
2022-05-23 - 花花大脸猫 👍(4) 💬(2)
第三个问题:最大的阻力还是在于上层不care,别人3天,你要5天。。。。结果不骗人。短平快与结构化设计,在这一段至少对上而言。是劣势!对于只管业务的人来说,越快的结果越ok
2022-12-29 - 罗均 👍(3) 💬(1)
东白老师的课程,总是可以给学生全新的技术视角,为学生们打开了更大的思考空间。 作业第三题,回顾写代码的历程,最大的障碍还是自身的认知局限。例如,有些场景command pattern与strategy pattern的选择,或者mediator pattern或subscription pattern的选择,在implementation过程中,因为自己的水平有限,没办法融汇贯通达到最优效果。也没有什么好的克服方法,就是多总结多实践(重构),更多地与业务方讨论深入理解业务——学生比较好,七八年前从类似产品的角色开始写代码。另外的“捷径”就是多向东白老师这样的顶级大牛学习,这节课的singleton案例,实在是“美妙”之极,至心感恩老师的课程! 要再尝试推荐老师的课程给一些朋友!
2022-05-18 - Michael 👍(2) 💬(2)
老师有没有什么推荐的书?
2022-06-19 - spark 👍(2) 💬(2)
郭老师,take away~~~结构性是一个结果,追求结构性是需要思考、路径,追求结构的思考需要系统性~~~ a.编程维度,面向对象、面向过程,设计原则,设计模式,代码规范(Google Java Format),重构~~~ b.思考维度,架构思维和结构的因果关系,架构思维是因,结构是果???计算机系统需要自顶向下设计,分治思维是关键,如何定义问题和拆解问题。递归思维是关键,如何确定递归调用的第一步,很关键。因为递归思维和分治思维会引导我们得到一个结构性的、多层网络状的、嵌套调用的模块关系~~~
2022-05-17 - 罗杰 👍(1) 💬(1)
第三个问题:在当前的公司干了五个年头了,当时加入的时候,整个后端就我一个人,整体后端架构的设计与客户端的交互都是我基本上是我在主导。说起来比较尴尬,我其实水平真的很一般,在做这些架构之前,也就三年的工作经验。追求结构化的过程其实是相当痛苦的,最开始最大的疑惑是怀疑自己是不是对的。但是随着需求的不断变化,你会发现真的多虑了,因为没有正确的设计。所有的设计都是 trade-off。在心理上解决了这个问题之后,接下来的问题就是别人的质疑了。首先正确表达自己的观点,其实多去学习。解决是不是自己视角太窄,别人的想法我们不能左右,尽可能去说服,有时候无法说服,只是因为该有设计理念我还没有内化。对于正确的理论,正确的观点,践行就好了。他人的埋怨与不解,我只能说:“求仁而得仁,有何怨”。
2022-05-22 - Steven 👍(0) 💬(1)
分享一个小例子:参与过的一个表单项目,临时组的这个团队成员在类命名,数据库表命名上,习惯采用的是拼音首字母的方式,比如【暂停人口-ZZRK】,当然一些简单的还是用英文。 一个原因是他们以前都是这么用的,另一个他们的英文也确实都比较差。 在这部分,我的要求就是必须要有中文注释/对照
2022-06-08 - 少年锦时 👍(0) 💬(0)
老师也喜欢玩德州扑克呀
2024-04-29 - softbaddog 👍(0) 💬(0)
软件上结构化设计最大的好处就是可以不断演进,甚至推到从来。罗马不是一天建成的,需要不断地迭代优化再优化。当然很多时候演进是随着业务的调整而进行的,也没有必要过度使用设计模式,KISS,SOLID等架构原则更有用。
2022-07-20