像前辈一样编写代码:编写代码的 5 个必知技巧

在本文中,我们将讨论 5 个简单的技巧,您可以从此刻开始使用它们来提高代码质量。

无论您使用哪种编程语言,也无论您有多少年的编程经验,您都可以在日常工作中使用这些技巧。

那就开始吧。

嵌套和提前返回

当然,每个人都会在自己的项目中遇到一些繁重的嵌套代码。

嵌套代码是指一个代码块包含另一个代码块的代码结构。这通常会导致多级缩进。虽然嵌套代码并非坏事,但过度嵌套会导致一些问题。这些问题会增加代码的维护和调试难度。

嵌套可能会产生问题,因为

  • 复杂性和可读性--嵌套代码很难阅读
  • 维护 - 第一点的副作用,嵌套代码更难维护
  • 调试 - 嵌套代码更难调试
  • 范围界定问题--变量往往相互重叠,造成范围界定问题
  • 测试--为繁重的嵌套代码编写测试比较困难

请看这段代码:

if (user && user.isAuthenticated) {
  // ...20 lines of code
    
  if (timezone) {
    // ...10 lines of code
      
    if (isQualified) {
      // ...50 lines of code
    }
  }
}

应该怎么做?

const userAuthenticated = user && user.isAuthenticated;
if (!userAuthenticated) {
  return;
}

// ...20 lines of code
const timezone = ...
if (!timezone) {
  return;
}

// ...10 lines of code
const isQualified = ...
if (!isQualified) {
    return;
}

// ...50 lines of code

你看这样多好。没有缩进,一切都更容易阅读。

与嵌套有关的另一个概念是提前返回。

提前返回是一个编程概念,即函数在完成代码之前退出并返回一个值。这通常用于处理特殊情况或错误条件。

让我们来看看这个例子:

function getDailyRewards(user){
    if(user.isAuthenticated){
        // ...50 lines of code
    }
}

这个 if 条件包装了约 50 行代码。丑得要命。让我们重写它:

funcion getDailyRewards(user) {
  if (!user.isAuthenticated) {
    return;
  }

  // ...50 lines of code
}

好多了

如果用户未通过身份验证,我们就提前退出,而不是用 if 条件来封装整个代码逻辑。代码的其余部分都在 guard 子句下面,阅读起来更方便。

无用的 if/else 块

这一条是专门针对初学者的。当学生在代码中写入无用的 if/else 块时,我喜欢在练习中对他们进行提示。

下面就是一个典型的例子:

function isEven(number) {
  if (number % 2 === 0) {
    return true;
  } else {
    return false;
  }
}

当我看到这样的代码时,我会问他们能不能写得更短、更优雅一些。然后,有些人就会这样做

function isEven(number) {
  if (number % 2 === 0) {
    return true;
  }
  return false;
}

然后,我问他们能否将这四行写成一行。然后他们会这样做

function isEven(number) {
  return number % 2 === 0 ? true : false; 
}

我再次询问他们是否可以不使用 if/else 或三元运算符,最后我们得到了这样的结果:

function isEven(number) {
  return number % 2 === 0;
}

下面还有一个与第一种情况类似的例子。

function getScheduleKey() {
  const hasSchedule = this.hasTeleVisitScheduled();
  if (hasSchedule) {
    return this.schedule.key;
  } else {
    return this.schedule.cancelSchedule.key;
  }
}

如果仔细观察,这里不需要写 else 块。反正函数已经结束,也有返回。

所以我们可以这样解决:

function getScheduleKey() {
  const hasSchedule = this.hasTeleVisitScheduled();
  if (hasSchedule) {
    return this.schedule.key;
  }
  return this.schedule.cancelSchedule.key;
}

这个故事的寓意是:避免使用无用的 if/else 块。

硬编码字符串和数字

硬编码字符串和数字可在代码中嵌入值。而不是使用变量或常量来表示它们。

![[Pasted image 20231122154527.png]]

