Community程式設計與開發github.com

yanghangmvp/sap-abap-pitfalls

ABAP 開發踩坑與解法庫(跨項目通用)。沉澱自實際客户 DEV ERP 專案 100+ 份交付報告與逐日糾錯記錄,共約 120 條「現象 → 根因 → 解法」,另附新專案開工 checklist(摸底/動手前/編碼自檢/交付/收尾五階段)。覆蓋 ADT 激活假成功/假鎖判定、ABAP 語法硬約束、「返回成功但數據沒變」的靜默失效清單、事務與 LUW、F4 與彈窗與 ALV、介面 JSON 與閘道、DDIC 建物件、ST22 與探針排查手法、打補丁腳本陷阱。開新 ABAP 專案做開場閱讀與開工檢查,或遇到「編譯通過但激活失敗 / 激活成功但運行不對 / 返回 S 但值沒變 / 報錯文案與真實原因無關 / 按鈕點了沒反應 / 數據莫名錯位」時載入本技能。

sap-abap-pitfalls 是什麼?

這是一個專為 ABAP 開發者設計的實戰經驗知識庫,旨在幫助開發者避免和解決 SAP ABAP 開發中常見的隱蔽問題。本技能收錄了約 120 條精選的「現象 → 根因 → 解法」案例,這些案例源自真實的 DEV ERP 專案交付報告和逐日除錯記錄,確保經驗的跨項目可複用性。技能內容涵蓋了從編譯、激活到運行階段的各種陷阱,特別是那些 SAP 系統不會報錯,但數據卻悄悄出錯的「靜默失效」情況。支援的開發平台包括 ADT (ABAP Development Tools) 和 MCP,並提供了結構化的使用指引:新專案開工前應閱讀 §11 的開工 checklist,遇到問題則可查閱 §10 的症狀速查表。此外,技能還深入探討了 ABAP 語法中的硬性約束、事務與 LUW 的處理、F4 幫助與 ALV 報表的開發細節、以及使用 ST22 短轉儲進行問題排查的進階技巧,是每個 ABAP 開發者不可或缺的案頭參考。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/yanghangmvp/MCP_ABAP/tree/HEAD/skills/sap-abap-pitfalls

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

ABAP 開發踩坑與解法庫

用途:下個專案開工前通讀一遍,排障時按症狀查。本技能只收「跨項目可複用」的經驗,環境地址、請求號、物件清單見項目側總綱(如 sap-abap-project-ops)。 完整 120 條(含程式碼片段與出處)見 references/casebook.md。

怎麼用:新專案開工 → 直接翻 §11 開工 checklist(按時間順序執行);已開工遇到問題 → §10 症狀速查 定位到分節;想深挖某條的完整程式碼與出處 → references/casebook.md。

三條元規則(比任何單條坑都重要):

  1. 報錯文案經常與真實原因無關 —— ABAP 編譯器的報錯點往往是「它解析到的位置」,不是「你寫錯的位置」。先假設是段序/關鍵字順序/型別不匹配,而不是語法本身。
  2. SAP 最危險的不是報錯,是靜默失效 —— 「返回 S、激活 200、編譯通過」三重假象下數據悄悄錯。凡涉及 BAPI、SELECT INTO TABLE、EXTENSIONIN、文字元素、增強欄位,一律做落庫對帳,不信返回值。
  3. 失敗必查 ST22,不猜 —— 短轉儲的 Error analysis 直接給出 procedure "XXX" "(FORM)" 與 CX_SY_* 異常類,比逐個 FORM 試快一個數量級。

0. 十條鐵律(違反即返工)

  1. 改任何已存在物件,第一步回讀現網。用戶常在 SE80 直改生產物件且不回寫歸檔,本地歸檔只作比對、不作基線。get_source → 與備份 diff 確認 0 差異 → 打補丁 → 回讀再 diff → 反向覆蓋歸檔。
  2. 文字元素同屬基線,get_source 不含 TEXTPOOL。覆蓋式推文字前必須 read_textelements 取激活版全文存檔、與本地 JSON diff、人工合併再推。否則用戶在 SE38 前台改的文案被無痕沖掉,源碼 diff 完全看不出來。
  3. 絕不直接 UPDATE / MODIFY 標準表。改已過帳 FI 憑證走 FI_DOCUMENT_CHANGE(組 FACG,T_ACCHG 只填 FDNAME+NEWVAL,抬頭不傳 I_BUZEI、行項目才傳)。
  4. 請求號不自己造。推可傳輸物件缺 CORR_NR ⇒ 停手報告,等用戶給號;新建 TADIR 物件前先報「類型+建議名+TADIR 查重結果+編號延續理由」,點頭再建。已掛請求的既有物件後續改動複用原號,不再問。
  5. 不用 Update* 類寫介面做試探性呼叫。它會真寫——曾用 UpdateFunctionModule 只傳函數體「試試」,把 inactive 源碼覆蓋成 620 字元殘版。探針只走唯讀介面。
  6. 交付四步:生成 → 推源碼 → 推文字元素(PROG/PX 須單獨激活)→ 生成 GUI 狀態(CUA)。文字元素不激活 = 運行時 TEXT-xxx 恆空。
  7. 生產源碼註解:留骨架、刪敘述。保留程式頭(單位/作者/日期/摘要/變更記錄)+ *&--- 框線 + FORM 橫幅 + -->/<-- 形參文件 + 判據型短註解;刪除設計依據、口徑說明、聯調實測、TODO 理由、scripts/work/ADT/ZTEST_* 引用。剝離後必須 diff 證明程式碼本體零改動。
  8. 測試/探針物件測完即刪。先 get_source 留檔 backup/ → 刪 → 回查 TADIR 清零。只刪本輪自己建的;他人建的、已掛包掛請求的、在用的 DEMO 一律不動。
  9. 先查鎖、先備份再寫 SAP。憑據檔案不外傳、不回顯、不寫進交付物。
  10. 用戶說「數據沒問題」時,更要認定程式有問題。用「報錯金額+張號」反推輸入特徵,寫探針逐字複刻該報錯;複刻成功即鎖定根因。

