Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >我独到的技术见解--大型前端项目的常见问题和解决方案

我独到的技术见解--大型前端项目的常见问题和解决方案

原创
作者头像
被删
发布于 2024-02-02 00:20:38
发布于 2024-02-02 00:20:38
6K1
举报

或许你会感到疑惑,怎样的项目算是大型前端项目呢?我自己的理解是,项目的开发人员数量较多(10 人以上?)、项目模块数量/代码量较多的项目,都可以理解为大型前端项目了。

在前端业务领域中,除了大型开源项目(热门框架、VsCode、Atom 等)以外,协同编辑类应用(比如在线文档)、复杂交互类应用(比如大型游戏)等,都可以称得上是大型前端项目。对于这样的大型前端项目,我们在开发中常常遇到的问题包括:

  1. 项目代码量大,不管是编译、构建,还是浏览器加载,耗时都较多、性能也较差。
  2. 各个模块间耦合严重,功能开发、技术优化、重构工作等均难以开展。
  3. 项目交互逻辑复杂,问题定位、BUG 修复等过程效率很低,需要耗费不少精力。
  4. 项目规模太大,每个人只了解其中一部分,需求改动到不熟悉的模块时常常出问题。

其实大家也能看到,大型前端项目中主要的问题便是“管理混乱”。所以我个人觉得,对于代码管理得很混乱的项目,你也可以认为是“大型”前端项目(笑)。

问题 1:项目代码量过大

对于代码量过大(比如高达 30W 行)的项目,如果不做任何优化直接全量跑在浏览器中,不管是加载耗时增加导致用户等待时间过久,还是内存占用过高导致用户交互卡顿,都会给用户带来不好的体验。

性能优化的解决方案在《前端性能优化--归纳篇》一文中也有介绍。其中,对于代码量、文件过多这样的性能优化,可以总结为两个字:

  • :拆模块、拆公共库、拆组件库
  • :分流程、分步骤

项目代码量过大不仅仅会影响用户体验,对于开发来说,代码开发过程中同样存在糟糕的体验:由于代码量过大,开发的本地构建、编译都变得很慢,甚至去打水 + 上厕所回来之后,代码还没编译完。

从维护角度来看,一个项目的代码量过大,对开发、编译、构建、部署、发布流程都会同样带来不少的压力。因此除了浏览器加载过程中的代码拆分,对项目代码也可以进行拆分,一般来说有两种方式:

1. multirepo,多仓库模块管理,通过工作流从各个仓库拉取代码并进行编译、打包

  • 优点:模块可根据需要灵活选择各自的编译、构建工具;每个仓库的代码量较小,方便维护
  • 缺点:项目代码分散在各个仓库,问题定位困难(使用npm link有奇效);模块变动后,需要更新相关仓库的依赖配置(使用一致的版本控制和管理方式可减少这样的问题)

2. monorepo,单仓库模块管理,可使用 lerna 进行包管理

  • 优点:项目代码可集中进行管理,使用统一的构建工具;模块间调试方便、问题定位和修复相对容易
  • 缺点:仓库体积大,对构建工具和机器性能要求较高;对项目文件结构和管理、代码可测试和维护性要求较高;为了保证代码质量,对版本控制和 Git 工作流要求更高

两种包管理模式各有优劣,一般来说一个项目只会采用其中一种,但也可以根据具体需要进行调整,比如统一的 UI 组件库进行分仓库管理、核心业务逻辑在主仓库内进行拆包管理。

题外话:很多人常常在争论到底是单仓好还是多仓好,个人认为只要能解决开发实际痛点的仓,都是好仓,有时候过多的理论也需要实践来验证。

问题 2:模块耦合严重

