代码整洁之道——2、函数

一、函数参数(两个或者更少)

限制函数的参数至关重要,因为这样测试函数会更简单。有超过三个以上的参数,测试的时候就要使用不同参数测无数的场景。

一个或者两个参数是理想情况。如果可能的话避免三个参数。三个以上的参数应该杜绝。通常,如果有两个以上的参数,说明这个函数做的太多了。大多数情况下,一个高质量的对象就足够充当一个参数了,当你发现你需要多个参数的时候,你可以使用一个对象来代替。

可以用ES6解构赋值,使函数期望的参数清晰化。这样做有几个好处:

1、他人阅读的时候,一下子就看明白了使用了哪些属性

2、解构赋值同时也克隆了对象中的同名值。注:从参数对象解构的对象和数组没有被克隆。(没看明白注释啥意思)

3、检测工具可以提示你哪些属性没有用到,不使用解构赋值就不能了。

Bad:
//传入了四个参数
function createMenu(title, body, buttonText, cancellable) {
  // ...
}

Good:
//将四个参数放入到一个对象中
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}
//通过解构赋值给函数传参
createMenu({
  title: ‘Foo‘,
  body: ‘Bar‘,
  buttonText: ‘Baz‘,
  cancellable: true
});

二、一个函数只做一件事

这是目前为止软件工程领域最重要的一条法则。当一个函数做了不止一件事情的时候,它们就变得难以创作、测试和解释了。当你可以把一个函数拆解成只做一件事情的时候,你的代码将变得非常清晰并且容易重构。如果这篇文章你只学会了这一条,那你也比很多开发者优秀了。

Bad:
//函数共做了两件事情:1、判断是否为活动客户 2、给客户发邮件
function emailClients(clients) {
  clients.forEach((client) => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

Good:
//给客户发邮件
function emailActiveClients(clients) {
  clients
    .filter(isActiveClient)
    .forEach(email);
}
//判断是否为活动客户
function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

三、函数名应该说明这个函数做的事情

Bad:
function addToDate(date, month) {
  // ...
}
const date = new Date();
// 从函数名很难看出把什么添加到date中
addToDate(date, 1);

Good:
function addMonthToDate(month, date) {
  // ...
}
const date = new Date();
addMonthToDate(1, date);

四、函数应该只是一个维度的抽象

当你的函数有超过一个维度的抽象,那么通常是你的函数做了太多的事情。把他们拆分开,可以使复用和测试更加简单。(感觉跟上一条一个意思呢)

Bad:
//在一个函数中,先得到token,再得到ast,最后对ast处理
function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

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

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

  ast.forEach((node) => {
       // parse...
  });
}
Good:
//获取token
function tokenize(code) {
  const REGEXES = [
    // ...
  ];

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

  return tokens;
}
//获取ast
function lexer(tokens) {
  const ast = [];
  tokens.forEach((token) => {
    ast.push( /* ... */ );
  });

  return ast;
}
//整合,对ast处理
function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const ast = lexer(tokens);
  ast.forEach((node) => {
    // parse...
  });
}    

五、删除重复的代码

尽最大的努力避免重复代码。重复的代码很糟糕,因为它意味着,如果你需要改变一些逻辑,则要更改多个地方。

设想一下如果你经营了一家餐馆,你需要跟踪库存,包括所有的土豆、洋葱、大蒜、调料等。如果你有多分列表记录这些,那么当你提供了一盘土豆的时候,你就需要更新多份列表。如果你只有一份列表,那么你只需要更新这一份就可以了。

通常有重复的代码是因为,两个或多个事情有些细微的不同但是大部分是相同的,迫使你使用两个或多个函数来做这些相同的事情。删除重复的代码意味着,抽象出一个函数/模块/类来处理这些不同的事情。

正确的抽象是至关重要的,这就是为什么在类模块你需要遵循SOLID原则(follow the SOLID principles laid out in the Classes section)?不好的抽象比重复代码还要糟糕,所以小心点。如果你做了个很好的抽象,一定要抽出来。别重复,否则你会发现,更新一个内容需要更改多个地方。

Bad:
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);
  });
}

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

    let data = {
      expectedSalary,
      experience
    };

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

    render(data);
  });
}

六、用Object.assign设置默认对象值

Bad:
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);