1. 激活與寫入通道(ADT / MCP)

  • HTTP 200 ≠ 激活成功 — 成敗寫在 body:必須顯式檢測 activationExecuted="false" 並拋錯;驗證激活唯一可靠方式是回讀 include 或 ?version=active 與 inactive 雙版本比對。TRDIR-RSTAT 在 S/4 不寫值。
  • 假成功最難發現 — 舊腳本按 <chklst:shortText> / <msg>text</msg> 正則抓訊息,真實結構是 chkl: 前綴 + <shortText><txt> + msg 帶屬性 ⇒ 一條訊息都打不出來,連報三次「激活成功」實際是 131 字元空殼。抓不到訊息就必須當失敗。
  • 文字元素是獨立可激活物件 PROG/PX — 激活父程式不連帶;push_textelements 若只有 GET→LOCK→PUT→UNLOCK 沒有 ACTIVATE,PUT 只寫 inactive 版。傳 PROG/P 時 SAP 認為無可激活內容,回 activationExecuted="false" 且無任何 msg ⇒ 靜默無效。激活介面不接受 corrNr(帶了 400)。
  • push_textelements 的 PUT 是整塊覆蓋,推序不能反 — 新增符號:先文字元素後源碼;刪除符號:先源碼後文字元素。TE_FILE 必須連同既有 selections 一起給全。
  • 文字元素只有 inactive 版時,程式須重新生成才生效 — 只推文字元素 + 再 PUT 內容相同的源碼不會重新整理。改動源碼一行(哪怕註解)後重推,或重建程式。
  • 三種「假鎖」:① 報「已在使用者 xxx 的請求 yyy 中鎖定」其實是漏傳 transport_request/corrNr;② UpdateDataElement 報 locked by another user 其實是 400「已存在」——handler 的 isAlreadyExistsError() 只認英文,中文訊息判不出,撞上 403 兜底被譯成「被鎖」(⇒ ZH 環境下 MCP 改不了已存在的資料元素,UpdateDomain 無此前置故不受影響);③ 403 … not specified for this client 是集團不對。
  • 真鎖只有 SM12 能刪 — ESDICT/ESRDIRE/SEOCLSENQ 三類鎖掛在 ADT 會話上,DEQUEUE_ALL、_scope='3'、DEQUEUE_EESDICT 全部無效。ENQUEUE_READ 確認後請使用者在 SM12 手工刪,立刻重跑;別等會話逾時。
  • 兩句 403 含義完全不同 — User XXX is currently editing ZFIDxxx = 真人開著 SE80,等;Object ... locked in task DEVK90011x not specified for this client = 集團不對,換請求所屬集團重推。混為一談會白繞一圈。
  • ADT LOCK 的 Accept 頭必須帶 datatype — 只發 application/xml 時 SAP 已加鎖成功但響應 406 拿不到 LOCK_HANDLE ⇒ 鎖洩漏,之後所有請求報「使用者 X 目前編輯」,一直騙你以為是別人佔著。正確值:application/vnd.sap.as+xml;dataname=com.sap.ad.lock.result。
  • 腳本 fail() 裡 process.exit() 跳過 finally ⇒ 入隊鎖殘留 — 類推送腳本異常退出後 SEOCLSENQ 鎖能留 40 分鐘,之後所有 LOCK 報 403 像「使用者開著 SE80」。寫腳本一律先 UNLOCK 再退出,或改 process.exitCode = 1。
  • 不要替使用者強刪編輯鎖 — ADT 刪鎖會丟對方未儲存的修改。
  • include 是獨立 ADT 資源 — POST 打到 /programs/programs 一律建成可執行程式(TRDIR-SUBC=1),之後改不回來;include 打 /sap/bc/adt/programs/includes(根元素 <include:abapInclude>)。主程式與 include 必須同一條 activation 請求,否則互報未定義。
  • INSERT REPORT ... STATE 'A' 造出的 include 會把源碼寫成 active 版 — 之後改 inactive 版,主程式激活仍按舊 active 編譯,報的錯指向舊程式碼。改完須 INSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'.(STATE 必須在 PROGRAM TYPE 之前)重新整理 active 版。
  • 本機代理會把 ADT 請求打掛 — 表象與「連不上 SAP」一模一樣(socket hang up / CSRF 失敗)。spawn MCP 子行程前刪 *_proxy 並設 NO_PROXY=*;寫請求一律 proxy: false。
  • ADT 無 dynpro / GUI Status / Menu 資源 — 但 CUA 可程式碼化(RS_CUA_INTERNAL_FETCH + _WRITE,state='A',寫回即生效)。D020S 是 dynpro 螢幕頭不是 GUI 狀態表;RS_CUA_GET_STATUS 不可用於判定,只認 RS_CUA_INTERNAL_FETCH(10 個必輸 TABLES 全給,ORDER BY pposition 防 25 行截斷)。

