像前輩一樣編寫代碼:編寫代碼的 5 個必知技巧

在本文中,我們將討論 5 個簡單的技巧,您可以從此刻開始使用它們來提高代碼質(zhì)量。

無論您使用哪種編程語言,也無論您有多少年的編程經(jīng)驗,您都可以在日常工作中使用這些技巧。

那就開始吧。

嵌套和提前返回

當然,每個人都會在自己的項目中遇到一些繁重的嵌套代碼。

嵌套代碼是指一個代碼塊包含另一個代碼塊的代碼結(jié)構(gòu)。這通常會導致多級縮進。雖然嵌套代碼并非壞事,但過度嵌套會導致一些問題。這些問題會增加代碼的維護和調(diào)試難度。

嵌套可能會產(chǎn)生問題,因為

  • 復雜性和可讀性--嵌套代碼很難閱讀
  • 維護 - 第一點的副作用,嵌套代碼更難維護
  • 調(diào)試 - 嵌套代碼更難調(diào)試
  • 范圍界定問題--變量往往相互重疊,造成范圍界定問題
  • 測試--為繁重的嵌套代碼編寫測試比較困難

請看這段代碼:

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

你看這樣多好。沒有縮進,一切都更容易閱讀。

與嵌套有關(guān)的另一個概念是提前返回。

提前返回是一個編程概念,即函數(shù)在完成代碼之前退出并返回一個值。這通常用于處理特殊情況或錯誤條件。

讓我們來看看這個例子:

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

當我看到這樣的代碼時,我會問他們能不能寫得更短、更優(yōu)雅一些。然后,有些人就會這樣做

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

然后,我問他們能否將這四行寫成一行。然后他們會這樣做

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

我再次詢問他們是否可以不使用 if/else 或三元運算符,最后我們得到了這樣的結(jié)果:

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 塊。反正函數(shù)已經(jīng)結(jié)束,也有返回。

所以我們可以這樣解決:

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

這個故事的寓意是:避免使用無用的 if/else 塊。

硬編碼字符串和數(shù)字

硬編碼字符串和數(shù)字可在代碼中嵌入值。而不是使用變量或常量來表示它們。

![[Pasted image 20231122154527.png]]

硬編碼值通常被認為是不好的做法,這有幾個原因:

  • 缺乏靈活性和可維護性:如果在整個代碼庫中對值進行硬編碼,那么更改這些值時就需要找到并修改每個出現(xiàn)的值。這意味著,如果你在代碼中將一個字符串硬編碼了 100 次,而 2 個月后你需要更改該字符串,你將需要在 100 個地方再次更改它。
  • 可重用性:對數(shù)值進行硬編碼會使相同的代碼難以在不同情況下重用。如果要在不同的值中使用相同的邏輯,就需要復制代碼。然后,您可以更改值,這就導致了代碼的重復。
  • 可讀性:帶有硬編碼值的代碼會變得更難閱讀和理解。尤其是對于不熟悉上下文的其他開發(fā)人員來說。有意義的變量名能讓值的目的更加明確。
  • 調(diào)試和錯誤檢測:當發(fā)生錯誤時,如果使用命名變量或常量,就更容易發(fā)現(xiàn)錯誤。
  • 可擴展性:隨著項目的增長,維護硬編碼值變得具有挑戰(zhàn)性。在整個代碼庫中更改一個值可能會變得繁瑣且容易出錯。
  • 本地化和國際化:如果您的代碼需要支持多種語言或本地化,硬編碼值可能會導致翻譯困難。

總之,在代碼中硬編碼字符串和數(shù)字是不好的。

現(xiàn)在,讓我們來看看這個精美的示例,它激發(fā)了我寫這篇文章的靈感。這個例子的原版要長得多,但現(xiàn)在的版本更簡短,減少了 "如果 "條件:

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

乍一看,你可以看到一堆相似的數(shù)字和字符串,但你根本不知道這些代碼在做什么。你只知道它在做與健身訓練時間表有關(guān)的事情。要理解這段代碼,你需要看看其他腳本。

讓我們看看重構(gòu)后的版本:

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

這段代碼無疑更長,但更易讀、易懂。你可以看到這段代碼在做什么,參數(shù)的含義是什么。

