满纸荒唐言,一把心酸泪,都云作者痴,谁解其中味。 技术博客 心情随笔 登录
如何开发一款世界一流的上位机软件
2026/3/12 60

导航

1前言

2软件架构师

3领域驱动设计

4抽象与扩展

5插件化

6单元测试

7敏捷开发与持续集成

8自动化测试

9测试驱动开发

10后记

1 前言

大约2000~2020的20年间,我主要编写B/S构架的软件,有幸见证了中国互联网从星星之火,到燎原之势,再到逐渐消退的全部过程。2021年,我义无反顾的告别互联网一头扎进了制造业,在汇川工业机器人部门,负责应用软件构架设计与开发工作,也由此开启了上位机软件开发之旅。
汇川工业机器人应用软件应该是比较具有代表性的一款上位机软件,它规模较大,代码量百万行以上;生命周期长,一款迭代开发10年的工业机器人软件在业内只能算是初出茅庐;需求变化多,这些年中国制造业的发展突飞猛进,国内卷的程度可想而知。要让百万行的代码,高频迭代10年仍保持敏捷,快速响应各种需求,协调几十人的团队同时工作,这其中还是有不少值得分享的内容,本文结合这些年自己的经验,围绕如何打造一款世界一流的上位机软件展开,如果碰巧能对中国制造业的发展有那么一丝贡献,深感荣幸。来源:https://www.wubayue.com

2 软件架构师

在进入正题之前,我想先聊一聊软件架构师(Software Architect)这个角色,如果把软件开发类比为房屋建造,那么软件架构师就是建筑图纸设计师,在现实世界中建一座100层的高楼需要设计图纸,在公园中盖一座公厕同样也需要设计图纸。但遗憾的是在很多软件项目中,百层高楼并没有设计图纸,客户只验收盖好的房子有100层,至于怎么通达每一层,开发工程师们八仙过海各显神通,有电梯直达,有绳索速降,还有徒手攀爬......于是随着项目日积月累的迭代,终于有一天,它不再是一栋100层的建筑,而成为了一座让所有工程师闻之色变避之不及的屎山。

无架构代码示意

软件架构师在项目中有着举足轻重的作用,甚至可以说软件架构师主导了软件的灵魂。但在大多数的中小型公司,甚至大型的硬件驱动型公司,领导们对软件架构知之甚少,他们信奉大力出奇迹,认为不论是技术问题、质量问题、还是进度问题,都可以通过投入更多的人力来解决,在没有架构设计的项目中,更多的人只是更快的堆出了更大的屎山。从另一个角度来看待软件项目中架构师缺失的问题,我认为国内35岁职场斩杀线功不可没,与我同龄(45)还在一线编码的软件工程师几乎凤毛麟角,在中国,很少公司愿意招聘45岁的程序员。而反观国外,Guido van Rossum在35岁时创造了Python语言,Richard Hipp在40岁时创造了SQLite,Werner Vogels在50岁时打造了亚马逊的AWS,55岁的Linus仍经常在Linux内核代码Code Review时口吐芬芳。终有一天,当我们认为软件构架的优雅比进度重要,开发工程师的创造力比他的剩余价值重要时,我们才能从一个软件大国成为一个真正的软件强国。来源:https://www.wubayue.com

3 领域驱动设计

在软件发展的历史进程中,诞生了很多伟大的思想和流派。比如面向对象(OOP,Object-Oriented Programming)是所有现代高级语言的基石;微服务是现代大型网站的骨架;敏捷开发模式以及围绕敏捷的一系列思想,是现代软件工程的灵魂。而在这一系列具有划时代意义的思想中,我认为对于软件架构师和软件开发工程师最为重要的,与软件设计与开发关联最为紧密的,就是领域驱动设计(DDD,Domain-Driven Design),毫不夸张的说,领域驱动设计就是软件开发的第一性原理,为最佳的软件设计和开发提供理论支撑。

找到事物的本质

电影《教父》中有句台词“花一秒钟就看透事物本质的人,和花半辈子都看不清事物本质的人,注定是截然不同的命运”。那么在软件项目中,如何找到和看透事物的本质呢?以这些年爆发的人形机器人为例:

用户眼中的人形机器人

