案例※
能很快找到这个 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个很好的实践※
条件表达式通常有两种风格:
- 两个条件分支都属于正常行为,不同分支的重要性相似;
- 只有一个条件分支是正常行为,另一个分支是异常情况。
这两类条件表达式有不同用途,在代码中的表现也有区别:
- 若两条分支都是正常行为,就应该使用形如if...else...的条件表达式;
- 若某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。
第一种方式就是叶斋的实践,第二种方式叫做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;
