Subroutine은 Java의 메서드를 생각하면 된다. 형식은 FORM으로 시작해서 ENDFORM으로 끝난다.
PERFORM WRITE_DATA.
FORMWRITE_DATA.
WRITE : /'SUBROUTINE'.
ENDFORM.
Subroutine 파라미터
서브루틴을 호출할 때 사용하는 파라미터를 Actual Parameter라 하고 서브루틴에서 사용되는 파라미터를 Formal Parameter라고 한다.
파라미터를 3가지 유형으로 주고받을 수 있다.
- Call by Value : Actual Parameter와 Formal Parameter가 물리적으로 다른 메모리 영역을 갖는다.
- Call by Reference : 물리적으로 같은 메모리 영역을 공유하여 넘겨준다. 즉, 주소값을 넘겨주는 것
- Call by Value and Result : 변수의 값을 넘겨주고 받는 구문에서 작업을 성공적으로 수행하였을 경우 변경된 값을 돌려준다. 물리적으로는 다른 영역을 사용한다.
Call by Value
- USING 키워드 다음에 파라미터를 사용하고 VALUE 구문으로 완성한다.
DATA : GV_VAL TYPE CLENGTH30VALUE'CALL BY VALUE CHECK'.
PERFORM CALL_BYVALUE USING GV_VAL. " CALL BY VALUE로 수행
WRITE : / GV_VAL. " CALL BY VALUE CHECK
FORMCALL_BYVALUEUSING VALUE(P_VAL).
CONCATENATE P_VAL '!!!!!'INTO P_VAL.
WRITE : / P_VAL. " CALL BY VALUE CHECK!!!!!
ENDFORM.
- PERFORM 이후에도 서로 다른 메모리를 공유하기 때문에 파리미터 GV_VAL의 값이 변하지 않은 것을 확인할 수 있다.
- 즉, FORM 구문에서 USING과 VALUE 키워드를 같이 사용하면 새로운 메모리 영역에 값을 복사하여 값을 전달하는 것이다. P_VAL은 FORM내 에서만 쓰이는 지역 변수이다.
Call by Reference
- Java 프로그래밍에서 메서드를 호출할 때 변수가 아니라 참조변수를 파라미터로 주는 것과 같다고 볼 수 있다. 참조변수는 객체의 주소값을 가진 변수이기 때문에 파라미터로 참조변수를 던져주면 서브루틴 내에서 객체의 주소값을 가지고 해당 객체를 메모리에서 가져오기 때문에 Actual Parameter와 Formal Parameter가 같아진다.
DATA : GV_VAL TYPE CLENGTH30VALUE'CALL BY REFERENCE CHECK'.
WRITE : / GV_VAL. " CALL BY REFERENCE CHECK
PERFORM CALL_BYREF CHANGING GV_VAL. " CALL BY REFERENCE 수행
WRITE : / GV_VAL. " VALUE HAS BEEN CHANGED
FORMCALL_BYREFCHANGING P_VAL.
P_VAL='VALUE HAS BEEN CHANGED'.
ENDFORM.
- VALUE 구문을 쓰지 않으면 USING과 CHANGING 모두 Call by Reference를 이용한다. 하지만 가독성 차원에서 CHANGING을 쓰는 것이 좋을 것이다.
Call by Value and Result
- CHANGING 구문과 VALUE 구문이 함께 사용되면 Subroutine이 정상 종료 시 Actual Parameter값이 변경된다.
DATA : GV_VAL1 TYPE IVALUE2.
DATA : GV_VAL2 TYPE IVALUE3.
DATA : GV_SUM TYPE I.
PERFORM SUM_DATA USING GV_VAL1 GV_VAL2
CHANGING GV_SUM.
WRITE : /'RESULT : ', GV_SUM.
FORMSUM_DATAUSING VALUE(P_VAL1)
VALUE(P_VAL2)
CHANGING VALUE(P_SUM).
P_SUM= P_VAL1 + P_VAL2.
ENDFORM.
파라미터
파라미터 타입 정의
- FORM 구문 내의 Formal Parameter는 타입을 명시적으로 지정하지 않으면 Generic Type으로 정의되고 Actual Parameter의 기술적 속성을 상속받게 된다.
- FORM의 Formal Parameter를 I로 지정하고 호출 시 파라미터로 TYPE C를 던져주면 구문 에러가 발생한다.
- Formal Parameter 타입 지정 방식
1. FORM SUBR CHANGING P_VAL. - Generic Type 사용
2. FORM SUBR CHANGING P_VAL TYPE D. - Actual Parameter와 같은 타입 사용
3. FORM SUBR CHANGING P_VAL LIKE GV_VAL. - Actual Parameter와 같은 타입의 변수 사용
구조체 파라미터
DATA : BEGINOF GS_STR,
COL1 VALUE'A',
COL2,
ENDOF GS_STR.
GS_STR-COL2='B'.
PERFORM WRITE_DATA USING GS_STR.
FORMWRITE_DATAUSING PS_STR STRUCTURE GS_STR.
WRITE : PS_STR-COL1, PS_STR-COL2.
ENDFORM.
- 구조체를 파라미터로 전달할 때 타입을 명시적으로 지정하지 않으면 컬럼이 없다는 에러가 발생한다.
- 즉, 구조체를 파라미터로 쓸 때에는 타입을 명시적으로 지정하거나 그렇지 않으면 필드 심볼을 이용해야 한다.
DATA : BEGINOF GS_STR,
COL1 VALUE'A',
COL2,
ENDOF GS_STR.
GS_STR-COL2='B'.
FIELD-SYMBOLS<FS>.
PERFORM WRITE_DATA USING GS_STR.
FORMWRITE_DATAUSING PS_STR.
ASSIGNCOMPONENT1OFSTRUCTURE PS_STR TO<FS>.
WRITE<FS>.
ASSIGNCOMPONENT2OFSTRUCTURE PS_STR TO<FS>.
WRITE<FS>.
ENDFORM.
- 위와 같은 결과를 반환한다.
인터널 테이블 파라미터
TYPES : BEGINOF T_STR,
COL1 TYPE C,
COL2 TYPE I,
ENDOF T_STR.
TYPES : T_ITAB TYPE TABLEOF T_STR.
DATA : GS_STR TYPE T_STR.
DATA : GT_ITAB TYPE T_ITAB.
GS_STR-COL1='A'.
GS_STR-COL2=1.
APPEND GS_STR TO GT_ITAB.
GS_STR-COL1='B'.
GS_STR-COL2=2.
APPEND GS_STR TO GT_ITAB.
PERFORM TEST_ITAB USING GT_ITAB.
FORMTEST_ITABUSING PT_ITAB TYPE T_ITAB.
READ TABLE PT_ITAB WITH KEYCOL1='A'INTO GS_STR.
IFSY-SUBRCEQ0.
WRITE : GS_STR-COL1, GS_STR-COL2.
ENDIF.
ENDFORM.
TABLES 구문
- USING과 CHANGING구문 대신 사용
- 예전 아밥에서 많이 사용되어 현재도 많이 사용되고 또 소스코드에 많이 남아있는 시스템이 많다.
- Call by Value로는 사용할 수 없다.
TYPES : BEGINOF T_STR,
COL1 TYPE C,
COL2 TYPE I,
ENDOF T_STR.
TYPES : T_ITAB TYPE TABLEOF T_STR.
DATA : GT_ITAB TYPE T_ITAB.
PERFORM TEST_ITAB TABLES GT_ITAB.
PERFORM WRITE_DATA TABLES GT_ITAB.
FORMTEST_ITABTABLES PT_ITAB TYPE T_ITAB.
DATA : LS_STR TYPE T_STR.
LS_STR-COL1='A'.
LS_STR-COL2=1.
APPEND LS_STR TO PT_ITAB.
LS_STR-COL1='B'.
LS_STR-COL2=2.
APPEND LS_STR TO PT_ITAB.
ENDFORM.
FORMWRITE_DATATABLES PT_ITAB LIKE GT_ITAB.
DATA : LS_STR TYPE T_STR.
LOOP AT PT_ITAB INTO LS_STR.
WRITE : / LS_STR-COL1, LS_STR-COL2.
ENDLOOP.
ENDFORM.
Subroutine 호출
- 서브루틴 호출은 Internal, External 두 가지 방식이 있음
- Internal : 프로그램 내에서 서브루틴 호출
- External : 다른 아밥 프로그램 내의 서브루틴 호출
REPORT ZABAP_HYOWON_TEST1.
DATA : GV_CHAR(20) VALUE'EXTERNAL CALL!'.
PERFORM TEST_EXRN_SUBR(ZABAP_HYOWON_4) USING GV_CHAR.
PERFORM TEST_EXRN_SUBR(ZABAP_HYOWON_4) USING GV_CHAR.
ENDDO.
- WHILE - ENDWHILE : 표현식이 참이면 반복순환, 횟수는 SY-INDEX에 저장
WHILE GV_INDEX <10.
PERFORM TEST_EXRN_SUBR(ZABAP_HYOWON_4) USING GV_CHAR.
WRITE : GV_INDEX.
GV_INDEX= GV_INDEX +1.
ENDWHILE.
- LOOP ~ ENDLOOP : 인터널 테이블의 라인을 차례대로 WORK AREA나 HEADER LINE으로 이동하는 순환 구문
- 현재 순환 횟수는 SY-TABIX에 저장, 인터널 테이블의 라인 번호
LOOP AT PT_ITAB INTO LS_STR.
WRITE : / LS_STR-COL1, LS_STR-COL2.
ENDLOOP.
Subroutine 종료
- EXIT 구문을 만나면 서브루틴 빠져 나오고,
- CHECK 구문을 만나면 값을 비교하여 참이면 이후 구문 수행
- 개인적인 생각으로 IF문을 통해 EXIT 구문을 수행하느냐 마느냐를 정하는 게 소스코드는 길어도 소스코드에 일관성이 있지 않을까함. 다른 언어들도 Return문을 수행하느냐 마느냐로 분기 처리를 하는 경우가 많기 때문에..
PARAMETERS : P_VAL TYPE CHAR10.
PERFORM END_SUBR USING P_VAL.
FORMEND_SUBRUSING VALUE(P_VAL).
CASE P_VAL.
WHEN'EXIT'.
WRITE'SUBROUTINE EXIT'.
EXIT.
WHEN'CHECK'.
WRITE : 'VALUE CHECK : ', P_VAL.
CHECK P_VAL NE'CHECK'.
WHENOTHERS.
ENDCASE.
WRITE'SUBROUTINE HAS NOMALLY DONE'.
ENDFORM.
PERFORM ON COMMIT
- ON COMMIT : COMMIT WORK를 만나면 서브루틴 구문 호출
- ON ROLLBACK : ROLLACK WORK를 만나면 서브루틴 구문 호출
DATA : GS_SCARR LIKE SCARR,
GV_FLG TYPE C.
SELECTSINGLE*FROM SCARR INTO GS_SCARR
WHERECARRID='AA'.
PERFORM DELETE_DATA USING GS_SCARR.
PERFORM INSERT_DATA ONCOMMIT.
IFGV_FLG='X'.
COMMITWORK.
ENDIF.
FORMDELETE_DATAUSING VALUE(PS_SCARR) TYPE SCARR.
DELETE SCARR FROM PS_SCARR.
IFSY-SUBRCEQ0.
GV_FLG='X'.
ENDIF.
ENDFORM.
FORMINSERT_DATA.
GS_SCARR-ZCOL1='Z'.
INSERT SCARR FROM GS_SCARR.
ENDFORM.
- COMMIT WORK가 수행될 때 PERFORM INSERT_DATA 구문이 수행되도록 함.
MACRO
- 같은 구문을 여러 번 사용해야 할 때 미리 구문 덩어리를 매크로라는 하나의 덩어리로 정의하고 계속해서 가져다 쓰는 것이다.
- PERFORM을 사용하는 것과 다르지 않지만 더 소스코드가 짧다는 장점이 있다. "MACRO_NAME 변수." 가 끝이기 때문에 단순하지만 이 구문이 어떤 명령어인지 어디서 왔는지를 알려주는 PERFORM과 같은 명령어가 없어 가독성이 떨어진다. 아마 예전에 SUBROUTINE이나 FUNCTION MODULE 개념이 없었을 때 만들어진 기능이 아닌가 싶다. 안 쓰는 게 좋을 듯.