2. 語法硬約束(一次過編譯)

  • FORM 形參四禁忌:① 不能寫 TYPE c LENGTH n(報 Numeric literals cannot be used as field names)⇒ 用 TYPE char<n> 或 DDIC 類型;② 定長形參收不同長度實參編譯期報 incompatible,文字符號例外但運行期仍 500 Type conflict when calling a FORM ⇒ 跨長度用泛型 TYPE c;③ 不能內聯 TYPE STANDARD TABLE OF(形參被算成 8 個,報 Different number of parameters)⇒ 先 TYPES 命名;④ FIELD-SYMBOL 在 FORM 裡宣告是區域性,跨 FORM 必須改全域性。
  • FORM ... TABLES 必須寫在 USING/CHANGING 之前 — 寫成 USING ... CHANGING ... TABLES 報 Field "TABLES" is unknown.(把關鍵字當形參名)。PERFORM 呼叫側同理。
  • CALL FUNCTION 段序強制 — EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS。把 TABLES 寫在 IMPORTING 之前報 Unable to interpret "EV_OBJNR",完全看不出是順序問題。被調方 EXPORTING 形參要寫在呼叫方 IMPORTING 段,寫反報 CALL_FUNCTION_PARM_UNKNOWN——極易誤判成「FM 介面沒生成」。
  • 多欄位 SELECT 必須逗號分隔,@ 宿主變數必須轉義 — 報 elements in the "SELECT LIST" list must be separated using commas。UPDATE ... SET 多賦值同理;WHERE 裡常數(含 CONSTANTS)同樣要 @。
  • INTO 與 UP TO n ROWS 位置以激活器報錯為準 — 帶 JOIN/子查詢/SQL 表達式的語句屬嚴格模式,強制 INTO 在最後(SELECT 563);UP TO n ROWS 必須在 WHERE 之前。兩種寫法在不同語句上表現不同,照抄記憶會翻車。
  • ASSIGN ... CASTING TYPE c LENGTH <變數> 不支援 — 長度必須是字面量;去掉 LENGTH 又在下標越界時 DUMP Invalid subfield access。正解用 cl_abap_container_utilities=>fill_container_c( )(與標準函數 READ_CONTAINER_C 互逆)。
  • DESCRIBE FIELD 的 TYPE 必須寫在 COMPONENTS 之前 — 只想取元件數也得寫:DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count.
  • CONSTANTS 必須帶 VALUE — CONSTANTS: c_x TYPE flag. 報 "VALUE ..." is not specified.
  • MESSAGE TEXT-xxx TYPE 'S' WITH n 不合法 — 文字符號不能作帶 WITH 的訊息文字。走輔助 FORM:lv_text = pv_text → REPLACE '&' IN lv_text WITH lv_num → MESSAGE lv_text TYPE 'S'.
  • 定長類型沒有 char<n> 這個名字 — TYPE char1000/char55/char220 全部非法,報 Type "X" is unknown;一律 TYPE c LENGTH n。類型名猜錯=浪費一輪激活,不確定就查 DD04L 或 get_source。
  • class=>method 只能用於靜態方法 — 例項方法必須 CREATE OBJECT lo. lo->method( ),否則報 can only be specified with static methods。
  • 字串模板不能作 PERFORM ... USING 實參 — 報 Field "|" is unknown;先賦給變數再傳。
  • CALL TRANSACTION ... WITH f1 v1 不是合法語法 — WITH 只接 AUTHORITY-CHECK。傳參只能走 USING bdcdata;SM30 首屏只有表名一個欄位(VIMDYNFLDS 無更新模式欄位),進修改模式用 VIEW_MAINTENANCE_CALL + action = 'U'。
  • REPLACE ALL OCCURRENCES OF ... WITH '' 在本類型系統會 endless loop — 刪空格/刪字元改逐字元 DO strlen TIMES + lv_txt+lv_i(1) 過濾。
  • CONCATENATE 不接受數值型 — lv_cnt TYPE i 報 must be character-like;內建函數(strlen/lines)不能直接進 &&(報 The syntax for a method specification is objref->method,一次報一批誤導成箭頭語法錯)。數值一律用字串模板 |...{ lv_cnt }...|。
  • $TMP 探針內不能定義 FORM;方法體外的註解不會被儲存 — 探針激活報 Statement is not accessible(CheckProgram 反而不報);類裡 ENDMETHOD 與下一個 METHOD 之間的註解報 unknown comments which can't be stored。
  • SELECT-OPTIONS 名 ≤ 8 字元 — 9 字元首次激活被拒。改短名要同步改文字元素 selections 條目名(舊變式存舊名會被靜默忽略,只提示不報錯)。

3. 靜默失效清單(最貴的一節)

凡是「返回 S / 激活 200 / 編譯通過」但結果不對,先來這裡對號。

