昨天學會了用 ARS 做技術研究,今天馬上就派上用場了。
下午我正在開發 Grimo 的專案列表功能。
突然,應用程式崩潰了:
Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException:
at androidx.compose.ui.platform.DesktopOwners_desktopKt$setContent$3.invoke
at androidx.compose.ui.platform.DesktopOwners_desktopKt$setContent$3.invoke
... 47 more lines of stack trace ...
Caused by: java.lang.IllegalStateException
at ProjectListViewModel.kt:45
... 23 more lines ...
看著這堆 stack trace,我整個人都不好了。
錯誤發生在哪?UI 層?ViewModel?還是資料層?IllegalStateException
到底是什麼狀態出問題?為什麼 iOS 開發者總說 KMP 的錯誤處理很糟糕?
Compose Desktop 的 stack trace 混合了:
結果就是 100 行的 stack trace,只有 2 行是你的程式碼。
// ViewModel
viewModelScope.launch {
try {
val project = repository.getProject(id)
_state.value = Success(project)
} catch (e: Exception) {
// 這裡捕獲的是什麼?
// SQLException? IOException? 還是其他?
_state.value = Error(e.message ?: "Unknown error")
}
}
當錯誤從資料層傳到 UI,原始的錯誤資訊早就丟失了。
// iOS 端收到的錯誤
do {
let project = try await repository.getProject(id: projectId)
} catch {
// error 永遠是 NSError
// 沒有類型資訊
// 訊息可能是 null
print("Error: \(error)") // "Error: kotlin.Exception"
}
iOS 開發者:「你們的錯誤處理呢?」
我:「...」
面對這些問題,我意識到需要系統性的解決方案。
是時候請出 ARS(Architecture Research Specialist)了。
請以 ARS 角色研究 Kotlin Multiplatform 專案的錯誤處理最佳實踐,
特別關注:
1. Compose Desktop 的除錯體驗
2. iOS/Swift 互操作性
3. 2025 年的最新實踐
4. 實際專案案例
需求背景:
- 單人開發團隊
- Desktop 優先,未來支援 iOS/Android
- 需要清晰的錯誤追蹤
定義研究範圍 → 搜尋業界實踐 → 分析KMP生態 → 研究Swift Export → 評估解決方案 → 產出報告 → 實作建議
經過深入研究,ARS 很快產出了這份報告:
核心發現是傳統 try-catch 在 KMP 已過時。推薦方案是 Result Type + Sealed Class。關鍵技術包括 Kotlin 2.2.20 Swift Export。除錯效率會大幅提升。
為什麼傳統方式失敗?
// ❌ 傳統方式的問題
class ProjectRepository {
suspend fun getProject(id: String): Project {
return database.getProject(id) // 可能拋出 SQLException
}
}
// 在 iOS 端變成
func getProject(id: String) async throws -> Project
// throws 只能拋出 NSError,丟失所有類型資訊
JVM 特定異常污染也是個問題。
// ❌ commonMain 不應該有 JVM 特定類型
catch (e: SQLException) { // 編譯錯誤:iOS 沒有 SQLException
// ...
}
核心設計如下:
// ✅ Sealed Class 定義錯誤類型
sealed interface AppError {
val message: String
val isRecoverable: Boolean
data class Database(
override val message: String,
val operation: String? = null,
override val isRecoverable: Boolean = true
) : AppError
data class Network(
override val message: String,
val statusCode: Int? = null,
override val isRecoverable: Boolean = true
) : AppError
data class Validation(
val field: String,
val reason: String,
override val message: String = "$field: $reason",
override val isRecoverable: Boolean = false
) : AppError
}
// ✅ Result 包裝器
sealed class AppResult<out T, out E : AppError> {
data class Success<T>(val value: T) : AppResult<T, Nothing>()
data class Failure<E : AppError>(val error: E) : AppResult<Nothing, E>()
}
優雅的錯誤處理方式:
// Repository 層
class ProjectRepository {
suspend fun getProject(id: String): AppResult<Project, AppError> {
return AppResult.catching {
database.getProject(id)
}
}
}
// ViewModel 層
class ProjectViewModel {
fun loadProject(id: String) {
viewModelScope.launch {
repository.getProject(id).fold(
onSuccess = { project ->
logger.debug { "載入專案成功: ${project.name}" }
_state.value = UiState.Success(project)
},
onFailure = { error ->
logger.error { "載入專案失敗: ${error.message}" }
when (error) {
is AppError.Network -> handleNetworkError(error)
is AppError.Database -> handleDatabaseError(error)
else -> showGenericError(error)
}
}
)
}
}
}
expect/actual 機制:
// commonMain
expect fun mapThrowableToError(throwable: Throwable): AppError
// desktopMain
actual fun mapThrowableToError(throwable: Throwable): AppError {
return when (throwable) {
is SQLException -> {
when {
throwable.message?.contains("locked") == true ->
AppError.Database.locked()
throwable.message?.contains("no such table") == true ->
AppError.Database.tableNotFound(extractTableName(throwable))
else ->
AppError.Database(
message = throwable.message ?: "資料庫錯誤",
sqlState = throwable.sqlState
)
}
}
is SocketTimeoutException ->
AppError.Network.timeout(throwable.message ?: "")
is IOException ->
AppError.Network(message = "網路錯誤: ${throwable.message}")
else ->
AppError.Unknown(message = throwable.message ?: "未知錯誤")
}
}
Cash App (Square) 有 50+ 開發者,採用 Result + Sealed Class,錯誤報告準確度大幅提升。
Expedia 有 20+ 開發者,採用 Arrow-kt Either,iOS 團隊滿意度大幅提升。
JetBrains Fleet 有 100+ 開發者,採用自定義 Result + Coroutines,除錯效率明顯提升。
基於你的需求(單人團隊、Desktop 優先),建議:
立即實施
工具配置
// 協程異常處理
val errorHandler = CoroutineExceptionHandler { _, error ->
val appError = mapThrowableToError(error)
logger.error { "協程異常: ${appError.message}" }
}
除錯增強
// 開發模式的詳細錯誤
data class Unknown(
override val message: String,
val originalMessage: String? = null,
val stackTrace: String? = if (DEBUG) throwable?.stackTraceToString() else null
) : AppError
// 錯誤處理一團糟
try {
val project = repository.getProject(id)
// ...
} catch (e: Exception) {
println("Error: ${e.message}") // "Error: null"
// 然後呢?🤷
}
除錯流程很痛苦。看 stack trace、加 print statements、重現問題、猜測原因。每一步都很耗時。
// 清晰的錯誤處理
repository.getProject(id).fold(
onSuccess = { project ->
logger.info { "專案載入成功: ${project.name}" }
},
onFailure = { error ->
when (error) {
is AppError.Database -> {
logger.error { "資料庫錯誤: operation=${error.operation}" }
if (error.isRecoverable) {
retryWithBackoff()
} else {
showDatabaseError(error)
}
}
is AppError.Network -> {
logger.warn { "網路錯誤: status=${error.statusCode}" }
loadFromCache()
}
else -> handleUnexpectedError(error)
}
}
)
現在的除錯流程簡單多了。看錯誤類型、檢查錯誤詳情、定位問題、修復。每一步都很清楚。
除錯體驗改善了。錯誤定位變快、修復時間縮短、錯誤重現率降低。
程式碼品質也提升了:
// Before: 大量混亂的 try-catch
// After: 清晰的 Result 處理
// Before: 大量 catch (e: Exception)
// After: 無通用 Exception 捕獲
// Before: 錯誤無法追蹤
// After: 所有錯誤都有類型
iOS 準備就緒了:
// Swift 端的體驗(使用 Kotlin 2.2.20 Swift Export)
let result = await repository.getProject(id: projectId)
switch result {
case .success(let project):
print("載入成功: \(project.name)")
case .failure(let error):
switch error {
case let dbError as AppError.Database:
print("資料庫錯誤: \(dbError.message)")
case let netError as AppError.Network:
print("網路錯誤: 狀態碼 \(netError.statusCode ?? 0)")
default:
print("其他錯誤")
}
}
iOS 開發者:「終於有個像樣的錯誤處理了!」
ARS 不只幫我解決了錯誤處理問題,還帶來了架構層面的思考:
錯誤是領域知識
// 錯誤不是「異常」,而是業務邏輯的一部分
sealed interface ProjectError : AppError {
data class NotFound(val projectId: String) : ProjectError
data class NoPermission(val userId: String) : ProjectError
data class Archived(val archivedDate: Long) : ProjectError
}
可復原性設計
interface AppError {
val isRecoverable: Boolean
val retryStrategy: RetryStrategy?
}
// 自動重試機制
suspend fun <T> withRetry(
action: suspend () -> AppResult<T, AppError>
): AppResult<T, AppError> {
var lastError: AppError? = null
repeat(3) { attempt ->
action().fold(
onSuccess = { return AppResult.success(it) },
onFailure = { error ->
if (!error.isRecoverable) return AppResult.failure(error)
lastError = error
delay(exponentialBackoff(attempt))
}
)
}
return AppResult.failure(lastError!!)
}
錯誤觀察性
// 錯誤追蹤系統
class ErrorTracker {
fun track(error: AppError) {
analytics.track("error_occurred", mapOf(
"type" to error::class.simpleName,
"recoverable" to error.isRecoverable,
"message" to error.message
))
if (!error.isRecoverable) {
crashlytics.recordException(
ErrorTrackingException(error)
)
}
}
}
這次研究展現了 ARS 的核心價值。
全面性,不只看技術,還看團隊案例。時效性,關注 2025 最新實踐(Swift Export)。實用性,提供可執行的程式碼範例。深度,從問題根源到解決方案。
不要等到出問題才想錯誤處理:
// 專案初期就定義好
project-template/
├── core/
│ ├── error/
│ │ ├── AppError.kt
│ │ ├── AppResult.kt
│ │ └── ErrorHandler.kt
@Test
fun `當資料庫鎖定時應該回傳可復原錯誤`() {
// Given
every { database.query() } throws SQLException("database is locked")
// When
val result = repository.getData()
// Then
assertTrue(result is AppResult.Failure)
val error = result.error
assertTrue(error is AppError.Database)
assertTrue(error.isRecoverable)
}
/**
* 取得專案資料
*
* @return 可能的錯誤:
* - [AppError.Database.tableNotFound] - 資料表不存在
* - [AppError.Network.timeout] - 同步逾時
* - [AppError.Validation] - ID 格式錯誤
*/
suspend fun getProject(id: String): AppResult<Project, AppError>
不用 ARS 的話,要 Google 搜尋、看大量 Medium 文章、看 Stack Overflow、試錯實作。花很多時間,還不確定是否最佳實踐。
使用 ARS 就不一樣了。定義研究需求、ARS 研究、評估報告、實作。時間大幅縮短,而且有完整的最佳實踐支撐。
ARS 的研究報告包含 3 個實際公司案例、完整的程式碼範例、優缺點分析、2025 最新技術、實作步驟指南。
這是單純 Google 搜尋無法達到的深度。
今天的經驗再次證明,專業的問題需要專業的角色。
ARS 不只是一個「會搜尋的 AI」。它懂架構研究方法論、知道去哪找資料、會評估方案優劣、能給出實作建議。
這次的錯誤處理改造,讓我的除錯效率大幅提升。
更重要的是,建立了一套可持續的錯誤處理架構。
定義你的痛點,什麼問題最常困擾你?設計專門角色,為這個問題設計 AI 研究員。系統性解決,不要頭痛醫頭,要找根本方案。累積最佳實踐,把研究成果固化成架構。
記住,好的架構是研究出來的,不是猜出來的。
「錯誤不是異常,是系統設計的一部分。」
關於作者:Sam,一人公司創辦人。正在打造 Grimo,一個智能任務管理和分配平台。
專案連結:GitHub - grimostudio