本文探討使用 SDD (規格驅動開發) 概念在其它環境上的心得。
自從我在上一篇系列文中實做了產生類別的 SDD 練習之後, 我嘗試著將這個概念移植到我自己的 Android 專案中, 結果也大獲成功。
其實原理也是非常簡單的, 因為 Kotlin 與 C# 是極為相似的語言, 所以如果在 C# 行得通, 在 Kotlin 裡也應該行得通。
不過有個地方不太一樣。在 Kotlin 中並沒有 IEquatable 這樣的介面, 也不需要。但是你必須使用的類別是所謂的 data class, 它已經自帶 IEquatable 的必須方法, 它本來就必須 override 對應的方法。此外, 在 Kotlin 中實作 Comparable 介面後,不需要自己手動實作 >、<、>=、<= 這些運算子。這是 Kotlin 的「運算子過載(Operator Overloading)」特性帶來的便利, 也就是自動轉換。一旦實作了 compareTo 方法後,Kotlin 編譯器會自動將比較運算子轉換成對 compareTo 的呼叫。
於是我就把原來的 ClassGeneration.md 改了一下。此外我又把一些稍嫌累贅的部份刪除了, 看起來比較精簡:
<!-- 程式一. ClassGeneration.md -->
# Kotlin data 類別生成需求文件
## 概述
本文件指定了在 `MyStockApp` 方案中生成新 Kotlin data class 的需求。
## 類別定義
- **命名空間:** `MyStockApp`
- **類別名稱:** `[MyComparableObject]`(或開發者指定的名稱)
- **實作介面:**
- `Comparable<[MyComparableObject]>`
## 屬性
類別必須至少包含以下屬性:
1. **Id**
- **型別:** `string`
- **文件註解:** `/** 取得或設定識別碼 */`
2. **Name**
- **型別:** `string`
- **文件註解:** `/** 取得或設定名稱 */`
## 必需方法
必須實作以下方法以滿足 `Comparable` 介面以及對應的方法
### 比較邏輯建議
- **相等性比較**:建議基於 `Id` 屬性進行比較(如果 `Id` 是唯一識別符)
- **排序比較**:建議優先依據 `Name` 屬性,次要依據 `Id` 屬性進行排序
### 測試考量
生成的類別應能支援:
1. 物件相等性比較測試
2. 集合排序測試
3. `null` 參考處理測試
4. 雜湊碼一致性測試
## 使用指南
當需要生成符合此需求的類別時:
1. 確定類別名稱
2. 根據業務邏輯調整屬性的 KDoc
3. 根據實際需求調整相等性和比較邏輯
4. 確保所有必要的命名空間都已引用
在 Android Studio 裡有 Gemini 作為 AI 助手, 所以我把同樣的指令丟給它:
1. 根據 ClassGeneration.md 中述明的規格, 在本專案中建立一個 StockEntry data 類別檔案。該類別中除了規則中指定的屬性之外, 必須包括以下屬性: StockType (股票種類) (of EnumStockType), SalesType (買賣類別) (of EnumSalesType), Amount (買賣金額) (of float), SalesDate (買賣日期) (of Datetime), Cost (成本) (of float) 等等。
2. 上述的 EnumStockType (股票種類) (of Enum) 有以下幾個值: Listed=0 (上市股票), OTC=1 (上櫃股票), Innovation=2 (創新板), ETF=3, Unlisted=99 (未上市股票)。
3. 上述的 EnumStockType (買賣類別) 有兩個值: Buy=0 (買進), Sell=1 (賣出)。 然後 Gemini 就自動產生 StockEntry.kt 檔案並且準確地放進 data 子目錄下面:
/** 程式二. StockEntry.kt */
package com.khl.mystockapp.data
import java.time.LocalDateTime
/**
* 股票種類
*/
enum class EnumStockType(val value: Int) {
/** 上市股票 */
Listed(0),
/** 上櫃股票 */
OTC(1),
/** 創新板 */
Innovation(2),
ETF(3),
/** 未上市股票 */
Unlisted(99)
}
/**
* 買賣類別
*/
enum class EnumSalesType(val value: Int) {
/** 買進 */
Buy(0),
/** 賣出 */
Sell(1)
}
/**
* 股票交易項目
*/
data class StockEntry(
/** 取得或設定識別碼 */
val id: String,
/** 取得或設定名稱 */
val name: String,
/** 股票種類 */
val stockType: EnumStockType,
/** 買賣類別 */
val salesType: EnumSalesType,
/** 買賣金額 */
val amount: Float,
/** 買賣日期 */
val salesDate: LocalDateTime,
/** 成本 */
val cost: Float
) : Comparable<StockEntry> {
/**
* 排序比較
* 優先依據 Name 屬性,次要依據 Id 屬性進行排序
*/
override fun compareTo(other: StockEntry): Int {
val nameResult = this.name.compareTo(other.name)
if (nameResult != 0) {
return nameResult
}
return this.id.compareTo(other.id)
}
/**
* 相等性比較
* 基於 Id 屬性進行比較
*/
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StockEntry
return id == other.id
}
/**
* 雜湊碼
* 基於 Id 屬性
*/
override fun hashCode(): Int {
return id.hashCode()
}
}
不過話說回來, Gemini 幫我做出來的 compareTo 和 equals 方法的比較邏輯並不是完全一致, 這是有點奇怪的地方。像這樣的小細節, 仍然需要由開發者自己去發現和注意。如果你已經有自己偏好的比較邏輯, 而且希望未來它一次就把事情做好, 那麼可以考慮再去修改上述 .md 文件以符合你的需求。不過由於我在這裡只是為了做簡單的示範, 就不把我自己在專案裡的特殊需求貼上來了。