"
Apoiado por

Constantes inconstantes

As constantes não o são. Toda a gente sabe. Quando se define uma constante, é certo como a morte que mais cedo ou mais tarde alguém vem e muda-a. Quando isto acontece, quem usa os valores directamente no código ABAP está feito ao bife; Quem define constantes no código só tem de alterar o valor num sítio mas vê-se ainda assim obrigado a editar o programa e a transportá-lo, coisa que, dependendo do grau de burocracia do ambiente em que se está, se pode revelar complicada. Este artigo apresenta uma solução simples mas sofisticada para gerir de forma centralizada todas as constantes de um sistema SAP através do uso de uma tabela de utilizador central onde se manterá todos os valores constantes e de uma classe com alguns métodos estáticos que serão usados para os obter.

A Tabela

Para definir a tabela seguiremos os sequintes requisitos:

  1. Num sistema existem vários desenvolvimentos independentes (a que chamaremos RICEF)
  2. Num mesmo desenvolvimento poderemos necessitar de definir constantes para vários campos diferentes (a que chamaremos FIELDNAME)
  3. Para um mesmo campo podemos ter de ocorrer mais que uma constante (a que chamaremos OCCURENCE) (Ex. tipo de movimento no caso A e tipo de movimento no caso B)
  4. Uma única constante pode ser constituída por vários valores e usaremos um contador (a que chamaremos COUNTER) para os distinguir.
  5. Cada valor pode ser um número ou um intervalo de valores (um RANGE por isso usaremos SIGN, OPTION, LOW, HIGH).

A nossa tabela fica então assim:.


Nome Chave Elemento Descrição
MANDTXMANDTClient
RICEFXCHAR6RICEF ID
FIELDNAMEXNAME_FELDField name
OCCURRENCEXCHAR6Occurrence of RICEF
COUNTERXNUMC3Counter of RICEF
UE_SIGN TVARV_SIGNABAP: ID: I/E (include/exclude values)
UE_OPTION TVARV_OPTIABAP: Selection option (EQ/BT/CP/...)
UE_LOW CHAR45Low Value
UE_HIGH CHAR45High Value

Por ser muito utilizada, pouco alterada e ter relativamente poucos registos esta tabela deverá ser definida como “fully buffered”. Por os seus dados serem bastante críticos, deve ser também definida como uma tabela de “customizing” de forma a que as alterações tenham de ser efectuadas no ambiente de desenvolvimento e depois transportadas para produtivo.

As operações

Para evitar confusão evitaremos aceder directamente à tabela e aceder-lhe-emos sempre através de métodos. Identificámos 3 operações necessárias:

  1. Obter um valor constante
    Quando sabemos que uma constante está definida apenas como um valor fixo, podemos querer obtê-lo e colocá-lo numa variável.
  2. Obter um conjunto de valores
    Muitas vezes uma constante é representada não por um valor mas sim por muitos. Neste caso, podemos obter um RANGE de valores.
  3. Verificar um valor
    Verificar se o valor que temos é igual ao que está definido. Por exemplo, um user-exit só deve ser executado para um determinado tipo de movimento.

Optámos por definir um único método privado genérico que acede à tabela que é depois chamados pelos outros 3. Embora a tabela seja “fully buffered”, optámos também por manter uma cache local que permite um acesso ainda mais rápido. Em seguida apresentamos o código da classe com os seus métodos.

Primeiro a definição da classe:


class YIDV02_CL_HARDCODED definition
public
final
create public .

*"* public components of class YIDV02_CL_HARDCODED
*"* do not include other source files here!!!
public section.

constants C_BYPASS_CACHE_YES type XFELD value 'X'. "#EC NOTEXT
constants C_BYPASS_CACHE_NO type XFELD value ' '. "#EC NOTEXT

class-methods GET_VALUE
	importing
		value(RICEF) type YIDV02_RICEF
		value(FIELDNAME) type NAME_FELD optional
		value(OCCURRENCE) type YIDV02_OCCUR optional
		value(COUNTER) type YIDV02_CNT optional
	exporting
		value(FIELDVALUE) type CHAR45 .