硬编码值通常被认为是不好的做法,这有几个原因:

  • 缺乏灵活性和可维护性:如果在整个代码库中对值进行硬编码,那么更改这些值时就需要找到并修改每个出现的值。这意味着,如果你在代码中将一个字符串硬编码了 100 次,而 2 个月后你需要更改该字符串,你将需要在 100 个地方再次更改它。
  • 可重用性:对数值进行硬编码会使相同的代码难以在不同情况下重用。如果要在不同的值中使用相同的逻辑,就需要复制代码。然后,您可以更改值,这就导致了代码的重复。
  • 可读性:带有硬编码值的代码会变得更难阅读和理解。尤其是对于不熟悉上下文的其他开发人员来说。有意义的变量名能让值的目的更加明确。
  • 调试和错误检测:当发生错误时,如果使用命名变量或常量,就更容易发现错误。
  • 可扩展性:随着项目的增长,维护硬编码值变得具有挑战性。在整个代码库中更改一个值可能会变得繁琐且容易出错。
  • 本地化和国际化:如果您的代码需要支持多种语言或本地化,硬编码值可能会导致翻译困难。

总之,在代码中硬编码字符串和数字是不好的。

现在,让我们来看看这个精美的示例,它激发了我写这篇文章的灵感。这个例子的原版要长得多,但现在的版本更简短,减少了 "如果 "条件:

if (fitnessTrainingSchedule.name == "Training 03 Schedule") {
  checkWindowForTrainingSchedule(10, 16, "Milestone3Schedule", "Training 03 Schedule");
}
if (fitnessTrainingSchedule.name == "Training 04 Schedule") {
  checkWindowForTrainingSchedule(24, 30, "Milestone4Schedule", "Training 04 Schedule");
}
if (fitnessTrainingSchedule.name == "Training 05 Schedule") {
  checkWindowForTrainingSchedule(52, 58, "Milestone5Schedule", "Training 05 Schedule");
}
if (fitnessTrainingSchedule.name == "Training 06 Schedule") {
  checkWindowForTrainingSchedule(80, 86, "Milestone6Schedule", "Training 06 Schedule");
}
if (fitnessTrainingSchedule.name == "Training 07 Schedule") {
  checkWindowForTrainingSchedule(108, 114, "Milestone7Schedule", "Training 07 Schedule");
}
if (fitnessTrainingSchedule.name == "Training 08 Schedule") {
  checkWindowForTrainingSchedule(136, 142, "Milestone8Schedule", "Training 08 Schedule");
}
if (fitnessTrainingSchedule.name == "Training 09 Schedule") {
  checkWindowForTrainingSchedule(164, 170, "Milestone9Schedule", "Training 09 Schedule");
}
if (fitnessTrainingSchedule.name == "Training 10 Schedule") {
  checkWindowForTrainingSchedule(216, 230, "Milestone10Schedule", "Training 10 Schedule");
}

乍一看,你可以看到一堆相似的数字和字符串,但你根本不知道这些代码在做什么。你只知道它在做与健身训练时间表有关的事情。要理解这段代码,你需要看看其他脚本。

让我们看看重构后的版本:

const TRAINING_SCHEDULE_03 = {
  name: "Training 03 Schedule",
  milestone: "Milestone3Schedule",
  startDaysInterval: 10,
  endDaysInterval: 16
};
const TRAINING_SCHEDULE_04 = {
  name: "Training 04 Schedule",
  milestone: "Milestone4Schedule",
  startDaysInterval: 24,
  endDaysInterval: 30
};
const TRAINING_SCHEDULE_05 = {
  name: "Training 05 Schedule",
  milestone: "Milestone5Schedule",
  startDaysInterval: 52,
  endDaysInterval: 58
};
const TRAINING_SCHEDULE_06 = {
  name: "Training 06 Schedule",
  milestone: "Milestone6Schedule",
  startDaysInterval: 80,
  endDaysInterval: 86
};
const TRAINING_SCHEDULE_07 = {
  name: "Training 07 Schedule",
  milestone: "Milestone7Schedule",
  startDaysInterval: 108,
  endDaysInterval: 114
};
const TRAINING_SCHEDULE_08 = {
  name: "Training 08 Schedule",
  milestone: "Milestone8Schedule",
  startDaysInterval: 136,
  endDaysInterval: 142
};
const TRAINING_SCHEDULE_09 = {
  name: "Training 09 Schedule",
  milestone: "Milestone9Schedule",
  startDaysInterval: 164,
  endDaysInterval: 170
};
const TRAINING_SCHEDULE_10 = {
  name: "Training 10 Schedule",
  milestone: "Milestone10Schedule",
  startDaysInterval: 216,
  endDaysInterval: 230
};