不同的模块需要进行分工和配合,因此相互之间必然会产生耦合。在大型项目中,由于模块数量很多(很多时候也是因为代码量过多),常常会遇到模块耦合过于严重的问题:

  1. 模块职责和边界定义不清晰,导致模糊的工作可能存在多个模块内。
  2. 各个模块没有统一管理,导致模块在状态变更时需要手动通知相关模块。
  3. 模块间的通信方式设计不合理,导致全局事件满天飞、A 模块内直接调用 B 模块等问题,隐藏的引用和事件可能导致内存泄露。

对于模块耦合严重的模块,常见的解耦方案比如:

  • 使用事件驱动的方式,通过事件来进行模块间通信
  • 使用依赖倒置进行依赖解耦

事件驱动进行模块解耦

使用事件驱动的方式,可以快速又简单地实现模块间的解耦,但它常常又带来了更多的问题,比如:

  • 全局事件满天飞,不知道某个事件来自哪里,被多少地方监听了
  • 无法进行事件订阅的销毁管理,容易存在内存泄露的问题
  • 事件维护困难,增加和调整参数影响面广,容易触发 bug

依赖倒置进行模块解耦

我们还可以使用依赖倒置进行依赖解耦。依赖倒置原则有两个,包括:

  1. 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
  2. 抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口。

使用以上方式进行设计的模块,不会依赖具体的模块和细节,只按照约定依赖抽象的接口。

如果项目中有完善的依赖注入框架,则可以使用项目中的依赖注入体系,像 Angular 框架便自带依赖注入体系。依赖注入在大型项目中比较常见,对于各个模块间的依赖关系管理很实用,比如 VsCode 中就有使用到依赖注入。

VsCode:结合事件驱动与依赖倒置进行模块解耦

在 VsCode 中,我们也可以看到使用了依赖注入框架和标准化的Event/Emitter事件监听的方式,来对各个模块进行解耦(可参考《VSCode 源码解读:事件系统设计》):

  • 各个模块的生命周期(初始化、销毁)统一由框架进行管理:通过提供通用类Disposable,统一管理相关资源的注册和销毁
  • 模块间不直接引入和调用,而是通过声明依赖的方式,从框架中获取相应的服务并使用
  • 不直接使用全局事件进行通信,而是通过订阅具体服务的方式来处理:通过使用同样的方式this._register()注册事件和订阅事件,将事件相关资源的处理统一挂载到dispose()方法中

使用依赖注入框架的好处在于,各个模块之间不会再有直接联系。模块以服务的方式进行注册,通过声明依赖的方式来获取需要使用的服务,框架会对模块间依赖关系进行分析,判断某个服务是否需要初始化和销毁,从而避免了不必要的服务被加载。

在对模块进行了解耦之后,每个模块都可以专注于自身的功能开发、技术优化,甚至可以在保持对外接口不变的情况下,进行模块重构。

实际上,在进行代码编程过程中,有许多设计模式和理念可以参考,其中有不少的内容对于解耦模块间的依赖很有帮助,比如接口隔离原则、最少的知识原则/迪米特原则等。

除了解决问题,还要思考如何避免问题的发生。对于模块耦合严重这个问题,要怎么避免出现这样的情况呢?其实很依赖项目管理的主动意识和规范落地,比如:

  1. 项目规模调整后,对现有架构设计进行分析,如果不再合适则需要进行及时的调整和优化。
  2. 使用模块解耦的技术方案,将各个模块统一交由框架处理。
  3. 梳理各个模块的职责,明确每个模块负责的工作和提供的功能,确定各个模块间的边界和调用方式。

问题 3:问题定位效率低

在对模块进行拆分和解耦、使用了模块负责人机制、进行包拆分管理之后,虽然开发同学可以更加专注于自身负责模块的开发和维护,但有些时候依然无法避免地要接触到其它模块。

对于这样大型的项目,维护过程(熟悉代码、定位问题、性能优化等)由于代码量太多、各个函数的调用链路太长,以及函数执行情况黑盒等问题,导致问题定位异常困难。要是遇到代码稍微复杂点,比如事件反复横跳的,即使使用断点调试也能看到眼花,蒸汽眼罩都得多买一些(真的贵啊)。