Good:
const menuConfig = {
  title: ‘Order‘,
  // 不包含body
  buttonText: ‘Send‘,
  cancellable: true
};

function createMenu(config) {
  config = Object.assign({
    title: ‘Foo‘,
    body: ‘Bar‘,
    buttonText: ‘Baz‘,
    cancellable: true
  }, config);

  // config : {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

七、别用标识(flags)作为函数参数

flags告诉用户,这个函数做了不止一件事。函数应该只做一件事,如果是按照布尔值来区分不同代码,那么拆开函数。

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

Good:
function createFile(name) {
  fs.create(name);
}

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

八、避免副作用

(1)

一个函数如果不是只做一个值进返回另一个值,那么这个函数产生了副作用,副作用可能是写入一个文件,修改一些全局变量或者是意外的把你的钱给了陌生人。

有时候代码中不得不存在副作用。像之前的例子,你可能需要写入一个文件。你想做什么控制着你在哪里做这件事。别用多个函数或者类去写一个特定的文件,用且只用一个函数来做这件事。

主要就是要避免常见陷阱如:不结构化就在两个对象之间共享状态,使用可被重写的可变数据类型,并且不关注副作用可能发生的地方。(The main point is to avoid common pitfalls like sharing state between objects without any structure, using mutable data types that can be written to by anything, and not centralizing where your side effects occur)。如果你可以做到这样,你将会比绝大多数的程序员开心。

Bad:
//声明了一个全局变量name,在splitIntoFirstAndLastName函数中改变了name值,如果有其他函数调用,name已经变成了一个数组
let name = ‘Ryan McDermott‘;
function splitIntoFirstAndLastName() {
  name = name.split(‘ ‘);
}
splitIntoFirstAndLastName();
console.log(name); // [‘Ryan‘, ‘McDermott‘];

Good:
function splitIntoFirstAndLastName(name) {
  return name.split(‘ ‘);
}
//不改变现有全局变量,用新变量代替
const name = ‘Ryan McDermott‘;
const newName = splitIntoFirstAndLastName(name);

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

(2)在JS中,数字、字符串等按值传递,数组和对象按照引用传递。对于数组和对象,如果你的函数中改变了一份购物列表,比如添加了一项到购物清单中,那么其他的函数如果用到了这个列表(cart)就会受到影响。这可能会很严重很糟糕,让我们想象下糟糕的场景:

用户点击了购买,按钮出发了购买函数(purchase),发起了网络请求,将购买列表发送到服务器端。由于网络不好,购买函数需要重新发起请求。现在。如果同时用户碰巧点击了一个内容的"添加到购物车"按钮,但这个东西用户在发起网络请求的时候并不是真的想买。如果这种情况发生了,那个购物函数(purchase)将会发送意外添加的那个条目,因为它传递的是引用,这个购物清单在添加到购物车(addItemToCart)函数中被修改了。

一个很好的解决方案就是,永远都克隆一份购物清单(cart)。这样确保了其他的函数不能直接通过引用意外的修改原始清单。

这个方案的两个注意事项:

1、可能有场景是你真的想改变对象的引用,但是当你接受了这个编程习惯,你将会发现这样的场景是非常罕见的。大多数事情可以没有副作用的解决。

2、克隆大的对象在程序中的代价是非常昂贵的。幸运的是,在实际生产中这不是个大问题,因为有大量的库支持这样的操作,快速且不占内存,对你来说就只是克隆对象和数组。(Luckily, this isn‘t a big issue in practice because there are great libraries that allow this kind of programming approach to be fast and not as memory intensive as it would be for you to manually clone objects and arrays.)

Bad:
//直接对原数组操作
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

Good:
//返回新数组,原数组不变
const addItemToCart = (cart, item) => {
  return [...cart, { item, date : Date.now() }];
};

九、别写全局函数

全局变量污染在JS中是一个糟糕的实践,因为你可能与其他库冲突,并且使用你API的用户会一无所知,直到他们在生产环境中捕获到异常。让我们想一个例子:如果你想扩展JS原生库,给数组增加一个diff方法来比较两个数组的差异。你可以给Array.prototype添加一个新方法,但这样会与另一个企图做一样事情的库冲突。如果另一个库只是使用diff来找到一个数组首尾元素的差异呢?这就是为什么使用ES6的类去简单的扩展Array比较好的原因了。

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

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

十、函数式编程替代指令式编程

Bad:
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;
}

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