3.1 資料錯位與丟失

  • SELECT 顯式列清單 INTO TABLE <預宣告整表類型> 按位置賦值 — 型別恰好相容時不報錯不告警:DD07T 的 DOMVAL 落進 DOMNAME、COEP 的 WKGBTR 落進 MEGBTR(金額恆空)、ZTEPIC3A 報 component "ZJK" not compatible with "ZJYRQ"(靠 _act_dbg 拿座標)。一個坑在 5 份報告裡復發。 解法三選一:SELECT * / INTO TABLE @DATA(...) 內聯(結構由列表定型)/ 自建列序一致的窄結構;或 INTO CORRESPONDING FIELDS OF TABLE。
  • LEFT JOIN 右表欄位寫在 WHERE 裡 ⇒ 語義退化成 INNER JOIN — 右表無匹配時為 NULL,條件不成立。過濾條件移進 ON。
  • DELETE ADJACENT DUPLICATES ... COMPARING <非鍵欄位> 會吞資料 — 條件表 A017 主鍵含 DATBI、KNUMH 不是鍵,按 COMPARING knumh 去重把同 KNUMH 的後續有效期段全刪了。先 SORT BY 全鍵再 COMPARING 全鍵。
  • AT NEW / AT END OF 按物理相鄰判組,ALV 回寫後必失效 — REUSE_ALV_* 會就地重排 t_outtab(使用者按訊息列排序即觸發),同組不再相鄰、AT END OF 多次觸發。分組改「外層遍歷去重鍵 × 內層按鍵收集」,組邊界只認業務鍵。
  • APPEND 會改寫 sy-tabix — APPEND INITIAL LINE ... ASSIGNING <fs> 之後取 sy-tabix 得到的是段序號不是源行號 ⇒ 區間被放大,128 行輸出成 558 行(同一記錄重複 43 次)。迴圈首行先 lv_tabix = sy-tabix.。
  • READ TABLE ... WITH KEY 未命中把 sy-tabix 置 0 — 隨後 APPEND sy-tabix TO lt_del 記進刪除清單,DELETE ... INDEX 0 直接 DUMP TABLE_INVALID_INDEX。迴圈首行存行號 + 刪除前 IF idx >= 1 AND idx <= lines( )。
  • 內聯 DATA(...) 宣告在 IF 分支裡,條件不成立時內表從未建立 ⇒ 後續 LOOP 短轉儲。宣告提到方法頭部,或取數前置空判斷。

3.2 BAPI / 標準函數「返 S 或不報錯但沒寫進去」

  • BAPI 的 x 結構必須與原結構成對傳 — 只傳 depreciationareas 漏 depreciationareasx ⇒ 填了 AFASL 就報 No change parameter given for depreciation area 01。缺 x 結構不報語法錯,只在資料條件滿足時觸發。
  • SD 增強欄位識別結構的資料元素必須是 CHAR1 — SD_SALESDOCUMENT_CHANGE 走 GET_CHANGEABLE_FIELDS,只滾 WHERE rollname = 'CHAR1' OR 'CHAR01' 的欄位;用 BAPIUPDATE 的全部被排除 ⇒ 返回 S、值不變、零報錯,而建立路徑不走此清單 ⇒ 典型「建立能寫、修改寫不進」。不可改回 BAPIUPDATE。
  • BAPIPAREX-VALUEPART1 只有 240 字元 — 標準函數按偏移連續讀 VALUEPART1~4(合計 960),只填第一段則尾部靜默截斷(BAPE_VBAP 實測 271 字元,後 3 個欄位全空、無報錯)。按 240 分四段填。
  • 借貸項/無計劃行訂單類型:數量只認 TARGET_QTY — BAPISDITM 裡沒有任何欄位的參考欄位是 KWMENG;該類項目類別在 TVEPZ 無配置 ⇒ 不產生計劃行,下發的 BAPISCHDL 被靜默丟棄,返 S 但 KWMENG=0、NETWR=0、VBEP 0 行。判據用 TVEPZ 有無條目,不能用 TVAP-LFREL(Z001 與 Z014 的 LFREL 都為空)、也不能用 VBTYP。讀取側同一坑:VBAP-KWMENG 恆 0、數量在 VBAP-ZMENG ⇒ 回填口徑「KWMENG 非空優先、為空回退 ZMENG」。
  • BAPE_* 首欄位是鍵位,修改模式漏傳即清空單號 — 標準函數把 EXTENSIONIN 值 move-corresponding 進 E_VBAKKOM,VBELN 留空就把單號覆蓋成空 ⇒ V1 045 後跟 V4 219(根因看 V4 219 前面那條 E 訊息)。
  • SD 參考單據是三件套 — REF_DOC + REF_DOC_IT + REF_DOC_CA(參考憑證類別);只傳前兩個報 V2 068 不含憑證類別 但含 C(&2 空)。行號填 0 或單據不存在是同一條訊息但 &3 為空。類別取被參考單據自身的 VBAK-VBTYP,不是抬頭 VGTYP。
  • SD_SALESDOCUMENT_CHANGE 入口對兩個表各自排序 — 刪除行必須 ITEM_IN 與 ITEM_INX 都填 ITM_NUMBER;只填 ITEM_INX 會錯位,報完全不相關的 T184 沒有可用的項目類別。
  • BAPI_USER_EXISTENCE_CHECK 在本類系統對任何帳號都返 0(連 ZZZZZZ999 都通過)⇒ 校驗形同虛設,單據轉給無效帳號後兩個介面都看不到。直查 USR02 + 長度防護(0 < len <= 12)。
  • DELETE_TEXT 對不存在的文字 RAISING NOT_FOUND — 標準函數把該 E 訊息追加進 BAPIRET2,被 frm_check_return 判失敗 ⇒ 「清空」反而把介面打成 E。清空前先查 STXH(TDOBJECT+TDNAME+TDID+TDSPRAS),不存在就跳過(順帶讓操作冪等)。另:SD_SALES_DOCU_MAINTAIN 裡清空功能碼是 003(DELETE_TEXT),傳 &&/005 只是把文字重寫成一條空行。
  • 新增定價條件不能指定步驟號 — 硬編碼 COND_ST_NO='001' 而 ZP01 在 STUNR=010 ⇒ 報「定價類型不在步驟編號 001 中」。留空由定價過程自動定位;用「條件是否已存在」驅動 UPDATEFLAG 的 I/U。
  • 介面二次拆張時 OBJ_KEY 只回一個憑證號 ⇒ 張數少報、按行回寫指錯憑證。OBJ_KEY 佈局是 憑證號(10)+公司程式碼(4)+會計年度(4),年度在 +14(4)(常被誤寫 +10(4) 取到公司程式碼)。
  • BSEG-BUZEI 是 NUMC(3),單張憑證 999 行物理上限,BAPI 實際只吸納 990 行 — 分片上限取 989 明細 + 1 軋差;邊界必須覆蓋 999/1000/1997(1997 按 >999 判會切出 1000 行而溢位)。
  • BAPIACHE09 沒有 EXCH_RATE 欄位 — 雙幣(00+10 行)只給 00 行帶匯率時,系統按 OB08 重定抬頭匯率(填 7 落 8.28)。CURR_TYPE='10' 行也必須帶 exch_rate,明細行與軋差行兩處都要。
  • 雙幣憑證每個 ITEMNO_ACC 必須給兩條 CURRENCYAMOUNT,且每種幣種小計為 0 — 缺 10 行報 Inconsistent currency information;拆分產生的軋差行同樣要補。
  • UMSKZ 在三個 BAPI 結構裡都沒有欄位 — 票據類(W/S)走標準 BAPI 通道會被靜默丟棄、科目記錯。只能經 EXTENSION2 增強落庫,或整張走 POSTING_INTERFACE_*(FB01 批次輸入)。
  • READ_TEXT 的 NAME 形參是 TDOBNAME(70),實參 TDNAME(16) 觸發執行期類型衝突 — 編譯通過、空入參正常、一命中資料就 500 Type conflict during a function module call。形參寫 TYPE tdobname,或直讀 STXL 簇表。

