找回密码
 注册
搜索
查看: 165|回复: 0

求放过!同事的前端代码我真的改不动了?

[复制链接]
52RD网友  发表于 2024-11-27 16:52:11 |阅读模式
​在日常开发中,我们经常会遇到需要修改同事代码的情况。有时可能会花费很长时间却只改动了几行代码,而且改完后还可能引发新的bug。我们聊聊导致代码难以维护的常见原因,以及相应的解决方案。

常见问题及解决方案
1. 单文件代码过长
问题描述:

单个文件动辄几千行代码
包含大量DOM结构、JS逻辑和样式
需要花费大量时间才能理解代码结构
解决方案: 

将大文件拆分成多个小模块,每个模块负责独立的功能。
以一个品牌官网为例,可以这样拆分:

  1. <template>
  2.   <div>
  3.     <Header/>
  4.     <main>
  5.       <Banner/>
  6.       <AboutUs/>
  7.       <Services/> 
  8.       <ContactUs/>
  9.     </main>
  10.     <Footer/>
  11.   </div>
  12. </template>
复制代码


2. 模块耦合严重
问题描述:

模块之间相互依赖
修改一处可能影响多处
难以进行单元测试
❌ 错误示例:

  1. <script>
  2. export default {
  3.   methods: {
  4.     getUserDetail() {
  5.       // 错误示范:多处耦合
  6.       let userId = this.$store.state.userInfo.id 
  7.         || window.currentUserId
  8.         || this.$route.params.userId;
  9.       
  10.       getUser(userId).then(res => {
  11.         // 直接操作子组件内部数据
  12.         this.$refs.userBaseInfo.data = res.baseInfo;
  13.         this.$refs.userArticles.data = res.articles;
  14.       })
  15.     }
  16.   }
  17. }
  18. </script>
复制代码


✅ 正确示例:

  1. <template>
  2.   <div>
  3.     <userBaseInfo :base-info="baseInfo"/>
  4.     <userArticles :articles="articles"/>
  5.   </div>
  6. </template>

  7. <script>
  8. export default {
  9.   props: ['userId'],
  10.   data() {
  11.     return {
  12.       baseInfo: {},
  13.       articles: []
  14.     }
  15.   },
  16.   methods: {
  17.     getUserDetail() {
  18.       getUser(this.userId).then(res => {
  19.         this.baseInfo = res.baseInfo;
  20.         this.articles = res.articles;
  21.       })
  22.     }
  23.   }
  24. }
  25. </script>
复制代码


 3. 职责不单一

问题描述:

一个方法承担了多个功能
代码逻辑混杂在一起
难以复用和维护
❌ 错误示例:
 

  1. <script>
  2. export default {
  3.   methods: {
  4.     getUserData() {
  5.       userService.getUserList().then(res => {
  6.         this.userData = res.data;
  7.         // 一个方法中做了太多事情
  8.         let vipCount = 0;
  9.         let activeVipsCount = 0;
  10.         let activeUsersCount = 0;
  11.         
  12.         this.userData.forEach(user => {
  13.           if(user.type === 'vip') {
  14.             vipCount++
  15.           }
  16.           if(dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))) {
  17.             if(user.type === 'vip') {
  18.               activeVipsCount++
  19.             }
  20.             activeUsersCount++
  21.           }
  22.         })
  23.         
  24.         this.vipCount = vipCount;
  25.         this.activeVipsCount = activeVipsCount;
  26.         this.activeUsersCount = activeUsersCount;
  27.       })
  28.     }
  29.   }
  30. }
  31. </script>
复制代码


✅ 正确示例:

  1. <script>
  2. export default {
  3.   computed: {
  4.     // 将不同统计逻辑拆分为独立的计算属性
  5.     activeUsers() {
  6.       return this.userData.filter(user => 
  7.         dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))
  8.       )
  9.     },
  10.     vipCount() {
  11.       return this.userData.filter(user => user.type === 'vip').length
  12.     },
  13.     activeVipsCount() {
  14.       return this.activeUsers.filter(user => user.type === 'vip').length
  15.     },
  16.     activeUsersCount() {
  17.       return this.activeUsers.length
  18.     }
  19.   },
  20.   methods: {
  21.     getUserData() {
  22.       // 方法只负责获取数据
  23.       userService.getUserList().then(res => {
  24.         this.userData = res.data;
  25.       })
  26.     }
  27.   }
  28. }
  29. </script>
