If-else 代码的易读性

-
2021-03-14

案例

能很快找到这个 isInDrag() 方法判断主逻辑在哪里吗?

 

改一种写法之后呢?

 

不用找了,在最后面。很明显,第二种写法更容易找到 isInDrag 的主判断逻辑是 isInRange 的这个方法。

 

条件表达式if-else怎么写比较好

之前跟@叶斋关于 if-else 的用法有过讨论,这是当时叶斋的想法《有 if 必有 else 》。我认为这是 0.5 个很好的关于分支处理的实践。在这里引用一下他当时的观点:

这一条也许争议很大,所以放在第一条。

我尝试遵循的实践是:「有 if 必有 else」:如果你告诉我在某个条件下应该做什么,就应该同时告诉我不在这个条件下应该做什么 —— 即使什么都不做,也应该告诉我:「什么都不做」。

这与一些编程实践是相违背的,比如 ES-Lint 中就有名为 no-else-return 的规则,它会把下面的 foo1 优化成 foo2。

可是在我看来,foo1 更好。因为 foo1 在我的脑海里是二叉树结构,而 foo2 在我的脑海里是线性结构。当我在脑海中模拟代码运行时,二叉树结构更容易追溯,可以帮助我迅速定位到目标,而线性结构需要我跑完整个流程。

 

剩余的0.5个很好的实践

条件表达式通常有两种风格:

  1. 两个条件分支都属于正常行为,不同分支的重要性相似;
  2. 只有一个条件分支是正常行为,另一个分支是异常情况。

这两类条件表达式有不同用途,在代码中的表现也有区别:

  1. 若两条分支都是正常行为,就应该使用形如if...else...的条件表达式;
  2. 若某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。

第一种方式就是叶斋的实践,第二种方式叫做Guard clauses(卫语句)

 

卫语句

卫语句的精髓就是:给某一条分支以特别的重视。

如果使用 if-then-else 结构,对 if 分支和 else 分支的重视是同等的。这样的代码结构传递给阅读者的消息就是:各个分支有同样的重要性。

而卫语句是在告诉读者:“这种情况不是本函数的核心逻辑所关心的,如果它真发生了,请做一些必要的整理工作,然后退出。”

 

一个典型的卫语句场景 docker push image

func (s *composeService) pushServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPush bool) error {
	ref, err := reference.ParseNormalizedNamed(service.Image)
	if err != nil {
		return err
	}

	repoInfo, err := registry.ParseRepositoryInfo(ref)
	if err != nil {
		return err
	}

	key := repoInfo.Index.Name
	if repoInfo.Index.Official {
		key = info.IndexServerAddress
	}
	authConfig, err := configFile.GetAuthConfig(key)
	if err != nil {
		return err
	}

	buf, err := json.Marshal(authConfig)
	if err != nil {
		return err
	}

	stream, err := s.apiClient().ImagePush(ctx, service.Image, moby.ImagePushOptions{
		RegistryAuth: base64.URLEncoding.EncodeToString(buf),
	})
	if err != nil {
		return err
	}
	dec := json.NewDecoder(stream)
	for {
		var jm jsonmessage.JSONMessage
		if err := dec.Decode(&jm); err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		if jm.Error != nil {
			return errors.New(jm.Error.Message)
		}

		if !quietPush {
			toPushProgressEvent(service.Name, jm, w)
		}
	}
	return nil
}

 

总结

条件表达式有两种,一种适合 if-else,另一种适合 if-return;


目录