3.3 文字 / 描述 / 列標題變空

  • 欄位用匿名內建類型 ⇒ DD03T 文字為空 ⇒ ALV 列標題一片空白 — 欄位必須掛資料元素(掛上後 DD03T 變 0 行是正確狀態,文字上移)。結構欄位文字只能靠 DDL 逐欄位 @EndUserText.label;巢狀的 STRU/TTYP 型欄位永遠沒有 DD03T 文字(SAP 標準結構同樣如此)。CreateStructure 的 fields[].description 不入 DD03T。
  • 文字元素未激活 / 只有 inactive 版 ⇒ 執行期 TEXT-xxx 恆空 — 表現為 ALV 列標題退回 DDIC 短文字、狀態列為空。
  • 域只定義型別沒維護值集 ⇒ 選擇螢幕 F4 彈不出候選 — 查 DD07T 必須帶 AS4LOCAL='A'(否則取到非激活行)、ORDER BY VALPOS(否則得 1,10,11,2,3… 字典序)、語言回退域主語言 DD01L-DOMMASTER(欄位名不是 MASTERLANG)。
  • MESSAGE ... WITH 對整條文字約 70 字元上限 ⇒ 長 BAPI 錯誤被靜默截斷成 Field Reas...,誤導排查方向。改用短前綴 + 字串模板接原文(≤220 字元,對齊 BAPI_MSG)。
  • BAPIACGL09-ITEM_TEXT(50) / BAPIACHE09-HEADER_TXT(25) 靜默右截斷 — 拼「前綴+後輟」先把前綴截到 長度-後輟長度。
  • DDIC 元件名 30 字元靜默截斷 — JSON 鍵名對映時會莫名丟鍵。

3.4 許可權與過濾

  • AUTHORITY-CHECK 傳空值通常無授權 ⇒ 相關記錄永久不可見 — 角色裡不會授 EKGRP = space。組織層欄位為空時不做校驗直接放行:IF lv_ekgrp IS NOT INITIAL. AUTHORITY-CHECK ... ENDIF.。用行級過濾而非整屏拒絕,否則無權使用者連事務碼都進不去。
  • MODIF ID 只控顯隱,隱藏欄位已填的值仍參與過濾 — 隱藏場景用 RANGE 在取數側顯式攔住。