对于用户来说,不会关心机器人的工作原理,而只会关心它能做什么。因此用户(包括产品经理)会不断的提出他们想要的功能,需要机器人陪跑马拉松,需要机器人跳街舞,需要冲咖啡,需要切水果,需要提供情绪价值......如果研发完全按用户的需求不断的堆砌功能,最终的结果可想而知,系统越来越臃肿,功能无法复用,项目难以维护。因此,用户的需求并不是人形机器人的本质。

研发眼中的人形机器人

那么研发人员呢?毕竟他们缔造了整个软件系统,对产品的本质应该了如指掌了吧。但是很遗憾,大部分的软件研发人员都被打上了技术的思想钢印,他们习惯于以技术的维度来思考和构造整个系统,你能从“任务调度”、“网络通信”、“数据存储”、“日志监控”这些词中推断出这是一个人形机器人软件系统吗?恐怕并不能,因此研发人员理解的也不是人形器人的本质。

人形机器人的本质

领域驱动设计就是业务本质驱动软件设计,那什么是业务本质又如何找到业务本质呢?比如你是人形机器人的软件架构设计师,而你对人体生物学一窍不通,于是你需要与最懂人体生物学的专家们沟通,专家们告诉你人体是一件复杂精妙的机器,分为“感知”、“交互”、“决策”、“运动”......这些大的功能分类,而其中的“运动”可以由一系列基础动作构成,包括“推”、“拉”、“蹲”、“旋转”、“提举”、“行走”、“跑动”......你与专家们共同设计实现了这些基础运动,而客户要求的后空翻、跳街舞、跑马拉松无非都是这些基础动作的组合。恭喜你,通过领域驱动设计对人形机器人的工作原理进行了合理的分类,并得到一份目前看来复用性还不错的架构设计,这可以算找到了一部分事物的本质。

统一业务名词

对于那些从事软件开发有些年头的工程师来说,领域驱动设计一定会让他们有种相见恨晚的感觉。比如回忆起你和另一位开发工程师就“支付接口”讨论了半天,最后发现他说的是“退款接口”,你理解的是“付款接口”;再比如你考古某个模块的代码,一些名为Procedure()的函数中调用了一些名为Handler()的函数,这些Handler()的函数中又调用了一些名为Manager()的函数,从这一堆技术术语的函数名中你完全GET不到业务逻辑。

领域驱动设计的首要任务就是统一沟通语言,项目所有成员(业务、产品、开发、测试)共同创造并持续使用一套精确的、无歧义的术语词典。名词统一绝非小事,它是确保团队思维一致、减少认知摩擦、构建高质量软件系统的关键前提。

通过代码传承业务知识

代码和文档是两种不同的事物,要让二者之间维持同步一定需要研发人员额外的时间投入,因此不论是让研发人员写文档还是看文档,单纯从研发角度来看,就是一件低效的事情。良好的代码结构加上合理的注释,不仅可以让研发人员通过代码理解业务逻辑,甚至完全可以做到通过代码传承业务知识。

领域驱动设计并不是具体的武功招式秘籍,它是武功心法,是一种思想,因篇幅有限本文只是列举一二不作详细展开,但我想每位软件架构师和开发工程师都应该思考我们赖以维生的行业第一性原理,如何设计好一个项目,如何编写好一个函数,如何让我们的代码更贴切的去表达这个真实的世界。来源:https://www.wubayue.com

4 抽象与扩展

面向对象编程(OOP,Object-Oriented Programming)如雷贯耳的三大特性,分别是“封装”、“继承”和“多态”,前两者很容易理解,相信接触过OOP的同学对这两个概念耳熟能详。但如果提到多态,很多开发人员就不太理解了,包括我之前面试过很多资深开发工程师,对多态仍是一知半解,而对于软件构架设计,多态恰恰是最重要的一个概念,它保障了软件的可扩展性、可测试性,让软件在框架层面支持“开闭原则”,在软件架构设计中不可或缺。

多分支开发模式

