Communityコーディング&開発github.com

yanghangmvp/sap-abap-pitfalls

ABAP 開発における落とし穴と解決法を集めたライブラリ(プロジェクト横断で再利用可能)。100以上の事例を「現象 → 根本原因 → 解決策」の形式で収録し、新規プロジェクト開始時のチェックリストも含む。

sap-abap-pitfalls とは?

このスキルは、ABAP 開発者が陥りがちな落とし穴を、実際のプロジェクトで発生した100以上の事例に基づいて体系的にまとめたものです。各事例は「現象 → 根本原因 → 解決策」の形式で整理されており、特にSAPシステム特有の「静かなる失敗」(戻り値が成功を示しているのにデータが更新されない、コンパイルは通るが実行時エラーになるなど)に焦点を当てています。ADT/MCPのアクティベーションの落とし穴、ABAPの文法上の厳格な制約、BAPIや標準関数の予期せぬ動作、トランザクションとLUWの扱い、ALVやF4ヘルプの実装上の注意点など、多岐にわたるトピックをカバーしています。また、新規プロジェクト開始時に従うべきチェックリストや、トラブルシューティングのための症状別クイックリファレンスも含まれており、Claude CodeやCursorなどのAIエージェントが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に質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

ABAP 開発 落とし穴と解決法ライブラリ

用途: 次のプロジェクト開始前に一読し、トラブルシューティング時は症状から検索してください。このスキルには、プロジェクト横断で再利用可能な経験のみを収録しています。環境アドレス、リクエスト番号、オブジェクト一覧はプロジェクト側の総綱(例: sap-abap-project-ops)を参照してください。 完全な120件(コードスニペットと出典を含む)は references/casebook.md を参照してください。

使い方: 新規プロジェクト開始 → 直接 §11 開始チェックリスト(時系列順に実行)を確認する。既にプロジェクトが進行中で問題が発生した場合 → §10 症状クイックリファレンス で該当するセクションを特定する。特定の事例の詳細なコードと出典を深掘りしたい場合 → references/casebook.md を参照する。

3つのメタルール(個々の落とし穴よりも重要):

  1. エラーメッセージは実際の原因と無関係であることが多い — ABAPコンパイラのエラーポイントは「解析が止まった位置」であり、「実際に誤りがある位置」ではありません。まずは文の順序/キーワードの順序/型の不一致を疑い、文法そのものを疑わないでください。
  2. SAPで最も危険なのはエラーではなく、静かなる失敗である — 「戻り値 S、アクティベーション 200、コンパイル成功」という三重の偽装の下で、データが静かに壊れます。BAPI、SELECT INTO TABLE、EXTENSIONIN、テキスト要素、拡張フィールドに関わる処理は、必ずデータベースへの書き込みを照合し、戻り値を信用しないでください。
  3. 失敗したら必ず ST22 を確認し、推測しない — 短絡ダンプの Error analysis は、procedure "XXX" "(FORM)" と CX_SY_* 例外クラスを直接示します。一つずつ FORM を試すより桁違いに高速です。

