Skip to content

函数

函数参数数量控制(推荐两个以下)

限制函数参数数量至关重要,因为这会显著简化测试过程。当参数超过三个时,会导致组合爆炸问题,开发者必须测试每个单独参数的大量不同组合情况。

一至两个参数是最理想状态,应尽量避免使用三个参数。若参数数量超过此限制,通常意味着函数承载过多职责。这种情况应通过对象参数进行整合——通过创建高阶对象来封装多个相关参数。

鉴于JavaScript支持动态对象创建且无需繁琐的类模板,当函数需要大量参数时,推荐使用对象参数进行封装。

使用ES2015/ES6解构语法明确函数参数预期,具有以下优势:

  1. 函数签名直观展示所需属性,提升代码可读性
  2. 模拟命名参数功能,增强参数传递语义
  3. 解构操作会对传入的原始数据类型值进行克隆(注意:对象和数组等引用类型不会被克隆),有助于防止意外副作用产生。
  4. 静态检查工具(linters)可识别未使用属性,此功能在非解构场景下无法实现

不良实践:

javascript
function createMenu(title, body, buttonText, cancellable) {
  // ...
}

createMenu("Foo", "Bar", "Baz", true);

推荐实践:

javascript
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: "Foo",
  body: "Bar",
  buttonText: "Baz",
  cancellable: true
});

单一职责原则

这是软件工程领域最重要的准则。多任务函数会导致组合困难、测试复杂且逻辑推理成本升高。遵循单一职责的函数更易于重构与维护,仅此原则即可显著提升代码质量。

不良实践:

javascript
function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

推荐实践:

javascript
function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

函数命名语义化

不良实践:

javascript
function addToDate(date, month) {
  // ...
}

const date = new Date();

// It's hard to tell from the function name what is added
addToDate(date, 1);

推荐实践:

javascript
function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);

函数抽象层级控制

多层抽象往往意味着函数过载。通过函数拆分可提升代码复用性与测试便利性。

不良实践:

javascript
function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      // ...
    });
  });

  const ast = [];
  tokens.forEach(token => {
    // lex...
  });

  ast.forEach(node => {
    // parse...
  });
}

推荐实践:

javascript
function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node => {
    // parse...
  });
}

function tokenize(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      tokens.push(/* ... */);
    });
  });

  return tokens;
}

function parse(tokens) {
  const syntaxTree = [];
  tokens.forEach(token => {
    syntaxTree.push(/* ... */);
  });

  return syntaxTree;
}

消除重复代码

必须严格避免代码重复。重复代码会导致逻辑变更时需要维护多处副本,极大增加出错风险。

以餐厅库存管理为例:若使用多个列表记录西红柿、洋葱等食材,每次菜品销售都需要更新所有相关列表。统一管理清单能确保修改入口唯一性。

重复代码通常源于处理相似但略有差异的功能场景。消除重复的关键在于抽象公共逻辑——通过创建泛型函数/模块/类来统一处理这些变体。

正确抽象至关重要,建议参照类设计章节的SOLID原则。需注意低质量抽象比代码重复危害更大,但高质量的抽象能显著提升维护效率。

不良实践:

javascript
function showDeveloperList(developers) {
  developers.forEach(developer => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();
    const data = {
      expectedSalary,
      experience,
      githubLink
    };

    render(data);
  });
}

function showManagerList(managers) {
  managers.forEach(manager => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();
    const data = {
      expectedSalary,
      experience,
      portfolio
    };

    render(data);
  });
}

推荐实践:

javascript
function showEmployeeList(employees) {
  employees.forEach(employee => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();

    const data = {
      expectedSalary,
      experience
    };

    switch (employee.type) {
      case "manager":
        data.portfolio = employee.getMBAProjects();
        break;
      case "developer":
        data.githubLink = employee.getGithubLink();
        break;
    }

    render(data);
  });
}

使用Object.assign设置默认对象

不良实践:

javascript
const menuConfig = {
  title: null,
  body: "Bar",
  buttonText: null,
  cancellable: true
};

function createMenu(config) {
  config.title = config.title || "Foo";
  config.body = config.body || "Bar";
  config.buttonText = config.buttonText || "Baz";
  config.cancellable =
    config.cancellable !== undefined ? config.cancellable : true;
}

createMenu(menuConfig);

推荐实践:

javascript
const menuConfig = {
  title: "Order",
  // User did not include 'body' key
  buttonText: "Send",
  cancellable: true
};

function createMenu(config) {
  let finalConfig = Object.assign(
    {
      title: "Foo",
      body: "Bar",
      buttonText: "Baz",
      cancellable: true
    },
    config
  );
  return finalConfig
  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

避免标记参数

标记参数暗示函数执行多任务操作,违背单一职责原则。应根据布尔标记将不同执行路径拆分为独立函数。

不良实践:

javascript
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

推荐实践:

javascript
function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}

