【学习笔记】代码美学:无嵌套主义

【学习笔记】代码美学:不嵌套主义

记录来源于:【熟】代码美学:为何要成为“不嵌套主义者”-哔哩哔哩 及评论区

本人作为前端开发,仅站在前端角度编写该笔记,感兴趣的朋友可以前往视频总结自己的感受。

一、简介:为什么要有不嵌套主义

不嵌套主义旨在减少函数的嵌套层级结构与过长的代码行数,提高代码的可读性与整体美感。作者提出一个观点,将每次嵌套视为深度 +1 。如下代码示,视频作者表示最多忍受到深度为 3 的函数:

getComputedArr(type)
{ // 深度1
    for (let index = 0; index < array.length; index++) 
    { // 深度2
        if (type == index) 
        { // 深度3
            const element = array[index];
            return element;
        }
    }
};

当嵌套达到 4 时是什么样子的呢?看以下代码:

getComputedArr(type)
{ // 深度1
    for (let index = 0; index < array.length; index++) 
    { // 深度2
        if (type == index) 
        { // 深度3
            const element = array[index];
            if(index>10)
            { // 深度4
                return element;
            }
        }
    }
};

很多朋友觉得似乎还可以接受,但我们的业务代码远远没有那么简单,实际上呈现在你们面前的可能是这种代码:

postAffirmCollection函数

postAffirmCollection(id, isRentant) {
	let that = this;
	uni.showModal({
		content: isRentant ? '是否确认支付?' : '是否确认收款?',
		cancelText: "取消", // 取消按钮的文字  
		confirmText: isRentant ? '确认支付' : '确认收款', 
		confirmColor: '#175EFF',
		cancelColor: '#8F94A3', 
		success: function(res) {
			if (res.confirm) {
				if (isRentant) {
					that.$clientInterface.rentConfirmPayment(id)
						.then((res) => {
							this.getSelectBillDetail(this.id);
						})
						.catch((error) => {
							uni.showToast({
								icon: 'none',
								title: error
							})
						});
					// }

				} else {
					that.$clientInterface.postAffirmCollection(id)
						.then((res) => {
							console.log('obj:', res)
							that.obj = res.data;
							uni.$emit('detail', {
								strusDetail: that.strusDetail
							});
							uni.navigateBack();
						})
						.catch((error) => {
							uni.showToast({
								icon: 'none',
								title: error
							})
							console.log(error)
						});
				}
			} else if (res.cancel) {
				console.log('用户点击取消');
			}
		}
	});
},//53行 最大深度:7

在这个深度为 7 的长函数中包含了一个确认提示,两个请求数据函数,一个函数包含了三个功能,很难不显得臃肿。对于以上出现的问题,视频作者主要提出了两个解决方案:

  1. 从主函数中提取子函数
  2. 判断条件的尽快返回

二、提取子函数

一个过长的函数往往存在着多项功能或是复杂的验证信息,提取子函数就是将这些多项功能拆分成若干个小函数。

1、代码长度判别

对于一个函数是否过长,视频的评论区 @F2·NO 用户给出了如下评判标准:

如何判断一个函数是否需要抽象?可以把“50行”当作一个指标。
20年前一些老派的公司做 CodeReview(代码评审) 时,会把代码打印到纸上。如果哪个函数长到需要翻页才能看全,Reviewer(评审人) 就会摆出这样的表情→😒然后把你挂掉。

我认为这是一个很好的批判标准,例如我在上文提及的业务代码,长度就为 54 行。当你某个函数需要滚动屏幕多次才能完全浏览,这对后期代码维护或是代码重构都会造成很大的问题。

此外我个人还有一个批判标准,在编写代码时我习惯半屏编写代码,当某行代码超出了半屏,就说明嵌套的层级过多,需要提取子函数;或是判断条件过于复杂,需要修改。

2、抽象提取子函数

我们应当如何构建一个合适的子函数呢,视频的评论区 @F2·NO 用户给出了如下评判标准:

如何判断提炼的子函数的好坏?有两个硬指标:
一是子函数依赖的参数个数,二是子函数的复用次数。
子函数复用越多,参数越少,就说明拆解越是有效。
当你提炼出多个子函数,而它们之间使用到的参数有相似之处时,可以将它们进一步抽象成类。

当我们确定一个函数过长后,需要将其中一部分代码提取成为一个子函数,尽可能将每个函数的嵌套深度降低。在日常的业务中,常见的功能块,如提示、验证、跳转等,可以进一步抽象成一个公共函数,提高代码的复用率。

此外需要注意函数与函数间的耦合度,当子函数大量依赖于主函数的值(参数)传递时,应当对参数于功能模块标注清晰明了的注释,方便后期的代码更新迭代与修复。

我们以上文的业务代码为例,我们来简单拆分以下这个函数,我们可以先将确认提示的success回调提取出来:

postAffirmCollection(id, isRentant) {// 判断提示函数
	let that = this;
	uni.showModal({
		content: isRentant ? '是否确认支付?' : '是否确认收款?',
		cancelText: "取消", // 取消按钮的文字  
		confirmText: isRentant ? '确认支付' : '确认收款', 
		confirmColor: '#175EFF',
		cancelColor: '#8F94A3', 
		success: that.postSuccess(res);
	});
},//11行 最大深度:2

