函数
函数参数数量控制(推荐两个以下)
限制函数参数数量至关重要,因为这会显著简化测试过程。当参数超过三个时,会导致组合爆炸问题,开发者必须测试每个单独参数的大量不同组合情况。
一至两个参数是最理想状态,应尽量避免使用三个参数。若参数数量超过此限制,通常意味着函数承载过多职责。这种情况应通过对象参数进行整合——通过创建高阶对象来封装多个相关参数。
鉴于JavaScript支持动态对象创建且无需繁琐的类模板,当函数需要大量参数时,推荐使用对象参数进行封装。
使用ES2015/ES6解构语法明确函数参数预期,具有以下优势:
- 函数签名直观展示所需属性,提升代码可读性
- 模拟命名参数功能,增强参数传递语义
- 解构操作会对传入的原始数据类型值进行克隆(注意:对象和数组等引用类型不会被克隆),有助于防止意外副作用产生。
- 静态检查工具(linters)可识别未使用属性,此功能在非解构场景下无法实现
不良实践:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);推荐实践:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});单一职责原则
这是软件工程领域最重要的准则。多任务函数会导致组合困难、测试复杂且逻辑推理成本升高。遵循单一职责的函数更易于重构与维护,仅此原则即可显著提升代码质量。
不良实践:
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}推荐实践:
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}函数命名语义化
不良实践:
function addToDate(date, month) {
// ...
}
const date = new Date();
// It's hard to tell from the function name what is added
addToDate(date, 1);推荐实践:
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);函数抽象层级控制
多层抽象往往意味着函数过载。通过函数拆分可提升代码复用性与测试便利性。
不良实践:
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...
});
}推荐实践:
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原则。需注意低质量抽象比代码重复危害更大,但高质量的抽象能显著提升维护效率。
不良实践:
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);
});
}推荐实践:
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设置默认对象
不良实践:
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);推荐实践:
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);避免标记参数
标记参数暗示函数执行多任务操作,违背单一职责原则。应根据布尔标记将不同执行路径拆分为独立函数。
不良实践:
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}推荐实践:
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}避免副作用(第一部分)
函数副作用指除接收输入返回输出外的其他行为(如文件写入、修改全局变量等)。虽然某些场景需要副作用(如必要的文件操作),但必须集中管理相关操作入口。
重点防范无状态管理的共享变量、可被任意修改的可变数据类型等常见问题。通过中心化副作用管理可显著提升代码可靠性。
建立单一副作用服务能有效避免分散操作入口。例如文件写入操作应统一由专门服务处理。
不良实践:
// 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'];推荐实践:
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等专业库可实现高效处理。
需要注意的例外情况:
- 确有需要修改输入对象的特殊场景(极为罕见)
- 大对象克隆可能影响性能(但实际应用中可优化)
不良实践:
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};推荐实践:
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};避免污染全局对象
修改全局原型(prototype)(如Array.prototype)会导致与其他库的命名冲突。推荐使用ES6类继承机制扩展原生类型功能,而非直接修改原型。
不良实践:
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};推荐实践:
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}优选函数式编程范式
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;
}推荐实践:
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
);封装条件判断
不良实践:
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}推荐实践:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}避免否定式条件
不良实践:
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}推荐实践:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}减少条件语句使用
建议通过多态设计替代条件判断。函数中的if语句暗示多职责问题,应通过拆解函数保持单一功能。例如将不同条件分支转化为独立类方法实现。
不良实践:
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();
}
}
}推荐实践:
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弱类型特性允许函数接受任意参数类型。建议通过接口一致性设计避免类型检查,提升代码可维护性。
不良实践:
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"));
}
}推荐实践:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location("texas"));
}规避类型检查(第二部分)
基础类型场景如需要类型安全,推荐使用TypeScript替代手工类型校验。手工类型防护会导致代码冗长且维护成本升高,而TypeScript能在保障可读性的同时提供静态类型检查。
不良实践:
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");
}推荐实践:
function combine(val1, val2) {
return val1 + val2;
}避免过度优化
现代浏览器已内置运行时优化机制。多数人工优化属于无效劳动,应参考优化指南针对性处理真正瓶颈。
不良实践:
// 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++) {
// ...
}推荐实践:
for (let i = 0; i < list.length; i++) {
// ...
}清理无效代码
无效代码与重复代码同属技术债范畴。及时删除未调用代码以保持代码库清洁,历史版本可提供必要追溯保障。
不良实践:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");推荐实践:
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");