多态这一看起来甚至有些奇怪的词,到底代表了什么含义?又为什么在面向对象开发中如此重要呢?我们从一个小示例入手,时间回到1990年,你是一家电脑生产商,此时的个人电脑风生水起,无数外设厂商找你恰谈合作,键盘/鼠标厂家希望在你的电脑上开一个PS/2接口以支持他们的设备,拨号上网的调制协调器厂家希望开一个RS232接口以支持用户上网冲浪,音箱厂家需要开一个MIDI接口让电脑能发出声音......你一一照办,于是生产出来的电脑上遍布各种接口,PS/2、RS232、MIDI、并口、SCSI,如果把这个现象类比软件开发,可以称之为面向具体编程,即根据每一个具体的需求,开发实现一段具体的实现代码,各段代码之间不存在关联关系。它的缺点显而易见,电脑的体积只能容纳一定数量的接口,无法支持后续更多种类设备的扩展。

那么如何一劳永逸的解决这个问题呢?你作为一位聪明的电脑生产商,肯定想到了这个非常简单的方案,那就是只在电脑上开一种接口来支持所有不同的外部设备。这便是抽象,当年的Intel、IBM、Microsoft等IT巨头的做法与你的想法如出一辙,他们观察需要连接电脑的不同外部设备,抽象出接口基本特征包括尺寸、供电、数据传输等,然后制定标准让电脑外设厂商遵循,于是,大名鼎鼎的USB接口诞生。如果把USB类比软件开发,那就是面向抽象编程,电脑生产商只关注这个抽象的USB接口,而不再需要考虑层出不穷的电脑外部设备,个人电脑从此获得了无与伦比的扩展性。

USB抽象示例

面向抽象编程又叫面向接口编程,它是软件实现扩展性最重要的手段之一。在互联网行业,B/S(浏览器/服务器)架构的软件均部署于服务器端,因此不论软件架构重构还是BUG修复,都可以快速发布任意迭代,用户对软件问题的包容性也较强。而传统制造业中的软件,情况就大不相同了,软件一旦发布进入客户现场,鲜有机会升级迭代修复BUG,除非外派人员到达客户现场,成本极高。此时,软件设计中大名鼎鼎的开闭原则就该登场了,开闭原则要求一款设计优秀的软件即要支持软件持续增加新功能,对扩展开放,又要保证增加新功能时不修改任何旧代码,对修改关闭。我们再看一下USB接口的例子,对于电脑生产商而言,通过USB支持后续设备扩展的同时,不用对电脑进行任何改动,USB接口是一个开闭原则的设计典范,而USB接口的精髓,在于抽象。来源:https://www.wubayue.com

5 插件化

传统分层软件架构示意

有了领域驱动,有了抽象设计,然后软件架构师把SOLID原则、设计模式这些基本要素在项目中合理应用,基本就得到了一个不错的软件框架。但随着项目的迭代发展,尤其在代码量庞大、版本迭代频繁的项目中,很快就会暴露出一些棘手问题:

1、模块之间的依赖关系关系越来越复杂。
2、软件不够健壮,散布在各个功能模块中的代码问题,比如数组越界,空对象使用等,会导致主进程崩溃。
3、安装包大、启动慢、内存消耗大,不常用的功能均会安装、加载和占用内存。
4、版本发布节奏跟不上客户要求,比如一些紧急需求实现、突发BUG修复,不能快速交付到客户现场。
5、测试资源消耗大,研发的小规模代码变更经常导致大面积测试,测试人员无法精确锁定要测试的目标范围。
6、无法构建生态,让第三方共同参与软件功能的开发。

插件化软件架构示意

如果你正面临上述问题,或者你正计划打造一款具有一定规模,需要快速迭代的软件产品,那么插件化设计就是不可或缺的一项关键要素,并且像插件化这种对项目架构影响深远的设计,越早规划对项目越有利,我经历过很多项目都是经过多年忍受到了极限才引入插件化,而对海量代码的项目进行底层重构,其难度和资源消耗可想而知。如果从项目的投入产出比来看,一位有经验的软件架构师,前期很少的投入,就会为项目带来明确的质量收益和节省巨大的重构成本。

插件化通过抽象建立契约,然后借助反射/动态加载技术将履约对象(插件)集中管理,可以自己实现插件管理器,但我更推荐在项目中引入依赖注入和IoC容器,关于这两个概念及关注技术细节的同学可以参阅我的另一篇文章《依赖注入(DI)与控制反转(IoC)》来源:https://www.wubayue.com

6 单元测试

测试左移-单元测试