避免副作用(第一部分)

函数副作用指除接收输入返回输出外的其他行为(如文件写入、修改全局变量等)。虽然某些场景需要副作用(如必要的文件操作),但必须集中管理相关操作入口。

重点防范无状态管理的共享变量、可被任意修改的可变数据类型等常见问题。通过中心化副作用管理可显著提升代码可靠性。

建立单一副作用服务能有效避免分散操作入口。例如文件写入操作应统一由专门服务处理。

不良实践:

javascript
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
let name = "Ryan McDermott";

function splitIntoFirstAndLastName() {
  name = name.split(" ");
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];

推荐实践:

javascript
function splitIntoFirstAndLastName(name) {
  return name.split(" ");
}

const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

避免副作用(第二部分)

JavaScript中对象和数组为可变类型,函数内修改可能引发意外联动错误。例如购物车数组被多个函数共享使用时,某个函数的修改可能影响其他功能。

典型场景:用户点击购买后因网络重试机制,可能在其他操作(如误加商品)后发送错误数据。解决方案是对购物车进行克隆操作,确保操作隔离性。

通过返回克隆对象的方法,既能保持原始数据不变,又能有效隔离各个操作流程。

补充说明:对象克隆可能带来性能消耗,但借助如Immutable.js等专业库可实现高效处理。

需要注意的例外情况:

    1. 确有需要修改输入对象的特殊场景(极为罕见)
    1. 大对象克隆可能影响性能(但实际应用中可优化)

不良实践:

javascript
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

推荐实践:

javascript
const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};

避免污染全局对象

修改全局原型(prototype)(如Array.prototype)会导致与其他库的命名冲突。推荐使用ES6类继承机制扩展原生类型功能,而非直接修改原型。

不良实践:

javascript
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

推荐实践:

javascript
class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}

优选函数式编程范式

JavaScript支持函数式编程风格,这种范式能产生更清晰且易于测试的代码结构。建议在适用场景中优先采用。

不良实践:

javascript
const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

let totalOutput = 0;

for (let i = 0; i < programmerOutput.length; i++) {
  totalOutput += programmerOutput[i].linesOfCode;
}

推荐实践:

javascript
const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

const totalOutput = programmerOutput.reduce(
  (totalLines, output) => totalLines + output.linesOfCode,
  0
);

封装条件判断

不良实践:

javascript
if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}

推荐实践:

javascript
function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

避免否定式条件

不良实践:

javascript
function isDOMNodeNotPresent(node) {
  // ...
}

if (!isDOMNodeNotPresent(node)) {
  // ...
}

推荐实践:

javascript
function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}

减少条件语句使用

建议通过多态设计替代条件判断。函数中的if语句暗示多职责问题,应通过拆解函数保持单一功能。例如将不同条件分支转化为独立类方法实现。

不良实践:

javascript
class Airplane {
  // ...
  getCruisingAltitude() {
    switch (this.type) {
      case "777":
        return this.getMaxAltitude() - this.getPassengerCount();
      case "Air Force One":
        return this.getMaxAltitude();
      case "Cessna":
        return this.getMaxAltitude() - this.getFuelExpenditure();
    }
  }
}

推荐实践:

javascript
class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude();
  }
}

class Cessna extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getFuelExpenditure();
  }
}

规避类型检查(第一部分)

JavaScript弱类型特性允许函数接受任意参数类型。建议通过接口一致性设计避免类型检查,提升代码可维护性。

不良实践:

javascript
function travelToTexas(vehicle) {
  if (vehicle instanceof Bicycle) {
    vehicle.pedal(this.currentLocation, new Location("texas"));
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location("texas"));
  }
}

推荐实践:

javascript
function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location("texas"));
}

规避类型检查(第二部分)

基础类型场景如需要类型安全,推荐使用TypeScript替代手工类型校验。手工类型防护会导致代码冗长且维护成本升高,而TypeScript能在保障可读性的同时提供静态类型检查。

不良实践:

javascript
function combine(val1, val2) {
  if (
    (typeof val1 === "number" && typeof val2 === "number") ||
    (typeof val1 === "string" && typeof val2 === "string")
  ) {
    return val1 + val2;
  }

  throw new Error("Must be of type String or Number");
}

推荐实践:

javascript
function combine(val1, val2) {
  return val1 + val2;
}

避免过度优化

现代浏览器已内置运行时优化机制。多数人工优化属于无效劳动,应参考优化指南针对性处理真正瓶颈。

不良实践:

javascript
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

推荐实践:

javascript
for (let i = 0; i < list.length; i++) {
  // ...
}

清理无效代码

无效代码与重复代码同属技术债范畴。及时删除未调用代码以保持代码库清洁,历史版本可提供必要追溯保障。

不良实践:

javascript
function oldRequestModule(url) {
  // ...
}

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");

推荐实践:

javascript
function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");