重构的艺术:五个小妙招助你写出好代码

全文共8912字,预计学习时长14分钟javascript

做者lloorraa,来源need.pixjava

糟糕的代码能够运做,但迟早会让咱们付出代价。你有没有遇到过这样的问题:几周后,你没法理解本身的代码,因而不得不花上几个小时,甚至几天的时间来弄清楚到底发生了什么。程序员

解决这个常见问题的方法是使代码尽量清晰。若是作得更好的话,即便是非技术人员也应该能理解你的代码。es6

是时候中止寻找借口,提升咱们的代码质量了!golang

编写清晰的代码并无那么复杂。本教程将向你展现五种改进代码的简单技巧,并提供一些实例:数据库

1. 不用switch语句json

咱们一般使用switch语句来代替大型if-else-if语句。可是,switch语句很是冗长,很难维护,甚至很难调试。这些switch语句把咱们的代码弄得乱七八糟,并且这些语句的语法很奇怪,很不舒服。在添加更多的case时,咱们不得没必要须手动添加每一个case和break语句,而这就很容易出错。数组

接下来看一个switch语句的例子:微信

function getPokemon(type) {
async

let pokemon;

switch (type) {

case 'Water':

pokemon = 'Squirtle';

break;

case 'Fire':

pokemon = 'Charmander';

break;

case 'Plant':

pokemon = 'Bulbasur';

break;

case 'Electric':

pokemon = 'Pikachu';

break;

default:

pokemon = 'Mew';

}

return pokemon;

}

console.log(getPokemon('Fire')); // Result: Charmander

Switch语句

若是须要在switch语句中添加更多的case的话,须要编写的代码量是至关大的。咱们可能最终会复制粘贴代码,而其实咱们都知道这种行为的后果是什么。

那么,如何避免使用switch语句呢?能够经过使用对象文本。对象文本简单,易于编写,方便读取,维护轻松。咱们都习惯用javascript处理对象,对象文本语法比switch语句更新鲜。下面举个例子:

const pokemon = {

Water: 'Squirtle',

Fire: 'Charmander',

Plant: 'Bulbasur',

Electric: 'Pikachu'

};

function getPokemon(type) {

return pokemon[type] || 'Mew';

}

console.log(getPokemon('Fire')); // Result: Charmander

// If the type isn't found in the pokemon object, the function will return the default value 'Mew'

console.log(getPokemon('unknown')); // Result: Mew

使用对象文本替代switch

如你所见,可使用运算符 || 添加默认值。若是在pokemon对象中找不到type,getpokemon函数将使mew返回为默认值。

小贴士:你可能已经注意到,咱们在函数外部而不是内部声明pokemon对象。这样作是为了不每次执行函数时都从新建立pokemon。

用映射也能达到一样的效果。映射就像对象同样是键-值对的集合。不一样的是映射容许任何类型的键,而对象只容许字符串做为键。此外,映射还有一系列有趣的属性和方法。

如下是使用映射的方法:

const pokemon = new Map([

['Water', 'Squirtle'],

['Fire', 'Charmander'],

['Plant', 'Bulbasur'],

['Electric', 'Pikachu']

]);

function getPokemon(type) {

return pokemon.get(type) || 'Mew';

}

console.log(getPokemon('Fire')); // Result: Charmander

console.log(getPokemon('unknown')); // Result: Mew

用映射代替switch语句

如你所见,当用对象文本或映射替换switch语句时,代码看起来更清楚、更直接。

2. 把条件语句写的更有描述性

在编写代码时,条件语句是绝对必要的。然而,他们很快就会失控,最终让咱们没法理解这些语句。这致使咱们要么必须编写注释来解释语句的做用,要么必须花费宝贵的时间来一条一条检查代码来了解发生了什么。这很糟糕。

看一下下面的语句:

function checkGameStatus() {

if (

remaining === 0 ||

(remaining === 1 && remainingPlayers === 1) ||

remainingPlayers === 0

) {

quitGame();

}

}

复杂的条件语句

若是只看前面函数里if语句中的代码,很难理解发生了什么。代码表意不清楚,不清楚的代码只会致使技术错误,还会让人们很是头痛。

怎样改善条件语句呢?能够把它写成一个函数。如下是具体操做方法:

function isGameLost() {

return (

remaining === 0 ||

(remaining === 1 && remainingPlayers === 1) ||

remainingPlayers === 0

);

}

// Our function is now much easier to understand:

function checkGameStatus() {

if (isGameLost()) {

quitGame();

}

}

把条件语句写成函数

经过将条件提取到具备描述性名称isGameLost()的函数中,checkGameStatus函数如今就变得很容易理解。为何?由于代码表意更清晰。它可以告诉咱们发生了什么,这是咱们应该一直努力的方向。

3. 用卫语句(Guard Clauses)代替嵌套的if语句

嵌套if语句是在代码中可能遇到的最可怕的事情之一。你要是想可以彻底掌握代码的状况,这绝对会让你精疲力竭。下面是一个嵌套if语句的例子(这个嵌套有三层):

