)
最近在把一套 Flutter 項目從 Android 往 OpenHarmony 上遷移時第一個讓我專門停下來重新設計的不是業(yè)務頁面而是已經(jīng)維護了兩年的三方庫 api_exception_manager。它在原來 Android 和 iOS 雙端項目里的職責很明確全局網(wǎng)絡異常攔截、統(tǒng)一錯誤歸因、任務生命周期治理。說白了App 里所有請求的超時、斷網(wǎng)、業(yè)務報錯以及頁面銷毀后的異步任務清理都歸它管。到了鴻蒙上這套邏輯不能直接從 Java 和 Kotlin 平移到 ArkTS而且它恰好是一個把 Dart 層和原生層串起來的插件所以適配難度比普通業(yè)務代碼高出不少。這篇文章不是 SDK 文檔的翻譯而是我實際遷移過程中的完整記錄。里面包括了哪些能力能直接復用、哪些必須重寫、全局異常攔截在 OpenHarmony 上怎么做才不丟數(shù)據(jù)以及任務生命周期管理從 Flutter 引擎?zhèn)鹊?UIAbility 側的橋接方案。如果你也在做 Flutter 庫的鴻蒙適配或者準備把現(xiàn)有 App 遷到鴻蒙生態(tài)這篇應該能幫你少踩幾個坑。1. 這個庫在我的項目里到底管了哪些事1.1 沒有統(tǒng)一治理時的網(wǎng)絡錯誤長什么樣做過 Flutter 網(wǎng)絡層的同學應該都體會過這種場景Dio 的 onError 回調分散在各個模塊里有人把超時錯誤提示成“網(wǎng)絡異?!庇腥税褬I(yè)務錯誤碼直接拋給 UI更常見的是頁面已經(jīng)銷毀后異步任務還在跑最終回調訪問了 dispose 之后的 State控制臺里刷出一串 Unhandled Exception。這些問題單獨看都不致命但累積到一定規(guī)模就會變成線上排查的災難現(xiàn)場——同一個接口的錯誤在十個模塊里有十種表現(xiàn)形式。我項目里實際出過一次比較典型的線上事故服務端高峰期超時客戶端一個頁面里有 6 個并發(fā)請求同時出錯因為每處都自己處理錯誤結果彈了 6 個不同的錯誤提示框而且其中一個頁面已經(jīng)切走了錯誤回調還在往 ViewModel 里塞狀態(tài)。當時排查了很久才發(fā)現(xiàn)是某個模塊的 catch 分支沒有判斷 mounted。這個教訓直接讓我決定把網(wǎng)絡異常統(tǒng)一收口。治理前后的差別可以用一張表簡單對照維度治理前治理后超時提示各模塊自行彈窗文案混亂統(tǒng)一文案、統(tǒng)一 UI 行為請求重試部分模塊有本地重試大量重復代碼重試策略集中配置全程可觀測失敗歸因依賴開發(fā)翻日志信息零散統(tǒng)一 traceId 加階段標記一鍵溯源頁面銷毀后回調偶發(fā) Unhandled Exception任務被取消回調被安全攔截業(yè)務錯誤碼各端映射邏輯不一致單一錯誤碼歸因器1.2 api_exception_manager 的核心抽象方式api_exception_manager 這個庫做的事可以概括成三個層面。第一是異常模型統(tǒng)一。它把 DioException、SocketException、業(yè)務錯誤碼甚至是本地緩存讀寫失敗全部包裝成統(tǒng)一的 ApiException 對象帶上錯誤碼、發(fā)生階段、堆棧、請求上下文。業(yè)務層不需要關心底層是什么異常來源只需要處理一種類型。第二是攔截器鏈。在請求發(fā)出前和響應返回后各有一個攔截環(huán)節(jié)前者處理鑒權、鏈路追蹤 ID 注入后者處理錯誤歸一化、重試判斷、降級策略。攔截器鏈不是簡單的 List 遍歷每層攔截器都消費異常并返回一個 ActionResult決定是放行、重試、降級還是終止。第三是任務生命周期管理。它內部維護一個 TaskManager每個網(wǎng)絡請求都會被注冊成一個 Task關聯(lián)到當前的頁面或業(yè)務 scope。頁面銷毀時統(tǒng)一取消未完成的任務并且對已經(jīng)排隊的回調做安全檢查避免銷毀后還收到網(wǎng)絡響應。這套設計在 Android 上的實現(xiàn)依賴了 Activity 的 lifecycle 回調和網(wǎng)絡狀態(tài)監(jiān)聽而這兩塊恰恰是遷移到 OpenHarmony 時最先要動刀的地方。我適配時定下的原則是Dart 層只做協(xié)議和調度不改任何對外 API把“感知系統(tǒng)生命周期”“獲取網(wǎng)絡狀態(tài)”“收集原生側異常堆棧”這三類能力抽成 Platform 接口分別寫 Android 實現(xiàn)和 OpenHarmony 實現(xiàn)。這樣上層業(yè)務代碼在換平臺后一行都不用改。2. 鴻蒙適配的第一道坎Flutter 插件的平臺層改造2.1 OpenHarmony 上 Flutter 插件的目錄結構與腳手架Flutter 插件在 OpenHarmony 上的工程結構和標準 Flutter 插件相比多了一個 ohos 目錄api_exception_manager/ ├─ lib/ # Dart 代碼跨端復用 ├─ android/ # Android 原生實現(xiàn) ├─ ios/ # iOS 原生實現(xiàn) ├─ ohos/ # OpenHarmony 原生實現(xiàn)ArkTS │ ├─ index.ets # 插件入口需要實現(xiàn) FlutterPlugin 接口 │ ├─ src/main/ets/ # 實際原生邏輯 │ └─ build-profile.json5 # 鴻蒙構建配置 └─ pubspec.yaml有一個細節(jié)值得注意OpenHarmony 社區(qū)對 Flutter 插件的規(guī)范還在演進不同版本的 SDK 對插件注冊方式有細微差異。我在適配時鎖定了社區(qū)維護的 flutter_flutter 分支版本沒有追最新主分支因為穩(wěn)定版的 API 文檔和示例更全遇到問題也更容易在社區(qū)找到同路人。這一點對庫適配特別重要——庫是給所有人用的優(yōu)先兼容穩(wěn)定方案而不是追新。在 ohos/index.ets 中插件需要注冊原生的 MethodChannel handler。以我用的版本為例核心結構大致是import { FlutterPlugin, MethodCall, MethodChannel } from flutter/engine; export class ApiExceptionManagerPlugin implements FlutterPlugin { private channel?: MethodChannel; onAttachedToEngine(binding: FlutterPluginBinding): void { this.channel new MethodChannel( binding.getBinaryMessenger(), api_exception_manager ); this.channel.setMethodCallHandler((call: MethodCall) { // 處理來自 Dart 側的方法調用 }); } onDetachedFromEngine(): void { this.channel?.setMethodCallHandler(null); this.channel undefined; } }這里要特別強調 onDetachedFromEngine 里把 handler 置空。Android 插件開發(fā)時很多人會漏掉這一步在鴻蒙上一樣會踩雷——插件被 detach 之后如果 handler 還活著很容易出現(xiàn)僵尸調用Dart 側隨機收到一條來自舊通道的響應。2.2 原生能力從 Kotlin 平移到 ArkTS原來的 Android 實現(xiàn)里有幾件事依賴系統(tǒng) API遷移到鴻蒙后對應關系如下能力Android 實現(xiàn)OpenHarmony 實現(xiàn)網(wǎng)絡狀態(tài)感知ConnectivityManager NetworkCallback網(wǎng)絡管理 API 加系統(tǒng)廣播監(jiān)聽生命周期感知Application.registerActivityLifecycleCallbacksEntryAbility.onForeground / onBackground / onDestroy原生線程堆棧Thread.getAllStackTraces目前獲取受限采用降級方案崩潰捕獲UncaughtExceptionHandler對接系統(tǒng) crash 日志服務實際寫下來發(fā)現(xiàn)前兩類在鴻蒙上都有關鍵 API 可用只是回調時機和 Android 不完全一樣。而“原生線程堆?!边@一項OpenHarmony 上目前能拿到的信息比 Android 少我的處理是降級只采集 Dart 層堆棧加上請求啟動時的時間戳和 Task 狀態(tài)足夠定位絕大多數(shù)網(wǎng)絡問題不糾結于完整的原生線程棧。另外還要注意 ArkTS 的語法風格和 Kotlin 差異很大最明顯的是空安全處理更嚴格。Kotlin 里一個可空類型用?.就帶過去了ArkTS 里對 nullable 的檢查要求更細稍不注意就會出現(xiàn)編譯告警。我遷移時出現(xiàn)過一次比較典型的編譯不過一個成員變量可能在 onBackground 之前沒被初始化ArkTS 編譯器要求必須顯式判空才能使用被迫把所有晚初始化字段都改成了undefined初始化加運行時校驗。2.3 方法通道的類型映射差異Flutter 的 MethodChannel 在 Android 上和鴻蒙上的類型映射有細微差異。Android 標準映射里 Java 的 Map、List、Int 都有明確對應鴻蒙 ArkTS 側通過兼容層做轉換實際踩到的坑是Dart 側傳 Int64 類型的 ID 給鴻蒙原生時到達 ArkTS 側后可能變成 number 或 string取決于通道序列化方式。這會導致原生側用嚴格相等比較時判斷失敗進而引發(fā)回調匹配不到任務的 bug。我的解決方案很土但很有效在所有跨通道傳遞的 ID、時間戳、錯誤碼上統(tǒng)一使用 String 類型禁止傳數(shù)字。雖然多了一次字符串轉換的開銷但對通道消息來說完全可以忽略而且徹底規(guī)避了類型不一致造成的隱性 bug。這個規(guī)范也寫進了團隊的三方庫開發(fā)文檔里后續(xù)其它插件做鴻蒙適配時直接沿用。3. 全局網(wǎng)絡異常攔截的適配實操3.1 異常模型統(tǒng)一與攔截鏈設計適配第一件事是保證 Dart 側對外 API 不變。庫原來的用法大致是ApiExceptionManager.instance.configure( onException: (ApiException e) { // 統(tǒng)一處理埋點、提示、上報 return ExceptionAction.retry; }, retryCount: 2, retryDelay: const Duration(milliseconds: 500), );底層攔截鏈我拆成了三段請求攔截器、響應攔截器、異常歸因器。請求攔截器負責把 Task 注冊到 TaskManager并注入 traceId響應攔截器把各種底層異常翻譯成 ApiException異常歸因器根據(jù)錯誤碼決定重試、降級還是直接拋給業(yè)務層。在鴻蒙適配中沒有改這個模型因為 Dart 層并不關心底層是哪個平臺發(fā)的請求。核心代碼結構大致是這樣class ExceptionInterceptorChain { final ListExceptionInterceptor _interceptors []; void process(ApiException exception, TaskContext context) { var current exception; for (final interceptor in _interceptors) { final result interceptor.handle(current, context); if (result.action ExceptionAction.retry) { _scheduleRetry(result, context); return; } if (result.action ExceptionAction.cancel) { context.task.cancel(); return; } current result.exception ?? current; } } }這里有一個容易被忽略的設計點攔截器鏈必須持有 TaskContext而不是只持有異常本身。因為重試和降級都需要知道當前任務的剩余次數(shù)、所屬 scope、是否已經(jīng)被頁面取消。如果沒有 TaskContext攔截器就只能做純靜態(tài)判斷無法感知動態(tài)的任務狀態(tài)。3.2 重試、降級與熔斷的開關配置對網(wǎng)絡異常做重試不是無腦重試我在庫里的默認配置是超時、斷網(wǎng)、連接重置可重試最多 2 次指數(shù)退避500ms 到 1000ms4xx 業(yè)務錯誤不重試直接進入業(yè)務提示5xx 服務端錯誤可重試 1 次但如果連續(xù) 3 次 5xx觸發(fā)熔斷10 秒內不再發(fā)起新請求重試邏輯放在異常歸因器里而不是放在 Dio 攔截器之外是為了保證重試也經(jīng)過異常模型統(tǒng)一歸因避免某次重試失敗后錯誤信息風格突變。鴻蒙適配中有一個和 Android 不同的點OpenHarmony 的網(wǎng)絡棧在部分設備上對弱網(wǎng)場景的表現(xiàn)不如 Android 穩(wěn)定DNS 解析偶爾會卡到 3 秒以上。所以我把超時配置從原來的 connectTimeout 10 秒、receiveTimeout 15 秒調整為 connectTimeout 15 秒、receiveTimeout 20 秒并且把 DNS 解析失敗歸入可重試集合。這個調整在實測中把弱網(wǎng)場景的請求成功率從 96.1% 提到了 98.7%代價是錯誤提示晚出現(xiàn)幾秒但用戶感知反而是變好的——因為多數(shù)情況下重試一次就成功了。注意一個細節(jié)熔斷狀態(tài)是全局共享的不是單請求維度。我實現(xiàn)了一個簡單的滑動窗口計數(shù)器在原生層保持同步。這樣即使同時有多個請求并發(fā)失敗熔斷也能生效而不是每一個請求都單獨重試把自己打成對服務端的二次攻擊。3.3 把異常上報做扎實避免假陽性異常模型接好、攔截鏈跑通之后我發(fā)現(xiàn)線上上報的“斷網(wǎng)異?!睌?shù)量異常高后來一查不是真的斷網(wǎng)而是 OpenHarmony 上部分設備在冷啟動后網(wǎng)絡權限尚未完成初始化時第一個請求就發(fā)出去直接被底層判定為 no route to host。這些請求發(fā)生在網(wǎng)絡能力就緒之前屬于典型的假陽性。處理方案是在請求攔截器里加一個網(wǎng)絡就緒閘門調用一個由原生側提供的 isNetworkReady 能力如果返回 false請求統(tǒng)一進入延時隊列等網(wǎng)絡狀態(tài)廣播確認就緒后再發(fā)出。這個閘門只在 App 冷啟動后的前 5 秒內啟用避免影響正常請求速度。我特意沒有把閘門做成永久性的否則每次請求都多一次原生調用反而引入額外延遲。同時上報邏輯本身也要做緩沖。原來 Android 端異常上報是直接走 OkHttp 打點到統(tǒng)計服務鴻蒙上我改成了先寫入本地緩沖隊列每 10 秒批量 flush 一次。好處是避免異常風暴時打爆鏈路層壞處是如果應用被強殺最后幾秒的緩沖數(shù)據(jù)會丟。但在實際場景里丟幾秒的統(tǒng)計數(shù)據(jù)和打爆網(wǎng)絡棧兩者之間我選前者。4. 任務生命周期管理別等引擎告訴你要自己感知4.1 Flutter 自帶生命周期的盲區(qū)Flutter 里開發(fā)者最熟悉的是 WidgetsBindingObserver.didChangeAppLifecycleState它能收到 resumed、inactive、paused、detached 這些狀態(tài)。但這套機制感知的是 Flutter 引擎所在窗口的生命周期在鴻蒙上有兩個明顯盲區(qū)。第一應用的 UIAbility 已經(jīng) onBackground但 Flutter 引擎的 paused 可能延遲幾百毫秒才到這段時間內的異步任務可能在錯誤時機執(zhí)行。第二頁面級的銷毀比如用戶從最近任務里劃掉應用Flutter 側可能直接走到 detached但中間不會給業(yè)務層一個明確的“立即取消所有任務”的信號。在 Android 上我可以用 LifecycleObserver 精確拿到 onStop、onDestroy鴻蒙上對應的則是 EntryAbility 的 onBackground、onDestroy。這些回調需要橋接到 Dart 層才能讓 TaskManager 在正確的時間點做清理。無視這個盲區(qū)的后果是任務取消時機不穩(wěn)定部分狀態(tài)回調泄漏到銷毀后的頁面里用戶體感就是偶發(fā)閃退或者頁面復用異常。4.2 UIAbility 生命周期到 Dart 層的橋接我在 ohos 側使用 EventChannel 把生命周期事件推送到 Dart 側// EntryAbility.ets import { UIAbility } from kit.AbilityKit; export default class EntryAbility extends UIAbility { onBackground() { // 通過 EventChannel 廣播給 Dart LifecycleBridge.instance?.sendEvent(onBackground, { timestamp: Date.now(), }); } onDestroy() { LifecycleBridge.instance?.sendEvent(onDestroy, { timestamp: Date.now(), }); } }Dart 側在 TaskManager 里訂閱_lifecycleChannel.receiveBroadcastStream().listen((event) { final name event[name] as String; if (name onBackground || name onDestroy) { _taskManager.cancelAllTasks(reason: name); } });這里有一個值得細說的點onBackground 時我并不是立即取消所有任務而是標記任務進入“遲到風險”狀態(tài)給一個 5 秒的寬限期。因為后臺立即取消任務會導致用戶切回 App 時那些本來馬上能返回結果的請求全部需要重發(fā)。5 秒之后仍未完成的任務才強制取消并觸發(fā)錯誤歸因器生成一個“任務被生命周期中斷”的 ApiException。這個寬限期設計來自一次真實事故用戶在看新聞詳情頁時切到微信回消息不到 10 秒切回來發(fā)現(xiàn)詳情頁空白重新加載了一遍。原因就是 onBackground 后所有請求被立即取消。加了寬限期后短后臺切換的任務成功率明顯提升。4.3 在生命周期節(jié)點做任務清理的真實收益庫內置的 TaskManager 會記錄每個任務的啟動時間、綁定的業(yè)務 scope、當前狀態(tài)。業(yè)務層在頁面銷毀時只需要調用ApiExceptionManager.instance.taskManager .cancelTasksByScope(pageScopeId);被取消的任務在回調層會收到一個 CancelledException而不是靜默消失。這樣做的核心收益是杜絕 Unhandled Exception以及避免回調在銷毀后的 State 上執(zhí)行。實測接入這套生命周期管理后崩潰日志里的 LateInitializationError 和 Unhandled Exception 數(shù)量減少了大概八成。另外要注意鴻蒙側 UIAbility 的 onDestroy 觸發(fā)時機在部分設備上可能晚于 Flutter 引擎 detach所以我在 TaskManager 里加了一道兜底當 Flutter 引擎?zhèn)仁盏?detached 時把該應用會話內所有 scope 的任務全部取消。兩道清理邏輯是互補關系不能互相替代。只依賴其中任何一邊都會有漏網(wǎng)之魚。生命周期事件在適配過程中實際上組成了這樣一套處理矩陣系統(tǒng)事件Dart 收到時機TaskManager 動作onForeground引擎 resumed 前約 200ms解除后臺暫停標記允許新任務入隊onBackground引擎 paused 前約 500ms啟動 5 秒寬限期不立即取消寬限期超時精確計時觸發(fā)強制取消未完成任務生成中斷異常onDestroy引擎 detach 前或后設備差異立即取消當前 scope 全部任務engine detached引擎生命周期終點兜底取消全部任務5. 實測中踩過的一組坑及其排查鏈路5.1 任務取消事件滯后導致的回調泄漏第一次跑通完整鏈路后發(fā)現(xiàn)一個詭異問題頁面已經(jīng)銷毀網(wǎng)絡請求已經(jīng)被 TaskManager 標記為 cancelled但過了一會控制臺還是打印出了網(wǎng)絡回調。排查鏈路是這樣的。先懷疑取消邏輯沒執(zhí)行打印日志確認 TaskManager 確實在 onDestroy 觸發(fā)了 cancelTasksByScope。再懷疑任務沒有真正中斷Dio 的 cancel 確實會讓請求拋 CancelledException這點也驗證過了。最后才發(fā)現(xiàn)問題不在請求側而在響應側請求已經(jīng)發(fā)出原生層 hold 住的 HttpClient Future 并沒有因為 cancel 而真正 abortDart 層 Future 提前完成后回調鏈路上那個陳舊的 Future 仍然執(zhí)行了 onSuccess 分支。解決方法是在 Task 內部維護一個 cancelled 標志位所有回調在分發(fā)前先檢查標志位if (_taskCancelled || !scope.isActive) return;這個標志位檢查發(fā)生在 Future.then 之前而不是之后保證已經(jīng)排隊回調的 Future 也過不了閘門。這個改動看起來簡單但確實需要回調分發(fā)和任務狀態(tài)更新之間嚴格按照“先更新狀態(tài)再觸發(fā)取消通知”的順序執(zhí)行否則狀態(tài)更新晚于通知就會出現(xiàn)競態(tài)。5.2 網(wǎng)絡狀態(tài)監(jiān)聽的重復注冊另一個坑出現(xiàn)在鴻蒙側網(wǎng)絡狀態(tài)監(jiān)聽上。Android 實現(xiàn)里我在 Application 初始化時注冊一個全局網(wǎng)絡回調鴻蒙側也照著做了但忽略了 UIAbility 的 onForeground 和 onBackground 多次切換導致的監(jiān)聽器重復注冊。第一次測試時只是多打印兩行日志沒在意直到線上出現(xiàn)“網(wǎng)絡狀態(tài)回調風暴”的告警同一個廣播在一秒內觸發(fā)了 37 次把攔截器里的降級邏輯反復觸發(fā)部分請求被重復重試。修復方式很直接在 onBackground 中注銷網(wǎng)絡監(jiān)聽在 onForeground 中重新注冊并確保注冊前注銷舊實例。但這里又引出一個新問題重新注冊之間如果有幾毫秒間隙恰巧來了網(wǎng)絡狀態(tài)變化就會丟失一次狀態(tài)通知。所以我改成在 unregister 前先把當前狀態(tài)緩存register 后立刻用緩存補發(fā)一次狀態(tài)。這套“先緩存后重注冊再補發(fā)”的處理既避免了重復監(jiān)聽又不丟事件。排查這類問題的經(jīng)驗是不要只盯著實現(xiàn)代碼看先確認平臺回調的注冊和注銷是不是成對出現(xiàn)。鴻蒙側的 UIAbility 生命周期方法和 Android 的 Activity 生命周期非常相似但應用模型下回調觸發(fā)次數(shù)和嵌套關系不完全一致最容易出的問題就是“只注冊不注銷”。5.3 在 OpenHarmony 上驗證穩(wěn)定性的方法庫適配完最怕的是“本機跑通了但不敢保證線上穩(wěn)”。我這邊驗證穩(wěn)定性用的是一套組合拳。第一用 OpenHarmony 官方的 XTS 認證套件跑基礎兼容性測試重點看應用狀態(tài)切換和網(wǎng)絡異常注入這兩類用例。XTS 認證測試對開發(fā)者來說不是可選項鴻蒙生態(tài)分發(fā)時這是硬門檻早跑早發(fā)現(xiàn)問題別等適配全部完成后才去碰。第二在弱網(wǎng)環(huán)境下做真實場景模擬。用路由器限速和丟包工具把上下行延遲調到 800ms 以上、丟包率 5%重跑首頁 20 個接口的全鏈路用例。主要觀察三件事超時后重試是否觸發(fā)、任務取消后是否還有回調泄漏、降級方案是否在預期時間內生效。第三做小范圍真機驗證覆蓋不同 SoC 方案和系統(tǒng)裁剪程度的設備。OpenHarmony 的碎片化比 Android 好一些但不同設備廠商對系統(tǒng)裁剪程度不同生命周期回調時序會有差異。有的設備 onDestroy 會先于 Flutter engine detach有的會反過來。我建議在庫的初始化階段輸出一份“生命周期時序診斷日志”把 UIAbility 回調、Flutter 引擎狀態(tài)、TaskManager 事件統(tǒng)一打點。上線后如果遇到生命周期相關異常直接拉日志對照不用再靠猜。這個診斷日志幫我定位了至少三次來自設備差異導致的任務取消順序問題。6. 一點個人經(jīng)驗與后續(xù)計劃這次適配最終沒有改 Dart 層任何對外接口所有平臺差異都被壓在 ohos 原生層和少量 Dart 內部適配代碼里。這個結果符合我一開始定的原則三方庫做鴻蒙適配優(yōu)先保證 API 穩(wěn)定讓上層業(yè)務遷移成本趨近于零。實際交付后項目里其他模塊接鴻蒙的過程也確實沒有遇到因為異常庫引發(fā)的額外改動。如果接下來你還想在這個方向上繼續(xù)深入我個人建議優(yōu)先研究兩件事一是 OpenHarmony 上 Flutter 引擎的渲染落地方式它會直接影響頁面銷毀時生命周期回調的精確時機二是庫的自動化測試建設把生命周期時序和異常攔截鏈路的用例用 flutter_test 和鴻蒙側集成測試一起固化下來。穩(wěn)定性的價值往往在平臺切換那一刻體現(xiàn)得最明顯。最后分享一個小技巧做鴻蒙適配時別急著把原生能力一次性配齊先跑通一條最核心的鏈路比如“請求失敗、異常模型轉換、任務取消”這一段把基礎打通后再往上疊加網(wǎng)絡監(jiān)聽、堆棧采集、冷啟動閘門這些能力。每加一塊就回歸一次之前的用例。整個適配過程會可控很多出問題時也容易定位。這個節(jié)奏比一次性重寫全部邏輯再集中調試要高效得多。