class-methods GET_RANGE
	importing
		value(RICEF) type YIDV02_RICEF
		value(FIELDNAME) type NAME_FELD optional
		value(OCCURRENCE) type YIDV02_OCCUR optional
		value(COUNTER) type YIDV02_CNT optional
	exporting
		!IT_RANGE type YIDV02_T_RANGE .

class-methods CHECK_VALUE
	importing
		value(RICEF) type YIDV02_RICEF
		value(FIELDNAME) type NAME_FELD optional
		value(OCCURRENCE) type YIDV02_OCCUR optional
		value(COUNTER) type YIDV02_CNT optional
		value(FIELDVALUE) type CHAR45
		!IGNORE_NOT_DEFINED type XFELD optional
	exceptions
		NOT_FOUND
		NOT_DEFINED .

Agora a secção privada:


*"* private components of class YIDV02_CL_HARDCODED
*"* do not include other source files here!!!
private section.

types:
BEGIN OF ty_cache,
	ricef TYPE yidv02_ricef,
	fieldname TYPE name_feld,
	occurrence TYPE yidv02_occur,
	counter TYPE yidv02_cnt,
	hardct TYPE yidv02_t_hardcoded,
END OF ty_cache .
types:
	ty_t_cache TYPE STANDARD TABLE OF ty_cache .

class-methods FETCH_DATA
	importing
		value(RICEF) type YIDV02_RICEF
		value(FIELDNAME) type NAME_FELD
		value(OCCURRENCE) type YIDV02_OCCUR optional
		value(COUNTER) type YIDV02_CNT optional
		!BYPASS_CACHE type XFELD optional
	exporting
		!ROW_COUNT type I
		!IT_HARDCODED type YIDV02_T_HARDCODED .

O método privado FETCH_DATA:


METHOD fetch_data .
* 0. local data structures
	DATA: wa_yidv02_cache TYPE ty_cache.

	STATICS: lt_yidv02_cache TYPE ty_t_cache.

	DATA: lv_occurrence TYPE yidv02_occur,
			lv_counter TYPE yidv02_cnt.

* 0. Occurrence Internal Format
	CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
		EXPORTING
			input = occurrence
		IMPORTING
			output = occurrence.

* 1. Check cache
	IF bypass_cache IS INITIAL.
		READ TABLE lt_yidv02_cache INTO wa_yidv02_cache
		WITH KEY ricef = ricef
					fieldname = fieldname
					occurrence = occurrence
					counter = counter.
		IF sy-subrc = 0.
			it_hardcoded[] = wa_yidv02_cache-hardct.
			DESCRIBE TABLE it_hardcoded LINES row_count.
			EXIT.
		ENDIF.
	ENDIF.

* 2. Inicialize cache criteria
	CLEAR wa_yidv02_cache.
	wa_yidv02_cache-ricef = ricef.
	wa_yidv02_cache-fieldname = fieldname.
	wa_yidv02_cache-occurrence = occurrence.
	wa_yidv02_cache-counter = counter.

* 3. Select from database
	CLEAR: lv_occurrence,
	lv_counter.

	IF ricef EQ space.
		ricef = '%'.
	ENDIF.

	IF fieldname EQ space.
		fieldname = '%'.
	ENDIF.

	IF occurrence IS INITIAL.
		lv_occurrence = 'ZZZZZZ'.
	ELSE.
		lv_occurrence = occurrence.
	ENDIF.

	IF counter IS INITIAL.
		lv_counter = '999'.
	ELSE.
		lv_counter = counter.
	ENDIF.

	SELECT *
	INTO TABLE it_hardcoded
	FROM yidv02_hardcoded
	WHERE ricef EQ ricef
	AND fieldname LIKE fieldname
	AND occurrence BETWEEN occurrence AND lv_occurrence
	AND counter BETWEEN counter AND lv_counter.

	IF sy-subrc EQ 0.
		DESCRIBE TABLE it_hardcoded LINES row_count.
	ELSE.
		row_count = 0.
	ENDIF.

* 4. Save to cache
	IF bypass_cache IS INITIAL.
		wa_yidv02_cache-hardct[] = it_hardcoded[].
		APPEND wa_yidv02_cache TO lt_yidv02_cache.
	ENDIF.
	
ENDMETHOD.

O método público GET_VALUE:


METHOD get_value .
* 0. local data
	DATA: it_yidv02_hardcoded TYPE TABLE OF yidv02_hardcoded,
	wa_yidv02 TYPE yidv02_hardcoded.

* 1. initialize structures
	REFRESH: it_yidv02_hardcoded.
	CLEAR: wa_yidv02.

* 2. Recover Hardcode
	CALL METHOD yidv02_cl_hardcoded=>fetch_data
		EXPORTING
			ricef = ricef
			fieldname = fieldname
			occurrence = occurrence
			counter = counter
			bypass_cache = c_bypass_cache_yes
		IMPORTING
			it_hardcoded = it_yidv02_hardcoded.

* 3. Get the first value
	IF sy-subrc = 0 AND NOT it_yidv02_hardcoded[] IS INITIAL.
		READ TABLE it_yidv02_hardcoded INTO wa_yidv02 INDEX 1.
		MOVE wa_yidv02-ue_low TO fieldvalue.
	ENDIF.

ENDMETHOD.

O método público GET_RANGE:


METHOD get_range .
* 0. local structures
	DATA: it_yidv02_hardcoded TYPE TABLE OF yidv02_hardcoded,
	wa_yidv02_range TYPE rsdsselopt.

	FIELD-SYMBOLS:  TYPE yidv02_hardcoded.

	REFRESH: it_yidv02_hardcoded, it_range.

* 1. Recover Hardcode
	CALL METHOD yidv02_cl_hardcoded=>fetch_data
		EXPORTING
			ricef = ricef
			fieldname = fieldname
			occurrence = occurrence
			counter = counter
	bypass_cache = c_bypass_cache_yes
	IMPORTING
	it_hardcoded = it_yidv02_hardcoded.

* 2. Fill the Range Table
	IF sy-subrc = 0.
		LOOP AT it_yidv02_hardcoded ASSIGNING .
			CLEAR wa_yidv02_range.
			wa_yidv02_range-sign = -ue_sign.
			wa_yidv02_range-option = -ue_option.
			wa_yidv02_range-low = -ue_low.
			wa_yidv02_range-high = -ue_high.
			APPEND wa_yidv02_range TO it_range.
		ENDLOOP.
	ENDIF.

ENDMETHOD.

O método público CHECK_VALUE:


METHOD check_value .
* 0. local data
	DATA: it_yidv02range TYPE yidv02_t_range.

* 1. call method GET_RANGE
	CALL METHOD yidv02_cl_hardcoded=>get_range
		EXPORTING
			ricef = ricef
			fieldname = fieldname
			occurrence = occurrence
			counter = counter
		IMPORTING
			it_range = it_yidv02range.

* 1. check internal table contains any records
	IF it_yidv02range[] IS INITIAL AND ignore_not_defined IS INITIAL.
		RAISE not_defined.
	ENDIF.

* 2. check if value is in the internal table
	IF fieldvalue NOT IN it_yidv02range.
		RAISE not_found.
	ENDIF.

ENDMETHOD.

Desenvolvido por Renato Oliveira e Nuno Godinho.

Nota: RICEF é um acrónimo que significa Report, Interface, Conversion, Enhancement, Form.

Este mecanismo, quando bem utilizado, introduz uma forma eficaz e simples de permite fazer uma gestão unificada e controlada das constantes do sistema evitando a confusão que normalmente acontece.

Ainda assim, há muitas constantes que, pela sua natureza imutável, podem ficar codificadas directamente em ABAP. Coisas como por exemplo o código de uma transacção que sabemos não ir mudar. Há que ponderar cada situação para não ser mais papista que o Papa.

O Abapinho saúda-vos.

2 comentários a “Constantes inconstantes”

  1. Renato Oliveira Diz:

    Caro Nuno,

    Eu fiz umas alterações ao method FETCH_DATA, se foi ontem que tudo isto foi “posted” no abapinho, penso eu de que (onde é que eu já ouvi esta frase :-) estará actualizado…

    Sarava (quer dizer inté)

    ROL :-)

  2. Jorge Diz:

    Obrigado pela dica Nuno. :)

Deixe um comentário


Acerca do Abapinho
O Abapinho é suportado pelo WordPress
Artigos (RSS) e Comentários (RSS).