在開發過程中,我們會使用各種資料結構來表示不同類型的資料,例如 List、Set、Map 等。List 作為一種有序的元素集合,允許重複元素,並提供基於索引(Index)的存取方式,適用於需要保持元素順序的場景。在程式中,我們經常需要與 List 互動,例如取出所有資料來顯示,或對 List 進行篩選 (where) 與轉換 (map) 等操作。
大多數情況下,對 List 的操作並不複雜,程式碼的可讀性也不會受到影響。然而,某些特定的寫法可能會降低可讀性,尤其是當我們使用索引來存取 List 時,可能會帶來潛在的問題。今天我們就來看看這些情況。
使用 Index 表示資料
在較舊的開發方式中,為了避免 API 直接暴露資料的屬性,可能會讓 API 回傳一個 JSON 陣列,其中每個位置代表某個特定的資料屬性。例如:
[123, "Jonh", 27, "[email protected]", "male"] 當客戶端接收到這樣的回應時,必須透過 Index 來解析資料:
Future<User> getUser() async {
final data = await api.get("/user").data;
return User.from(
id: data[0],
name: data[1],
age: data[2],
email: data[3],
gender: data[4],
);
} 類似地,當某個方法需要回傳多個值時,我們可能會選擇用 List 來存放結果,讓呼叫端透過 Index 來取出對應的值:
Widget build(BuildContext context) {
final results = summarizeFee(orders);
return Column(
children: [
Text("Price ${result[0].toString()}"),
Text("Fee ${result[1].toString()}"),
],
);
}
List<int> summarizeFee(List<Order> orders) {
var price = 0;
var fee = 0;
for (var order in orders) {
price += order.price;
fee += order.fee;
}
return [price, fee];
} 在這些例子中,當我們閱讀 data[0] 或 results[0] 時,完全無法從字面上理解它們的意義,必須對照命名參數或其他使用處,才能弄清楚這些 Index 所代表的資訊。
那要如何解決呢?
以第一個例子來說,即便使用 List 避免了直接暴露屬性名稱,但這種方法終究是「防君子不防小人」,沒有太大的實際意義,就盡量避免使用。而對於回傳多個值的情境,我們可以改用更具可讀性的方式,例如使用具名類別或 Record 來改善可讀性:
({int price, int fee}) summarizeFee(List<Order> orders) {
var price = 0;
var fee = 0;
for (var order in orders) {
price += order.price;
fee += order.fee;
}
return (price: price, fee: fee);
} 接著我們來看看另一個 Index 造成的問題。
Index 判斷
在以下程式碼中,我們根據 index 是否為 0 來決定是否要顯示標題:
Widget build(BuildContext context) {
return Column(
children: users.mapIndexed((index, user) {
return Column(
children: [
if (index == 0)
Text("User List Title"),
Text(user.name),
]
);
}).toList(),
);
} 乍看之下,這段程式碼可能不容易理解為何會有這個判斷條件。但細看後會發現,這是為了讓標題只顯示一次。
與其依賴 Index 來決定顯示標題的時機,我們可以直接將標題作為 Column 的第一個子元素,如下所示:
Widget build(BuildContext context) {
return Column(
children: [
Text("User List Title"),
...users.map((user) {
return Text(user.name);
}),
],
);
} 這樣的寫法不僅更直覺,也更清楚地表達了「標題應該只顯示一次,並位於使用者名稱清單的最上方」。
然而,這仍然不完全等價於原始程式碼。原始版本的邏輯還包括「當 users 為空時,不應顯示標題」。因此,我們可以進一步調整程式碼,使其更明確地表達這一邏輯:
Widget build(BuildContext context) {
if (users.isEmpty) {
return SizedBox.shrink();
}
return Column(
children: [
Text("User List Title"),
...users.map((user) {
return Text(user.name);
}),
],
);
} 如此一來,讀者能夠一眼看出程式的意圖:標題僅在 users 非空時顯示,並且只會出現一次。
小結
使用 Index 並非絕對不妥,而是應視情境而定。我們應該考慮索引是否真正表達了程式的意圖,還是讓讀者需要額外推理才能理解它的用途。透過適當的資料結構與語法特性,我們可以讓程式碼更易讀、更容易維護。