4. 事務與 LUW

  • EXPORT ... TO DATABASE(cluster 日誌)受事務控制 — 「落底表 → 建憑證 → 失敗回滾」使日誌與底表同處一個 LUW,ROLLBACK 把 indx=01 入參快照一起吃掉(indx=02 還在)。且底層過帳 FM 內部還會 BAPI_TRANSACTION_ROLLBACK,所以「刪掉自己的 ROLLBACK」或「調順序」都無效。 ⇒ 解法:在唯一的寫庫動作之前、且在調 BAPI 之前插一句獨立 COMMIT WORK,ROLLBACK 原樣保留。附帶收益:調 BAPI 前先結算自身 LUW 本就是規範姿勢。
  • 要原子性就別各自提交;要獨立提交就保留回讀 — SD_SALESDOCUMENT_CHANGE 的 CALL_FROM_BAPI='X' 把更新掛進 update 佇列,未提交查不到 ⇒ 刪掉 COMMIT WORK AND WAIT 後保留的 SELECT 回讀校驗必然誤判「標誌未更新」。過帳失敗分支必須補 ROLLBACK WORK,否則掛起的 SD 更新會懸空到下一次提交被誤落庫。
  • 平臺快照只綁 FM 參數,函數內區域性變數一律不綁 — 報文在 lv_req/lv_resp 裡就平臺上看不到;監控器 serialize 不帶 name_mappings,輸出 DDIC 大寫名而非契約報文。業務側自建報文日誌表 + 一行 save( ) 寫在報文唯一產生地(frm_send 末尾),獨立提交正好補上平臺被回滾吃日誌的短板。

5. 彈框 / F4 / ALV / 選擇螢幕

  • POPUP_GET_VALUES 的 RETURNCODE 語義反直覺 — 'A' = 取消;空格 = 確認且 FIELDS 已回填(LSPO4U01:IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.)。寫成 IF lv_rc <> 'A' 把「確認」當「取消」⇒ 現象是點彈窗確認後毫無反應(無訊息、無動作、無轉儲)。
  • POPUP_GET_VALUES 只在欄位掛得住 DDIC 搜尋幫助時才彈 F4 — 判據 F4IF_DETERMINE_SEARCHHELP(0=有 / 2=欄位在但無幫助 / 1=欄位不存在)。不要靠改成 SKA1-SAKNR 藉幫助——會引入 ALPHA 補零,'8000' 變 '000008000',校驗誤報且存庫格式變。 ⇒ 改用 POPUP_GET_VALUES_USER_HELP + F4_FORMNAME + F4_PROGRAMNAME = sy-repid,欄位屬性不變。出口 FORM 由 FM 以 5 個 USING 呼叫(tabname/fieldname/display_flag/returncode/value),把選中值寫回第 5 個形參並把 returncode 置 'F' 彈框才重新整理(拿不到 sy-stepl,不要走 dynprofield 回填)。
  • POPUP_GET_VALUES 不支援按條件隱藏欄位 — SVAL 只能控 FIELD_ATTR(必輸/唯讀)。按類型只裝載對應欄位集,使用者改了類型就按新類型重彈(類型未變即退出,不會迴圈)。
  • F4IF_INT_TABLE_VALUE_REQUEST 傳 FIELD_TAB 時每行必須能推出 INTTYPE — 推導鏈 TABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME,任一環缺就預設 c 引發轉儲。傳 VALUE_TAB 即跳過推導,建議直接用。
  • RS_VARIANT_VALUES 透過 SELECT_OPTIONS 傳參會被 F4IF_SHLP_EXIT_EXAMPLE 強制輸出所有選項值 ⇒ 大量資料時撐爆前端。用 SHLP 無 SHLPEXIT 的域進行保留。
  • CL_GUI_ALV_GRID 的 set_ready_for_input 傳 1 觸發 ENTER,不檢查即出口 — 始料未及的寫入,所有掛了 DATA_CHANGED 的欄位被清空。改用 REUSE_ALV_GRID_DISPLAY 的佈局結構 UCOMM。
  • ALV 可編輯欄位用 DRAGDROP 時 CHECKBOX 全選會失效 — 系統在 transfer_data_to_spreadsheet 時重新整理格子,完整保留清單。
  • LS_HANDLE-DRAGDROP 與 handle_drag 的 EVENT 互動時,EV_ROW_NO 常被返回 0 ⇒ 起始位置索引不起作用。用 CHANGING 保留內部對映表。
  • 列標題在 CATALOG_ENTRY 中的 OUTPUTLEN 必須 ≤ DD03L 的 OUTPUTLEN — 違反時列被隱藏、返回空,無報錯。
  • REUSE_ALV_VARIANT_F4 在 T_REPOT 未正確傳入時直接崩潰 — T_REPOT 必須與 REPID 一致;不一致時 FCAT_COMPLETE 返回空。
  • RS_REFRESH_FROM_SELECTOPTIONS 不會清理 RANGES 中的空白行 — 循環 WHERE 條件時空白行引起 INCLUDE 失效,過濾條件被跳過。
  • DD_SHLP 中的 OPTION 不支援 'EQ',只支援 'E' — 帶 'EQ' 的 RANGES 在 F4 時直接轉儲。

6. JSON 與 SAP 閘道

  • SAP 閘道 OData 名稱輸出大寫 — json_name 不影響。要去小寫加 ~text 節點的「serialize」加上 name_mappings(結構型別為 NAME_MAPPING)。
  • /IWFND/MAINT_SERVICE 激活時 活動 / Upload Metadata 若原 EDMX 已經激活則不覆蓋 — 必須先停用服務再啟用,然後重新啟用。 cleanup 無法解決。
  • /IWFND/IWSV 的重新生成會覆蓋 MP / DPC 的所有自定義修改 — 永遠保留 *_DPC_EXT 中的修改,_DPC 由閘道自定義覆蓋且不寫回。
  • Satety 檢查: max_hits 型別為 NUMC,數字型別不匹配時,/IWFND/ERROR 返回 200 但資料為空 — max_hits 明確定義在 $top 為 INT4 時返回正確資料。
  • 自定義 MP 實體中,KEY 欄位<16 字元不被允許 — 使用 GUID。
  • SEGW 建立的專案,CREATE 方法中的 BOPF 實體自動附加 increase 為真 ⇒ UPDATE 等對應操作時報 key has key structure。每次 CREATE 需手動設置 increase = abap_false。