在软件开发领域,通过测试左移来提升软件质量,从而整体降低成本已是业内共识,就像汽车在研发阶段修复一个问题和销往市场之后大规模召回修复一个问题,成本具有天壤之别。相信老板、领导、项目管理者们看到这张图,一定会对测试左移和单元测试感兴趣,因为它会显著降低成本,但在实际实施过程中,能够做好单元测试的项目,其实非常少。关于单元测试的优点和技术实施,网上的资料很多,关注单元测试技术细节的同学可以参考我之前的一篇文章《单元测试从入门到精通》。这里不谈技术,我想从另外的视角来探讨:单元测试既然这么好,为什么在大多数项目中都实施不起来?

首先我们明确一下单元测试的范畴,单元测试虽然包含“测试”二字,但与测试部门没有任何关系,单元测试是软件开发阶段的一项活动,研发人员编写一些代码(测试用例)来测试自己的另一些代码(业务代码),因此,单元测试需要研发的额外投入。据我所知,国内大部分软件项目的交付周期都非常紧张,在研发时间都被压缩的情况下,开展单元测试就是逆水行舟,如果单元测试挤占了开发时间,就会让研发人员抵触和排斥。除此之外,后期的软件变更也会带来单元测试用例的同步变更,这对研发人员来说,会是一项额外的负担。所以,项目经理必须合理的规划单元测试时间,如果一个项目在制定进度计划时,没有合理的规划单元测试时间,那么进行单元测试几乎必然会失败,根据我的经验,单元测试需要的额外时间,不应低于项目编码时间的50%。此外,技术Leader应该清晰的知道哪些代码相对稳定适合做单元测试,哪些代码频繁变更不适合做单元测试,如果盲目追求单元测试覆盖率,地毯式的进行覆盖,不仅会导致单元测试半途而废,还会给研发团队带来沉重负担。

如果说实施单元测试的成败关键是相应资源的规划,那么软件架构就是能否实施单元测试的门槛。我接触过的很多项目,大多没有可测试性软件架构支持,而单元测试恰恰需要架构先行,就好像我们房屋装修,一定需要先硬装水管电线,然后再软装家电,没有可测试性构架设计就像家电进场时没有电插板。虽然有一些自动生成测试用例,自动为被测函数打桩的单元测试框架,并且随着AI技术的发展,通过AI替代人为编写繁琐枯燥的测试用例会成为一个趋势,但我认为这些仅可作为辅助,良好的可测试性软件架构仍必不可少,原因也非常简单,当我们写出了很烂的代码,自动工具、AI只能保障这些烂代码运行起来没有大问题,而作为一名优秀的开发人员,需要的是发现并重构这些烂代码。单元测试,就是最好的嗅探器,当你发现自己的项目无法进行单元测试时,那就需要审视软件架构问题并考虑是否需要重构了。来源:https://www.wubayue.com

7 敏捷开发与持续集成

敏捷开发

近些年,埃隆·马斯克的SpaceX在航天领域异军突起,猎鹰、星舰、星链都以王炸的方式颠覆着传统航天。而支持SpaceX快速发展的底层逻辑,就是老马从互联网和软件行业移植过去的敏捷开发:以人为中心,打造跨领域团队,快速试错迭代,持续交付产品。

敏捷开发

敏捷开发当前已是现代软件开发的主流模式了,码龄比较长的同学可能还记得瀑布模式,一种诞生于互联网之前的古老开发模式,瀑布讲究上一阶段完成了再进入下一阶段,就像瀑布流水一样。而敏捷模式则推崇将一个大功能拆分为多个小功能,快速迭代分批交付。本文不展开敏捷模式的细节,但需要提醒读者的是敏捷模式已是当前软件开发绝对的主流,并且在其背后有一系列强大思想、工具、以及生态,包括每日构建、持续集成、测试驱动开发、DevOps等等。

持续集成

多分支开发模式

在瀑布模式中,比如一个项目需要开发三个功能,通常会采用多分支策略,分别为这三个功能拉出三个分支,每个分支独立开发,开发完成后,分支合并回主干,然后集中测试,最后构建版本进行交付。在多分支开发模式下,存在显著缺陷:

1、代码集成难度大,当分支合并回主干时,会面临巨大的代码集成风险,包括合并代码量大,冲突多,功能之间的相互影响大等,并且这些风险与分支的数量以及分支的开发周期成正比,分支数量越多风险越高,分支开发周期越长代码集成难度越大。
2、团队协作困难,多个分支长期独立开发,开发团队之间难以共享、复用代码,比如A、B分支中存在相同的功能,但两者之间未能及时同步,导致相同功能在A、B中各实现了一份,降低了代码的复用性。再比如A分支依赖于B分支中的某些功能,由于两者之间长时间未同步,B已经将功能改的面目全非了,而A只能在合并那一刻才发现,这种代码集成的滞后会引入非常多的问题。
3、测试压力大,研发人员需要在功能开发分支与合并后的主干上进行重复的测试,比如当单元测试在功能分支上运行正常但合并至主干后大量失败时,对研发人员编写单元测试的积极性会造成巨大打击。同样,短时间庞大代码行的合并对测试人员开展集成测试也是一项巨大挑战,测试范围广,功能之间相互影响大,时间进度紧张。甚至研发与测试之间的矛盾很多都来源于多分支开发这种落后的模式。

共主干持续集成

与瀑布模式对应的多分支开发在互联网高速发展的商业场景下几乎走向了绝境,而与敏捷开发对应的共主干持续集成目前已成为主流。共主干持续集成可分为两部分,其一是共主干,就是在一个版本中不论有多少功能,均在共同的主干分支上开发;其二是持续集成,就是即时提交代码,高频的(每天多次)对整个分支进行版本构建和自动化测试,以保证提交代码的正确性以及随时都有可用的软件版本。共主干与持续集成二者相辅相成,优势也显而易见:

1、代码集成风险低,持续集成通常结合CodeReview,要求每次提交的代码量在500行甚至300行以内,因此代码冲突以及解决冲突的风险非常低。
2、团队协作效率高,因为整个团队的代码几乎是即时提交,所以代码之间的相互影响能被及时发现,具有相同功能的模块也能被迅速复用。
3、即时测试,通过高频的冒烟、准入测试,随时监测和发现代码提交包含的问题,让问题在前期即时暴露而不是累积到后期集中暴露。随时提交代码随时具有可用版本,也为研发人员的开发调试带来了非常大的帮助。

毫不夸张的说,持续集成一旦用上,就再也回不到从前了,因为它非常的高效、丝滑。但缺点就是部署成本较高,对于缺乏软件工程能力的小型团队来说,自己部署确实具有一定难度,但有GitLab这样的开源平台,也有阿里云效这样开箱即用的云服务,如果你的项目还是瀑布、多分支开发模式,我想是时候拥抱敏捷和尝试一下持续集成了。来源:https://www.wubayue.com

8 自动化测试

自动化测试很早就存在,但这些年的发展突飞猛进,我觉得主要是几个因素共同作用的结果。首先是敏捷开发逐渐成为主流,其衍生的CI/CD、TDD等对自动化测试的刚需日益剧增;其次是由AI带动的Python大爆发,让自动化测试的门槛显著降低,从业人员呈几何级增长;最后人力成本的上升也促使更多的企业考虑通过机器换人来降低成本,机器更稳定不易出错,并且在AI的加持下正越来越聪明。

软件自动化测试

针对业务逻辑的自动化测试主要分为两种,一种基于软件的UI界面,自动化脚本模拟人的操作,比如鼠标点击、键盘输入等行为来进行测试,这种方式的优点是简单直观,只要有交互界面基本就能进行自动化测试,但缺点也显而易见,一旦软件UI界面发生变化就会影响已覆盖的自动化测试脚本,而UI随着需求而频繁变化难以避免,因此自动化用例的维护成本高昂,同时基于UI的自动化测试脚本开发也较为繁琐,还需要借助第三方控件识别工具;另一种更好的方式是基于API接口,跨过UI将软件功能以API接口的方式提供给自动化测试,虽然这对于研发人员来说有额外的接口开发工作量,但如果在软件架构设计层面就考虑到对开放接口的支持,后期的开发工作量其实非常小,并且对外开放接口也会驱使研发人员的代码具有更好的设计和更完善的注释。当前自动化测试主流模式是基于接口的,甚至在ATDD(验收测试驱动开发)这样的敏捷模式中要求在编码前的设计阶段就由研发与测试共同确认了自动化测试接口,后续的工作均围绕接口展开。