function writeTweet() {

const tweet = writeSomething();

if (isLoggedIn()) {

if (tweet) {

if (isTweetDoubleChecked()) {

tweetIt();

} else {

throw new Error('Dont publish without double checking your tweet');

}

} else {

throw new Error("Your tweet is empty, can't publish it");

}

} else {

throw new Error('You need to log in before tweeting');

}

}

嵌套的if语句

你可能须要花几分钟时间上下阅读,才能了解函数运做的流程。嵌套的if语句很难让人阅读和理解。那么,如何才能摆脱讨厌的嵌套if语句呢?能够反向思考,使用卫语句来替代这些句子。

“在计算机程序设计中,卫语句是一个布尔表达式,若是程序要在有问题的分支里继续运行的话,它的求值必须为true。”——维基百科

经过颠倒函数的逻辑,并在函数开始时放置致使早期退出的条件,它们将变为保护程序,而且只容许函数在知足全部条件时继续执行。这样能够避免else语句。下面是如何重构以前的函数以使用卫语句的方法:

function writeTweet() {

const tweet = writeSomething();

if (!isLoggedIn()) {

throw new Error('You need to log in before tweeting');

}

if (!tweet) {

throw new Error("Your tweet is empty, can't publish it");

}

if (!isTweetDoubleChecked()) {

throw new Error('Dont publish without double checking your tweet');

}

tweetIt();

}

用卫语句重构函数

如你所见,代码更清晰,更容易理解。咱们能够简单向下阅读来了解函数的做用,遵循函数的天然流动,不像之前那样上下阅读。

4. 不要写重复的代码

写重复的代码老是以失败了结。它会致使以下状况:“我在这里修复了这个bug,可是忘记在那里修复”或“我须要在五个不一样的地方更改/添加一个新功能”。

正如DRY(Don’t repeat yourself不要重复)原则所说:

每一部分知识或逻辑都必须在一个系统中有单一的、明确的表示。

所以,代码越少越好:它节省了时间和精力,更易于维护,并减小了错误的出现。

那么,如何避免重复代码呢?这有点难,可是将逻辑提取到函数/变量一般效果很好。让咱们看看下面的代码,我在重构应用程序时看到了这些代码:

function getJavascriptNews() {

const allNews = getNewsFromWeb();

const news = [];

for (let i = allNews.length - 1; i >= 0; i--){

if (allNews[i].type === "javascript") {

news.push(allNews[i]);

}

}

return news;

}

function getRustNews() {

const allNews = getNewsFromWeb();

const news = [];

for (let i = allNews.length - 1; i >= 0; i--){

if (allNews[i].type === "rust") {

news.push(allNews[i]);

}

}

return news;

}

function getGolangNews() {

const news = [];

const allNews = getNewsFromWeb();

for (let i = allNews.length - 1; i >= 0; i--) {

if (allNews[i].type === 'golang') {

news.push(allNews[i]);

}

}

return news;

}

重复代码示例

你可能已经注意到for循环在这两个函数中彻底相同,除了一个小细节:咱们想要的新闻类型,即javascript或rust新闻。为了不这种重复,能够将for循环提取到一个函数中,而后从getJavascriptNews,getRustNews和getGolangNews 函数调用该函数。如下是具体操做方法:

function getJavascriptNews() {

const allNews = getNewsFromWeb();

return getNewsContent(allNews, 'javascript');

}

function getRustNews() {

const allNews = getNewsFromWeb();

return getNewsContent(allNews, 'rust');

}

function getGolangNews() {

const allNews = getNewsFromWeb();

return getNewsContent(allNews, 'golang');

}

function getNewsContent(newsList, type) {

const news = [];

for (let i = newsList.length - 1; i >= 0; i--) {

if (newsList[i].type === type) {

news.push(newsList[i].content);

}

}

return news;

}

在将for循环提取到getNewsContent函数中以后,getJavaScriptNews, getRustNews和getGolangNews函数变成了简单、清晰的程序。

进一步重构

可是,你是否意识到,除了传递给getNewsContent的类型字符串以外,这两个函数彻底相同?这是重构代码时常常发生的事情。一般状况下,更改一个会致使另外一个更改,以此类推,直到重构后的代码最终变成原始代码的一半大小。代码告诉你它须要什么:

function getNews(type) {

const allNews = getNewsFromWeb();

return getNewsContent(allNews, type);

}

function getNewsContent(newsList, type) {

const news = [];

for (let i = newsList.length - 1; i >= 0; i--) {

if (newsList[i].type === type) {

news.push(newsList[i].content);

}

}

return news;

}

getJavaScriptNews, getRustNews和getGolangNews函数去了哪里?将它们替换为getNews函数,该函数将新闻类型做为参数接收。这样,不管添加多少类型的新闻,老是使用相同的功能。这称为抽象,容许咱们重用函数,所以很是有用。抽象是我在写代码的时候最经常使用的技术之一。

补充:使用es6特性使for循环更具可读性

