在日常开发中,我们经常会遇到需要修改同事代码的情况。有时可能会花费很长时间却只改动了几行代码,而且改完后还可能引发新的bug。我们聊聊导致代码难以维护的常见原因,以及相应的解决方案。
常见问题及解决方案
1. 单文件代码过长
问题描述:
单个文件动辄几千行代码
包含大量DOM结构、JS逻辑和样式
需要花费大量时间才能理解代码结构
解决方案:
将大文件拆分成多个小模块,每个模块负责独立的功能。
以一个品牌官网为例,可以这样拆分:
- <template>
- <div>
- <Header/>
- <main>
- <Banner/>
- <AboutUs/>
- <Services/>
- <ContactUs/>
- </main>
- <Footer/>
- </div>
- </template>
复制代码
2. 模块耦合严重
问题描述:
模块之间相互依赖
修改一处可能影响多处
难以进行单元测试
❌ 错误示例:
- <script>
- export default {
- methods: {
- getUserDetail() {
- // 错误示范:多处耦合
- let userId = this.$store.state.userInfo.id
- || window.currentUserId
- || this.$route.params.userId;
-
- getUser(userId).then(res => {
- // 直接操作子组件内部数据
- this.$refs.userBaseInfo.data = res.baseInfo;
- this.$refs.userArticles.data = res.articles;
- })
- }
- }
- }
- </script>
复制代码
✅ 正确示例:
- <template>
- <div>
- <userBaseInfo :base-info="baseInfo"/>
- <userArticles :articles="articles"/>
- </div>
- </template>
- <script>
- export default {
- props: ['userId'],
- data() {
- return {
- baseInfo: {},
- articles: []
- }
- },
- methods: {
- getUserDetail() {
- getUser(this.userId).then(res => {
- this.baseInfo = res.baseInfo;
- this.articles = res.articles;
- })
- }
- }
- }
- </script>
复制代码
3. 职责不单一
问题描述:
一个方法承担了多个功能
代码逻辑混杂在一起
难以复用和维护
❌ 错误示例:
- <script>
- export default {
- methods: {
- getUserData() {
- userService.getUserList().then(res => {
- this.userData = res.data;
- // 一个方法中做了太多事情
- let vipCount = 0;
- let activeVipsCount = 0;
- let activeUsersCount = 0;
-
- this.userData.forEach(user => {
- if(user.type === 'vip') {
- vipCount++
- }
- if(dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))) {
- if(user.type === 'vip') {
- activeVipsCount++
- }
- activeUsersCount++
- }
- })
-
- this.vipCount = vipCount;
- this.activeVipsCount = activeVipsCount;
- this.activeUsersCount = activeUsersCount;
- })
- }
- }
- }
- </script>
复制代码
✅ 正确示例:
- <script>
- export default {
- computed: {
- // 将不同统计逻辑拆分为独立的计算属性
- activeUsers() {
- return this.userData.filter(user =>
- dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))
- )
- },
- vipCount() {
- return this.userData.filter(user => user.type === 'vip').length
- },
- activeVipsCount() {
- return this.activeUsers.filter(user => user.type === 'vip').length
- },
- activeUsersCount() {
- return this.activeUsers.length
- }
- },
- methods: {
- getUserData() {
- // 方法只负责获取数据
- userService.getUserList().then(res => {
- this.userData = res.data;
- })
- }
- }
- }
- </script>
复制代码
4. 代码复制代替复用
问题描述:
发现相似功能就直接复制代码
维护时需要修改多处相同的代码
容易遗漏修改点,造成bug
解决方案:
提前抽取公共代码
将重复逻辑封装成独立函数或组件
通过参数来处理细微差异
5. 强行复用/假装复用
问题描述:
将不该复用的代码强行糅合在一起,比如:
将登录弹窗和修改密码弹窗合并成一个组件
把一个实体的所有操作(增删改查)都塞进一个方法
❌ 错误示例:
- <template>
- <div>
- <UserManagerDialog ref="UserManagerDialog"/>
- </div>
- </template>
- <script>
- export default {
- methods: {
- addUser() {
- this.$refs.UserManagerDialog.showDialog({
- type: 'add'
- })
- },
- editName() {
- this.$refs.UserManagerDialog.showDialog({
- type: 'editName'
- })
- },
- deleteUser() {
- this.$refs.UserManagerDialog.showDialog({
- type: 'delete'
- })
- }
- }
- }
- </script>
复制代码
✅ 正确做法:
不同业务逻辑使用独立组件
只抽取真正可复用的部分(如表单验证规则、公共UI组件等)
保持每个组件职责单一
6. 破坏数据一致性
问题描述: 使用多个关联状态来维护同一份数据,容易造成数据不一致。
❌ 错误示例:
- <script>
- export default {
- data() {
- return {
- sourceData: [], // 原始数据
- tableData: [], // 过滤后的数据
- name: '', // 查询条件
- type: ''
- }
- },
- methods: {
- nameChange(name) {
- this.name = name;
- // 手动维护 tableData,容易遗漏
- this.tableData = this.sourceData.filter(item =>
- (!this.name || item.name === this.name) &&
- (!this.type || item.type === this.type)
- );
- },
- typeChange(type) {
- this.type = type;
- // 重复的过滤逻辑
- this.tableData = this.sourceData.filter(item =>
- (!this.name || item.name === this.name) &&
- (!this.type || item.type === this.type)
- );
- }
- }
- }
- </script>
复制代码
✅ 正确示例:
- <script>
- export default {
- data() {
- return {
- sourceData: [],
- name: '',
- type: ''
- }
- },
- computed: {
- // 使用计算属性自动维护派生数据
- tableData() {
- return this.sourceData.filter(item =>
- (!this.name || item.name === this.name) &&
- (!this.type || item.type === this.type)
- )
- }
- }
- }
- </script>
复制代码
7. 解决方案不“正统”
问题描述:
使用不常见或不合理的方案解决问题,如:
直接修改 node_modules 中的代码,更好的实践:
优先使用框架/语言原生解决方案
遵循最佳实践和设计模式
进行方案评审和代码审查
对于第三方库的 bug:
向作者提交 issue 或 PR
将修改后的包发布到企业内部仓库
*寻找替代方案
使用 JS 实现纯 CSS 可实现的效果
❌ 错误示例:
- // 不恰当的鼠标悬停效果实现
- element.onmouseover = function() {
- this.style.color = 'red';
- }
- element.onmouseout = function() {
- this.style.color = 'black';
- }
复制代码
✅ 正确示例:
- /* 使用 CSS hover 伪类 */
- .element:hover {
- color: red;
- }
复制代码
过度使用全局变量
如何进行代码重构
重构的原则
不改变软件功能
小步快跑,逐步改进
边改边测试
随时可以暂停
重构示例
以下展示如何一步步重构上面的统计代码:
第一步:抽取 vipCount
删除data中的vipCount
增加计算属性vipCount,将getUserData中关于vipCount的逻辑挪到这里
删除getUserData中vipCount的计算逻辑
- <script>
- export default {
- computed: {
- vipCount() {
- return this.userData.filter(user => user.type === 'vip').length
- }
- },
- methods: {
- getUserData() {
- userService.getUserList().then(res => {
- this.userData = res.data;
- let activeVipsCount = 0;
- let activeUsersCount = 0;
-
- this.userData.forEach(user => {
- if(dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))) {
- if(user.type === 'vip') {
- activeVipsCount++
- }
- activeUsersCount++
- }
- })
-
- this.activeVipsCount = activeVipsCount;
- this.activeUsersCount = activeUsersCount;
- })
- }
- }
- }
- </script>
复制代码
完成本次更改后,测试下各项数据是否正常,不正常查找原因,正常我们继续。
第二步:抽取 activeVipsCount
删除data中的activeVipsCount
增加计算属性activeVipsCount,将getUserData中activeVipsCount的计算逻辑迁移过来
删除getUserData中关于activeVipsCount计算的代码
- <script>
- export default {
- computed: {
- vipCount() {
- return this.userData.filter(user => user.type === 'vip').length
- },
- activeVipsCount() {
- return this.userData.filter(user =>
- user.type === 'vip' &&
- dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))
- ).length
- }
- },
- methods: {
- getUserData() {
- userService.getUserList().then(res => {
- this.userData = res.data;
- let activeUsersCount = 0;
-
- this.userData.forEach(user => {
- if(dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))) {
- activeUsersCount++
- }
- })
-
- this.activeUsersCount = activeUsersCount;
- })
- }
- }
- }
- </script>
复制代码
最终版本:
- <script>
- export default {
- computed: {
- activeUsers() {
- return this.userData.filter(user =>
- dayjs(user.loginTime).isAfter(dayjs().subtract(30, 'day'))
- )
- },
- vipCount() {
- return this.userData.filter(user => user.type === 'vip').length
- },
- activeVipsCount() {
- return this.activeUsers.filter(user => user.type === 'vip').length
- },
- activeUsersCount() {
- return this.activeUsers.length
- }
- },
- methods: {
- getUserData() {
- userService.getUserList().then(res => {
- this.userData = res.data;
- })
- }
- }
- }
- </script>
复制代码
总结
要写出易维护的代码,需要注意:
1. 合理拆分模块,避免单文件过大
2. 降低模块间耦合
3. 保持职责单一
4. 使用计算属性处理派生数据
5. 定期进行代码重构
记住:重构是一个渐进的过程,不要试图一次性完成所有改进。在保证功能正常的前提下,通过小步快跑的方式逐步优化代码质量。
作者:Cyrus丶
|