7. DDIC 建置物件

  • CreateStructure 不產生 DD03T;Activate 才產生 — fielddesc 只寫 DD04L、DD03L,不寫 DD03T;僅在 Activate 激活後生成。
  • CreateDomain 的 ddlanguages 只後端覆蓋,無法從 ddlanguages 修改域文字 — 域文字從 DIALOG 寫入 DD01L-DDTEXT。
  • CreateDataElement 在 domain 已存在但 DOMNAME 類型不匹配時報 data element includes an invalid name — 域類型必須與 ES_DD07V-DOMVAL 匹配。域值長度與類型不匹配會報 domain value can not be assigned。
  • Activate 對 ENQMODE(鎖物件)SWITCH 時,S 模式只檢查 TABLE 部分,而 E 模式檢查 ——全域啟用。
  • 鎖物件包含 VIMDYNFLDS 時 Activate 會加上對應 VIEW 及 FIELD 的所有鎖模式 — 實際使用 SM30 時不再需要自定義鎖邏輯。
  • GETMETHOD 在服務巢狀用法時只作用自身層次,不作用下層 — 啟用後需要迭代處理。
  • TRWBO_CORR_LOG 不支援對 CreateDomain 的版本管理 — 建立後無法用 delete 清空。
  • update_function 的 TRFUNC 在模組池(M)中不維護 — 僅在功能組中可用。

8. 打補丁腳本陷阱

  • 補丁推 STABLE STANDARD 內部表前沒有 REFRESH 清空,APPEND 會累加 — 第 2 次部署後資料翻倍。每次推之前 REFRESH lt_data。
  • INSERT INTO ... VALUES 提交不自動,關聯 COMMIT WORK — 批次寫入需顯式 COMMIT,否則 SQL 在執行環節未完成。
  • FIELD-SYMBOL 記憶體的 LOOP 資料或 ASSIGN 的變數在 INSERT 後失效 — INSERT 重建後指向舊位址。走 REF 或重新賦值。
  • 定時補丁 JOB 不可直接 DELETE — JOB_CLOSE 會將 STATUS 變為 S,解排必須 JOB_CLOSE + JOB_OPEN。
  • BDC 的 MSGID 若與 T100 表不一致,補丁回寫失敗且跳過錯誤 — 只檢查 IKPF 的 MSGID 對應 T100 表。
  • SM30 維護檢視時對 VIEW_MAINTENANCE_CALL 的 ACTION 若為 *,則補丁匯入後會自動建立表記錄 — 即使 UPDATE 沒被定義。VIEW_MAINTENANCE_CALL 先 DELETE 刪除 TABSTRIP 再 INSERT 時容易出現唯一鍵衝突。

9. ST22 與探針排查手法

  • ST22 短轉儲 ERROR 在 SOURCE CODE 中只顯示 前 100 行 程式碼 — 必須從 LONG TEXT 或 DUMP_ANALY 中讀取完整上下文;SOURCE_CODE 根據偏移來定位,不能直接用來查。
  • CX_SY_DYN_CALL_* 轉儲的 DYNAMIC TABLE 參數,在 ST22 中檢視為空白 — 因內部表呼叫時分配給實例,ST22 記錄的是無上下文分配的垃圾記憶體。使用 CATCH 後,在 %CX_ROOT->GET_TEXT( ) 的文字中能找到引數。
  • 對於 DATA: gt_dyn TYPE TABLE OF” 等換行宣告,LOOP若編譯時類別未指定(全型別),短轉儲一定會出現** — 原因:動態內表不確定索引結構 ⇒ 必須用CAST到已知類型。**使用TRY / CATCH” 捕捉 CX_SY_ITAB_LINE_ERROR。
  • Explorer 無法從 ST22 轉到 ABAP_SOURCE∆ ⇒轉儲對應到封裝類的方法時,METHOD_NAME = 'IF_EX_BADI~EXECUTE' 不會記到。SELECT ... SINGLE ... FOR ALL ENTRIES 在出現重複鍵時短轉儲 — 鍵重複觸發 CX_SY_OPEN_SQL_DB 錯誤。始終 SORT BY key_fields DESCENDING 再 DELETE ADJACENT DUPLICATES。
  • SE30 分析 SQL_TRACE 中使用 BYPASSING BUFFER 時導致 NEXT 索引覆蓋問題 — BYPASSING BUFFER + FOR ALL ENTRIES 不等於 FOR ALL ENTRIES IN c。BYPASSING BUFFER 直接到資料庫讀取最新數值,不受全域性緩衝影響。
  • CALL TRANSACTION ... USING bdcdata 的第 1 步不使用 F(即必須是 O) — BDC_OKCODE = '=XXXX' 視作 O。BDC_OKCODE = 'FC' 等於不送 ENTER,不觸發 PBO ⇒ 後續所有回寫皆不成功。

10. 症狀速查