对于这些问题,其实可以有两个优化方式:

  1. 维护模块指引文档,方便新人熟悉现有逻辑。文档主要介绍每个模块的职责、设计、相关需求,以及如何调试、场景的坑等。
  2. 尝试将问题定位过程进行自动化实现,比如模块负责人对自身模块执行的关键点进行标记(使用日志或者特定的断点工具),其他开发可根据日志或是通过开启断点的方式来直接定位问题

这个过程,其实是将模块负责人的知识通过工具的方式授予其他开发,大家可以快速找到某个模块经常出问题的地方、模块执行的关键点,根据建议和提示进行问题定位,可极大地提升问题定位的效率。

除了问题定位以外,各个模块和函数的调用关系、调用耗时也可以作为系统功能和性能是否有异常的参考。之前这块我也有简单研究过,可以参考《大型前端项目要怎么跟踪和分析函数调用链》。

因此,我们还可以通过将调用堆栈收集过程自动化、接入流水线,在每次发布前合入代码时执行相关的任务,对比以往的数据进行分析,生成系统性能和功能的风险报告,提前在发布前发现风险。

问题 4:项目复杂熟悉成本过高

即使在项目代码量大、项目模块过多、耦合严重的情况下,项目还在不断地进行迭代和优化。遇到这样的项目,基本上没有一个人能熟悉所有模块的所有细节,这会带来一些问题:

  • 对于新需求、新功能,开发无法完整地评估技术方案是否可以实现、会不会带来新的问题
  • 需求开发时需要改动不熟悉的代码,无法评估是否存在风险
  • 架构级别的优化工作,难以确定是否可以真正落地
  • 一些模块遗留的历史债务,由于工作进行过多次交接,相关逻辑已无人熟悉,无法进行处理

导致这些问题的根本原因有两个:

  • 开发无法专注于某个模块开发
  • 同一个模块可能被多个人调整和变更

对于这种情况,可以使用模块负责人的机制来对模块进行所有权分配,进行管理和维护:

  1. 每个开发都认领(或分配)一个或多个模块,并要求完全熟悉和掌握模块的细节,且维护文档进行说明。
  2. 对于需求开发、BUG 修复、技术优化过程中涉及到非自身的模块,需要找到对应模块的负责人进行风险评估和代码 Review。
  3. 模块的负责人负责自身模块的技术优化方案,包括性能优化、自动化测试覆盖、代码规范调整等工作。
  4. 对于较核心/复杂的模块,可由多个负责人共同维护,协商技术细节。

通过模块负责人机制,每个模块都有了对应的开发进行维护和优化,开发也可以专注于自身的某些模块进行功能开发。在人员离职和工作内容交接的时候,也可以通过文档 + 负责人权限的方式进行模块交接。

结束语

大型项目的这些痛点,其实只是我们工作中痛点的缩影。技术上能解决的问题都是小事,管理和沟通上的事情才更让人头疼。

除此之外,在我们的日常工作中,通常也会局限于某块功能的实现和某个领域的开发。如果这些内容并没有足够的深度可以挖掘,对个人的成长发展也可能会有限制。在这种情况下,我们还可以主动去了解和学习其它领域的知识,也可以主动承担起更多的工作内容。

