在本文中,我們將討論 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é)果。
這將使代碼更簡潔、更易于維護和測試。