在一些中大型项目中,被测对象通常会包含多种技术栈,比如自动化测试需要覆盖C++的服务端、C#的桌面端、Java编写的Web端等,对于测试部门,当然希望这些不同技术栈的软件,提供的是相同协议的接口。测试与研发可以协商使用一些具备跨语言特性的通用协议,比如HTTP、Socket等,但我认为更好的方案还是使用RPC框架,它会极大的简化接口提供与调用的过程,并且提供更好的稳定性与性能。各个大厂都有自己的开源RPC框架,比如Facebook的Thrift,阿里的Dubbo,以及Google大名鼎鼎的gRPC等,对技术细节感兴趣的同学可参考我之前的文章《gRPC基础:C#服务端与客户端代码示例》《gRPC基础:C++服务端与客户端代码示例》《gRPC进阶:通过stream实现观察者模式》来源:https://www.wubayue.com

9 测试驱动开发

当你的项目中实施了敏捷开发,又具备了持续集成与自动化测试能力,那么恭喜,你获得了一张敏捷开发最佳实践的入场券:测试驱动开发。测试驱动开发有别于传统“先编码,后测试”的项目流程,而是要求在编写业务代码之前,先明确验收标准,它也符合我们的常识,一件事情在实施之前先明确要达成的目标。

分享一个自己生活中的真实案例,2025年,我装修了自己的第一套房子,接触过装修的同学都知道,前期硬装阶段的地面和墙面是两个重要环节。一开始我找了师傅A做地面,让他把开发商精装交房的地面打掉重新铺贴瓷砖,因为是第一次,对地面施工不了解,再加上觉得只是铺帖瓷砖没那么复杂,所以对师傅也没提出什么具体的要求,两周后师傅告诉我基本完工了,结果我去验收发现一堆问题,瓷砖空鼓、缝隙不均匀、切割失误损毁的瓷砖比例高等等。我要求师傅返工,对方不接受说之前并没有约定这些验收标准,结果双方脸红脖子粗不欢而散。第二次找师傅B做墙面,因为有了前车之鉴,所以我花了些时间了解墙面施工流程,在施工前给师傅提出了非常明确的验收标准,比如告诉师傅在验收时会使用两米靠尺测量墙面平整度偏差不大于3mm、阴阳角会使用激光水平仪确保横平竖直、天花会使用大功率LED灯照射不能出现明显水波纹。同时在施工周期内,几乎每个重要环节都提前确认了施工流程与验收标准,包括刷墙固、挂网、腻子找平、打磨、乳胶漆一底两面等,每个工序验收完成后再进入下一工序。最终我获得了自己期望的墙面效果,师傅也拿到了与工序匹配的相应报酬,大家都很满意。

测试驱动开发其实并不局限于软件,它总结了如何做好一件事情的方式方法,在软件开发中,它告诉我们要提前制定验收测试用例。在装修中,它告诉我们一定要先与施工师傅明确验收标准。关于测试驱动开发的更多细节,感兴趣的同学可参考我之前的一篇文章《测试驱动开发(TDD)浅析》来源:https://www.wubayue.com

测试驱动开发流程示意图

10 后记

这篇文章算是这些年的一次技术总结吧,内容比较杂,从软件设计思想、到技术框架、到项目流程实施模式。有时候我在想,中国的制造能力已稳居世界第一了,但我们有与之匹配的软件能力吗?作为一名从业人员,可能我能看到更多的细节,我们的软件,尤其的制造业相关的软件,与美日欧仍有相当大的差距。如果是人有我无的技术,像汽车、芯片、AI,我们可以奋起直追,以中国人的聪明勤劳,没有什么是追赶不上的;甚至像Windows、Android这样的生态,以中国的市场规模和发展速度,假以时日,在未来的某些领域,比如AI、比如新能源,我们一定能构建出自己的生态。真正让人担忧的,是我们的企业对待员工的态度,在长期劳动密集型生产的惯性下,我们一直践行的是道格拉斯·麦格雷戈的X理论,以胡萝卜加大棒的方式管理着员工,但随着中国的产业升级, 我们越来越需要Y理论,人的创造力和潜能的激发,才是大国博弈致胜最关键的要素。未来某一天,当职场35岁斩杀线消失了,那我们就迈出了对人才尊重的第一步。来源:https://www.wubayue.com

<全文完>

赞同 0
反对 0
登录注册会员 后发表评论。
评论列表