はじめに
プロジェクトでプログラム開発する上で必ず存在するのが開発標準(命名規約、コーディング規約など)です。
プライムベンダなら各社で開発標準がありますし、二次請け・SES等で案件に参画する場合は案件毎に提示される開発標準に基づいて開発を行います。
自社の開発標準しか見たことが無いという方も多いと思いますが、参画した案件毎に様々なベンダの開発標準を見てきた開発者の方は、開発標準にも良し悪しがあることをなんとなく感じていることと思います。
メジャーな開発言語であれば言語毎のコミュニティの中でノウハウが共有されて開発標準が普及することもありますが、SAPの場合は昔は情報も限られており、SAP社自身も開発標準を提供していなかったことから(最近はあります1)、ABAP開発標準は各社で独自の進化を遂げていました。SAPの製品自体もABAPで書かれていますが、昔のプログラムを見ると標準機能の中でもコーディングに統一感が無い状態です。
開発標準とは
では、開発標準とは何でしょうか。私は、「プログラムをこう作るべき」という標準化担当者の思想だと考えています。
ある要求仕様に対するプログラムの実装方法には様々なものがあり、唯一無二の正解は存在しません。それでも、良いプログラムと悪いプログラムというものは確実に存在します。一方は可読性が高く処理が効率的で、仕様変更が将来発生してもどこを変更すれば良いかがわかりやすい。他方は処理の構造も変数宣言も何をやっているのかわかりづらく無駄な処理もあって、仕様変更時に既存ロジックを読み解くにも苦労するしどこかを変更すると破綻して動かなくなるようなプログラムができあがっている。
無能な開発者に好き勝手に作らせると時間をかけて試行錯誤した結果悪いプログラムができあがるので、プログラムを型にはめて決まった作りにさせることで質の悪いプログラムが作られることを未然に防ぐというのが開発標準の存在意義です。
なので「こういう場合はこういう書き方にする」という有能な開発者のノウハウに基づくものであり、自分なりの正解を持っている人が標準化担当として開発標準を定義すべきだと思っています。
ただ、残念ながら現実はそうなっていません。別案件の事例から持ってきたものとか、数年前から改訂されていない埃をかぶったような開発標準をとりあえず用意して、開発案件を過去に経験したベテランと思われる人を開発チームリーダとか標準化担当に据えただけ、というのが現実のプロジェクト体制ということが多くあります。
そのリーダ自身が開発標準を理解していません。なぜそんな規約になっているのか理由も説明できないけど、以前からこんなルールらしいからそれに反するのはダメと言っているだけです。
以下で説明するように、存在意義の無いダメな開発標準はいろいろあります。若手の開発者の方は、開発リーダとか標準化担当が必ず正しい雲の上の存在とは考えずに、間違いは間違いと指摘して訂正させることで、開発標準をより良いものにしていくことを考えてください。
よくある開発標準
以下、よくある開発標準と、なぜそんなルールが存在するのか、そのルールは妥当かをいくつか見ていきます。(個人の見解です。)
本稿では次の5つの内容について見ていきます。
- 共通インクルード
- ハードコーディング禁止
- 変更履歴コメント
- 命名規則
- 共通テーブル/汎用テーブル/固定値テーブル
共通インクルード
複数のプログラムで共通的に使用する定数やサブルーチンを共通インクルードで定義しておくことで、各プログラムではプログラム冒頭で「INCLUDE 共通インクルード名.」とすることでその定数やサブルーチンを使用できるようになります。
たぶんC言語の「#include <stdio.h>」とかを参考に昔の人が考えたやり方です。別ソースに記述された共通部品を参照することで、記述を効率化できるということで使われ始めました。
これが使われ始めた初期のABAP言語の頃は共通処理の切り出しの手段が限られていたので必要な記述方法でしたが、以下のような重大な問題があります。現在は共通部品クラスを作って定数は属性で公開、処理はクラスメソッドで公開するというより良いやり方があるので、共通インクルードの新規作成は禁止すべきです。
- 利用先のプログラムで「INCLUDE 共通インクルード名.」を書いた箇所にインクルードのソースコードが展開されてコンパイルされます。そのため、共通インクルード内と利用先のプログラムで定数名・グローバル変数名・サブルーチンが重複すると構文エラーとなります。共通処理A用のインクルードA・共通処理B用のインクルードBがあって、今回の機能ではAとBの両方を使いたいという場合、AとBで重複がある場合は両方をインクルードすることができません。
- 共通インクルード内のサブルーチンに機能追加するためにパラメータ追加すると、そのサブルーチンを利用している全てのプログラムが構文エラーの状態となります。ある程度開発が進んだ段階や本稼働後の追加開発の場合、利用先の全てのプログラムを同時に改修して移送リリースというのが簡単にできない場合があります。
これらの問題を命名ルールや運用ルールで対処するという場合もありますが、後工程や運用保守でこれを受け取る人にとっては「便利な共通部品」ではなく、変更の妨げになる技術的負債でしかありません。一刻も早く絶滅することを願います。
ハードコーディング禁止
プログラム内でリテラル(固定の文字や数値)をハードコーディングすることは禁止で、定数にすべき、ということはほとんどの開発標準に書かれています。
ただ、どんな目的でこのルールが存在するのかを正確に理解せずに、誤った定数化をしているソースコードを多く見かけます。
定数化の本来の目的は、可読性の向上や保守性の向上です。
- 可読性の向上:例えば、ABAPの中ではフラグ項目のオンオフは「X」とブランクということは説明無しで分かるかもしれませんが、他システムからの連係データのフラグ項目が「1」「0」だと、オンオフがどちらか見ただけだと分かりません。そこで、オン=1・オフ=0と定数で定義しておき、ソース中でこのフラグを使用する場合は「1」という値ではなく定数名を使うことで、ソースを見ただけでフラグがオンだということが分かるようにします。
- 保守性の向上:例えば、現在は「A」という値だけど将来的に別の値に変わる可能性がある(その値も「B」とか決まっているわけではなく現時点では定義しておくことができない)という場合があるとします。その場合、ソースの複数箇所に「A」が書かれているとそれを全て変更する必要があります。ソースの中には別の意味の「A」も存在する可能性があるので、単純にテキストの全置換するだけでは正しい変更ができません。この「A」が1箇所で定数で宣言されていればそこを変更するだけで済みます。
ハードコーディングするよりも定数にすることでメリットがあるから定数を使うべき、というのがこのルールを定義した人の主張です。ということは、これらに該当せず、ハードコーディングした方が良い場面が存在します。
- BDCデータを作ってCALL TRANSACTIONでバッチインプット処理を行う場合は、BDCデータの画面処理内容はハードコーディングすべきです。仮にカスタマイズ変更やSAPのバージョンアップで画面の挙動が変わったら処理自体を作り直す必要があります。定数化しておいて解決できることは何もありません。CALL TRANSACTIONで呼び出すトランザクションコードもハードコーディングでよいです。SAP標準のトランザクションが同一機能のまま別のトランザクションコードに変わるということはあり得ませんし、アドオントランザクションの場合でも定数にすることのメリットはありません。必要ならトランザクションコード(SE93)から使用先一覧でCALL TRANSACTIONの使用先を確認すれば良いだけです。
- メッセージ関連で、メッセージタイプ、メッセージクラス、メッセージ番号も定数ではなくハードコーディングすべきです。メッセージタイプの種類と挙動はABAPの言語仕様で決まっていることで、定数にしたところで可読性は上がりません。メッセージクラスとメッセージ番号は2つの組み合わせでメッセージを特定するものなので、定数にして一方だけ変えるということもあり得ません。
- メッセージに関しては、プレースホルダ「&」に渡す可変文字列についてはまた別の話です。例えば画面処理モードによってプレースホルダに「変更」または「照会」という文字列を渡したい場合、この文字列はハードコーディングは禁止ですが、定数でもありません。SAPの画面項目やメッセージは多言語対応のために言語依存なので、テキストエレメントで「(JA)変更 / (EN)Change」「(JA)照会 / (EN)Display」のように定義します。
残念ながら、これらを理解せずに盲目的に全て定数にしてしまう残念な開発者が世の中には存在します。さらに残念なことに、開発チームのレビュアーやリーダが理解しないまま無駄に定数化したものをレビューOKとしたり、定数化するのがルールだからレビューNGとして無駄な定数化を強要する、という場面が存在します。そんなレビュアーはスキル不足として解任できる仕組みが欲しいところです。
変更履歴コメント
プログラムの修正時には、次のようなコメントをソースコードに記述するというのが一般的です。
- プログラムヘッダ部(機能名・機能概要・作成者・作成日などが書かれている)に変更履歴として、変更者・変更日・障害票や仕様変更票などの管理番号・変更内容を追加
- ソースコードの変更箇所は、削除箇所をコメントアウトしてここからここまで削除というコメント記載、追加箇所はここからここまで追加というコメント記載
多くの開発標準がこうなっているので当然のように従っていることと思いますが、本当に必要か考えたことはありますか。
- ABAP以外の開発言語では、昔はソースコードはテキストエディタで編集するだけでした。なので、ソースコードを見たときに変更内容がわかるように、削除箇所を物理削除せずにコメントアウトすることが先人の知恵として慣習となりました。現在はどんな言語でも統合開発環境+バージョン管理ツールで開発するのが一般的になっているので、バージョン管理ツールで過去のバージョンとの差分比較ができますし、変更後のバージョンを登録する際には変更目的などの情報をあわせて残すことができるので、ソース上にコメントとして昔の削除済のゴミソースを残しておく必要は無くなりました。
- ABAPの場合は初期の頃から開発環境がバージョン管理ツールと統合されています。移送リリース済のソースコードはバージョンとして保存されており差分比較も簡単にできます。なので昔の先人の知恵は元々必要無かった訳です。ここからは想像ですが、初期のSAPに触った人たちは知らないものを手探りしながら開発を始めたために、SAPに備わっている便利な機能を知らず、ソースコードを変更するなら変更箇所はコメントで残すという古代の慣習を当時の開発標準として取り入れてしまった。その後も先人たちがそうしているからという理由で意味なく残り続けた、というところだと思います。
変更履歴については、いつから履歴管理すべきか、という別の観点での問題もあります。
- 通常の開発プロジェクトでは、初回の単体テスト完了後から履歴管理するのが一般的です。まあ開発プロジェクトの管理上は妥当なのですが、結果として本稼働時点(ユーザから見たら初期バージョンのリリース時点)では、既によく分からないバージョン番号とゴミコメントが大量に付いたソースコードがリリースされる、という状態になります。開発と保守で組織が異なる場合、保守担当はいきなりこのゴミを押し付けられるというわけです。
- 変更履歴を残す目的って何でしょうか。本来は、ソースコードを見たときに、過去の仕様変更時にどういう修正対応をしたかを見る、それによって次に同種の仕様変更が発生した場合の対応箇所を特定する参考にするとか、修正時の注意点を把握できるようにすることだと思います。そのソースが当初バグだらけで(特に仕様変更があったわけでもないのに)原型をとどめないくらい何度も書き換えられたということや、全くまともに動かない過去時点のゴミソースは、情報として全く価値はありません。
なぜ皆さんは多大な労力をかけて無価値なゴミを作るのでしょうか。このあたりはやり方を考え直した方が良いと思います。
命名規則
開発標準として必ずあるのが、各種の命名規則(ネーミングルール)です。
ソースコードの可読性を高める上で本来は有用なものですが、なぜそんな命名なのかの根拠が不明だったり、意味の無い文字が含まれていたり、イマイチな命名基準も多いのが実際のところです。いくつか順に見ていきます。
ルーチン・選択画面・定数命名規約
変数命名規約
パラメータ命名規約
よくありがちなテーブル構造とその問題点
テーブルはこんなイメージで、キー1~nの組合せ毎に用途を決めて、それに対する値を複数持たせるような形です。後はキーの部分に連番を持たせてキーの組合せに対して複数の値を持たせるとか値部分にSIGN,OPTION項目を持たせて値からRange型変数を作れるようにしたりなど、細かい作りはベンダ毎に異なります。
キー1 | キー2 | … | キーn | 値1 | 値2 | 値3 |
---|---|---|---|---|---|---|
このテーブルには以下のような問題点があります。
- テーブルの属性(移送対象のカスタマイジングテーブルか、各環境で設定するアプリケーションテーブルか)
- 定義内容・設定値の管理方法
- キー値の指定内容
- テーブル設定値の使用方法
- 保守性(仕様変更時の対応)
では、順に見ていきます。
問題点1. テーブルの属性(移送対象のカスタマイジングテーブルか、各環境で設定するアプリケーションテーブルか)
このテーブルはカスタマイジングテーブル(開発機で設定して移送する)とアプリケーションテーブル(本番環境含めて各環境で設定する)のどちらであるべきでしょうか。アドオン機能の挙動を制御するので移送対象と考えることが多いですが、いろいろなアドオン機能の設計者が設定値管理したいものを好き勝手に放り込んでいくため、両方の位置付けの設定値が混在してしまいます。少なくとも移送対象のデータとマスタ扱いのデータは格納先のテーブルから明確に区別すべきでしょう。
場合によっては数千レコードの値が入ったりするのに、誰も全体を管理していない、当初の設計者が離任したらもう何だか分からない管理不全のテーブルが簡単にできてしまいます。
開発段階で既にこの状態だと、開発が完了して検証環境を構築する頃には既に正しい設定値リストが存在しない状態になっています。設定値台帳らしきものはあるけど実機と合っていない、実機には単体テスト用に本番想定と異なる値が設定されている。仕方が無いので設計者に聞き回ってExcel台帳の内容をその時点で一旦正しいと思われる状態にして、全件アップロード登録、検証環境には全件エントリ移送で対応という方法が採られます。移送対象として運用すべきが最初の段階でもう破綻しているわけです。
問題点2. 定義内容・設定値の管理方法
そこで、少しまともな人がいるところだと、実際の設定値シートとは別に設定内容の用途・目的を書いた定義シートを作ろうということになります。このシートが正しく管理される限りは、管理上の問題は生じないように見えます。
キー1 | キー2 | … | キーn | 用途 | 設定内容 | 使用先機能 | 更新タイミング |
---|---|---|---|---|---|---|---|
XXX伝票登録時にXXXチェック処理を行う | 値1:伝票タイプ、値2:チェック種別 | XXX機能 | XXXの伝票タイプを追加する場合にチェック要否を確認して値を追加する | ||||
ただ、所詮は実装と一致することの保証が無い単なる台帳管理なので、調べるための参考情報として無いよりまし、という程度の信頼性しかありません。(これはExcel台帳管理全般に言えることですが、実装はテストされる、設計ドキュメントはレビューされるのに対して、台帳はレビューもされず更新漏れが頻発して納品時には実装から丸ごと転記して作ったことにする、という程度の信頼性の低いドキュメントです。)
問題点3. キー値の指定内容
このようなテーブルを作る目的は何でしょうか。「共通」とか「汎用」というという名前から考えると、本来は複数機能で共通的に使用する値を1箇所に集約して持たせることで保守性の向上を目指したはずだと思います。ただ、実際にはそのような使われ方にはなっていません。
- 例えば、受注伝票登録をユーザが画面入力から実行する場合とEDIデータ受信で自動登録する場合があるとします。画面入力する場合だけ動かしたいチェック処理、EDIの場合だけ動かしたいチェック処理があって、伝票タイプで画面入力かEDI受注かを識別するために共通テーブルにそれぞれの伝票タイプを持ってチェック処理の判定ロジックで使うとします。この段階では、キーを受注の処理種別(画面入力かEDIか)としてそれぞれに含まれる伝票タイプを値のレコードで持たせておけば、その後に伝票タイプが追加されても画面入力かEDIかによってそれぞれのキーに合わせた値を追加するだけで簡単に対応できて、テーブル化したメリットがありそうです。
- 次に、別の機能で同様の判定をしたいという要件が出てきました。その場合は、既存のキー値をそのまま使って同じ値を取得すれば、その機能でも使えて、「共通」テーブルとした意味もありそうです。
- さらに別の機能でも同様の判定をしたいとなりました、ただし今回は既存の分類のままだと都合が悪く、その中の特定の伝票タイプだけは処理対象外にしたいそうです。仕方が無いので既存のキー値とは別のキー値を新設して値を持つことにしました。
- このように共通化しようと思ったものが共通化できない場面があったとなると、「それなら機能毎の個別要件にも対応しやすいように、キー値の先頭にプログラムIDを入れよう」と考える人が出てきても不思議ではありません。結果としてプログラムID×保持したい値の種類毎という膨大なリストのできあがりです。
そうなるとこのテーブルからの読込にプログラムID(SY-CPROG)を使うようになり、開発途中で元プログラムをコピーした一時プログラムが作られる度に開発機に一時プログラム用のゴミデータも増えていくことになります。
問題点4. テーブル設定値の使用方法
このような共通テーブルを作りたがる人たちは、それとセットで「共通テーブルデータ取得共通部品」も作りたがります。キーを渡すとその条件に該当するデータを全件そのまま取得するという、保守性の観点では最悪の部品です。この部品を呼び出す側は、SELECT回数を減らすべきという開発標準に従い、プログラム冒頭の処理処理でまとめて必要な情報を全件取得するということをします。結果として、ABAPエディタから使用先一覧であるキー値の実際の使用箇所を探そうにもなかなか探せない、という状況になります。
問題点5. 保守性(仕様変更時の対応)
上述のように、本来は保守性の向上(仕様変更時にテーブルのレコード追加だけで容易に対応できる)を目指していたはずが、実際には管理不全なテーブルになることで、逆に保守性の低下を招くという本末転倒なことになっています。
仕様変更で例えば伝票タイプが追加となる場合、このテーブルのどのキーの値がどこの処理で使われているのかが分からない状態で、どのようにテーブルを変更すれば目的の動作になるのかがわからず修正対象が特定できない。結果として影響がありそうな実装箇所を全部洗い出してレコードの追加方法を確認して、その対応内容で問題無いかを全て検証する必要がある、ということになってしまいます。
問題点に対する代替案
このように設定値の台帳管理には問題があるとしても、テーブル等で別管理したい情報はいろいろあり、何らかの各種設定値の格納先は必要だと考える人は多いと思います。
ではどうするか。私がお勧めする代替案は、
- 用途別の設定値テーブルを個別に作る
- 共通クラスのクラス属性で固定値を保持する
というやり方です。
代替案1. 用途別の設定値テーブル
これはSAPの標準機能を知っていればわかりやすいと思います。標準機能は多数の設定値がパラメータとして多数のテーブルに用途別に分類されて格納されています。それと同様の仕組みをアドオンに実装するという考え方です。例えば伝票タイプ毎に特定のチェック処理を実行する/しないを制御したいのであれば、標準の伝票タイプのパラメータ設定を拡張するイメージで伝票タイプがキーのテーブルを作り、制御したい内容毎に項目を分けて設定値を持たせます。仕様追加で伝票タイプを追加する場合はそれに合わせてアドオンテーブルの設定を追加するだけです。
用途別にテーブルを多数作る必要があり面倒と思われるかもしれませんが、設定値の登録内容は一目瞭然でわかりやすく保守性が高いので、最初は面倒でもやるだけのメリットはあると思います。
また、この方法で用途別にテーブルが分かれていれば、新しい要件が出てきたときにそのための設定値をどこに追加すべきかの判断も明確にできます。伝票タイプによる制御をしたいのであればこのテーブルへの項目追加だし、別の粒度での制御ならそのキーのテーブル(それが無ければキーに合わせてテーブルを新設)です。
テーブルが分かれていることで、そのテーブルの値をどの機能がどこで参照しているかも使用先一覧で容易に検索することができます。
代替案2. 共通クラスのクラス属性で固定値を保持する
こちらはもっと単純です。
例えば特定の複数機能で伝票タイプとか組織のコードで固定値を設定したいという場合、ハードコーディング禁止、複数機能で使用となると、共通テーブルに保持しようと考えたくなると思います。
でも値の本質が複数機能で共通の固定値ということを考えると、文字列にタブ記号を入れるのに
CL_ABAP_CHAR_UTILITIES=>HORIZONTAL_TAB
という定数を使うのと同様に、共通クラスの定数を使うのがシンプルでわかりやすいです。このタブ記号の例のように、クラス名・属性名自体にその定数の意味が分かるような名前を付ければソースコード上も一目瞭然ですし、SE24でクラス定義を見れば内容説明も実際の値も参照できます。共通テーブルでよく分からないキーで取得する値よりも明らかに可読性が高いです。
また、クラス属性は固定値にする以外にも、任意の型の変数にして値を格納しておくことができます。固定値的な意味合いで使うけどSELECTのWHERE句でIN条件で使えるようにRange型の値を持つ('Z001'という単項目の値ではなく、SIGN='I'/OPTION='EQ'/LOW='Z001'というRange型を固定値のように扱う)ことができます。このやり方なら、仕様変更で'Z001'以外に'Z003'も対象にする必要が出た、という場合にRange型のレコードを追加(参照する側はクラス属性名で参照するので変更不要)とするだけです。
これらのやり方で共通テーブル使用は無くせるはずです。
「テーブルを多数作るのは煩雑」「共通テーブルデータ取得共通部品が1つで済むのが簡単」という理由で反対する方は、先々の保守性・可読性などを考慮して、本当にそのやり方が最善か、一度考え直してみてください。
5,プログラム処理規則
DB処理:データ更新
それでもどうしても共通テーブルを使いたいというなら、「共通テーブルデータ取得共通部品」で雑多なデータ取得することだけでも止めてみることをお勧めします。共通テーブルデータ取得用の共通クラスを作って、取得したい値の種類毎にアクセサメソッドを作り、必ずそれを使ってデータ取得することを徹底しましょう。そうすれば各アクセサメソッドの使用先を参照することで、その値がどこで使われているかが簡単に特定できます。
更新処理
標準テーブルの直接更新を禁止する。必ずBAPIか汎用モジュールを使用すること。
MODIFYは原則、禁止とする。
一括INSERTする際には、「ACCEPTING DUPLICATE KEYS」を使用し、ショートダンプを防ぐこと。
DELETEは、全件削除の要件が無い限り、必ず条件を記載する。
特別な要件がない限り、UPDATEはSETを使用し、構造・内部テーブルを使用してのUPDATEは禁止する。
コミット・ロールバック
暗黙的なデータベースのCOMMIT、ROLLBACKに制御を任せることなく、明示的にCOMMIT、ROLLBACKを行うこと。
EXIT等の標準を拡張するロジック内ではCOMMIT、ROLLBACKを禁止する。
まとめ
以上、SAPのABAP開発標準によくある内容をいくつか見てきました。
開発標準を作った先人たちは何らかの目的を持って、あるいは他言語の開発標準を参考にして、手探りしながら作ってきたはずです。
ただ、実際に使っていくとそのルールが妥当ではない場面が出てきます。
先人が決めた利用実績のあるルールだからと盲信せずに、内容を見直してより良いルールに改善していくことを目指しましょう。
本稿がABAP開発者や開発チームリーダ・標準化担当者・品質管理担当者の参考になれば幸いです。