if (fitnessTrainingSchedule.name == TRAINING_SCHEDULE_03.name) {
  checkWindowForTrainingSchedule(
    TRAINING_SCHEDULE_03.startDaysInterval,
    TRAINING_SCHEDULE_03.endDaysInterval,
    TRAINING_SCHEDULE_03.milestone,
    TRAINING_SCHEDULE_03.name
  );
}
if (fitnessTrainingSchedule.name == TRAINING_SCHEDULE_04.name) {
  checkWindowForTrainingSchedule(
    TRAINING_SCHEDULE_04.startDaysInterval,
    TRAINING_SCHEDULE_04.endDaysInterval,
    TRAINING_SCHEDULE_04.milestone,
    TRAINING_SCHEDULE_04.name
  );
}
if (fitnessTrainingSchedule.name == TRAINING_SCHEDULE_05.name) {
  checkWindowForTrainingSchedule(
    TRAINING_SCHEDULE_05.startDaysInterval,
    TRAINING_SCHEDULE_05.endDaysInterval,
    TRAINING_SCHEDULE_05.milestone,
    TRAINING_SCHEDULE_05.name
  );
}
if (fitnessTrainingSchedule.name == TRAINING_SCHEDULE_06.name) {
  checkWindowForTrainingSchedule(
    TRAINING_SCHEDULE_06.startDaysInterval,
    TRAINING_SCHEDULE_06.endDaysInterval,
    TRAINING_SCHEDULE_06.milestone,
    TRAINING_SCHEDULE_06.name
  );
}
if (fitnessTrainingSchedule.name == TRAINING_SCHEDULE_07.name) {
  checkWindowForTrainingSchedule(
    TRAINING_SCHEDULE_07.startDaysInterval,
    TRAINING_SCHEDULE_07.endDaysInterval,
    TRAINING_SCHEDULE_07.milestone,
    TRAINING_SCHEDULE_07.name
  );
}
if (fitnessTrainingSchedule.name == TRAINING_SCHEDULE_08.name) {
  checkWindowForTrainingSchedule(
    TRAINING_SCHEDULE_08.startDaysInterval,
    TRAINING_SCHEDULE_08.endDaysInterval,
    TRAINING_SCHEDULE_08.milestone,
    TRAINING_SCHEDULE_08.name
  );
}
if (fitnessTrainingSchedule.name == TRAINING_SCHEDULE_09.name) {
  checkWindowForTrainingSchedule(
    TRAINING_SCHEDULE_09.startDaysInterval,
    TRAINING_SCHEDULE_09.endDaysInterval,
    TRAINING_SCHEDULE_09.milestone,
    TRAINING_SCHEDULE_09.name
  );
}
if (fitnessTrainingSchedule.name == TRAINING_SCHEDULE_10.name) {
  checkWindowForTrainingSchedule(
    TRAINING_SCHEDULE_10.startDaysInterval,
    TRAINING_SCHEDULE_10.endDaysInterval,
    TRAINING_SCHEDULE_10.milestone,
    TRAINING_SCHEDULE_10.name
  );
}

这段代码无疑更长,但更易读、易懂。你可以看到这段代码在做什么,参数的含义是什么。

当然,这还远远不是完美的代码。它包含了很多重复的代码,我们下一步将对其进行修复。

重复代码

代码重复也称为代码重复。它是指在一个项目中的许多地方出现相同或相似的代码块。在编程中,代码重复通常被认为是不好的,原因有以下几点:

  • 维护复杂:当相同的代码出现在许多地方时,对该代码的任何更改或更新都需要在所有这些地方进行。这会增加引入错误的风险。这也增加了维护的难度和时间。
  • 错误传播:如果重复代码中存在错误,则需要在每个实例中进行修复。漏掉哪怕一个实例都可能导致不一致的行为或意想不到的问题。
  • 可读性和理解性:重复代码会使代码库更难阅读和理解。特别是对于可能需要处理或维护代码的其他开发人员来说。它掩盖了项目的逻辑和意图。
  • 代码膨胀:重复代码会导致代码臃肿,使项目变得更大更慢。这会影响性能和效率。
  • 代码审查与协作:在代码审查过程中,重复的代码会被标记为问题。这将引发有关代码组织和设计选择的讨论。这也会在版本控制系统中产生冲突。尤其是当许多开发人员都在开发类似的代码时。


    Pasted image 20231122154527.png

我们之前重构的代码示例有一个很大的问题,那就是代码重复。它违反了 DRY 原则,即 "不要重复自己"。

