小酌重构系列[21]——避免双重否定

避免双重否定

在自然语言中,双重否定表示肯定。但是在程序中,双重否定会降低代码的可读性,使程序不易理解,容易产生错觉。
人通常是用“正向思维”去理解一件事情的,使用双重否定的判断,需要开发者以“逆向思维”的方式去理解它的含义。
另外,在写程序时,"!"符号很容易被疏忽和遗漏,一不小心则会编写出错误的代码,从而产生bug。
所以,在程序中,我们应当尽量避免使用双重否定。

优惠券是否未被使用?

还是以在线商城给用户发放优惠券为例,由于优惠券的初始状态是未被使用的,所以设计人员将优惠券的使用状态设计为IsUnused。

/// <summary>
/// 优惠券
/// </summary>
public class Coupon
{
    /// <summary>
    /// 是否未被使用
    /// </summary>
    public bool IsUnused { get; set; }
}

这样设计会带来两个小问题

  • IsUnused表示“优惠券是否未被使用”,这句话本身是比较拗口的,开发人员需要“逆向思维”去理解它的含义。

  • 在写程序时,如果要判断“优惠券已经被使用”,则需要编写比较绕弯的程序
// 如果优惠券已经被使用了
if (!coupon.IsUnused)
{
    // 业务逻辑
}

这段代码如果没有第1行的注释,是比较难于理解的,也许你是用以下方式理解的。

理解这段代码看起来颇为费劲,我们应该换种方式来理解它。

因此,将属性设计为IsUsed更为合适。

/// <summary>
/// 优惠券
/// </summary>
public class Coupon
{
    /// <summary>
    /// 是否被使用
    /// </summary>
    public bool IsUsed { get; set; }
}

编写的判断语句,可读性良好,也易于理解。

// 如果优惠券已经被使用了
if (coupon.IsUsed)
{
    // 业务逻辑
}

PS:设计程序毕竟不是唱Rap,你没必要把自己饶进去了,又把别人也绕进去,大家都能轻易读懂的代码才可能是好的代码。

示例

重构前

这段代码使用!customer.IsNotFlagged判断“客户账户被标记”,如果没有注释,这个判断就比较难理解。

public class Order
{
    public void Checkout(IEnumerable<Product> products, Customer customer)
    {
        // 如果客户账户被标记了
        if (!customer.IsNotFlagged)
        {
            // 记录错误并返回
            return;
        }

        // 正常的订单处理流程
    }
}

public class Customer
{
    public decimal Balance { get; private set; }

    public bool IsNotFlagged
    {
        get { return Balance < 30m; }
    }
}

程序本意是为了表达一个肯定的语义——“如果客户账户是被标记的”,既然如此,我们何不直接用肯定的语义来表示它呢?

重构后

重构后,代码读起来就更加直观了,也很容易被理解。

public class Order
{
    public void Checkout(IEnumerable<Product> products, Customer customer)
    {
        // 如果客户账户被标记了
        if (customer.IsFlagged)
        {
            // 记录错误并返回
            return;
        }

        // 正常的订单处理流程
    }
}

public class Customer
{
    public decimal Balance { get; private set; }

    public bool IsFlagged
    {
        get { return Balance >= 30m; }
    }
}

小结

在设计bool类型的属性时,不仅要表达清楚它所表示的业务含义,还应当考虑编写代码时的复杂性,尽量避免使用双重否定。

【关注】keepfool

时间: 2024-10-26 00:57:27

小酌重构系列[21]——避免双重否定的相关文章

小酌重构系列[11]&mdash;&mdash;提取基类、提取子类、合并子类

概述 继承是面向对象中的一个概念,在小酌重构系列[7]--使用委派代替继承这篇文章中,我"父子关系"描述了继承,这是一种比较片面的说法.后来我又在UML类图的6大关系,描述了继承是一种"is a kind of"关系,它更偏向于概念层次,这种解释更契合继承的本质.本篇要讲的3个重构策略提取基类.提取子类.合并子类都是和继承相关的,如果大家对继承的理解已经足够深刻了,这3个策略用起来应该会得心应手. 提取基类 定义:如果有超过一个类有相似的功能,应该提取出一个基类,并

小酌重构系列目录汇总

为了方便大家阅读这个系列的文章,我弄了个目录汇总. 方法.字段重构 移动方法 (2016-04-24) 提取方法.提取方法对象 (2016-04-26) 方法.字段的提升和降低 (2016-05-01) 分解方法 (2016-05-02) 为布尔方法命名 (2016-05-03) 引入对象参数 (2016-05-04) 类.接口重构 使用委派代替继承 (2016-05-07) 提取接口 (2016-05-08) 解除依赖 (2016-05-09) 分离职责 (2016-05-11) 提取基类.提