當然,這還遠遠不是完美的代碼。它包含了很多重復的代碼,我們下一步將對其進行修復。

重復代碼

代碼重復也稱為代碼重復。它是指在一個項目中的許多地方出現(xiàn)相同或相似的代碼塊。在編程中,代碼重復通常被認為是不好的,原因有以下幾點:

  • 維護復雜:當相同的代碼出現(xiàn)在許多地方時,對該代碼的任何更改或更新都需要在所有這些地方進行。這會增加引入錯誤的風險。這也增加了維護的難度和時間。
  • 錯誤傳播:如果重復代碼中存在錯誤,則需要在每個實例中進行修復。漏掉哪怕一個實例都可能導致不一致的行為或意想不到的問題。
  • 可讀性和理解性:重復代碼會使代碼庫更難閱讀和理解。特別是對于可能需要處理或維護代碼的其他開發(fā)人員來說。它掩蓋了項目的邏輯和意圖。
  • 代碼膨脹:重復代碼會導致代碼臃腫,使項目變得更大更慢。這會影響性能和效率。
  • 代碼審查與協(xié)作:在代碼審查過程中,重復的代碼會被標記為問題。這將引發(fā)有關(guān)代碼組織和設(shè)計選擇的討論。這也會在版本控制系統(tǒng)中產(chǎn)生沖突。尤其是當許多開發(fā)人員都在開發(fā)類似的代碼時。


    Pasted image 20231122154527.png

我們之前重構(gòu)的代碼示例有一個很大的問題,那就是代碼重復。它違反了 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
);

正如你所看到的,我們將所有對象移到了一個數(shù)組中。然后,我們不再對每一個可能的健身訓練計劃重復 if 條件,而是找到我們要找的那個。然后,我們將目標訓練計劃的參數(shù)傳遞到函數(shù)中。

這看起來比以前漂亮多了。

函數(shù)參數(shù)混亂

函數(shù)參數(shù)又稱參數(shù),是在調(diào)用函數(shù)時提供給函數(shù)的值。這些參數(shù)為函數(shù)執(zhí)行任務或計算提供了必要的數(shù)據(jù)。

如果不小心使用函數(shù)參數(shù),就會在代碼中造成混亂。我曾經(jīng)遇到過這樣一個函數(shù)

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
)

當一個函數(shù)有大量參數(shù)時,其理解、閱讀和維護就會變得更加困難。要跟蹤每個參數(shù)的作用以及它們之間的相互作用,都是一項挑戰(zhàn)。

帶有許多參數(shù)的函數(shù)會使代碼更難閱讀和理解。尤其是對于剛接觸代碼庫的開發(fā)人員來說。

此外,測試具有多個參數(shù)的函數(shù)也變得更加復雜。每個參數(shù)都可能有不同的值和相互作用。這就增加了涵蓋函數(shù)行為所需的測試用例數(shù)量。此外,調(diào)試帶有多個參數(shù)的函數(shù)中的問題也更具挑戰(zhàn)性。

一個有許多參數(shù)的函數(shù)可能與其調(diào)用者緊密耦合。如果一個參數(shù)發(fā)生變化,可能會影響函數(shù)的行為,需要在很多地方進行更改。

因此,上面這個案例后來被重構(gòu)成了這樣:

function createUser(userData) 

這比以前好多了。

此外,在前面的示例中,函數(shù)從一個對象接收數(shù)據(jù)。沒有必要逐個傳遞所有數(shù)據(jù)。您可以傳遞整個對象:

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

// this is OK
checkWindowForTrainingSchedule(targetedTrainingSchedule);

歸根結(jié)底,好的做法是使用參數(shù)數(shù)量合理的函數(shù)。

如果你發(fā)現(xiàn)需要很多參數(shù)才能實現(xiàn)某種功能,可以考慮使用對象等數(shù)據(jù)結(jié)構(gòu)。對象允許您將相關(guān)參數(shù)組合在一起。您還可以將函數(shù)分解為更小更集中的函數(shù)。這些函數(shù)可以相互影響,以實現(xiàn)所需的結(jié)果。

這將使代碼更簡潔、更易于維護和測試。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容