让我们来解决这个问题:

const TRAINING_SCHEDULES = [
  {
    name: "Training 03 Schedule",
    milestone: "Milestone3Schedule",
    startDaysInterval: 10,
    endDaysInterval: 16
  },
  {
    name: "Training 04 Schedule",
    milestone: "Milestone4Schedule",
    startDaysInterval: 24,
    endDaysInterval: 30
  },
  {
    name: "Training 05 Schedule",
    milestone: "Milestone5Schedule",
    startDaysInterval: 52,
    endDaysInterval: 58
  },
  {
    name: "Training 06 Schedule",
    milestone: "Milestone6Schedule",
    startDaysInterval: 80,
    endDaysInterval: 86
  },
  {
    name: "Training 07 Schedule",
    milestone: "Milestone7Schedule",
    startDaysInterval: 108,
    endDaysInterval: 114
  },
  {
    name: "Training 08 Schedule",
    milestone: "Milestone8Schedule",
    startDaysInterval: 136,
    endDaysInterval: 142
  },
  {
    name: "Training 09 Schedule",
    milestone: "Milestone9Schedule",
    startDaysInterval: 164,
    endDaysInterval: 170
  },
  {
    name: "Training 10 Schedule",
    milestone: "Milestone10Schedule",
    startDaysInterval: 216,
    endDaysInterval: 230
  }
];

const targetedTrainingSchedule = TRAINING_SCHEDULES.find(
  (trainingSchedule) => trainingSchedule.name === fitnessTrainingSchedule.name
);

if (!targetedTrainingSchedule) {
  return;
}

checkWindowForTrainingSchedule(
  targetedTrainingSchedule.startDaysInterval,
  targetedTrainingSchedule.endDaysInterval,
  targetedTrainingSchedule.milestone,
  targetedTrainingSchedule.name
);

正如你所看到的,我们将所有对象移到了一个数组中。然后,我们不再对每一个可能的健身训练计划重复 if 条件,而是找到我们要找的那个。然后,我们将目标训练计划的参数传递到函数中。

这看起来比以前漂亮多了。

函数参数混乱

函数参数又称参数,是在调用函数时提供给函数的值。这些参数为函数执行任务或计算提供了必要的数据。

如果不小心使用函数参数,就会在代码中造成混乱。我曾经遇到过这样一个函数

function createUser(
  username,
  email,
  password,
  firstName,
  lastName,
  birthdate,
  gender,
  phoneNumber,
  profilePicture,
  registerPurpose,
  isActive,
  isAdmin,
  isVerified,
  registrationDate,
  status,
  timezone,
  theme,
  accountType,
  subscriptionPlan,
  preferredLanguage,
  expiryDate,
  lastPasswordUpdate,
  lastLogin,
  notificationSettings,
  customFields
)

当一个函数有大量参数时,其理解、阅读和维护就会变得更加困难。要跟踪每个参数的作用以及它们之间的相互作用,都是一项挑战。

带有许多参数的函数会使代码更难阅读和理解。尤其是对于刚接触代码库的开发人员来说。

此外,测试具有多个参数的函数也变得更加复杂。每个参数都可能有不同的值和相互作用。这就增加了涵盖函数行为所需的测试用例数量。此外,调试带有多个参数的函数中的问题也更具挑战性。

一个有许多参数的函数可能与其调用者紧密耦合。如果一个参数发生变化,可能会影响函数的行为,需要在很多地方进行更改。

因此,上面这个案例后来被重构成了这样:

function createUser(userData) 

这比以前好多了。

此外,在前面的示例中,函数从一个对象接收数据。没有必要逐个传递所有数据。您可以传递整个对象:

// this is not OK
checkWindowForTrainingSchedule(
  targetedTrainingSchedule.startDaysInterval,
  targetedTrainingSchedule.endDaysInterval,
  targetedTrainingSchedule.milestone,
  targetedTrainingSchedule.name
);

// this is OK
checkWindowForTrainingSchedule(targetedTrainingSchedule);

归根结底,好的做法是使用参数数量合理的函数。

如果你发现需要很多参数才能实现某种功能,可以考虑使用对象等数据结构。对象允许您将相关参数组合在一起。您还可以将函数分解为更小更集中的函数。这些函数可以相互影响,以实现所需的结果。

这将使代码更简洁、更易于维护和测试。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容