查看Github有更多内容噢: https://github.com/godbasin

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
1 条评论
热度
最新
🍟
🍟
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
我独到的技术见解--技术方案的调研和设计过程
技术方案设计属于架构能力中的一种,当我们开始作为某些功能/应用的 Owner 或是技术负责人来参与项目时,便会面临独立完成技术方案的调研和设计这样的工作内容。
被删
2024/02/05
6811
我独到的技术见解--技术方案的调研和设计过程
谈谈依赖和解耦
大型项目总避免不了各种模块间相互依赖,如果模块之间耦合严重,随着功能的增加,项目到后期会越来越难以维护。今天我们一起来思考下,大家常说的代码解耦到底要怎么做?
被删
2024/08/01
3880
谈谈依赖和解耦
依赖倒置原则详解
在大型系统架构设计中,依赖倒置原则(Dependency Inversion Principle,DIP)被广泛视为增强系统灵活性和可维护性的核心原则之一。最近在架构设计审查中,我们经常遇到由于依赖关系设计不当导致的模块耦合问题,这些问题直接影响了系统的扩展性和可测试性。DIP 提供了一种思维框架,旨在通过抽象化依赖关系,构建更加稳健的系统架构。
井九
2024/10/12
1900
依赖倒置原则详解
代码中的解耦思维
解耦思维是一种设计和思考问题的方法,旨在将复杂的系统或问题拆分为独立的组件或子问题,以降低系统的耦合度和提高可扩展性。以下是一些关于解耦思维的要点:
明志德道
2023/12/31
7890
【技术创作101训练营】代码设计与单元测试
我们本次分享的主题是“设计原则与单元测试”。这两个概念对于每个开发人员都耳熟能详,但有多少同学可以在实际开发中真正落地并有效提高研发质量呢?代码的设计原则关注点在开发阶段,单元测试关注点在测试阶段,这两者又有什么联系呢?本次分享我将和大家一起探索其中的奥秘。
大耳鼠
2021/01/19
1K3
【技术创作101训练营】代码设计与单元测试
干货 | 关于前端构建大型知识应用,你知道多少?
与其说是构建大型应用,或许更多地是常用的应用构建吧。很多时候我们的项目在搭建的时候通常都不会定位为大型项目,但我们还是需要考虑到拓展性,或者说是在当项目开始变得较难维护的时候,要进行调整的一些方面。
腾讯NEXT学位
2019/03/04
1.2K0
干货 | 关于前端构建大型知识应用,你知道多少?
项目中的技术债务
身为一名程序员,我们经常会调侃自己每天的工作就是在屎山上拉屎。这里的屎山还有一个更好的名称,叫做技术债务。
被删
2024/07/02
6551
项目中的技术债务
在线文档的网络层开发思考--依赖关系梳理
最近在负责通用网络层的设计和开发,会记录该过程中的一些思考,本文主要介绍接入层设计过程中的一些依赖关系,以及处理这些依赖关系的一些思考。
被删
2024/02/01
4510
在线文档的网络层开发思考--依赖关系梳理
06.依赖倒置原则介绍
设计模式Git项目地址:https://github.com/yangchong211/YCDesignBlog
杨充
2025/03/11
1300
【ASP.NET Core 基础知识】--最佳实践和进阶主题--设计模式在ASP.NET Core中的应用
设计模式是在软件设计过程中反复出现的、经过验证的、可重用的解决问题的方法。它们是针对特定问题的通用解决方案,提供了一种在软件开发中可靠的指导和标准化方法。设计模式通常描述了一种在特定情景下的解决方案,包括了问题的描述、解决方案的结构以及相互之间的协作方式。设计模式有助于开发人员更有效地进行沟通、理解和实现复杂系统,同时还可以提高系统的可维护性、可扩展性和可重用性。
喵叔
2024/05/24
4800
干货 | Android工程模块化平台的设计
作者简介 张涛,饿了么资深Android工程师,“开源实验室”博主,Kotlin 技术推广者。2013年开始从事Android开发,带过团队,做过架构,写过应用,做过开源社区。目前在饿了么商户端负责应
携程技术
2018/07/05
1.2K0
我独到的技术见解--如何设计与管理一个前端项目
如果说基础知识的掌握是起跑线,那么使大家之间拉开差距的更多是前端项目开发经验和技能。对于一个项目来说,从框架选型和搭建,到项目维护、工程化和自动化、多人协作等各个方面,都需要我们在参与项目中不断地思考和改进,积累经验。
被删
2024/02/03
5061
我独到的技术见解--如何设计与管理一个前端项目
保姆级教程!Golang微服务简洁架构实战
导语 | 本文从简洁架构的理论出发,依托trpc-go目录规范,简单阐述了整体代码架构如何划分,具体trpc-go服务代码实现细节,和落地步骤,并讨论了和DDD的区别。文章源于我们组内发起的go微服务最佳实践的第一部分,希望从开发和阅读学习中总结出一套go微服务开发的方法论,互相分享一下在寻求最佳的实践过程中的思考和取舍的过程。本次主要讨论目录如何组织,目录的组织其实就是架构的设计,一套通用架构的设计,可以让开发专注于逻辑设计和具体场景的代码设计,好的架构设计可以预防代码的腐败,并且相关的规范操作简单,可以
腾讯云开发者
2022/03/10
4.6K0
大型前端项目的断点调试共享化和复用化实践
作者:enoyao,腾讯工程师 背景 随着我们项目越来越大,我们有可能需要维护很多的模块,我们腾讯文档 Excel 项目大模块有 10 几个,而每个大模块分别有 N 个小模块,每个大模块下的小模块都有主要的负责人在跟进模块问题。 这就会导致一个很大的问题是,模块负责人大部分情况只会关注自己模块的问题,而不甚了解其他负责人手上模块的具体问题。 比如:当我们有用户反馈使用复制粘贴有问题的时候,我们想要快速去定位这个问题,就只能找复制粘贴对应的模块负责人处理,如果复制粘贴模块负责人请假了,那么其他负责人
腾讯技术工程官方号
2020/10/10
8752
我独到的技术见解--从面试角度了解前端基础知识体系
这两年大裁员过后,带来了一系列的人员变动,常常面临着不受宠的被辞退了,能干的人跑了,剩下的人在努力维护着项目。于是乎老板们才发现人好像又不够了,然后又开始各种招人。机会一直都有,重要的还是得努力提升自己的能力,才能在这场战斗中存活下来。
被删
2024/02/04
5251
我独到的技术见解--从面试角度了解前端基础知识体系
1. Spring启示录
开闭原则是这样说的:在软件开发过程中应当对扩展开放,对修改关闭。也就是说,如果在进行功能扩展的时候,添加额外的类是没问题的,但因为功能扩展而修改之前运行正常的程序,这是忌讳的,不被允许的。因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试。这是相当麻烦的过程。导致以上问题的主要原因是:代码和代码之间的耦合度太高。如下图所示:
捞月亮的小北
2024/06/26
1030
1. Spring启示录
6大设计原则之依赖倒置原则
High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.
烟草的香味
2019/07/25
5690
两大绝招,教你为大型项目编写单元测试
推进过程自然困难重重,最大的障碍还是该系统的规模太大,代码质量太糟糕。为了更好地洞察代码状态,我通过SonarQube分析了该项目。由于规模太大,分析的机器也不太给力,整个代码静态分析耗费了惊人的1:58:52.282秒。
张逸
2023/03/23
7020
两大绝招,教你为大型项目编写单元测试
【愚公系列】2023年11月 面向对象设计原则(四)-依赖倒置原则(Dependence Inversion Principle DIP)
面向对象设计原则是一些通用的软件设计原则,用于指导软件设计人员开发高质量、可扩展、可维护的软件系统。这些原则的作用如下:
愚公搬代码
2023/11/25
2770
Java新人常问:什么是依赖倒置原则?万字案例给你讲懂!
Dependence Inversion Principle,DIP High level modules should not depend upon low level modules.Both should depend upon abstractions.高层模块不应该依赖低层模块,二者都应该依赖其抽象 Abstractions should not depend upon details.Details should depend upon abstractions.抽象不应该依赖细节;细节应该依赖抽象
JavaEdge
2021/12/07
1.6K0
Java新人常问:什么是依赖倒置原则?万字案例给你讲懂!
推荐阅读
相关推荐
我独到的技术见解--技术方案的调研和设计过程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档