一、前言
当提到"前端系统设计" ,我想大部分小伙伴都有类似的疑问:前端系统设计?有必要吗?有啥好设计的?
今天基于这些疑问,本人结合自身的一些实践案例、思考总结,给大家做一次分享。希望能够帮助大家,具象对前端系统设计的想象、建立前端系统设计的基本认识、识别出前端工作中的设计内容。
分享内容共分为四个章节:
- 系统设计的基本概念
- 前端系统设计的必要性
- 前端系统设计包含的内容
- 如何提高系统设计能力
二、系统设计的基本概念
本章节以"系统设计"展开,描述系统设计是什么,并梳理其中的相关概念与名词
1.1 系统设计是什么
系统设计是根据系统分析的结果,运用系统科学的思想和方法,设计出能最大限度满足所要求的目标 (或目的) 的新系统的过程。系统设计内容,包括确定系统功能、设计方针和方法,产生理想系统并作出草案,通过收集信息对草案作出修正产生可选设计方案,将系统分解为若干子系统,进行子系统和总系统的详细设计并进行评价,对系统方案进行论证并作出性能效果预测。
上面是百科中对"系统设计"的定义,拆解出语句骨干:系统设计就是设计出 满足需求目标的新系统 的过程。
这个过程一般处于研发流程中的"需求分析"之后与"编码"之前,交付物是《系统概要设计说明书》。只是说我们前端较少参与该过程,对它还比较陌生。
1.2 系统设计的分类
咱们开发人员,不可避免的会经常会听到各种xx设计的名词,初次听到可能会让人疑惑、傻傻分不清楚,其实它们是对系统设计不同层次、不同维度的表达。这里提纲挈领的对这些名词做一个归类,以快速的帮助我们建立对系统设计的整体认识。
- 按设计的阶段分为:概要设计、详细设计
- 按设计的层次分为:总体/架构设计、程序结构设计
- 按设计的领域分为:后端设计、前端设计
- 按设计的内容分为:接口设计、数据库设计、部署设计、组件设计
下面我们就其中有关联、较模糊或存在争议的概念名词,做一些比较、澄清。
1.3 架构设计与程序结构设计
1.3.1 架构设计
架构设计是指在软件开发过程中,对系统的顶层结构进行规划和决策的过程。比如:
-
架构模式的抉择:单体架构还是微前端架构、单页应用还是多页应用、服务端渲染还是客户端渲染
-
系统架构图的绘制:从全局视角描述平台的系统构成、外部依赖和各个子系统的职责边界以及它们之间的关系。
1.3.2 程序结构设计
程序结构设计更侧重于软件的内部结构,即代码层面的组织和布局。比如:
- 关键业务流程
- 组件的划分与封装
1.3.3 二者的区别
二者在系统设计中的范围和层次不同
- 范围:架构设计关注于系统的宏观结构,而程序结构设计关注于代码层面的微观结构。
- 层次:架构设计通常在程序结构设计之前进行,为程序结构提供指导和约束。
总之,架构设计和程序结构设计是两个在软件开发过程中至关重要的环节,它们相互配合、相互支持,只是各自有不同的目标和关注点。架构设计为程序设计提供了宏观的指导和框架 ,侧重于从整体上把握系统的功能和结构,而程序设计则是实现系统设计的具体方法和手段,更关注程序内部的实现细节。
1.4 前端与后端系统设计
常规的web应用一般会由客户端和服务端两部分组成。前端系统设计,指的是其中客户端应用的系统设计,相应的服务端的设计就是后端系统设计。
这里着重介绍一下二者的区别,帮助我们顺着后端设计的"藤"摸摸前端设计的"瓜"。
1.4.1 关注的对象不同
后端:接口设计、数据库设计、部署设计
前端:组件设计、页面设计
1.4.2 面对的问题不同
后端:大流量、高并发、数据同步、数据安全、服务治理
前端:多端运行、浏览器兼容性、屏幕适配、主题定制、页面性能、国际化
1.4.3 设计侧重点不同
它们遵循一致的设计原则,但侧重点略有不同
后端:
- 可扩展性:多节点部署的设计,支持把系统的不同组件或服务部署在多个服务器或计算节点上,允许在需求增长时通过增加资源来提升性能和容量。
- 容错性:负载均衡的设计,会把用户请求均匀地分配到各个节点上,防止单个节点过载。
前端:
- 可访问性:离线访问、对残障用户友好的无障碍设计
- 一致性:在UI设计中保持一致性,颜色、字体、按钮等元素的一致性
- 复用性:组件化开发
PS:这些差异,时刻提醒我们设计时不能生搬硬套,应该因地制宜、具体情况具体分析。
三、前端系统设计的必要性
2.1 前端到底需不需要设计
在过去的很长一段时间,或者现有的绝大部分系统,都是没有前端设计的。这一点从你看过的《系统概要设计说明书》中可以窥探一二,里面的内容大多是系统整体设计(架构图)、接口设计、数模设计、关键业务流程,压根没有前端应用的位置和内容体现。
不可避免的,有不少人(比如曾经的我)就会基于过往经验,发出"前端压根就不需要设计"这样的声音,因为没有它,我们依然做了很多的项目,且没见出现有很大的问题。那么前端到底需要需要设计呢?
现在的我的答案是:需要,但要不要以文档的形式体现,这取决于"复杂度"以及"是否需要协作"。
原因是,在前端应用相对简单的过去,它作为子系统不展开描述是合理的,但时代变了。随着技术的演进,我们能明显的感受到,前端应用在不断的承载、容纳更多、更复杂的功能。
那些软件危机上演过的问题,在前端应用中日益凸显。即便在我们已经默认选用组件化框架,在MVVM模式加持的情况下。
- 系统规模庞大,内部耦合严重,开发效率低;
- 系统耦合严重,牵一发动全身,后续修改和扩展困难;
- 系统逻辑复杂,容易出问题,出问题后很难排查和修复。
基于良好的设计,开展编码工作,能够帮助我们有效规避或延缓上述问题。
2.2 设计的价值和作用
前端开发人员受限于编程语言的特性 和专业出现较晚,普遍没有设计的习惯和意识。我们非常有必要构建对前端系统设计必要性的统一认识,这有助于我们更积极的开展设计活动(这在后端通常是不需要的,他们习以为常)。
关于前端系统设计的价值和作用,我总结了三点:
-
帮助开发人员提前构思,避免边想边干
有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要"动大手术"来修改它,相信每个有过类似经历的人都一定深知那种感受一越改越乱,越改越着急。
编码之前的设计,可以让我们在开发之前,将技术上不确定的点确定好,以减少后续开发出现风险和问题的可能性。
-
阐明设计思路和方案,方便组内成员了解你的设计,以便帮助你判断设计方案的优劣、分析你方案中潜在的需求和影响
-
可以帮助其他成员或者自己了解你的系统,有利于将来的持续迭代,或者问题的快速定位和解决
系统设计特别是架构层面的信息很难从具体的代码中获取,单独的表述有利于团队成员快速了解你的系统。把复杂处理逻辑图形化描述,也有利于将来问题的快速定位和解决。
四、前端系统设计包含的内容
3.1 架构设计
3.1.1 整体架构
在这个章节,一方面我们需要结合业务实际问题和需要,抉择系统的顶层设计。比如 单体架构还是微前端、单页应用还是多页应用、服务端渲染还是客户端渲染。
另一方面我们还需要画一些架构图,从全局视角描述平台的系统构成、外部依赖和各个子系统的职责边界以及它们之间的关系。
3.1.2 应用架构
在这一章节,前端一般会以分层架构的形式,表达应用中包含的技术组件,基础组件、业务组件、公共方法等
3.1.3 不要局限于此
架构没有银弹,除了通用的架构抉择外,我们往往需要根据产品实际面临的问题,做出改进。
3.2 技术选型
在架构设计之后,我们就需要考虑使用何种技术实现架构目标。比如微前端架构,选择qiankun还是micor-app;组件化/数据驱动开发框架,选择vue还是react; 跨端开发框架,选择uni-app还是reactNative。这就是在技术选型。
只是在大多数情况下,我们基于项目要求或者个人惯性,被动的默认了某一种。
且大多时候选定的技术框架都会或多或少的包含一揽子架构决策,比如我们使用vue框架开发,被动的就是组件化软件开发方法、MVVM软件设计模式。架构层面,我们自主技术选型的机会并不多。
更多的时候,我们是基于某项具体问题或需求,选择使用何种插件,比如富文本编辑器、比如node应用中的中间件等。这里就不过多举例了。
3.3 关键技术方案
这一章节我们需要基于产品特定需求,制定关键技术方案。
前端常见的关键技术方案有:权限控制流程、屏幕适配方案、离线访问方案等。
这里结合自身经验,分享两个浅显易懂的案例。
3.3.1 例子一:差异化配置
之前给银行打工的时候做过一个产品,它在不同的渠道/端(手机银行、微信银行和浏览器)存在定制、差异化需求。比如不同的渠道,购买是否需要交易密码、是否需要短信验证码、是否能直接展示余额、是否允许支取等都是不一样的。
基于此我们做了 "基础功能可配置"的设计
arduino
// 抽象出的差异功能
{
needPassWord: true, // 购买是否需要交易密码
needSendCode: false, // 购买是否需要短信验证
canDirectShowAmount: true, // 是否可以直接显示月
allowDraw: true, // 是否允许支取
canEditBenefit: false // 是否可以维护结息账户
}
这样的好处是我们在业务处理时,只需关注功能的开关,而无需关注是哪个渠道,实现了"业务功能和渠道的解耦"。
kotlin
onPwdInputOver (password) {
this.password = password
-- // 基于渠道判断
-- if (isWx||isAPP) {
++ // 基于功能开关判断
++ if (this.$config.needSendCode) {
this.isShowMsgCode = true
} else {
this.submit()
}
}
两种代码的在可读性和可维护性上有明显的差异,特别是当渠道、差异越来越多的时候。
3.4 组件设计
在前端vue一把梭的当下,组件化几乎成为了我们前端开发的本能。在拿到UI设计稿之后,我们会下意识识别里面有哪些可复用的组件,并做简单的划分。这就是组件设计。
我这里把组件设计的过程拆分为两个环节:①组件的识别与划分;②组件的定义与API设计。
3.4.1组件的识别与划分
组件的拆分无外乎两种目的:一种是复用、一种是降低复杂度。我们基于这两项目标,就能快速、直观的识别出前端应用中需要的组件。
拿一个常见的搜索框组件举例
应用范围:首页、页面A、页面B、页面C、...
功能说明:吸顶;支持62/38px两种高度;支持自定义palceholder;派发点击搜索事件。
项目中多个页面都需要使用,这种就是显然就是基于复用的考量,而划分的组件。
3.4.2 组件的定义与API设计
组件设计的内容无非是定义组件的功能、约定组件的职责边界,以及API说明。沿用上面搜索组件的例子,设计如下:
Props、event、插槽,都是我们耳熟能详的东西,这里就不多赘述了。
3.4.3 一些经验教训
分享、探讨一些个人遇到的问题和总结的经验教训。
-
不要过度拆分
下沉/封装的组件不是越多越好,每一次的封装、每一层的下沉,都是有代价、有副作用的。
如果嵌套层级过多,势必会带来组件间跨层级传参、通信困难的问题,也势必会增加开发人员理解和维护代码的成本。
当我们拆分带来的复杂度,大于拆分降低的复杂度时,就得不偿失了。
个人建议是组件不要抽象、嵌套超过三层。
-
不要忽略使用便捷性的考虑
3.5 页面设计
页面设计关注的内容一般有页面构成、关键业务处理逻辑等。
我个人没有很典型的案例可以分享,这里仅列一些通用的内容,大家借鉴一二。
- 页面中使用的组件
- 页面中要调用的接口
- 页面内复杂业务处理流程
3.6 非功能需求
本章节我们需要对系统中哪些非功能需求做对应的设计和实现方案,比如浏览器兼容性、页面性能、构建与部署等。
在实际工作中较少涉及,这里简单提一下。提醒我们在做系统设计时一定要多维度、全面的思考,在关注功能性需求之余,不能忽略非功能层面的思考和设计。
3.7 一些个人见解
-
不是所有的系统、应用都需要设计
判断是否需要设计的唯一准则是:复杂度。比如在逻辑不大发生变化的地 方,没有必要去做过多的设计,应用各种花俏的设计模式等浪费时间。
当业务的复杂度提升到必须由更复杂的技术架构承载时,对架构的研究才有意义。
-
不设计不意味着快
成本并没有消失,边想边干推翻重来只会让你更慢。
-
设计没有标准答案,只有"探索与权衡",合适就是最好的
合适原则:避免过度设计,适合自己的才是最好的。
-
不要坚持只有一种可能的解决方案
好的问题通常有几种可能的解决方案,每种解决方案是否合适取决于具体情况。即使其他解决方案明显不好,也要提及并简要解释为什么不好。
-
系统设计不应该只包含结果
仅仅在系统设计中留下最后的结果是不够的,如何推导出这个结果,其"过程"也同样重要。当前的系统设计并不是终点,未来也会有优化或重构。对于未来从事这些工作的同事来说,系统设计中所留下的"设计思路"是非常有价值的。我们有时也会看到很多系统在重构后,并没有比以前的 更好,甚至在很多地方重蹈了之前的错误,这和系统设计文档 中"设计思路"的缺失有很大关系。
-
当你采用一项设计去降低复杂度的时候,必然会带来新的复杂度
不管是软件工程还是架构师设计 其底层逻辑都是 "分而治之"。但拆分势必带来系统复杂度的提升,集成、通信成本。
-
尽量避免一人设计一人开发
特别是详细设计,每个人的思维逻辑与编程习惯天差地别,它会带来昂贵的沟通、理解成本。
五、如何提高系统设计能力
听一场培训、看一篇文章,绝对不可能让我们凭空获取某项能力, 系统设计能力的获取或提升,需要下功夫,以下是我对此的一些建议。
5.1 多学
学习基础知识、学习设计原则、学习架构模式、学习设计模式......
和"你永远无法赚到你认知之外的钱"一个道理,你也做不出认知之外的设计。只有充分了解、深刻理解浏览器渲染原理、编程语言特性、HTTP这些基础知识的前提下,才能制定出适用于前端并且与Web整体架构相契合的设计方案。
在基本功足够扎实的前提下,推荐一些学习资源
整洁代码:《代码的艺术》、《代码整洁之道》、《重构:改善既有代码的设计》
设计模式:《JavaScript设计模式与开发实践》、《架构整洁之道》
架构设计:《前端架构从入门到微前端》、前端基础建设与架构30讲、《从零开始学架构》
5.2 多看
阅读优秀代码:研究优秀的开源项目,理解它们的设计决策,参看它们的API设计。
如果我们一上来就去啃源码,难度极高。建议从开源作者分享的系列文章起手,然后再结合源码分析的文章啃啃vue之类的知名框架,这样由易到难的顺序可以有效避免被劝退。
墙裂推荐vue-element-admin作者的《手摸手,带你用vue撸后台系列》文章,还有 Mall-Cook作者的《从零开始搭建低代码平台》系列文章。
此外,利用好GitHub Copilot、通义灵码、MarsCode等AI编程助手的代码解释功能,能够降低理解学习难度。
5.3 多练
学而不思则罔,思而不学则殆。知识的获取,是学习+实践反复揉搓的过程。我们需要把握日常工作中的每一次设计需要,去思考、去实践。
需要提醒的是,不是需要写入设计文档的才算设计,我们接到的每一个需求,开发的每一个功能,都可以进行设计上的思考。分析已有的实现是怎样的、计划做怎样的变更、变更的影响有哪些、有没有更好的处理方式,等我们养成先想再做、先设计再开发的习惯后,你的设计能力会在不知不觉间水涨船高。
六、结语
很多前端开发(包括我自己)很难迈出第一步的原因在于,思维上还不习惯区分设计和编码。
现在、就在此时此刻,在你心里把设计和编码的那道线画出来,打开系统设计的大门,准备好迎接挑战,享受创造的过程吧!