复制代码


 4. 代码复制代替复用
问题描述:

发现相似功能就直接复制代码
维护时需要修改多处相同的代码
容易遗漏修改点,造成bug
解决方案:

提前抽取公共代码
将重复逻辑封装成独立函数或组件
通过参数来处理细微差异
 5. 强行复用/假装复用

问题描述:
将不该复用的代码强行糅合在一起,比如:

将登录弹窗和修改密码弹窗合并成一个组件
把一个实体的所有操作(增删改查)都塞进一个方法
❌ 错误示例:

 

  1. <template>
  2.   <div>
  3.     <UserManagerDialog ref="UserManagerDialog"/>
  4.   </div>
  5. </template>

  6. <script>
  7. export default {
  8.   methods: {
  9.     addUser() {
  10.       this.$refs.UserManagerDialog.showDialog({
  11.         type: 'add'
  12.       })
  13.     },
  14.     editName() {
  15.       this.$refs.UserManagerDialog.showDialog({
  16.         type: 'editName'
  17.       })
  18.     },
  19.     deleteUser() {
  20.       this.$refs.UserManagerDialog.showDialog({
  21.         type: 'delete'
  22.       })
  23.     }
  24.   }
  25. }
  26. </script>
复制代码


✅ 正确做法:

不同业务逻辑使用独立组件
只抽取真正可复用的部分(如表单验证规则、公共UI组件等)
保持每个组件职责单一
6. 破坏数据一致性

问题描述: 使用多个关联状态来维护同一份数据,容易造成数据不一致。
❌ 错误示例:
 

  1. <script>
  2. export default {
  3.   data() {
  4.     return {
  5.       sourceData: [], // 原始数据
  6.       tableData: [], // 过滤后的数据
  7.       name: '', // 查询条件
  8.       type: ''
  9.     }
  10.   },
  11.   methods: {
  12.     nameChange(name) {
  13.       this.name = name;
  14.       // 手动维护 tableData,容易遗漏
  15.       this.tableData = this.sourceData.filter(item => 
  16.         (!this.name || item.name === this.name) && 
  17.         (!this.type || item.type === this.type)
  18.       );
  19.     },
  20.     typeChange(type) {
  21.       this.type = type;
  22.       // 重复的过滤逻辑
  23.       this.tableData = this.sourceData.filter(item => 
  24.         (!this.name || item.name === this.name) && 
  25.         (!this.type || item.type === this.type)
  26.       );
  27.     }
  28.   }
  29. }
  30. </script>
复制代码


✅ 正确示例:

  1. <script>
  2. export default {
  3.   data() {
  4.     return {
  5.       sourceData: [],
  6.       name: '',
  7.       type: ''
  8.     }
  9.   },
  10.   computed: {
  11.     // 使用计算属性自动维护派生数据
  12.     tableData() {
  13.       return this.sourceData.filter(item =>
  14.         (!this.name || item.name === this.name) && 
  15.         (!this.type || item.type === this.type)
  16.       )
  17.     }
  18.   }
  19. }
  20. </script>
复制代码


 7. 解决方案不“正统”

问题描述:
使用不常见或不合理的方案解决问题,如:

直接修改 node_modules 中的代码,更好的实践:

优先使用框架/语言原生解决方案
遵循最佳实践和设计模式
进行方案评审和代码审查
对于第三方库的 bug:
 向作者提交 issue 或 PR
 将修改后的包发布到企业内部仓库
*寻找替代方案
使用 JS 实现纯 CSS 可实现的效果


❌ 错误示例:

  1. // 不恰当的鼠标悬停效果实现
  2. element.onmouseover = function() {
  3.   this.style.color = 'red';
  4. }
  5. element.onmouseout = function() {
  6.   this.style.color = 'black';
  7. }
