比較乾淨的JaveScript

clean soap

圖片來源

1.有意義的命名

變數與函式命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Don't ❌ 
const foo = 'Roman';
const abc = 'Chen'
const bbb = 'roman0507@gmail.com'
const user = ['Roman','Corgi'];

function submit() {
...
}

// Do ✔️
const name = 'Roman';
const lastName = 'Chen'
const email = 'roman0507@gmail.com'
const users = ['Roman','Corgi'];

function submitUserForm() {
...
}

有意義且駝峰大小寫

布林變數

1
2
3
4
5
6
7
8
9
// Don't ❌  
let active = true;
let subscribe = true;
let linkedAccount = true;

// Do ✔️
let isActive = true;
let didSubscribe = true;
let hasLinkedAccount = true;

會加入形容動作

重複的詞彙

1
2
3
4
5
6
7
8
9
10
11
12
13
// Don't ❌   
const user {
userName: 'Roman',
userLastName = 'Chen'
userEmail = 'roman0507@gmail.com'
};

// Do ✔️
const user {
name: 'Roman',
lastName = 'Chen'
email = 'roman0507@gmail.com'
};

有意義的常數

1
2
3
4
5
6
// Don't ❌
setTimeout(clearSessionData, 900000);

// Do ✔️
const SESSION_DURATION_MS = 15 * 60 * 1000;
setTimeout(clearSessionData, SESSION_DURATION_MS);

全域性常量可以採用SCREAMING_SNAKE_CASE風格命名。

2. 簡單函式

預設值當參數

1
2
3
4
5
6
7
8
9
10
11
12
13
// Don't ❌ 
function mapSlogan (slogan) {
...
arr[0] = slogan || '-';
...
}

// Do ✔️
function mapSlogan (slogan = '-') {
...
arr[0] = slogan;
...
}

避免重複的事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Don't ❌
function renderCarsList(cars) {
cars.forEach((car) => {
const price = car.getPrice();
const make = car.getMake();
const brand = car.getBrand();
const nbOfDoors = car.getNbOfDoors();

render({ price, make, brand, nbOfDoors });
});
}

function renderMotorcyclesList(motorcycles) {
motorcycles.forEach((motorcycle) => {
const price = motorcycle.getPrice();
const make = motorcycle.getMake();
const brand = motorcycle.getBrand();
const seatHeight = motorcycle.getSeatHeight();

render({ price, make, brand, nbOfDoors });
});
}

// Do ✔️
function renderVehiclesList(vehicles) {
vehicles.forEach((vehicle) => {
const price = vehicle.getPrice();
const make = vehicle.getMake();
const brand = vehicle.getBrand();

const data = { price, make, brand };

switch (vehicle.type) {
case "car":
data.nbOfDoors = vehicle.getNbOfDoors();
break;
case "motorcycle":
data.seatHeight = vehicle.getSeatHeight();
break;
}

render(data);
});
}

可以看起來有相似的功能就可以併起來,避免重複做一樣的事情

一次一件事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Don't ❌
function pingUsers(users) {
users.forEach((user) => {
const userRecord = database.lookup(user);
if (!userRecord.isActive()) {
ping(user);
}
});
}

// Do ✔️
function pingInactiveUsers(users) {
users.filter(!isUserActive).forEach(ping);
}

function isUserActive(user) {
const userRecord = database.lookup(user);
return userRecord.isActive();
}

在同個函式裡做太多不同的事,會導致可讀性變差。

避免過多的參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Don't ❌
function validateUser (name, age, isMember) {
//......
};

validateUser("Roman", 18, true)

// Do ✔️
const user = {
name: "Roman",
age: 18,
isMember: true,
};

function validateUser ({name, age, isMember}) {
//......
};

validateUser(user)

某些情況下可直接引入物件並直接解構賦值,可避免過多的參數

避免副作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Don't ❌
let date = "21-8-2021";

function splitIntoDayMonthYear() {
date = date.split("-");
}

splitIntoDayMonthYear();

// Another function could be expecting date as a string
console.log(date); // ['21', '8', '2021'];

// Do ✔️
function splitIntoDayMonthYear(date) {
return date.split("-");
}

const date = "21-8-2021";
const newDate = splitIntoDayMonthYear(date);

// Original vlaue is intact
console.log(date); // '21-8-2021';
console.log(newDate); // ['21', '8', '2021'];

大多數情況下都應該保持純函式。副作用可能會修改共享狀態和資源,導致一些奇怪的問題。

避免使用布林標誌作為參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Don't ❌
function createFile(name, isPublic) {
if (isPublic) {
fs.create(`./public/${name}`);
} else {
fs.create(name);
}
}

// Do ✔️
function createFile(name) {
fs.create(name);
}

function createPublicFile(name) {
createFile(`./public/${name}`);
}

函式含有布林值的參數意味這個函式是可以被簡化的。

Promise aysnc awit 處理非同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Don't ❌
getUser(function (err, user) {
getProfile(user, function (err, profile) {
getAccount(profile, function (err, account) {
getReports(account, function (err, reports) {
sendStatistics(reports, function (err) {
console.error(err);
});
});
});
});
});