让我们来看提取出来的 postSuccess(res) 函数,提取出来的子函数虽然已经简化了提示部分,但是依旧显得杂乱,我们需要进一步将函数进行拆分,提取出满足判断的两个获取数据的函数:

postSuccess(res)函数
postSuccess(res) {
    if (res.confirm) {
		if (isRentant) {
			this.$clientInterface.rentConfirmPayment(id)
				.then((res) => {
					this.getSelectBillDetail(this.id);
				})
				.catch((error) => {
					uni.showToast({
						icon: 'none',
						title: error
					})
				});
			 }
		} else {
			this.$clientInterface.postAffirmCollection(id)
				.then((res) => {
					console.log('obj:', res)
					this.obj = res.data;
					uni.$emit('detail', {
						strusDetail: this.strusDetail
					});
					uni.navigateBack();
				})
				.catch((error) => {
					uni.showToast({
						icon: 'none',
						title: error
					})
					console.log(error)
				});
		}
	} else if (res.cancel) {
		console.log('用户点击取消');
	}
}// 36行 最大深度:5

提取后,成功拆分为以下三个函数,并且它们的嵌套深度都没有超过 3 。通过对代码的实际拆分,可以验证了视频作者为何将深度 3 作为一个批判标准,嵌套深度小于 3 的函数更具有代码美感,也更好阅读。

postSuccess(res)-拆分函数
postSuccess(res) {
    if (res.confirm) {
		if (isRentant) {
			this.postSuccessTrue();
		} else {
			this.postSuccessFalse();
		}
	} else if (res.cancel) {
		console.log('用户点击取消');
	}
}// 11行 最大深度:3
postSuccessTrue()函数
postSuccessTrue() {
    this.$clientInterface.rentConfirmPayment(id)
	.then((res) => {
		this.getSelectBillDetail(this.id);
	})
	.catch((error) => {
		uni.showToast({
			icon: 'none',
			title: error
		})
	});
}// 12行 最大深度:3
postSuccessFalse()函数
postSuccessFalse() {
    this.$clientInterface.postAffirmCollection(id)
	.then((res) => {
		console.log('obj:', res)
		this.obj = res.data;
		uni.$emit('detail', {
			strusDetail: this.strusDetail
		});
		uni.navigateBack();
	})
	.catch((error) => {
		uni.showToast({
			icon: 'none',
			title: error
		})
		console.log(error)
	});
}// 18行 最大深度:3

三、尽快返回

当我们对某段代码进行验证时,总是会出现大量的嵌套。面对这种情况,我们往往可以将需要返回的条件置于顶层,一旦满足就会返回,核心代码则无需在做嵌套判断。

1、使用方法

我们先看一个简单的例子,下面的代码就是经典的顺序结构的写法,嵌套深度为 3

getComputedArr(type){
	if(type == 1){
        let obj = {item:6};
        this.getComputedObj(obj)
        uni.showToast({
			icon: 'none',
			title: '返回超过'
		})
    }else{
        return false;
    }
};

让我们来看看不嵌套主义是怎么处理这个函数的呢?当我们将需要返回的条件提前,则可以释放原本需要嵌套的核心代码,视频作者将这种写法称为 验证守护 。此时的嵌套深度降至了 2 ,核心代码也更加清晰明了。

getComputedArr(type){
    if(type != 1){
        return false;
    }
    let obj = {item:6};
    this.getComputedObj(obj)
    uni.showToast({
		icon: 'none',
		title: '返回超过'
	})
};

2、实际应用

在条件简单的代码中,尽快返回显得有点鸡肋,远没有提取子函数更重要,那么接下来我们看看这段代码。我们以上文还未拆分的 postSuccess(res) 为例,看看如何处理。我们通过尽快返回超过减少了一级深度,现在我们可以比较一下上下文的代码,即便现在的深度仍大于 3 代码也清晰明了了许多。

postSuccess(res)函数
postSuccess(res) {
    if (res.cancel) {
        console.log('用户点击取消');
    }
    if (isRentant) {
        this.$clientInterface.rentConfirmPayment(id)
            .then((res) => {
                this.getSelectBillDetail(this.id);
            })
            .catch((error) => {
                uni.showToast({
                    icon: 'none',
                    title: error
                })
            });
    }
    this.$clientInterface.postAffirmCollection(id)
        .then((res) => {
            console.log('obj:', res)
            this.obj = res.data;
            uni.$emit('detail', {
                strusDetail: this.strusDetail
            });
            uni.navigateBack();
        })
        .catch((error) => {
            uni.showToast({
                icon: 'none',
                title: error
            })
            console.log(error)
        });
}// 33行 最大深度:4

接下来我们进一步拆分子函数看看,确实相较于上面的代码更加简洁。尽快返回作用在需要判断的代码部分,可以提高代码的可读性,同时减少代码的深度和行数,是切实有效的方法。

postSuccess(res)-拆分函数
postSuccess(res) {
    if (res.cancel) {
        console.log('用户点击取消');
    }
    if (isRentant) {
		this.postSuccessTrue();
    }
    this.postSuccessFalse();
}// 9行 最大深度:2
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值