复制代码


✅ 正确示例:

  1. /* 使用 CSS hover 伪类 */
  2. .element:hover {
  3.   color: red;
  4. }
复制代码


过度使用全局变量

如何进行代码重构
重构的原则

不改变软件功能
小步快跑,逐步改进
边改边测试
随时可以暂停
重构示例
以下展示如何一步步重构上面的统计代码:

第一步:抽取 vipCount

删除data中的vipCount
增加计算属性vipCount,将getUserData中关于vipCount的逻辑挪到这里
删除getUserData中vipCount的计算逻辑
  1. <script>
  2. export default {
  3.   computed: {
  4.     vipCount() {
  5.       return this.userData.filter(user => user.type === 'vip').length
  6.     }
  7.   },
  8.   methods: {
  9.     getUserData() {
  10.       userService.getUserList().then(res => {
  11.         this.userData = res.data;
  12.         let activeVipsCount = 0;
  13.         let activeUsersCount = 0;
  14.         
  15.         this.userData.forEach(user => {
  16.           if(dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))) {
  17.             if(user.type === 'vip') {
  18.               activeVipsCount++
  19.             }
  20.             activeUsersCount++
  21.           }
  22.         })
  23.         
  24.         this.activeVipsCount = activeVipsCount;
  25.         this.activeUsersCount = activeUsersCount;
  26.       })
  27.     }
  28.   }
  29. }
  30. </script>
复制代码


完成本次更改后,测试下各项数据是否正常,不正常查找原因,正常我们继续。

第二步:抽取 activeVipsCount

删除data中的activeVipsCount
增加计算属性activeVipsCount,将getUserData中activeVipsCount的计算逻辑迁移过来
删除getUserData中关于activeVipsCount计算的代码
  1. <script>
  2. export default {
  3.   computed: {
  4.     vipCount() {
  5.       return this.userData.filter(user => user.type === 'vip').length
  6.     },
  7.     activeVipsCount() {
  8.       return this.userData.filter(user => 
  9.         user.type === 'vip' && 
  10.         dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))
  11.       ).length
  12.     }
  13.   },
  14.   methods: {
  15.     getUserData() {
  16.       userService.getUserList().then(res => {
  17.         this.userData = res.data;
  18.         let activeUsersCount = 0;
  19.         
  20.         this.userData.forEach(user => {
  21.           if(dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))) {
  22.             activeUsersCount++
  23.           }
  24.         })
  25.         
  26.         this.activeUsersCount = activeUsersCount;
  27.       })
  28.     }
  29.   }
  30. }
  31. </script>
复制代码



最终版本:
 

  1. <script>
  2. export default {
  3.   computed: {
  4.     activeUsers() {
  5.       return this.userData.filter(user => 
  6.         dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))
  7.       )
  8.     },
  9.     vipCount() {
  10.       return this.userData.filter(user => user.type === 'vip').length
  11.     },
  12.     activeVipsCount() {
  13.       return this.activeUsers.filter(user => user.type === 'vip').length
  14.     },
  15.     activeUsersCount() {
  16.       return this.activeUsers.length
  17.     }
  18.   },
  19.   methods: {
  20.     getUserData() {
  21.       userService.getUserList().then(res => {
  22.         this.userData = res.data;
  23.       })
  24.     }
  25.   }
  26. }
  27. </script>
复制代码



 总结

要写出易维护的代码,需要注意:

1. 合理拆分模块,避免单文件过大
2. 降低模块间耦合
3. 保持职责单一
4. 使用计算属性处理派生数据
5. 定期进行代码重构

记住:重构是一个渐进的过程,不要试图一次性完成所有改进。在保证功能正常的前提下,通过小步快跑的方式逐步优化代码质量。

作者:Cyrus丶

高级模式
B Color Image Link Quote Code Smilies

本版积分规则

Archiver|手机版|小黑屋|52RD我爱研发网 ( 沪ICP备2022007804号-2 )

GMT+8, 2024-12-23 04:25 , Processed in 0.057897 second(s), 16 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表