const INITIAL_VALUE = 0;
//函数式
const totalOutput = programmerOutput
  .map((programmer) => programmer.linesOfCode)
  .reduce((acc, linesOfCode) => acc + linesOfCode, INITIAL_VALUE);

十一、封装判断条件

Bad:
if (fsm.state === ‘fetching‘ && isEmpty(listNode)) {
  // ...
}

Good:
//判断条件封装在函数中返回
function shouldShowSpinner(fsm, listNode) {
  return fsm.state === ‘fetching‘ && isEmpty(listNode);
}

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

十二、避免否定判断

Bad:
function isDOMNodeNotPresent(node) {
  // ...
}
//否定判断
if (!isDOMNodeNotPresent(node)) {
  // ...
}

Good:
function isDOMNodePresent(node) {
  // ...
}
if (isDOMNodePresent(node)) {
  // ...
}

十三、避免条件判断

这似乎不太可能。很多人第一次听到的时候会说:“不使用if我能做什么呢"。答案是,在大多数场景下,可以使用多态来完成相同的任务。第二个问题是,“好吧,那我为什么要这样做呢?”答案是我们之前学到的:一个函数应该只做一件事。当你的类或者函数中使用if,你在告诉用户,你的函数做了不止一件事。记住:只做一件事。

Bad:

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();
    }
  }
}

Good: //没看懂,还是要用条件语句组织起来吧?
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();
  }
}

十四、避免类型检测 (1)

Bad:
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‘));
  }
}

Good:   ???
function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location(‘texas‘));
}

(2)

如果你用的是字符串、整形、数组等基本数据类型,你不可以使用多态,但仍然需要类型检查,你应该考虑TypeScript。它比起传统的JS是一个很好的选择,与传统JS语法相比,它提供静态类型检查。手动JS类型检查会写大量的代码,而失去了代码的可读性。保持你的代码干净、易于测试、便于code review。否则,用TypeScript进行类型检测

Bad:
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‘);
}

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

十五、避免过度优化

现代浏览器在运行时会进行很多代码优化。很多情况下的优化是浪费你的时间。这里有些很好的资源可以看到那些真正需要优化的地方There are good resources 。

Bad:
//老的浏览器中list.length未被缓存,会重复计算
//新浏览器已经做出了优化
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

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

十六、删除无效代码

无效代码就像重复代码一样糟糕。没有必要在代码中保留。如果没有被调用,就删掉它们。

Bad:
//未被调用的函数
function oldRequestModule(url) {
  // ...
}
function newRequestModule(url) {
  // ...
}
const req = newRequestModule;
inventoryTracker(‘apples‘, req, ‘www.inventory-awesome.io‘);

Good:
function newRequestModule(url) {
  // ...
}
const req = newRequestModule;
inventoryTracker(‘apples‘, req, ‘www.inventory-awesome.io‘);
时间: 2024-08-03 11:17:49

代码整洁之道——2、函数的相关文章

《代码整洁之道》读后感

众所周知,软件质量,不但依赖于架构及项目管理,而且与代码质量紧密相关.这一点,无论是敏捷开发派还是传统开发派,都不得不承认.<代码整洁之道>提出一种观念:代码质量与其整洁度成正比.干净的代码,既在质量上较为可靠,也为后期维护.升级奠定了良好的基础.作为编程领域的佼佼者,这些实践在<代码整洁之道>中体现为一条条规则(或称“启示”),并辅以来自现实项目的正.反两面的范例.只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量.以上便是<代码整洁之道>这本书的内容简介,

《代码整洁之道》精读与演绎】之四 优秀代码的格式准则

本系列文章由@浅墨_毛星云 出品,转载请注明出处.  文章链接:http://blog.csdn.net/poem_qianmo/article/details/52268975 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 这篇文章将与大家一起聊一聊,书写代码过程中一些良好的格式规范. 一.引言 以下引言的内容,有必要伴随这个系列的每一次更新,这次也不例外. <代码整洁之道>这本书提出了一个观点:代码质量与其整洁度成正比,干净的代码,既在质量上

&lt;代码整洁之道&gt;、&lt;java与模式&gt;、&lt;head first设计模式&gt;读书笔记集合