症狀分節重點條目
激活返回 200 但資料沒生效§1HTTP 200 ≠ 成功;假成功最難發現;文字元素為獨立物件
報錯文案明顯不對§0 元規則 1段序/關鍵字順序/型別不匹配優先
返回 S 但資料沒變或錯§3(尤其是 §3.2)BAPI x 結構成對;SD 增強必用 CHAR1;BAPIPAREX 四段;借貸項看 TVEPZ
編譯通過但激活或執行時報錯§2FORM 四禁忌;CALL FUNCTION 段序;INSERT REPORT STATE 順序;多欄位 SELECT 逗號
執行錯誤文案指向無關位置§0 元規則 1ABAP 編譯器報錯點是解析位置
ALV 列標題空白或文字消失§3.3欄位匿名內建類型;文字元素未激活;MESSAGE WITH 截斷
彈出框確認後無反應§5POPUP_GET_VALUES 的 RETURNCODE 反直覺
F4 不出現或內容錯誤§5域無值集;FIELD_TAB 推不出 INTTYPE;RS_VARIANT_VALUES 過載
鎖錯誤(假鎖)§1漏傳 corrNr;400 誤判為鎖;集團不對;ADT LOCK Accept 頭
資料重複或莫名丟失§3.1DELETE ADJACENT DUPLICATES 非全鍵;APPEND 覆蓋 sy-tabix;內聯 IF 分支
兩個介面跑結果不一致§4獨立提交需要回讀;SD_SALESDOCUMENT_CHANGE UPDATE 佇列
無法寫入 FI 憑證§3.2, §1.1UMSKZ 無欄位;BAPIACHE09 缺匯率;OBJ_KEY 年度偏移
批次輸入/BDC 不執行§9CALL TRANSACTION 第一步需 O
OData/閘道返回 200 但資料為空§6名稱大寫;max_hits 型別;KEY 小於 16 字元;MP 實體 increase

11. 開工 Checklist(新專案)

按時間順序執行,每步驗證完成再進下一步。

11.1 摸底階段 (Day 1)

【目標:弄清你在哪個環境、對接什麼人、用什麼工具,別踩進第一天的坑。】

  • 取得專案總綱(sap-abap-project-ops 或類似儲存庫):環境地址列表、請求號分配規則、物件命名約定。
  • 確認 MCP 伺服器配置:(路徑、使用者、密碼、用戶端)。確保 NO_PROXY=* 生效,本機無 *_proxy 環境變數。
  • 確認 ADT 連通性:get_programs / get_domains 能否正常返回資料。
  • 確認 ST22 許可權:用 ST22 能查轉儲,而不是被許可權拒絕。
  • 存放專案專用 Backup 目錄(backup/),內含初始源碼快照。
  • 建立初始 頻道.txt 頻道日記:日期、工作任務、聯絡人。

11.2 動手前 (Day 1-2)

【目標:做好防護,避免直接傷害生產資料。】

  • 通讀 §0 十條鐵律。確認理解每條的含義。
  • 給 MCP 加上 process.exitCode = 1 / 統一 finally 語句:防止意外鎖殘留。
  • 確認所有寫入命令都有 proxy: false。
  • 制定文字元素推流方案:尤其對多語言專案,明確 ReadTextElements + JSON diff 流程。
  • 設定來源碼備份策略:每次改動前,先 get_source 落庫。

11.3 編碼自檢

【目標:讓程式碼一次過編譯、避免靜默失效。】

  • 所有 SELECT 語句使用 @ 宿主變數(包括 CONSTANTS)。
  • 所有 CALL FUNCTION 按 EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS 順序。
  • 所有 FORM 參數不寫 TYPE c LENGTH n,用 TYPE char<n> 或 DDIC 類型。
  • 所有 FORM 的 TABLES 段寫在 USING / CHANGING 之前。
  • 所有 BAPI *X 結構與主結構成對出現。
  • 所有 LEFT JOIN 處的右表過濾條件移進 ON。
  • APPEND 後不使用 sy-tabix 作後續索引。
  • DELETE ADJACENT DUPLICATES 使用全鍵 COMPARING。
  • 內聯 DATA(...) 不在 IF / CASE 分支裡宣告。
  • 所有 READ TABLE ... WITH KEY 檢查返回值。

11.4 交付四步

【目標:確保交付物完全生效,無丟失。】

  • 生成:啟動所有交付物,若使用 CORR_NR,保證使用合法請求號。
  • 推原始碼:push_programs / push_includes push_domains 等。
  • 推文字元素:確認 PROG/PX 單獨激活;push_textelements 若無激活則須補啟動。
  • CUA / GUI 狀態生成:RS_CUA_INTERNAL_FETCH + _WRITE,確認 state='A'。
  • 驗證交付:回讀源碼與本地 diff 完全一致;運行功能測試確認業務正確。

11.5 專案收尾 (Day N)

【目標:清理測試殘留、歸檔知識、不留後門。】

  • 刪除所有測試/探針物件:$TMP、ZTEST_* 等,確保 TADIR 清零。
  • 確認所有 MCP 憑據、密鑰已清除或輪換:不留在交付物或備份中。
  • 歸檔交付報告:casebook.md 若有新經驗,附加至新記錄。
  • 刪除本地 backup/ 目錄(若安全)或歸檔至專案儲存庫。
  • 向專案總綱提交本輪變更摘要:改動了哪些物件、修復了哪些問題、新增了哪些經驗。

12. 版本歷史

  • v1.0: 初始版本,從 sap-abap-project-ops 中沉澱的 120+ 條經驗。

相關技能