踐指南)
1. 拆解 Apollo 6.0 Planning 模塊的 Protobuf 定義從消息結(jié)構(gòu)到工程實(shí)踐Apollo 6.0 的 Planning 模塊是整個(gè)自動駕駛系統(tǒng)里最“燒腦”的部分之一它負(fù)責(zé)把上游感知、定位、地圖、預(yù)測的結(jié)果揉在一起算出一條安全、平滑、可執(zhí)行的軌跡。而 Protobuf 定義就是這套復(fù)雜邏輯的“骨架”和“接口契約”。很多剛接觸 Apollo 的朋友一上來就扎進(jìn) EM Planner、Lattice Planner 的算法細(xì)節(jié)結(jié)果被各種proto文件里的字段繞得暈頭轉(zhuǎn)向。其實(shí)把 Planning 模塊的 Protobuf 定義吃透你就能像看地圖一樣清楚知道數(shù)據(jù)從哪來、到哪去、中間經(jīng)過了哪些關(guān)鍵節(jié)點(diǎn)。這篇文章我就結(jié)合自己實(shí)際調(diào)試和二次開發(fā)的經(jīng)驗(yàn)把 Apollo 6.0 Planning 模塊的 Protobuf 定義從頭到尾捋一遍。不管你是剛?cè)胄械淖詣玉{駛軟件工程師還是想基于 Apollo 做二次開發(fā)的愛好者只要你想搞清楚 Planning 模塊到底怎么“思考”、怎么“決策”這些內(nèi)容都能直接拿來參考。1.1 為什么 Protobuf 在 Planning 模塊里如此關(guān)鍵Apollo 是一個(gè)典型的分布式、模塊化系統(tǒng)。感知、預(yù)測、規(guī)劃、控制這些模塊可能跑在不同的進(jìn)程甚至不同的計(jì)算單元上。它們之間要交換數(shù)據(jù)就得有一套高效、跨語言、結(jié)構(gòu)化的序列化方案。Protobuf 正好滿足這些需求二進(jìn)制傳輸效率高、字段兼容性好、代碼生成機(jī)制成熟。在 Planning 模塊里Protobuf 不僅僅是“數(shù)據(jù)容器”它更是模塊間通信的“合同”。比如預(yù)測模塊輸出一個(gè)PredictionObstacles規(guī)劃模塊拿到后會把它轉(zhuǎn)換成內(nèi)部需要的障礙物信息再結(jié)合地圖和定位生成最終的ADCTrajectory。如果你不理解這些 proto 里每個(gè)字段的含義調(diào)試時(shí)看到一堆二進(jìn)制或者 JSON 輸出根本無從下手。我剛開始調(diào) Planning 的時(shí)候遇到過一個(gè)典型問題車輛在路口莫名其妙急剎。查了半天最后發(fā)現(xiàn)是PredictionObstacles里某個(gè)障礙物的priority字段被錯誤地設(shè)成了高優(yōu)先級導(dǎo)致規(guī)劃器把它當(dāng)成了必須避讓的目標(biāo)。如果當(dāng)時(shí)我能快速定位到 proto 定義看看這個(gè)字段的取值邏輯可能幾分鐘就解決了。所以熟悉 Protobuf 定義不只是“看懂代碼”更是提升調(diào)試效率、避免踩坑的基本功。1.2 Apollo 6.0 Planning 模塊的 Proto 文件組織方式Apollo 的代碼倉庫里modules/planning/proto目錄下存放著 Planning 模塊所有的 Protobuf 定義。這些文件大致可以分成幾類第一類是規(guī)劃器配置類比如planning_config.proto、planner_config.proto它們定義了各種規(guī)劃算法的參數(shù)和開關(guān)第二類是決策與場景類比如decision.proto、scenario.proto描述了規(guī)劃器在不同場景下的決策邏輯第三類是軌跡與調(diào)試類比如ADCTrajectory.proto、planning_internal.proto它們定義了最終輸出的軌跡和內(nèi)部調(diào)試信息。這些 proto 文件之間互相引用形成了一個(gè)有層次的依賴關(guān)系。比如PlanningConfig里會包含PlannerConfig而ADCTrajectory里又會引用Decision和PlanningDebug。理解這種組織方式你就能在需要修改或擴(kuò)展功能時(shí)快速找到對應(yīng)的文件而不是像無頭蒼蠅一樣亂翻。2. 核心 Proto 消息逐個(gè)拆解從配置到軌跡2.1 PlanningConfig規(guī)劃模塊的“總控面板”PlanningConfig是 Planning 模塊的頂層配置它決定了規(guī)劃器用哪種算法、以什么頻率運(yùn)行、在哪些場景下啟用哪些功能。這個(gè) proto 里最關(guān)鍵的字段是planner_type它指定了當(dāng)前使用的規(guī)劃器類型比如EM、LATTICE、NAVI等。Apollo 6.0 默認(rèn)使用的是PUBLIC_ROAD模式內(nèi)部會根據(jù)場景動態(tài)切換規(guī)劃器。另一個(gè)重要字段是default_task_config它包含了任務(wù)級別的配置比如DP_POLY_PATH_OPTIMIZER、QP_SPEED_OPTIMIZER等。這些任務(wù)配置直接影響了軌跡生成的平滑度和計(jì)算耗時(shí)。在實(shí)際項(xiàng)目中我經(jīng)常需要調(diào)整PlanningConfig里的planning_cycle_time和emergency_planning_cycle_time。前者是正常規(guī)劃周期默認(rèn) 100ms后者是緊急情況下的規(guī)劃周期默認(rèn) 50ms。如果你發(fā)現(xiàn)車輛在緊急避障時(shí)反應(yīng)不夠快可以適當(dāng)降低這個(gè)值但要注意計(jì)算負(fù)載。另外PlanningConfig里還有一個(gè)enable_scenario_based_planning開關(guān)打開后會啟用基于場景的規(guī)劃框架這是 Apollo 6.0 的一個(gè)重要特性。我建議在調(diào)試新場景時(shí)先把這個(gè)開關(guān)打開這樣能更清晰地看到每個(gè)場景的決策過程。2.2 ADCTrajectory規(guī)劃模塊的“最終答卷”ADCTrajectory是 Planning 模塊輸出的核心消息它包含了車輛未來一段時(shí)間內(nèi)的完整軌跡。這個(gè) proto 里最重要的字段是trajectory_point它是一個(gè)數(shù)組每個(gè)點(diǎn)包含了path_point和v、a、relative_time等信息。path_point里又有x、y、z、theta、kappa、s等字段分別表示位置、航向角、曲率、縱向位移。這些數(shù)據(jù)直接送給控制模塊控制模塊再根據(jù)它們計(jì)算油門、剎車和轉(zhuǎn)向。我踩過的一個(gè)坑是ADCTrajectory里的trajectory_point數(shù)組長度不是固定的。它取決于規(guī)劃的時(shí)間窗口和采樣間隔。如果你在代碼里硬編碼了數(shù)組長度一旦配置改變就會越界。正確的做法是動態(tài)遍歷或者用trajectory_point_size()獲取實(shí)際長度。另外ADCTrajectory里還有一個(gè)estop字段當(dāng)規(guī)劃器無法生成安全軌跡時(shí)會置位這個(gè)字段控制模塊收到后會執(zhí)行緊急停車。調(diào)試時(shí)如果看到車輛突然停下先檢查這個(gè)字段往往能快速定位問題。2.3 Decision規(guī)劃器的“思考過程”Decision這個(gè) proto 記錄了規(guī)劃器在每個(gè)規(guī)劃周期做出的決策比如跟車、換道、避讓、停車等。它里面最核心的是main_decision包含了stop、cruise、follow、overtake等子決策。每個(gè)子決策又有自己的參數(shù)比如stop里有stop_point和reasonfollow里有target_obstacle_id和distance。這些信息對于調(diào)試非常有用因?yàn)樗鼈兡芨嬖V你規(guī)劃器“為什么”這么做。舉個(gè)例子有一次車輛在跟車時(shí)距離前車過近我查看Decision里的follow決策發(fā)現(xiàn)target_obstacle_id指向的障礙物并不是正前方的車輛而是一個(gè)相鄰車道被誤判的物體。順著這個(gè)線索我檢查了預(yù)測模塊的輸出發(fā)現(xiàn)那個(gè)障礙物的位置估計(jì)有偏差。如果沒有Decision這個(gè) proto我可能得花大量時(shí)間在日志里大海撈針。所以我強(qiáng)烈建議在調(diào)試時(shí)把Decision信息打印出來或者用 Apollo 的可視化工具查看它能極大提升排查效率。2.4 PlanningInternal調(diào)試信息的“百寶箱”PlanningInternal是 Planning 模塊內(nèi)部使用的調(diào)試信息 proto它包含了大量的中間計(jì)算結(jié)果比如DP_StGraph、SpeedPlan、PathPlan等。這些信息在正常運(yùn)行時(shí)不會輸出但當(dāng)你需要深入分析規(guī)劃器的行為時(shí)它們就是無價(jià)之寶。比如DP_StGraph里的total_s和total_t能告訴你動態(tài)規(guī)劃在時(shí)空圖上的搜索范圍SpeedPlan里的speed_point能展示速度曲線的細(xì)節(jié)。我通常會在planning.conf里把enable_planning_debug設(shè)為true這樣PlanningInternal就會被填充并發(fā)布到/apollo/planning_internal話題上。然后用rosbag錄下來再用 Python 腳本解析。這個(gè)過程雖然有點(diǎn)繁瑣但當(dāng)你需要優(yōu)化軌跡平滑度或者分析規(guī)劃器為什么選擇某條路徑時(shí)這些數(shù)據(jù)能提供最直接的證據(jù)。需要注意的是打開調(diào)試信息會增加計(jì)算和通信開銷所以只在調(diào)試階段使用上線時(shí)要關(guān)掉。3. 實(shí)操如何基于 Proto 定義進(jìn)行二次開發(fā)與調(diào)試3.1 環(huán)境準(zhǔn)備與 Proto 編譯在開始二次開發(fā)之前你需要確保 Apollo 的編譯環(huán)境已經(jīng)就緒。Apollo 6.0 使用 Bazel 作為構(gòu)建工具Proto 文件的編譯也是通過 Bazel 自動完成的。你不需要手動運(yùn)行protoc只要在BUILD文件里正確聲明依賴Bazel 就會生成對應(yīng)的 C 和 Python 代碼。不過如果你需要單獨(dú)查看某個(gè) proto 生成的代碼可以在bazel-bin目錄下找到。比如modules/planning/proto/ADCTrajectory.pb.h就是編譯后生成的 C 頭文件。我建議在修改任何 proto 之前先運(yùn)行一次完整的編譯確保基線是干凈的。命令很簡單./apollo.sh build。如果編譯失敗先解決環(huán)境問題再動代碼。另外Apollo 的 Proto 文件里有很多optional和repeated字段修改時(shí)要注意兼容性。比如如果你把一個(gè)optional字段改成repeated可能會導(dǎo)致舊版本的代碼解析失敗。所以在團(tuán)隊(duì)協(xié)作中修改 proto 一定要謹(jǐn)慎最好遵循“只增不改”的原則。3.2 自定義規(guī)劃器配置的實(shí)操步驟假設(shè)你想調(diào)整 EM Planner 的路徑優(yōu)化權(quán)重讓軌跡更貼近車道中心線。這個(gè)參數(shù)在EMPlannerConfig里具體是path_optimizer_config下的lateral_weight。你可以直接修改modules/planning/proto/em_planner_config.proto里的默認(rèn)值但更推薦的做法是在planning.conf里覆蓋。Apollo 的配置系統(tǒng)支持從文件加載配置你可以在modules/planning/conf/planning.conf里添加一行--lateral_weight1.5。這樣就不需要重新編譯改完重啟 Planning 模塊即可生效。不過這里有個(gè)細(xì)節(jié)planning.conf里的參數(shù)名必須和 proto 里的字段名完全一致包括大小寫。我見過有人寫成--lateralWeight1.5結(jié)果配置沒生效查了半天才發(fā)現(xiàn)是命名風(fēng)格不匹配。另外修改配置后最好用cyber_monitor看一下/apollo/planning話題的輸出確認(rèn)軌跡確實(shí)發(fā)生了變化。如果沒變化可能是配置沒加載成功或者被其他配置覆蓋了。3.3 利用 PlanningInternal 進(jìn)行軌跡分析的完整流程前面提到PlanningInternal是調(diào)試?yán)鬟@里我詳細(xì)說一下怎么用。首先確保planning.conf里enable_planning_debug為true。然后啟動 Apollo讓車輛或模擬器跑一段。接著用rosbag record /apollo/planning_internal錄制數(shù)據(jù)。錄完后你可以用 Apollo 自帶的planning_trajectory_analyzer工具或者自己寫 Python 腳本解析。解析時(shí)重點(diǎn)關(guān)注DP_StGraph里的total_s和total_t它們能告訴你規(guī)劃器在時(shí)空圖上的搜索范圍。如果total_s太小可能意味著規(guī)劃器沒有充分考慮遠(yuǎn)距離的障礙物如果total_t太大可能會增加計(jì)算負(fù)擔(dān)。我通常會把SpeedPlan里的speed_point畫成速度-時(shí)間曲線直觀地看加速、減速是否平滑。有一次我發(fā)現(xiàn)曲線在某個(gè)點(diǎn)突然抖動檢查后發(fā)現(xiàn)是QP_SPEED_OPTIMIZER的max_acceleration設(shè)得太大導(dǎo)致求解器在邊界附近震蕩。把值調(diào)小后曲線就平滑了。這個(gè)經(jīng)驗(yàn)告訴我調(diào)試規(guī)劃器不能只看最終軌跡中間過程的數(shù)據(jù)往往更能說明問題。4. 常見問題與排查技巧實(shí)錄4.1 Proto 字段解析失敗或默認(rèn)值異常在實(shí)際調(diào)試中最常見的問題之一是 Proto 字段解析失敗或者讀出來的值是默認(rèn)值。這通常有幾個(gè)原因一是 proto 文件版本不匹配比如你用的代碼是舊版但數(shù)據(jù)是新版序列化的二是字段被optional修飾但發(fā)送方?jīng)]有設(shè)置接收方就會拿到默認(rèn)值三是網(wǎng)絡(luò)傳輸過程中數(shù)據(jù)被截?cái)唷E挪闀r(shí)可以先檢查cyber_monitor里話題的發(fā)布頻率和消息大小如果消息大小異常小可能是序列化出了問題。另外Apollo 的日志里通常會有protobuf相關(guān)的錯誤信息比如Failed to parse看到這類日志就要警惕。我遇到過一次ADCTrajectory里的trajectory_point數(shù)量總是 0。查了半天發(fā)現(xiàn)是發(fā)送方在填充 proto 時(shí)忘記調(diào)用add_trajectory_point()而是直接用了mutable_trajectory_point()但沒有設(shè)置大小。這種錯誤在 C 里很隱蔽因?yàn)榫幾g不會報(bào)錯。所以在寫填充 proto 的代碼時(shí)一定要仔細(xì)檢查add_和mutable_的使用場景。4.2 規(guī)劃器決策與預(yù)期不符的排查思路當(dāng)規(guī)劃器的決策和你的預(yù)期不符時(shí)比如該換道時(shí)不換道該停車時(shí)不停車第一步應(yīng)該是查看Decisionproto 里的main_decision。如果main_decision是cruise但你認(rèn)為應(yīng)該follow那就要檢查PredictionObstacles里是否有障礙物被正確識別。有時(shí)候障礙物的priority或is_static字段設(shè)置不當(dāng)會導(dǎo)致規(guī)劃器忽略它。第二步是檢查PlanningInternal里的PathPlan和SpeedPlan看看路徑和速度曲線是否合理。如果路徑規(guī)劃正常但速度規(guī)劃異常那問題可能出在QP_SPEED_OPTIMIZER的約束上。我總結(jié)了一個(gè)簡單的排查順序先看Decision再看PlanningInternal最后看ADCTrajectory。這個(gè)順序能幫你快速縮小問題范圍。另外Apollo 提供了一個(gè)planning_visualizer工具可以把這些 proto 信息可視化出來比看日志直觀得多。如果條件允許我強(qiáng)烈建議用可視化工具輔助排查。4.3 性能問題與 Proto 大小的權(quán)衡Planning 模塊對實(shí)時(shí)性要求很高通常要在 100ms 內(nèi)完成一次規(guī)劃。如果 Proto 消息太大序列化和反序列化的開銷就會增加進(jìn)而影響整體性能。PlanningInternal雖然有用但它包含大量中間數(shù)據(jù)如果一直開啟會顯著增加 CPU 和內(nèi)存負(fù)擔(dān)。所以我的經(jīng)驗(yàn)是只在調(diào)試階段開啟enable_planning_debug并且盡量縮小錄制范圍只錄關(guān)鍵話題。另外ADCTrajectory里的trajectory_point數(shù)量也要控制一般 50-100 個(gè)點(diǎn)就足夠控制模塊使用了太多點(diǎn)不僅浪費(fèi)帶寬還可能引入不必要的抖動。還有一個(gè)容易被忽視的點(diǎn)Proto 里的string字段。比如Decision里的reason字段如果填充了很長的字符串也會增加消息大小。在性能敏感的場合可以考慮用枚舉代替字符串或者限制字符串長度。這些細(xì)節(jié)在開發(fā)初期可能不明顯但到了系統(tǒng)集成階段往往會成為性能瓶頸。4.4 常見問題速查表問題現(xiàn)象可能原因排查方法解決建議軌跡點(diǎn)數(shù)量為 0發(fā)送方未正確填充trajectory_point檢查add_trajectory_point()調(diào)用確保每次規(guī)劃都添加至少一個(gè)點(diǎn)規(guī)劃器忽略障礙物PredictionObstacles里priority或is_static設(shè)置錯誤查看Decision里的follow或stop決策調(diào)整預(yù)測模塊的優(yōu)先級邏輯速度曲線抖動QP_SPEED_OPTIMIZER的max_acceleration過大分析PlanningInternal里的SpeedPlan適當(dāng)降低加速度上限配置不生效planning.conf里參數(shù)名與 proto 字段名不匹配對比 proto 文件和配置文件確保命名完全一致包括大小寫調(diào)試信息缺失enable_planning_debug未開啟檢查planning.conf調(diào)試時(shí)設(shè)為true上線時(shí)關(guān)閉這個(gè)表是我在實(shí)際項(xiàng)目中慢慢積累的每次遇到新問題我都會補(bǔ)充進(jìn)去?,F(xiàn)在它已經(jīng)成了我排查 Planning 問題的第一手資料。5. 從 Proto 定義看 Planning 模塊的擴(kuò)展與優(yōu)化5.1 如何安全地?cái)U(kuò)展 Proto 字段Apollo 的 Planning 模塊還在不斷演進(jìn)你很可能需要添加新的字段來支持新功能。比如你想在ADCTrajectory里增加一個(gè)confidence字段表示軌跡的置信度。這時(shí)候一定要遵循 Protobuf 的兼容性規(guī)則新增字段必須用新的字段編號不能復(fù)用已刪除字段的編號。另外新字段最好是optional或repeated避免用required因?yàn)閞equired在跨版本通信時(shí)容易出問題。修改完 proto 后記得重新編譯并更新所有依賴該 proto 的代碼。如果團(tuán)隊(duì)里有多個(gè)分支最好提前溝通避免沖突。我個(gè)人的習(xí)慣是在修改 proto 時(shí)會在文件頭部加一行注釋說明修改日期和原因。這樣以后回溯時(shí)能快速知道這個(gè)字段是為什么加的。雖然看起來有點(diǎn)繁瑣但在長期維護(hù)的項(xiàng)目里這個(gè)習(xí)慣能省下很多溝通成本。5.2 基于 Proto 的規(guī)劃器性能調(diào)優(yōu)經(jīng)驗(yàn)Planning 模塊的性能調(diào)優(yōu)很大程度上是在 Proto 配置里調(diào)參數(shù)。比如DP_POLY_PATH_OPTIMIZER里的sample_points數(shù)量直接影響路徑搜索的精度和耗時(shí)。如果sample_points太少路徑可能不夠平滑太多計(jì)算時(shí)間會線性增長。我通常的做法是先在模擬環(huán)境里用默認(rèn)值跑一遍記錄耗時(shí)和軌跡質(zhì)量然后逐步增加sample_points觀察變化找到一個(gè)平衡點(diǎn)后再在實(shí)車上驗(yàn)證。這個(gè)過程需要耐心但效果通常很顯著。另一個(gè)關(guān)鍵參數(shù)是planning_cycle_time。Apollo 默認(rèn)是 100ms但在某些復(fù)雜場景下規(guī)劃器可能算不過來。這時(shí)候你可以考慮降低規(guī)劃頻率比如改成 150ms給規(guī)劃器更多時(shí)間。但要注意降低頻率會影響控制的實(shí)時(shí)性所以必須配合控制模塊的調(diào)參。我試過在低速園區(qū)場景下把planning_cycle_time調(diào)到 200ms車輛依然運(yùn)行平穩(wěn)但高速場景下就不行了。所以這個(gè)參數(shù)一定要結(jié)合具體場景來定。5.3 利用 Proto 進(jìn)行跨模塊協(xié)同調(diào)試Planning 模塊不是孤立的它和感知、預(yù)測、控制都有緊密的交互。通過 Proto 定義你可以清晰地看到模塊間的數(shù)據(jù)流。比如PredictionObstacles從預(yù)測模塊傳到規(guī)劃模塊規(guī)劃模塊處理后生成ADCTrajectory再傳給控制模塊。如果車輛行為異常你可以沿著這條數(shù)據(jù)流逐個(gè)模塊檢查 Proto 消息。我常用的方法是在cyber_monitor里同時(shí)打開/apollo/prediction、/apollo/planning和/apollo/control三個(gè)話題對比它們的輸出。如果預(yù)測模塊輸出的障礙物位置和規(guī)劃模塊使用的障礙物位置不一致那問題就出在數(shù)據(jù)傳遞或轉(zhuǎn)換環(huán)節(jié)。這種跨模塊調(diào)試方法能幫你快速定位問題是在哪個(gè)模塊產(chǎn)生的而不是盲目地改代碼。我見過很多新手一遇到問題就懷疑規(guī)劃算法結(jié)果查了半天發(fā)現(xiàn)是感知模塊的誤差。所以熟悉 Proto 定義不僅能讓你看懂 Planning 模塊還能讓你對整個(gè)自動駕駛系統(tǒng)的數(shù)據(jù)流有更全面的認(rèn)識。5.4 一個(gè)真實(shí)的調(diào)試案例換道決策異常最后分享一個(gè)我實(shí)際遇到的案例。有一次車輛在高速上應(yīng)該換道超車但規(guī)劃器一直不執(zhí)行。我按照前面的排查順序先看Decision發(fā)現(xiàn)main_decision是cruise沒有overtake子決策。然后看PlanningInternal里的PathPlan發(fā)現(xiàn)路徑規(guī)劃器確實(shí)生成了一條換道路徑但速度規(guī)劃器沒有跟上。再查QP_SPEED_OPTIMIZER的配置發(fā)現(xiàn)max_lateral_acceleration設(shè)得太小導(dǎo)致速度規(guī)劃器認(rèn)為換道時(shí)橫向加速度會超限于是拒絕了換道。把max_lateral_acceleration從 1.0 調(diào)到 1.5 后換道就正常了。這個(gè)案例說明Proto 里的參數(shù)不是孤立的它們之間會相互影響。調(diào)試時(shí)不能只看一個(gè)字段要結(jié)合多個(gè) Proto 消息一起分析。另外這個(gè)案例也提醒我配置參數(shù)的默認(rèn)值不一定適合所有場景必須根據(jù)實(shí)際道路條件和車輛特性進(jìn)行調(diào)整。每次調(diào)整后最好用rosbag錄下來反復(fù)回放分析確保改動確實(shí)解決了問題而不是引入了新的問題。5.5 關(guān)于 Proto 定義維護(hù)的個(gè)人體會在長期維護(hù) Planning 模塊的過程中我最大的體會是Proto 定義不僅僅是技術(shù)文檔它更是團(tuán)隊(duì)協(xié)作的“共同語言”。當(dāng)算法工程師、軟件工程師和測試工程師都基于同一套 Proto 定義來溝通時(shí)很多歧義和誤解就會自然消失。所以我建議在項(xiàng)目初期就建立 Proto 定義的評審機(jī)制任何修改都要經(jīng)過團(tuán)隊(duì)討論確保每個(gè)人都理解字段的含義和影響。另外定期更新 Proto 定義的注釋把每個(gè)字段的用途、取值范圍、默認(rèn)值都寫清楚這對后來者非常友好。還有一點(diǎn)不要害怕修改 Proto。雖然修改 Proto 可能會帶來一些兼容性問題但只要遵循規(guī)則這些問題都是可控的。相反如果因?yàn)楹ε滦薷亩岩恍┡R時(shí)邏輯硬編碼在代碼里后期維護(hù)會更痛苦。我在早期項(xiàng)目中就犯過這個(gè)錯誤把一些配置寫死在 C 代碼里結(jié)果每次調(diào)整都要重新編譯效率極低。后來改成 Proto 配置后調(diào)試效率提升了好幾倍。所以該用 Proto 的地方就用 Proto這是 Apollo 設(shè)計(jì)哲學(xué)的一部分。