干净代码应用于 JavaScript — 第六部分:避免条件复杂性

2025-05-24

干净代码应用于 JavaScript — 第六部分:避免条件复杂性

介绍

条件复杂度会使代码更难理解,因此也更难维护。此外,条件复杂度通常表明代码耦合度高。如果我们想要提高代码质量,建议避免生成包含条件复杂度的代码。

本文将介绍一些适用于任何代码的技巧和建议,以避免条件复杂性。在本例中,我们将使用 JavaScript/TypeScript 编程语言,但本文讨论的概念可以推广到任何编程语言,因为我们提供的建议和技巧并非针对特定编程语言的技巧。

不要使用标志作为函数参数

我给你的第一个建议是避免复杂性,即消除将标志作为函数参数的做法。相反,我们必须创建两个函数来实现问题的逻辑,而不是使用一个包含两个功能逻辑的函数,因为它们是不同的。

第一个例子展示了如何使用参数isPremium,它将决定使用哪个函数。另一方面,最正确的做法是,用声明式的方式,使用两个不同的函数来描述这两个功能。

// Dirty
function book(customer, isPremium) {
  // ...
  if (isPremium) {
    premiumLogic();
  } else {
    regularLogic();
  }
}
// Clean (Declarative way)
function bookPremium (customer) {
  premiumLogic();
}

function bookRegular (customer) {
  retularLogic();
}

封装条件

别让我思考!请将条件封装到具有语义值的函数中。

在第一个例子中,您可以看到存在一个让任何人思考的复杂条件,而在第二个例子中,当阅读函数名称时,它很容易理解。

if (platform.state === 'fetching' && isEmpty(cart)) {
    // ...
}  
function showLoading(platform, cart) {
    return platform.state === 'fetching' && isEmpty(cart);
}

if (showLoading(platform, cart)) {
    // ...
}

用 Guard Clause 替换嵌套条件

这条建议对程序员来说至关重要。你不应该使用嵌套的条件语句。避免嵌套条件语句的主要技巧之一是使用保护子句。想象一下,完全不需要else关键字就能完美地进行开发。

下面的示例代码展示了保护子句的使用方法,其中的代码可读性得到了显著提升,甚至可以通过 IDE 实现自动化。因此,当你觉得有趣时,尽管大胆使用它,你只需要根据编程课程中教给你的逻辑进行思考即可。

如果您想深入了解您保留的保护条款,我建议您阅读我的特定文章:保护条款

function getPayAmount() {
    let result;
    if (isDead){
        result = deadAmount();
    }else {
        if (isSeparated){
            result = separatedAmount();
        } else {
            if (isRetired){
                result = retiredAmount();
            }else{
                result = normalPayAmount();
            }
        }
    }
    return result;
}
function getPayAmount() {
    if (isDead) return deadAmount();
    if (isSeparated) return separatedAmount();
    if (isRetired) return retiredAmount();
    return normalPayAmount();
}

空对象模式

初级程序员代码中另一个常见的错误是不断检查对象是否为空,并根据该检查结果来决定是否显示默认操作。这种模式被称为空对象模式。

下面的示例展示了如何检查数组中的每个对象,看看动物是否为空,才能发出声音。

另一方面,如果我们创建一个封装了空对象行为的对象,我们就不需要执行上述验证,如应用该模式的代码所示。

class Dog {
  sound() {
    return 'bark';
  }
}

['dog', null].map((animal) => {
   if(animal !== null) { 
       sound(); 
   }
 });

class Dog {
  sound() {
    return 'bark';
  }
}

class NullAnimal {
  sound() {
    return null;
  }
}

function getAnimal(type) {
  return type === 'dog' ? new Dog() : new NullAnimal();
}

['dog', null].map((animal) => getAnimal(animal).sound());
// Returns ["bark", null]

如果你想深入了解这个模式,我建议你阅读我的具体文章:空对象模式

使用多态性删除条件

大多数程序员认为switch控制结构比嵌套更简洁(无论它们是否有不同的行为),因为我们应该考虑其他所有事情。如果我们的代码中有一个switch 我们必须意识到我们刚刚给代码引入了极大的复杂性,最终会让我们思考得太多。

下面的示例展示了这些条件语句的误用,即根据对象的类型定义方法的逻辑。在这种情况下,我们可以利用基于继承的解决方案,利用多态性来避免这种复杂性,因为将为每种特定类型创建一个类。这样,我们将得到一个更具声明性的解决方案,因为我们将在每种具体对象类型中定义方法。

function Auto() {
}
Auto.prototype.getProperty = function () {
    switch (type) {
        case BIKE:
            return getBaseProperty();
        case CAR:
            return getBaseProperty() - getLoadFactor();
        case BUS:
            return (isNailed) ? 
            0 : 
            getBaseProperty(voltage);
    }
    throw new Exception("Should be unreachable");
};
abstract class Auto { 
    abstract getProperty();
}

class Bike extends Auto {
    getProperty() {
        return getBaseProperty();
    }
}
class Car extends Auto {
    getProperty() {
        return getBaseProperty() - getLoadFactor();
    }
}
class Bus extends Auto {
    getProperty() {
        return (isNailed) ? 
                0 : 
                getBaseProperty(voltage);
    }
}
// Somewhere in client code
speed = auto.getProperty();

使用策略模式(组合)/命令模式删除条件

允许我们避免代码中条件复杂性的其他模式是应用策略和命令设计模式。

如果您想加深对这两种模式的了解,我建议您阅读我深入研究这些模式的具体文章:策略模式命令模式

在本节的具体示例中,您可以看到策略模式,其中策略是动态选择的。请注意,如何使用不同的策略来解决这个问题,从而消除开关控制结构的复杂性。

function logMessage(message = "CRITICAL::The system ..."){
    const parts = message.split("::"); 
    const level = parts[0];

    switch (level) {
        case 'NOTICE':
            console.log("Notice")
            break;
        case 'CRITICAL':
            console.log("Critical");
            break;
        case 'CATASTROPHE':
           console.log("Castastrophe");
            break;
    }
}
const strategies = {
    criticalStrategy,
    noticeStrategy,
    catastropheStrategy,
}
function logMessage(message = "CRITICAL::The system ...") {
    const [level, messageLog] = message.split("::");
    const strategy = `${level.toLowerCase()}Strategy`;
    const output = strategies[strategy](messageLog);
}
function criticalStrategy(param) {
    console.log("Critical: " + param);
}
function noticeStrategy(param) {
    console.log("Notice: " + param);
}
function catastropheStrategy(param) {
    console.log("Catastrophe: " + param);
}
logMessage();
logMessage("CATASTROPHE:: A big Catastrophe");

结论

在这篇文章中,我们提出了一些避免条件复杂性的建议。

条件复杂度使代码的可读性变得更差。此外,它通常表明代码耦合性差,因此灵活性不够。

在本文中,我们提出了不同的技术和建议,使我们能够通过提高质量来避免代码中的条件复杂性。

最后,我们讨论的要点如下:

文章来源:https://dev.to/carlillo/clean-code-applied-to-javascript-part-vi-avoid-conditional-complexity-2j8i
PREV
设计模式 - JavaScript 中的策略模式
NEXT
应用于 JavaScript 的整洁代码 — 第三部分。函数简介使用默认参数代替短路或条件函数参数(理想情况下 2 个或更少)避免副作用 - 全局变量避免副作用 - 对象可变函数应该只做一件事函数应该只有一个抽象级别倾向于函数式编程而不是命令式编程使用方法链结论