// Do ✔️
getUser()
.then(getProfile)
.then(getAccount)
.then(getReports)
.then(sendStatistics)
.catch((err) => console.error(err));

// or using Async/Await

async function sendUserStatistics() {
try {
const user = await getUser();
const profile = await getProfile(user);
const account = await getAccount(profile);
const reports = await getReports(account);
return sendStatistics(reports);
} catch (e) {
console.error(err);
}
}

callback hell是很可怕的

3. 條件語句

使用非負條件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Don't ❌
function isUserNotVerified(user) {
// ...
}

if (!isUserNotVerified(user)) {
// ...
}

// Do ✔️
function isUserVerified(user) {
// ...
}

if (isUserVerified(user)) {
// ...
}

某些判斷可簡寫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Don't
if (isActive === true) {
// ...
}

if (firstName !== "" && firstName !== null && firstName !== undefined) {
// ...
}

const isUserEligible = user.isVerified() && user.didSubscribe() ? true : false;

// Don't ❌
if (isActive) {
// ...
}

if (!!firstName) {
// ...
}

const isUserEligible = user.isVerified() && user.didSubscribe();

if else避免過多分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Don't
function addUserService(db, user) {
if (!db) {
if (!db.isConnected()) {
if (!user) {
return db.insert("users", user);
} else {
throw new Error("No user");
}
} else {
throw new Error("No database connection");
}
} else {
throw new Error("No database");
}
}

// Do ✔️
function addUserService(db, user) {
if (!db) throw new Error("No database");
if (!db.isConnected()) throw new Error("No database connection");
if (!user) throw new Error("No user");

return db.insert("users", user);
}

儘早return會使你的程式碼線性化、更具可讀性且不那麼複雜。

優先使用 map 而不是 switch 語句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Don't ❌
const getColorByStatus = (status) => {
switch (status) {
case "success":
return "green";
case "failure":
return "red";
case "warning":
return "yellow";
case "loading":
default:
return "blue";
}
};

// Do ✔️
const statusColors = {
success: "green",
failure: "red",
warning: "yellow",
loading: "blue",
};

const getColorByStatus = (status) => statusColors[status] || "blue";

減少複雜度且提升效能。

只有if的話

1
2
3
4
5
6
7
// Don't ❌ 
if (foo) {
doSomething();
}

✔️ // Do
foo && doSomething();

善用三元表達式

1
2
3
4
5
6
7
8
9
10
11
12
13
// Don't ❌
function getFee(isMember) {
if(isMember){
return '$2.00';
} else {
return '$10.00';
};
};

// Do ✔️
function getFee(isMember) {
return isMember ? '$2.00' : '$10.00';
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function example() {
if (condition1) { return value1; }
else if (condition2) { return value2; }
else if (condition3) { return value3; }
else { return value4; }
}

// Equivalent to:
function example() {
return condition1 ? value1
: condition2 ? value2
: condition3 ? value3
: value4;
}

如果有很多else if,可考慮改成三元表達式

三元好棒,但不要走火入魔

1
2
3
4
5
6
7
8
9
10
11
12
13
// Don't ❌
width = container > 960 ? (growWithContainer ? (container * .8) : 960) : container;

// Do ✔️
if (container > 960) {
if (growWithContainer) {
width = container * .8;
} else {
width = 960;
}
} else {
width = container;
}

有時不是寫得越短越好,有些比較複雜的邏輯不用硬要追求很短,
一般來說寫的越好的Code,通常越好維護

4. 其他

解構賦值

1
2
3
4
5
6
7
8
9
10
11
12

// Don't ❌
const name = user.name;
const lastName = user.lastName;
const email = user.email;

const a = arr[0];
const b = arr[1];

// Do ✔️
const {name, lastName, email} = user;
const [a, b] = arr;

把0縮寫

1
2
3
4
5
6
7
// Don't ❌
const SALARY = 150000000,
TAX = 15000000;

// Do ✔️
const SALARY = 15e7,
TAX = 15e6;

避免過多的註解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Don't ❌
function generateHash(str) {
// Hash variable
let hash = 0;

// Get the length of the string
let length = str.length;

// If the string is empty return
if (!length) {
return hash;
}

// Loop through every character in the string
for (let i = 0; i < length; i++) {
// Get character code.
const char = str.charCodeAt(i);

// Make the hash
hash = (hash << 5) - hash + char;

// Convert to 32-bit integer
hash &= hash;
}
}

// Do ✔️
function generateHash(str) {
let hash = 0;
let length = str.length;
if (!length) {
return hash;
}

for (let i = 0; i < length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}

有人說最乾淨的code就是不下註解
遵守有意義的命名,只註解複雜的程式邏輯。

小結

不是說一定怎麼寫會比較好,但前人的經驗告訴我們,在一般的情況下遵從以上的原則,可以提升程式碼的可讀性,更好維護。

參考資料

如何寫出乾淨的 JavaScript 程式碼
5 Best Practices for Writing Clean JavaScript
10 Clean code examples (Javascript).