小酌重构系列[12]&mdash;&mdash;去除上帝类

关于上帝类 神说:"要有光",就有了光.--<圣经>.上帝要是会写程序,他写的类一定是"上帝类".程序员不是上帝,不要妄想成为上帝,但程序员可以写出"上帝类".上帝是唯一的,上帝的光芒照耀人间,上帝是很爱面子的,他知道程序员写了"上帝类",抢了他的风头,于是他降下神罚要惩戒程序员.--既然你写了"上帝类",那么就将你流放到艰难地修改和痛苦的维护的炼狱中,在地狱之火中永久地熬炼. 你看,上帝也是有

小酌重构系列[15]&mdash;&mdash;策略模式代替分支

前言 在一些较为复杂的业务中,客户端需要依据条件,执行相应的行为或算法.在实现这些业务时,我们可能会使用较多的分支语句(switch case或if else语句).使用分支语句,意味着"变化"和"重复",每个分支条件都代表一个变化,每个分支逻辑都是相似行为或算法的重复.当追加新的条件时,我们需要追加分支语句,并追加相应的行为或算法. 上一篇文章"使用多态代替条件判断"中,我们讲到它可以处理这些"变化"和"重复&qu

小酌重构系列[20]&mdash;&mdash;用条件判断代替异常

概述 异常处理的关键在于何时处理异常以及如何使用异常,有些开发者会觉得try catch的处理和使用难以把握,于是他们秉承着"您可错杀一千,不可放过一个"的想法,给所有的方法添加try catch. 这种方式会对应用程序造成什么影响吗? 从用户角度出发,用户确实难以察觉到什么,应用程序运行正常,使用的体验好像也没什么差别. 从程序角度出发,大量的try catch会降低代码的可读性,只有在异常触发时才会对程序的性能造成较大的影响. 这两种角度有对错吗? 二者都没有错,第一种角度甚至要远

小酌重构系列[2]&mdash;&mdash;提取方法、提取方法对象

前言 "艺术源于生活"--代码也源于生活,你在生活中的一些行为习惯,可能会恰如其分地体现在代码中.当实现较为复杂的功能时,由于它包含一系列的逻辑,我们倾向于编写一个"大方法"来实现.为了使项目便于维护,以及增强代码的可读性,我们有必要对"大方法"的逻辑进行整理,并提取出分散的"小方法".这就是本文要讲的两种重构策略:提取方法.提取方法对象. 如何快速地找到想读的书? 在生活中,我是一个比较随意的人,平时也买了不少书去看.我的书

小酌重构系列[18]&mdash;&mdash;重命名

概述 代码是从命名开始的,我们给类.方法.变量和参数命名,我们也给解决方案.工程.目录命名.在编码时,除了应该遵守编程语言本身的命名规范外,我们应该提供好的命名.好的命名意味着良好的可读性,读你代码的人无需太多的注释,就能通过名称知道它是什么,它能做什么事儿,以及它应该怎么用. 我们命名.命名,不断地命名.既然有这么多命名要做,我们不妨做好他. 关于命名 取名字的成本 取个名字很简单,取个好的名字就不那么容易了.快速随意地取个名字,还不如花点时间取个好名字,因为好名字省下来的时间要比花掉的多.

小酌重构系列[7]&mdash;&mdash;使用委派代替继承

概述 子类可以继承父类的字段.属性和方法,使用"继承"可以较大程度地复用代码.在使用继承时,务必要确定代码中定义的"父类"和"子类"确实存在客观的"父子关系",而不要去做"为了代码复用而使用继承"的事情,这是舍本逐末的做法,也是滥用继承的体现.滥用继承会破坏类之间客观存在的关系,也会模糊代码所体现的语义. 使用委派代替继承 继承的误区 当多个类具有相似的属性.方法时,使其中一个类变成基类,其他的类去继承该基

小酌重构系列[23]&mdash;&mdash;封装条件

概述 当条件判断语句较为复杂时(有多个不同的检查项),就像下面这幅图所表示的,会使得代码的可读性会大打折扣,也难以清晰地传达判断意图. 再者,当判断逻辑变更时,我们不得不去修改if语句里面的判断代码.如果判断写得有问题,则会影响方法的正确性,也会给该方法的单元测试带来一些障碍. 我们可以根据检查项是否需要参数来封装条件,如果检查项不需要参数,则可以将其提取为属性:如果需要参数,则将其提取为方法.本文要讲的重构策略"封装条件"是基于"提取方法"这个重构策略的. 示例