昨天說到開箱即用的程式,今天來看看一眼看穿的程式。
先從專案的目錄結構說起,不管你用什麼語言開發,都必須要有一個固定的目錄結構,這樣才能讓其他人看得懂你的程式。
忌同一個小組,出現不同的目錄結構,這樣會讓人很頭痛,這樣的程式碼,只有原作者看得懂,其他人都看不懂。
而且其他小組的成員也無法來協助你,不管在日後的維護或是開發上,都會變得很困難。
相信不少團隊在開發的時候,應該有有一個 project boilerplate,這樣才能讓團隊的成員,都能夠快速的開發,而且不會有太多的問題,就是我們上一章說的開箱即用的程式。
這種 boilerplate 的設計百百種,但對於一個面向業務的程式,我建議專案中需要存在一個「core」的目錄
蝦米瓦「core」?
我們來舉例 core 是幹嘛用的,如果一個停車系統,那麼專案裡面的 core 應該會有(但不限於)以下的目錄
- core
- parking
- Lot
- Space
- Ticket
如果是一個麥當勞點餐系統,那麼專案裡面的 core 應該會有(但不限於)以下的目錄
- core
- order
- menu
- item
- payment
我猜你應該猜出來,core 是幹嘛,core 和我們設計軟體時的「核心業務」有關,也就是說,core 裡面的程式碼,都是和業務有關的,而且是核心業務,和 MVC 的 M 有點像,但不完全一樣,因為 core 裡面的程式碼,不一定是 model,也可能是 controller
當我們打開一個陌生的程式,我們可以很快的找到程式的核心業務,而且不會有太多的雜物(當然,這個前提是你的程式碼是乾淨的)。
有了核心業務為主的程式碼,我們可以從這裡開始閱讀程式碼,並且如果你的程式碼是乾淨的,要注意,我是說乾淨,不是簡單,也就是你可能會很複雜,就好像一副大型的拼圖,從規律中找出規律,這樣的程式碼,才是乾淨的,所以並非說業務很複雜,就無法寫出乾淨的程式碼。
市面上有很多目錄規則和範例,大家可按照自己的偏好來設計,並無傷大雅,但有幾個小建議:
建議:
- components
- List
- Items
- Item
- Title
- Content
- Action
不建議
- components
- List
- Items
- Item
- Title
- Content
- Action
使用 List 的時候,就會很明顯的知道,他是一個列表,而且他的子元件,都是和列表有關的,這樣的目錄結構,可以讓我們一目了然,並且程式碼需要寫成 compose components
// 這樣寫沒問題,但不建議,如果需要這樣寫,改成render props
<List>
<List.Items>
<List.Item>
<List.Title />
<List.Content />
<List.Action />
</List.Item>
</List.Items>
</List>
// 或者可以這樣寫
<List>
<List.Items>
{({ Item }) => (
<Item>
<List.Item>
{({ Title, Content, Action }) => (
<>
<Title />
<Content />
<Action />
</>
)}
</List.Item>
</Item>
)}
</List.Items>
</List>
但這樣寫好複雜,如果需要這樣的結構,它或許可以這樣寫
// 如果妳想客製化
const listData = []
<List
data={listData}
renderItem={({ item }) => (
<List.Item>
<List.Title />
<List.Content />
<List.Action />
</List.Item>
)}
/>
// 如果你只需要一個簡單的列表
const listData = []
<List data={listData} />
盡然要一眼看穿,那麼程式碼也要 self-explanatory,也就是說,程式碼要自我解釋,不需要註解,就能看懂。
來看看以下的程式碼
const isAdult = (age) => {
if (age >= 18) {
return true;
} else {
return false;
}
};
另外一個沒有 self-explanatory 的程式碼
const ia = (age) => {
if (age >= 18) {
return true;
} else {
return false;
}
};
另外一個極端的例子,就是去標籤化,也就是說,程式碼不僅限於某個 core,而是可以跨 core,這類的程式碼,以描述功能為主,而不是描述業務。
你可以這樣寫:
// 假設座位狀態存儲在一個物件中,座位號作為鍵,值表示是否已預約(true 表示已預約,false 表示未預約)
const seatStatus = {
A1: true,
A2: false,
A3: true,
B1: false,
B2: true,
// 其他座位...
};
const isSeatReserved = (seat) => {
// 檢查座位是否存在於 seatStatus 中,如果存在,返回其預約狀態,否則返回 false
return seatStatus.hasOwnProperty(seat) ? seatStatus[seat] : false;
};
// 測試座位是否已預約
console.log(isSeatReserved("A1")); // true
console.log(isSeatReserved("B1")); // false
console.log(isSeatReserved("C1")); // false(不存在的座位)
我們可以改成
const isReserved = (isReserved) => {
return isReserved === true;
};
console.log(isReserved("B1")); // true
console.log(isReserved("C1")); // false
如果一段程式碼,你不知道他的目的是什麼,那麼這段程式碼就是不好的,因為你不知道他的目的,在修改的時候,你可能會破壞他的目的。
舉個例子,我們在使用 redux 的時候,需要用 dispatch 來觸發 action,語法大概如下
import useDispath from "react-redux";
import { addTodo } from "./actions";
const dispatch = useDispath();
dispatch(
addTodo({
id: 1,
text: "hello world",
})
);
這樣寫沒有問題,但就是太麻煩,我們可以這樣寫
import redux from "globalState/redux";
redux.addTodo({
id: 1,
text: "hello world",
});
之後如果你想換 redux,你只需要改一個地方就好了,而且你不需要知道 dispatch 是什麼,你只需要知道 redux 是什麼,而且他的目的是什麼。
Declarative vs Imperative,這是一個很有趣的話題,也是一個很有趣的討論,但我們不會在這裡討論這個話題,我們只會討論一個簡單的問題,就是說,我們在面對業務的時候,儘量用 Declarative 的方式來寫程式碼,而不是 Imperative 的方式。
因為面對千變萬化的業務,我們永遠無法預測未來,也無法預測老闆的心,如果程式的順序即是程式的邏輯,那麼當業務變動的時候,我們的修改風險就會變大,因為我們不知道這個修改會不會影響其他的程式碼。
以下是一個簡單的例子
// Imperative
const isAdult = (age) => {
if (age >= 18) {
return true;
} else {
return false;
}
};
// Declarative
const isAdult = (age) => {
return age >= 18;
};
再來看另外一個比較複雜的例子
// Imperative
// 声明一个变量表示公交车的距离
var busDistance = 500; // 假设距离为500米
// 定义一个函数来检查公交车是否到站
function checkBusArrival() {
if (busDistance <= 0) {
console.log("公交车已经到站了!");
} else {
console.log("公交车还未到站,距离站台还有 " + busDistance + " 米。");
}
}
// 模拟定时器,每秒更新公交车的距离
var interval = setInterval(function () {
if (busDistance > 0) {
busDistance -= 50; // 假设每秒减少50米
checkBusArrival();
} else {
clearInterval(interval); // 公交车到站后停止定时器
}
}, 1000); // 每秒执行一次
以下是改寫後的例子
// 声明一个函数,用于检查公交车是否到站
function isBusAtStation(distance) {
return distance <= 0;
}
// 声明一个函数,用于显示公交车状态
function displayBusStatus(busAtStation, distance) {
if (busAtStation) {
console.log("公交车已经到站了!");
} else {
console.log("公交车还未到站,距离站台还有 " + distance + " 米。");
// 模拟定时器,每秒更新公交车的距离
setTimeout(function () {
const newDistance = distance - 50; // 假设每秒减少50米
if (newDistance > 0) {
displayBusStatus(false, newDistance);
} else {
displayBusStatus(true, 0);
}
}, 1000);
}
}
// 声明公交车的初始距离
const initialBusDistance = 500; // 假设初始距离为500米
// 使用声明式方式检查并显示公交车的状态
displayBusStatus(false, initialBusDistance);
下面的例子改起來,是不是比較「明確」和「有信心」呢?!
以上列舉了幾個小技巧,讓我們可以一眼看穿程式碼,但這些技巧並不是一定要遵守,而是一個建議,如果你有更好的方法,歡迎分享給我。
當然包括 clean code 和 SOLID 原則,也是一個很好的技巧,但這些技巧,我們會在之後的章節中討論。