一.前言                                                                                       几个月前的看书笔记,内容全部都是摘自书中比较精辟的句子.笔记都是一段一段的句子,故没有文章的篇幅概念,仅供温习之用,更多详细内容请看原书!!! <代码整洁之道>里面有很多前人编写简洁.漂亮代码的经验.当然书中作者的经验并不100%适合每个人,但大部分都是可借鉴的! <java与模式>这本书内容太多了,我

【读书笔记】--代码整洁之道

“相对于任何宏伟景愿,对细节的关注甚至是更为关键的专业性基础.首先,开发者通过小型实践获得可用于大型实践的技能和信用度.其次,宏伟建筑中最细小的部分,比如关不紧的门,有点儿没有铺平的地板,甚至是凌乱的桌面,都会将整个大局的魅力毁灭殆尽.这就是整洁代码之所系”----没有比书中的这段话更能说明这本书的意义了. <代码整洁之道>是第1期书山有路活动选出的读本.相对于记住那些如何写出整洁代码的那些法则,养成保持代码整洁.提高代码质量的习惯和思维更为重要.全书大致分为三个部分,第一部分1-10章都是介

代码整洁之道

命名,多花些时间推敲命名, 有意义的命名非常重要. 接口的命名,不使用"I"开头比较简洁,加上I以后是比较规范,但是比较繁琐以及废话.如果想区别接口和实现,不如在实现类中进行编码,比如添加后缀"Imp",android以及jdk中的大多数接口都没有使用I. 取名字带有简写要慎重, 比如"人事系统"的类, 前面都是"RSXT..",除了让快捷按钮找不到类以外,没有啥意义了,用包吧. 函数,函数要短小,要职责明确,最好功能单一,参

【《代码整洁之道》精读与演绎】之四 优秀代码的书写格式准则

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/52268975 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 这篇文章将与大家一起聊一聊,书写代码过程中一些良好的格式规范. 一.引言 以下引言的内容,有必要伴随这个系列的每一次更新,这次也不例外. <代码整洁之道>这本书提出了一个观点:代码质量与其整洁度成正比,干净的代码,既在质量

&lt;读书笔记&gt; 代码整洁之道

概述 1.本文档的内容主要来源于书籍<代码整洁之道>作者Robert C.Martin,属于读书笔记. 2.软件质量,不仅依赖于架构和项目管理,而且与代码质量紧密相关,本书提出一种,代码质量与整洁成正比的观点,并给出了一系列行之有效的整洁代码操作实践,只要遵循这些规则,就可以编写出整洁的代码,从而提升代码质量. 3.该书介绍的规则均来自于作者多年的实践经验,涵盖从命名到重构的多个编程方面,具有很好的学习和借鉴价值. 4.习艺要有二:知和行.你应当学习有关规则.模式和实践的知识,穷尽应知之事,并

【《代码整洁之道》精读与演绎】之五 整洁类的书写准则

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/52344732 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 这篇文章将与大家一起聊一聊,书写整洁类的一些法则. 一.引言 以下引言的内容,有必要伴随这个系列的每一次更新,这次也不例外. <代码整洁之道>这本书提出了一个观点:代码质量与其整洁度成正比,干净的代码,既在质量上可靠,也为

代码整洁之道读后感(三)

注释 注释不能美化糟糕的代码 用代码来阐述你的思路 好的注释是什么? 法律信息 提供信息的注释 对意图的解释 警示:例如 // Don't run unless you have some time  to kill TODO注释 公共API的JavaDoc 坏的注释是什么? 多余的注释 误导性的注释 循轨式注释:所谓每个函数都要有JavaDoc活每个变量都要有注释的规矩简直是愚蠢.这类注释只会让代码混乱不堪. 日志式注释 废话式注释: Default Constructor 信息过多:别再注释

代码整洁之道读后感(二)

有意义的命名和函数 命名: 名副其实---选个好名字要花很长时间,但省下来的时间更多.如果发现有更好的名称,就换掉旧的. 避免误导---比如,使用accountList指称一组帐号 使用读的出来的名称---比如,genymdhms(生成日期,年,月,日,时,分,秒),这个怎么读?? 类名---类名和对象名应该是名词或名词短语,如Customer.WiKIPage.Account,避免使用Manager.Processor.Date这样的类名. 方法名---方法名应当是动词或动词短语. 函数: 短