思维认知先行。
思维是什么?思维体现了思考问题的方向、方法和步骤。
程序员在解决具体技术问题时,会体现出不同于人文工作者的明显的技术思维。那么,技术思维包含哪些思维方式?程序员又是如何去思考和解决问题的呢?
技术思维
技术思维是客观思维范畴,讲求客观世界的真理,而非信仰。
技术思维主要有:逻辑思维、抽象设计思维、工具思维、量化思维、细节思维、工程思维、结构化思维、系统思维、解决问题思维。
逻辑思维
技术是实现目的的某种手段。而要实现目的,必定要遵循某些规律。逻辑是自此及彼的序列,是表现规律的某种形式。
逻辑关乎的问题是:何以认为是真。在“谈谈思想理性的基础”稍稍谈及了逻辑律。无论是人文学科还是理工学科,说话做事都必须符合逻辑律。不合逻辑的话,是难以站住脚,抬到桌面上的。
程序是算法步骤的实现,也是逻辑思维的最典型应用范例。程序里全是逻辑。严谨周密的逻辑思维,是技术思维的基础。
做一个需求,最基本的就是要具备良好的逻辑思维,组合数据库表设计和流程算法来实现,处理好各种必要的细节,并处理各种预期或非预期的错误和异常。在出现非预期情况时,要根据代码和逻辑来推断是哪里出问题了。程序设计开发,需要处理大量的逻辑,并保证逻辑正确。
说起编程这事,说难不难,说简单也不简单。说难,是因为编程需要经常动脑思考和持续学习,需要良好的逻辑感,需要驾驭大量的逻辑,对于那些不爱动脑的人或者缺乏逻辑感的人,基本是发送了劝退券;说简单,是因为程序员面对的是确定的局部的较小的语境和代码,只要在这个小范围内做好推理即可。
抽象设计思维
逻辑思维解决构造实现的基本问题。但要解决更高层的问题,就需要用到抽象设计思维。
抽象设计思维关乎质量属性,关乎系统整体的一致性和扩展,从统一、分离和扩展的层面来思考问题。譬如:
- 同一类相似需求,能否抽离共性,建立统一解决方法,或者可复用的部分?
- 同一类相似但又有差异的需求,能否将共性和差异分离开,只解决差异的部分?
- 业务需求发生变化时,如何进行扩展?
- 如何将系统中的大量元素有效地组织起来?
- 如何统一而优雅地处理应用中出现的大量而繁琐的错误和异常?
- 如何应对大流量大数据量?
- 如何从错误和异常中恢复?
- 如何保证多个系统之间的数据的一致性?
工具思维
工欲善其事,必先利其器。君子性非异也, 善假于物也。一个现代人最本质的能力之一,即是利用工具的能力。
放眼望去,工具已经成为工作和生活中的必不可缺的一部分。程序员的工作更是构建在各种工具(包括计算机)的基础之上的。离开了这些工具,程序员几乎难以做什么事情。
开发领域,版本管理工具 Git 和 编译部署工具 Maven & Gradle,集成开发环境 Intellj IDEA 是基本标配。此外还有 Terminal、Chrome&Edge、Postman、演示/表格/文字处理/笔记/翻译软件、Xmind&MindNode、飞书/微信、数据库客户端 MySQL & Mongo client 等。
我写一篇公众文,需要笔记软件、Mdnice.com 提供的公众文转换工具、公众号平台和公众号助手等,偶尔还需要图片素材库、绘图软件、思维导图软件;做网红或 UP 主的,需要直播设备、PS、视频剪辑软件等。
在很多时候,工具都是非常重要的。比如解决软件应用的性能问题,利用性能分析工具探察出“热点区域”,再针对热点区域进行针对性优化,是基本方法。没有工具的辅助,几乎无从下手。
最近兴起的 AI 工具,无疑是高级工程师的一个好帮手。对于那些已经知道怎么编程,但又懒得自己一遍遍重复写的代码,由 AI 来完成再合适不过了。学习 AI,用好 AI。
要善于寻找好的工具,持续开发高效、自动化的工具,积累和建立丰富的工具箱,如此才能成就高效之路。
量化思维
为了获得对系统更好的可控度,人们会制定一系列指标,对系统进行测量和量化。比如性能监控领域,在服务器层,有 CPU/内存/IO利用率、IO读写次数/读写速率;在应用层,有系统的 RT、QPS、TPS、Latency 等。站点层面,有 PV、UV、跳入跳出率。互联网领域,有衡量服务可用性的几个9。衡量用户生命周期的模型有 AARRR 模型。每一种模型都建立了一套量化分析方法。
在电商领域,商家通常要做营销活动,下单量会比往常高出数倍,此时就需要评估服务实例数,做好预备扩容工作,避免处理能力不足导致系统无法下单,影响商家销售。作为架构师,往往要算清楚成本账,即确定部署规格。如果采用的是 MySQL + Redis + Kafka,就需要评估 MySQL 分库数量及实例数量(以容纳未来增长数据量及抗住并发)、Redis 内存和实例数(保证所需内存容量,抗住每秒多少的读写)、Kafka 实例数(磁盘空间、抗住每秒多少的消息量)等。
一个人对所负责的系统的量化数字掌握越多,至少意味着他对系统的认识越深入。程序员不能仅仅停留在逻辑和定性的层面上,要去思考如何量化自己的工作和系统。
细节思维
细节思维的代名词是精确。不同于人文工作者,有时会用一些含糊其辞的话语搪塞,程序员必须保证每一个细节(符号)的正确性,才能保证系统能够长久稳定运行。
魔鬼在细节里。一个变量单词的某个字母写错了会让人看到眼瞎、返回值是空对象还是 null 、大括号放在小括号的左边还是另起一行影响美观度和可读性、边界条件处理是否得当、+1-1临界情况处理是否正确、时间戳位数是10位还是13位、参数不合法是提示并返回还是抛异常、配置是数值还是字符串、命令行的字符之间有空格还是没空格、线程数设置多少等。
有时,可能只是改了一个小小的参数,就能解决一个经常出现的超时问题,或者获得较大的性能提升。有时,一个细节处理不当,可能会导致系统运行出错甚至资损。关于细节的例子数不胜数。诸多繁琐的细节,程序员很容易就陷入在细节的泥沼中,但又必须注重细节。
要应对不可胜数的细节,显然不可能兵来将挡水来土掩。需要针对每一类细节制定相应的规范、机制、策略,来增强细节管理能力。比如说,对于无处不在的 NPE,可以采取的方法是建立空对象或 Optional 对象,或者编写 if-else 语句,或者加捕获异常,做防御式编程。
工程思维
工程思维即:在既定限制和约束条件下,有目标、有计划、有步骤地实现预期目标。
比如软件开发就体现了工程思维。软件开发活动整体划分为需求分析及评审、技术预研、技术评审、设计(概要设计和详细设计)、编码及联调、测试、发布等子环节,通过子环节的有序串联(每个子环节都有资源限制和时间约束),更好地管理软件开发活动。
任何需要一定预算、工期和人力投入的事情,都需要工程思维。比如给孩子办生日会、装修等。
工程思维更注重完成度,而非完美度。一件事情,能够完成 90% 的好,实际上已经达成了工程目标。再进一步完善,会增加更多的成本,这时候就要权衡成本和收益了。 ROI 是工程思维的一个主要考量点。
工程思维也要讲究约束和取舍。
任何时候做任何一件事,都存在资源限制和约束。工程思维在既定资源限制和约束的情况下,通过合理的权衡取舍,达到所需质量属性的一种最优解。如果暂时不存在可行性,工程思维也要说明这一点。
首先是可维护性和性能的权衡取舍。可维护性的意识已经深入人心。程序员首先要保证可维护性。达成可维护性,要保证代码清晰、简洁、容易理解、解耦得当。但为了达成性能目标,又不得不破坏解耦,把很多代码写在一起。对此的建议是:不要过早优化;仅优化确认的热点区域。可读性、清晰性和可维护性是第一位的。
其次是功能、质量与复杂度的权衡取舍。产品期望更多功能和更好的体验,测试期望覆盖尽可能完整的场景和更高的质量,运维期望产品易于部署和交付,架构师期望系统能够更加稳定可靠,在多节点情形下也运行良好,开发人员期望以更少的代码完成更多的事情。新增的功能、更好的质量属性,覆盖场景的完整度,往往会增加系统的复杂性。这时候就要面临功能、质量和复杂度的权衡。功能丰富、质量更佳固然更好,但系统复杂度增加会导致 BUG 更多,又会影响体验,增大后期维护成本,甚至可能导致故障,影响商业信誉。因此,我常常把清晰性放在第一位,在这个基础上去考虑产品需求实现和质量提升,不到迫不得已,尽量避免引入不必要的复杂度。遵循“奥卡姆法则”:如无必要,勿增实体。
工程思维还体现在风险管理上。在项目过程中,及时识别潜在的风险,并予以报备和解决,避免在后期凸显出来,打乱原有计划,造成较大的项目成本和项目延期。程序员即使不做项目经理,基本的风险意识还是要具备的。
结构化思维
结构化思维是技术思维的最生动体现,是用于构造系统和思考问题的强大的思维方式之一。
程序世界里,本质上都是各种结构的组合。针对一个需求,合理地建模和组织相关数据,存储在数据库里,构造合理的索引和流程去处理数据;针对一个系统,设计组成系统的各个模块及模块的交互,做到高内聚低耦合、依赖清晰。
需求有需求的结构,数据有数据的结构,流程有流程的结构,模式有模式的结构,框架有框架的结构,模块有模块的结构,系统有系统的结构,业务有业务的结构。可谓一切都在结构里。结构化抽象,是程序设计的第一性原理。
结构化思维用在编程设计上,注重复用和组合。程序员并不是从零开始,而是基于现有的语言、工具、平台、库、框架、组件等来构建应用。复用并能有效组合已有的工作成果,能够更高效地构造优良的系统。
结构化思维用在表达沟通上,能够让表达更清晰易懂。常见的 金字塔原理、STAR、KISS 等都蕴含结构化思维。我写文章也遵循了结构化思维。列大纲,组织成两级或三级标题结构,再丰富完善内容。结构化思维能够让表达更具可扩展性。
结构化思维可以批量产出很多方法论,在表达沟通、任务安排、面试、绩效评估等也有非常多的应用。
系统思维
结构化思维之上,是系统思维。从系统的角度去思考系统,能够突破原有认知,更深入全面地认识事物。
系统思考,必定要站在系统全局的层面上看问题。有全局思维,才能“一览众山小”。
- 系统的全景图是怎样的?
- 系统有哪些要素,这些要素是如何交互的?
- 系统的关键质量属性有哪些?如何权衡与取舍?
- 系统有哪些核心业务资产?能否抽离出来,成为持续演进的模型?
- 系统是否有反馈机制、自我保护机制和故障恢复机制?
- 如何可靠地添加组件和服务?
程序员的系统思维,初步体现在架构图绘制上。如何画一张架构图?如下图所示,是我在众人的思考结果下画的一张图。从这张图可以得到画架构图的一些套路:
- 分层。架构图通常采用分层架构,可划分为:技术底座层、业务底座层、核心业务能力层、子模块及交互层、产品应用层。每一层都界定好其职责。
- 技术底座层当然是跟技术有关,表明系统依赖的硬件环境、操作系统平台、各种技术设施、中间件、库和应用框架;
- 业务底座层是核心业务所依赖的业务能力的抽象,包括业务能力实现所依赖的存储模型、流程编排、数据标准、引擎、框架、安全防护、API等;
- 核心业务能力层是系统要实现的核心业务,体现客户所需要的价值;
- 模块层是系统所包含的各个子模块及交互;
- 最上层自然就是应用层或产品层,包括各种终端层。
解决问题思维
技术思维的终点,落在解决问题思维上。
解决问题以客户的痛点为中心,不在不必要的事情、次要的事情上浪费资源(时间、金钱、精力、人力等),致力于从多种途径、组合多种方案去解决问题,不局限于单一途径、单一方案。
比如产品提一个需求过来。是埋头就开始写代码,还是和产品讨论需求的来源、缘由、痛点、逻辑,分析需求的合理性和合理度,用一种兼顾系统实现成本和产品需要的方式来实现,还是能提出更好的见解,用一种更简单的非技术手段直接把需求化解掉,就体现了解决问题思维。
解决问题思维,注重治根本力图做彻底,而不是治标不治本,头痛医头脚痛医脚。
比如出现数据不一致问题,那么开发工具修复已有问题数据,就是治标不治本,因为根本“病症”没有找到,还会出现新的数据不一致问题。这时候,需要追根究底,找到并治理真正的源头。
比如系统中存在一个组件循环依赖的风险点。之前是采用 @Lazy 注解来保持加载顺序,但这个方法只针对自己要用到的组件有用(有局限性)。结果后来又有同事踩坑了(这种隐秘的问题很难发现,排查起来也比较费时,启动不报错,就是没加载某个组件,只有流经这个组件的时候才会报错)。最终采用 onContextReady 方法来彻底解决这个问题。
除了以上所述思维,还有决策思维、规划思维、管理思维等高阶思维。待有心得体会时再说。
成熟的高级工程师至少要掌握以上全部思维。高级工程师与技术专家的思维只在于深度,而不在于广度。
技术思维的特点
技术思维的特点主要有:可行性、严谨性、周密性。
可行性
可行性指技术思维的产出必须是可行的。在已有资源约束的情形下,在指定的预算下,通过特定步骤和方法,能够达到预期目标。
做技术方案时,必须要保证的是可行性。不具可行性的技术方案是无法通过的。
如何论证一个方案的可行性呢?
- 结合严谨的逻辑推理,通过理论的形式证明。
- 通过已有事实进行推断。比如性能提升,原来是 40ms + 60ms 的两个子流程,优化后,变成 40ms + 10ms 的子流程。那么可以推断出,性能预计能提升了一倍左右。
- 通过已有经验。经验即指已有的方法及能够达成的事项。如果能够将方案建立在已有的方法及组合之上,也可以论证可行性。应用此种方案时,要注意之前的语境和现在的语境是否一致,有木有明显的差异。如果有明显的差异,则需要做一些改动,否则照搬会出问题的。
可行性分析必须凸显主要依据、核心结论和风险因素。
严谨性
严谨性指技术思维在思考的过程中要始终保持推理的正确性和精确性,推理要符合合理的逻辑形式,确保能够达成目标。
数学是最能体现严谨性的范例。对于证明中“显而易见”的地方,一定要留心。一个典型例子是欧几里得几何和非欧几何。欧氏几何有五大公设。其中前4条公设都很简洁而无疑议,通过严谨的证明推出了很多有用的几何定理。但是第 5 条公设却并不那么显然。据此,后人在假设第 5 条公设的另外两种情形,分别推出了不相容却自洽的非欧几何。
程序员的严谨性体现在哪里呢?程序里全是逻辑,只要有一个地方逻辑不对,就可能导致整体结果出错。如果(A,B,C,D,E) 共同导致了结果 R。 当 R 不发生时,原因是哪一个?A,B,C,D,E 任一不满足都有可能。需要根据实际情况分析哪一种最有可能。这就是问题排查的基本思维。
考验思维严谨性的一个例子就是要在各种可能的情形下做到不重不漏。比如暴力破解攻击。服务端根据大数据返回的时间范围来界定一个告警的初试攻击范围,然后建立缓存,后续在命中缓存的登录记录都会累加到这个告警的攻击记录上。存在一种消息延迟的情况,比如登录事件在 31-48s 出现,但大数据在 45s 才收到登录事件,在 49s 时检测满足爆破条件发送告警,登录时间范围为 31-33s(只要第一次满足爆破条件的时间范围)给服务端,服务端在 49s 更新告警的攻击记录并建立缓存。这样,就会导致 33-49s之间的攻击记录被漏掉。在处理消息相关的逻辑时,如果与时间相关,则要考虑到消息延迟的影响。
差之毫厘,谬以千里。一个小的 BUG,可能导致巨额经济损失和项目失败。在事关人命和重大航空工业项目时,尤其要严谨之极,不能有一丝纰漏。错误往往在不起眼之处,而程序员则需要仔细防范各种错误。
周密性
周密性是指技术思维需要考虑到问题和路径的方方面面,尽可能全面细致,避免遗漏。
比如对敌斗争中,既要考虑敌方从大道上正面攻击的可能性,也要考虑从小道突袭的可能性。
比如设计系统或者编程实现时,不能仅仅只考虑到主要路径,也要考虑多条分支路径,还要考虑异常路径;既要考虑常规情形,也要考虑极端情形;既要考虑串行情形,也要考虑并发情形;既要考虑单个功能的正确性,还要考虑这个功能与其它功能联合起来使用的正确性。比如读取文件,要考虑文件不存在的情形、不具备文件权限的情形、文件不可访问的情形、文件非常大超过内存的情形、不同编码的情形、文件在远程机器上的情形、文件数量极多的情形等。
比如做代码改动后,要评估改动的影响范围,避免对原有功能造成破坏。这时候,就需要评估到影响的方方面面,如果评估不周全,轻则导致小的 BUG ,重则导致严重故障。尤其是修改公共代码时,更要评估到所有范围。业界有云:不要轻易修改老代码,原因是你永远没法知道老代码有多少地方用到了。
综上所述,不仅要具备相应的技术思维,还要磨炼技术思维的可行性、严谨性和周密性,才能促进思维的成熟和能力的完善。
小结
本文探讨了技术思维的多种思维形式,包括逻辑思维、抽象设计思维、工具思维、量化思维、细节思维、工程思维、结构化思维、系统思维、解决问题思维,并探讨了技术思维的三个主要特点,包括可行性、严谨性、周密性。技术思维是理性思维的一大组成部分。
熟练运用上述技术思维进行思考和求解,能够应对工作和生活中的大多数事情。
参考文献
- 软件研发三大思维之三:工程思维
- 软件设计的第一性原理:结构化抽象
- 软件的结构模式及结构的扩展
- 从系统整体观思考系统构建
- 让工作事半功倍的常用思考框架及关于方法论的思考
- 实际工作中结构化思维在表达中的应用
- 学习系统方法论:开篇
- 用系统方法论来管理软件开发活动
- 认识问题和求解问题的一种思考框架