for循环并不彻底可读。经过引入es6数组函数,能够有95%的机会避免使用它们。在本例中可使用array.filter和array.map组合来替换原始循环:

function getNews(type) {

const allNews = getNewsFromWeb();

return getNewsContent(allNews, type);

}

function getNewsContent(newsList, type) {

return newsList

.filter(newsItem => newsItem.type === type)

.map(newsItem => newsItem.content);

}

用 Array.filter 和 Array.map 来代替循环

• 用Array.filter只返回其类型等于做为参数传递的类型的元素。

• 用Array.map只返回item对象的content属性,而不是整个item。

恭喜你,通过三次简单的重构,最初的三个函数已经缩减为两个,这更容易理解和维护。另外,抽象让getNews函数变得能够从新利用。

5. 一个函数只用来作一件事

一个函数应该只作一件事。作不止一件事的函数是全部罪恶的根源,也是代码中可能遇到的最糟糕的事情之一(嵌套的if语句也是)。它们很混乱,搞得代码难以理解。下面是一个来自实际应用程序的复杂函数示例:

function startProgram() {

if (!window.indexedDB) {

window.alert("Browser not support indexeDB");

} else {

let openRequest = indexedDB.open("store", 1);

openRequest.onupgradeneeded = () => {};

openRequest.onerror = () => {

console.error("Error", openRequest.error);

};

openRequest.onsuccess = () => {

let db = openRequest.result;

};

document.getElementById('stat-op').addEventListener('click', () => {});

document.getElementById('pre2456').addEventListener('click', () => {});

document.getElementById('cpTagList100').addEventListener('change', () => {});

document.getElementById('cpTagList101').addEventListener('click', () => {});

document.getElementById('gototop').addEventListener('click', () => {});

document.getElementById('asp10').addEventListener('click', () => {});

fetch("employeList.json")

.then(res => res.json())

.then(employes => {

document.getElementById("employesSelect").innerHTML = employes.reduce(

(content, employe) => employe.name + "<br>",

""

);

});

document.getElementById("usernameLoged").innerHTML = `Welcome, ${username}`;

}

}

又多又复杂又让人难以理解的函数

小贴士:因为本例不须要事件侦听器的处理程序,因此删除了它们。

如你所见,这让人困惑,也很难理解里面发生了什么。若是有错误出现,都很难找到并修复它们。如何改进startProgram函数?能够将公共逻辑提取到函数中。如下是具体操做方法:

function startProgram() {

if (!window.indexedDB) {

throw new Error("Browser doesn't support indexedDB");

}

initDatabase();

setListeners();

printEmployeeList();

}

function initDatabase() {

let openRequest = indexedDB.open('store', 1);

openRequest.onerror = () => {

console.error('Error', openRequest.error);

};

openRequest.onsuccess = () => {

let db = openRequest.result;

};

}

function setListeners() {

document.getElementById('stat-op').addEventListener('click', () => {});

document.getElementById('pre2456').addEventListener('click', () => {});

document.getElementById('cpTagList100').addEventListener('change', () => {});

document.getElementById('cpTagList101').addEventListener('click', () => {});

document.getElementById('gototop').addEventListener('click', () => {});

document.getElementById('asp10').addEventListener('click', () => {});

}

async function printEmployeeList() {

const employees = await getEmployeeList();

document.getElementById('employeeSelect').innerHTML = formatEmployeeList(employees);

}

function formatEmployeeList(employees) {

return employees.reduce(

(content, employee) => content + employee.name + '<br>',

''

);

}

function getEmployeeList() {

return fetch('employeeList.json').then(res => res.json());

}

把逻辑提取到函数里

仔细看看startProgram函数的变化:

首先,经过使用一个卫语句替换掉if-else语句。而后,启动数据库所需的逻辑提取到initDatabase函数中,并将事件侦听器添加到setListeners函数中。

打印员工列表的逻辑稍微复杂一些,所以建立了三个函数:printEmployeeList, formatEmployeeList和getEmployeeList。

getEmployeeList负责向employeeList.json发出GET请求并以json格式返回响应。

而后由printEmployeeList函数调用getEmployeeList,该函数获取员工列表并将其传递给formatEmployeeList函数,formatEmployeeList函数格式化并返回该列表。而后输出列表。

如你所见,每一个功能只负责作一件事。

咱们仍然能够对函数进行一些修改,其实,应用程序很须要把视图从控制器中分离出来,但整体而言,startprogram函数如今信息很容易懂,理解它的功能绝对没有困难。若是几个月后必须从新用这段代码,那也不是什么难事。

小结

程序员是惟一负责编写高质量代码的人。咱们都应该养成从第一行就写好代码的习惯。编写清晰易懂的代码并不难,这样作对你和你的同事都有好处。

推荐阅读专题

留言 点赞 关注

咱们一块儿分享AI学习与发展的干货
欢迎关注全平台AI垂类自媒体 “读芯术”


(添加小编微信:dxsxbb,加入读者圈,一块儿讨论最新鲜的人工智能科技哦~)

相关文章
相关标签/搜索