0. 10の鉄則(違反したらやり直し)

  1. 既存のオブジェクトを変更する場合、最初のステップは現行システムからソースを読み戻すこと。ユーザーはしばしば SE80 で本番オブジェクトを直接変更し、アーカイブに書き戻しません。ローカルアーカイブは比較のためだけに使い、ベースラインとしては使用しません。get_source → バックアップと差分を確認し、差異がゼロであることを確認 → パッチ適用 → 再読み込みして 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. 納品の4ステップ: 生成 → ソースコードのプッシュ → テキスト要素のプッシュ(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 ≠ アクティベーション成功 — 成否はボディに書かれています: activationExecuted="false" を明示的にチェックし、エラーをスローする必要があります。アクティベーションを検証する唯一の信頼できる方法は、include を読み戻すか、?version=active と inactive の両方のバージョンを比較することです。TRDIR-RSTAT は S/4 では値が書き込まれません。
  • 偽の成功が最も発見しにくい — 古いスクリプトは <chklst:shortText> / <msg>text</msg> を正規表現でメッセージをキャプチャしていましたが、実際の構造は chkl: プレフィックス + <shortText><txt> + 属性付きの msg です。そのため、一つのメッセージも出力できず、3回「アクティベーション成功」と報告しても、実際は131文字の空のシェルでした。メッセージをキャプチャできない場合は、失敗として扱わなければなりません。
  • テキスト要素は独立したアクティベート可能オブジェクト PROG/PX である — 親プログラムのアクティベーションは连带しません。push_textelements が GET→LOCK→PUT→UNLOCK だけで ACTIVATE を含まない場合、PUT は inactive バージョンにのみ書き込みます。PROG/P に対して送信すると、SAP はアクティベートする内容がないと判断し、activationExecuted="false" を返し、メッセージは一切ありません ⇒ 静かに無効になります。アクティベーションインターフェースは corrNr を受け付けません(指定すると 400 エラー)。
  • push_textelements の PUT は全体を上書きするため、プッシュの順序を間違えてはいけない — 新しいシンボルの追加: テキスト要素を先に、ソースコードを後に。シンボルの削除: ソースコードを先に、テキスト要素を後に。TE_FILE は既存の selections もすべて含めて完全なものを渡さなければなりません。
  • テキスト要素が inactive バージョンのみの場合、プログラムを再生成する必要がある — テキスト要素だけをプッシュし、その後同じ内容のソースコードを再度 PUT してもリフレッシュされません。ソースコードの一行を変更してから再プッシュするか、プログラムを再作成します。
  • 3種類の「偽のロック」: ① 「ユーザー 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 の3種類のロックは ADT セッションにぶら下がっており、DEQUEUE_ALL、_scope='3'、DEQUEUE_EESDICT はすべて無効です。ENQUEUE_READ で確認後、ユーザーに SM12 で手動削除を依頼し、すぐに再実行してください。セッションのタイムアウトを待ってはいけません。
  • 403 の2つのメッセージは全く意味が異なる — 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 リクエストが「ユーザーが SE80 を開いている」かのような 403 エラーを報告します。スクリプトを作成する際は、常に最初に UNLOCK してから終了するか、process.exitCode = 1 に変更します。
  • ユーザーに代わって編集ロックを強制削除しない — ADT でのロック削除は、相手の未保存の変更を失わせます。
  • include は独立した ADT リソースです — POST を /programs/programs に送信すると、常に実行可能プログラム(TRDIR-SUBC=1)が作成され、後から変更できません。include の場合は /sap/bc/adt/programs/includes に送信します(ルート要素は <include:abapInclude>)。メインプログラムと include は同じアクティベーションリクエスト内になければなりません。そうしないと、互いに未定義と報告されます。
  • INSERT REPORT ... STATE 'A' で作成された include は、ソースコードをアクティブバージョンとして書き込む — その後 inactive バージョンを変更しても、メインプログラムのアクティベーションは古いアクティブバージョンでコンパイルされ、エラーは古いコードを指します。変更後は INSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'.(STATE は PROGRAM TYPE の前に指定する必要があります)でアクティブバージョンをリフレッシュする必要があります。
  • ローカルプロキシは ADT リクエストをハングさせる — 症状は「SAP に接続できない」と全く同じです(socket hang up / CSRF 失敗)。MCP 子プロセスを spawn する前に *_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 仮引数の4つの禁忌: ① 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 の前に置く必要があります。2つの書き方はステートメントによって動作が異なり、丸暗記は危険です。
  • ASSIGN ... CASTING TYPE c LENGTH <変数> はサポートされていない — 長さはリテラルでなければなりません。LENGTH を削除すると、インデックス範囲外で 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 を使用します。型名を間違えると、アクティベーションを1回無駄にします。不明な場合は 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 の最初の画面にはテーブル名という1つのフィールドしかありません(VIMDYNFLDS には更新モードフィールドはありません)。修正モードに入るには VIEW_MAINTENANCE_CALL + action = 'U' を使用します。
  • REPLACE ALL OCCURRENCES OF ... WITH '' はこの型システムでは無限ループになる — スペースや文字を削除する場合は、DO strlen TIMES + lv_txt+lv_i(1) で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つの報告書で再発しています。 解決策は3つ: 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 で直接 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文字ごとに4つのセグメントに分割して入力します。
  • 借方/貸方/計画なし行の注文タイプ: 数量は 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 参照伝票は3点セット — REF_DOC + REF_DOC_IT + REF_DOC_CA(参照伝票カテゴリ)。最初の2つだけを渡すと、V2 068 伝票カテゴリを含まないが C を含む(&2 は空)。行番号に0を指定するか、伝票が存在しない場合も同じメッセージですが、&3 は空です。カテゴリは参照される伝票自体の VBAK-VBTYP から取得します。ヘッダの VGTYP からではありません。
  • SD_SALESDOCUMENT_CHANGE のエントリは、2つのテーブルをそれぞれ並べ替える — 削除行は 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 は1つの伝票番号のみを返す ⇒ 伝票数が少なく報告され、行に基づいて書き戻すと誤った伝票を指します。OBJ_KEY のレイアウトは 伝票番号(10)+会社コード(4)+会計年度(4) であり、年度は +14(4) にあります(よく誤って +10(4) と記述され、会社コードを取得します)。
  • BSEG-BUZEI は NUMC(3) であり、1つの伝票の物理的な上限は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 には、2つの CURRENCYAMOUNT エントリが必要であり、各通貨の小計は0でなければならない — 10 行がないと Inconsistent currency information エラー。分割によって生成された差額行にも同様に追加する必要があります。
  • UMSKZ は3つの 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(クラスタログ)はトランザクション制御の対象である — 「データベーステーブルへの書き込み → 伝票の作成 → 失敗によるロールバック」により、ログとテーブルが同じ LUW 内に存在することになります。ROLLBACK は indx=01 の入力パラメータスナップショットも一緒にロールバックします(indx=02 は残ります)。また、基盤となる転記 FM は内部で BAPI_TRANSACTION_ROLLBACK も実行するため、「自分の ROLLBACK を削除する」や「順序を変更する」は無効です。 ⇒ 解決策: 唯一のデータベース書き込みアクションの前、かつ BAPI 呼び出しの前に、独立した COMMIT WORK を挿入します。これにより、ROLLBACK は元のまま保持されます。付随的な利点: BAPI を呼び出す前に、自身の LUW を決済することは、そもそも正しい手順です。
  • アトミック性が必要な場合は、それぞれコミットしてはいけません。独立したコミットが必要な場合は、読み戻しを保持する必要があります — SD_SALESDOCUMENT_CHANGE の CALL_FROM_BAPI='X' は、更新を更新キューに保留します。コミットしないと読み取れません ⇒ COMMIT WORK AND WAIT を削除した後に残した SELECT による読み戻しチェックは、必然的に「フラグが更新されていない」と誤判定します。転記失敗の分岐では、ROLLBACK WORK を追加する必要があります。そうしないと、保留された SD 更新が宙に浮き、次のコミットで誤ってデータベースに書き込まれる可能性があります。
  • プラットフォームスナップショットは FM パラメータのみをバインドし、関数内のローカル変数は一切バインドしない — メッセージが lv_req/lv_resp にある場合、プラットフォームからは見えません。モニターの serialize は name_mappings を持たず、契約メッセージではなく DDIC の大文字名を出力します。ビジネス側で独自のメッセージログテーブルと、save( ) の1行をメッセージが唯一生成される場所(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

…

[truncated for translation]

関連スキル