'enum'에 해당되는 글 3건

  1. 2011.03.30 [Java] 오늘의 교훈.
  2. 2010.03.24 [Java] enum을 사용해 분기문을 없애보자. (4)
  3. 2010.02.03 enum의 ordinary() 메서드 사용. (2)

[Java] 오늘의 교훈.

Java 2011.03.30 00:17

위와 같은 코드가 있었다.
 
위와 같은 상태에서 class A에 있던 public enum을 삭제하고
하나의 독립적인 클래스 파일로 E라는 enum 클래스를 생성하였다.

  어차피 public이고 Type도 그대로인지라
컴파일에러는 어디서도 떨어지지 않았고, 변경된 클래스인 A와 새로 만들어진 enum 클래스인 E만을
배포하여 컴파일하였다.

전혀 문제가 없을거라 생각했는데
문제는  User 클래스의 do() 메서드에서 발생하였다.

생각해보면 당연한 것인데...
기존의 E enum 클래스의 return type은
A$E 였고, 새로 뽑아낸 enum 클래스의 Type은 E였다. (기본적으로 앞에 붙는 패키지는 제외..)

결국, 타입이 맞지 않는 오류가 발생하여 위와 같이 기존의 E를 받아 쓰던 클래스들을
전부 다시 컴파일해주었다..

기본적으로 자바에서의 Type이라는 것이 패키지를 포함한 full name일 것인데
평소에 거의 망각하듯이 쓰다보니 아차 하는 상황이 발생하였음....
 
Posted by 용식
웹 사이트에서 "정렬" 이라던가 파라메터에 따른 "로그"등을 남길 때 if문이 많이 사용 됩니다.
일반적으로 처음 사이트를 구축시
파라메터를 정해 놓습니다.
정렬에 대한 파라메터..

처음에는 각 파라메터와 그 파라메터가 뜻하는 것들을 주석으로 잘 달아 놓습니다.
그리고 이것을 처리하는 클래스는 일반적으로 아래와 같은 모양을 갖게 되는 경우가 많습니다.


이제 사이트가 만들어지고 , 다른 개발자들에 의해서 유지보수가 되고
요구사항으로 정렬조건등이 늘어나게 되면서
처음 달아놨던 주석은 엉망이 되고, QueryBuilder의 if문은 요구사항이 생길 때 마다 늘어나게 됩니다.
그나마 Builder 클래스를 로직이 필요한 곳에서 모두 공통으로 사용하고 있다면 상황은 좋은 편이겠지만
혹시라도 위 로직이 여기저기 퍼져있다면, 정렬 조건이 하나 추가 될 때마다 손대야 하는 클래스도 많아지고
if를 통한 분기문이 길어지면서 수정 시 오류를 일으킬 확률도 커지고...
보기도 안 좋아집니다.

그리고, 파라메터를 set하는 부분에서도 파라메터의 타입이 String이기 때문에
사실 P,N 등 개발자가 처음 설계 했을 때 의도했던 문자가 아닌 "POP", "NEW"등 어떤 String이 들어가도
이 소스는 에러를 일으키지 않습니다. 다만, 정상적으로 작동을 하지 않을 뿐이지요..

때문에 이런 정렬 타입이 추가 될 때마다 어딘가에 정리를 해야하고
유지보수 하는 사람은 이 정리 된 문서가 있어야 각각의 파라메터가 뜻하는 것을 알 수가 있습니다.

주석이라도 최신화가 되어 있다면 조금 낫겠지만
사실 그렇게 되기는 많이 어렵죠.


이미 제가 있는 이곳에도 저런 코드가 있어서..
이번에 enum을 활용해서 위 코드를 정리해보았습니다.

일단 enum 클래스를 정의 합니다.


이런식이 되겠죠.. 일단 골격만 잡았습니다.
그리고 RequestDomain 클래스에서 이 enum을 사용하도록 해야 합니다.


일단 이렇게 해봤는데..
레거시에서는 이미 파라메터를 String으로 Setting해주고 있습니다.
이 부분들을 모두 찾아 다니면서 SortCodeEnum 타입으로 set해주도록 수정하면 좋겠지만
현실적으로 어려운 일입니다. 그래서, 기존의 레거시를 최대한 활용하는 쪽으로 방향을 잡았습니다.


enum 클래스를 수정하여 생성자에서 코드의 뜻을 받아주도록 하였습니다.
그리고 그 코드의 로그를 받아 낼 수 있는 getter 메서드 (getCodeName)를 만들었습니다.
또한, 각 enum의 타입들이 쿼리를 가지고 그것을 리턴 할 수 있도록 getSortQuery 메서드를 정의 하여
각 enum 타입에서 오버라이드 하고 있습니다.

그럼 이것을 RequestDomain 클래스에서는 어떻게 사용할까요.


기존의 코드들이 이미 String으로 된 파라메터를 사용하고 있기 때문에
valueOf 메서드를 활용하여 그 파라메터를 가지고 SortCodeEnum 을 생성하여 그에 따른
CodeName과 SortQuery를 받아오고 있습니다.

이것을 QueryBuilder 클래스에 적용하면 아래와 같이 됩니다.

이렇게 enum으로 수정 할 때 제가 생각하는 장점은 이렇습니다.

1. 소스 그 자체가 최신화 된 정보를 가지고 있습니다.
 - 저는 프로그램을 개발하면서 가장 중요하다고 생각하는 것이 코드가 읽기 쉽게 작성되어야 한다. 라는 것 입니다.
   이 방식은 enum 클래스 그 자체가 항상 최신화가 되면서, 그 자체로 자기가 가지고 있는 뜻을 모두 보여주고 있습니다.
   특별히 주석을 보지 않아도, 문서를 만들지 않아도 유지보수를 하는 사람의 입장에서는 큰 문제는 없을 것 입니다.
2. 과도한 분기문을 줄여, 소스를 간단하게 만들 수 있습니다.
3. 정렬조건 추가나 수정등의 요구사항에 간단하게 대처 할 수 있습니다.
4. 위 예제에서는 sortCode 를 String으로 set 해주고 있지만, 만약 code 자체를 set 할 때도 SortCodeEnum 타입으로
set 할 수 있게 해준다면, 사용하는 입장에서는 그만큼 오류를 일으킬 확률이 줄어듭니다. 정해진 타입만 파라메터로
넘겨줄 수 있기 때문이죠.

위 코드에서 주의 할 부분이 있습니다.
enum의 valueOf 메서드를 사용하고 있는데..
만약 valueOf메서드의 파라메터인 sortCode가 SortCodeEnum 클래스에 정의되어 있지 않는다면
위 소스는 exception을 발생시킵니다. 따라서, 이에 따른 방어코드가 필요합니다.

위에서는 RequestDomain에 SortCodeEnum이 들어갔지만..
그냥 QueryBuilder 쪽에서 SortCodeEnum을 생성하여 처리하는 방법도 있을 것 입니다.
exception에 대한 대처는 후자가 더 유용 할 것 같네요.

그리고 enum 타입으로 생성되는 객체들은 싱글턴입니다.
Posted by 용식
TAG enum, java
effective java를 보면
서수가 필요 할 때 enum의 ordinary() 메서드는 가급적 사용하지 말라고 되어있습니다.
enum을 구성하는 값이 지워지거나 하게 되면
ordinary() 메서드에서 리턴되는 순서 값도 같이 변하기 때문입니다.

이런식의 버그는 나중에 어디선가 발생하게 되면
상당히 찾기 난해합니다.

그런데 제가 이번에 ordinary를 쓸모있게 사용한 부분(이라고 혼자 생각하는 부분 ^^)이 있어서 소개하려 합니다.

ordinary 메서드는 enum을 구성 하는 각 값들의 순서를 리턴합니다.

 
일 때, FIELDNAME.NAME.ordinary()는 0을 리턴합니다.
FIELDNAME.PRICE.ordinary()는 1을 리턴하겠죠.

그러다가 PRICE가 빠지게 되면 FIELDNAME.SCORE.ordinary()가 1을 리턴하게 됩니다.

이렇게 가변적인 값을 갖기 때문에 effective java에서는 사용하지 말 것을 권하는데요..

이렇게 가정을 해보겠습니다.
결과 값을
List<String[]> 형으로 반환하는 솔루션이 있습니다.
솔루션이기 때문에 이 부분을 수정 할 수 는 없는 상황입니다.
이 결과를 사용하는 view에서는 배열의 index를 통해서 해당 값에 접근해야 합니다.

각 index는 위 코드 처럼 name price score를 뜻합니다.
즉 [0]=name, [1]=price, [2]=score이죠.

만약 솔루션에서 보여줘야 할 필드가 늘어나면 솔루션에 필드 설정을 해주고
배열의 index는 하나가 늘어나서 [3]=새로운필드 이런식으로 확장이 됩니다.

뭐 한 3, 4개 정도의 인덱스 숫자라면 사용하기 괜찮습니다.

그런데 비지니스가 바뀌고 업무가 늘어나면서
솔루션에서 처리해주고 리턴해줘야 할 필드가 늘어나게 되고 그렇게 되면서, 이 인덱스가 커지게 됩니다.
50개 60개까지 늘어납니다.

이정도 되면 각 인덱스가 어떠한 값을 가르키고 있는지
기억조차 할 수가 없습니다.
특정 필드의 값을 사용하고 싶은데 , 몇 번 인덱스가 그 값인지를 외우지 못 해서
매번 레퍼런스등을 찾아봐야 하죠.

그래도 이정도에서 끝나면 다행인데..
서비스가 변하면 기존에 사용하던 필드를 사용하지 않게 되었습니다.

그럼 이걸 지워야 하는데
무작성 안 쓴다고 빼버리면 전체의 인덱스가 하나씩 밀려버리는 현상이 발생합니다.
즉, 총 60개의 필드를 지정하여 사용하고 있었는데
그중 30번째 필드가 더 이상 필요하지 않아 삭제를 하였더니
전체 index 크기가 59로 변하면서, 기존에 [60]으로 접근하던 소스에서는
ArrayOutOfBoundsException이 발생하게 됩니다.

문제는 이게 런타임Exception이라서
이클립스 같은 IDE 툴에서 보이지 않고
그렇기 때문에 각 소스를 찾아다니며 수정을 해주고

31번째 부터는 모든 index를 -1씩 해줘야 정상적인 값을 가져 올 수 있다는 이야기가 됩니다.

이게 귀찮아서 안 쓰는 필드를 그냥 놔두면..
점점점점 배열의 크기는 커지고
이제 뭐가 쓰는 필드인지 안 쓰는 필드인지조차
모르게 되어버리는거죠...

이 상황을 좀 유연하게 넘어가고 싶었습니다. 그래서 생각한 것이 enum의 ordinary() 메서드 입니다.

enum을 맵퍼처럼 사용을 하였습니다.


이렇게 솔루션에 필드가 추가 되면 그 순서에 맞추어서 enum도 확장해 줍니다.

이정도만 되어도 감이 오시는 분도 계실거에요..

그리고 이 mapper를 사용하여 값을 리턴해주는 domain 클래스를 하나 만듭니다.


mapper의 역할을 하는 enum의 ordinary 값으로 배열에 접근하여 값을 리턴해주는 getter를 포함한 domain 클래스입니다.

일단, enum이 mapper 역할을 하기 때문에
각 인덱스가 무엇을 뜻하는지 알수있기도 하지만
무엇보다도 배열에 직접 접근 하는 것이 아니라 getter를 사용하기 때문에
getter 메서드 이름만으로도 이 값이 무엇을 뜻하는지 알 수 있습니다.

중요한 것은 중간 필드가 삭제 되었을 경우입니다.

위 예에서 sale_cnt라는 필드를 더 이상 사용하지 않아서 빼야 한다고 하면..
그냥 빼면 됩니다.

그러면 기존에 FIELDNAME.SALE_CNT로 접근하던 domain 클래스에서는 컴파일 에러가 발생 할 것입니다.

눈에 보이죠. 컴파일 에러. 이클립스에서 빨강색으로 표시해줍니다.

runtime 에러가 아니기 때문에 표시가 납니다.

그러면 이 getter 메서드에서 빈 값을 리턴해주도록 수정하던가
아니면 getter 메서드 자체를 지워버립니다.

getter메서드 자체가 지워지면 마찬가지로 이 메서드에 접근 하던 클래스등에서 또 컴파일 에러 표시가 날 것입니다.

하지만, 이렇게 지워버리면 위험요소가 존재할 수 있기 때문에
빈 값을 리턴해주도록 수정하고 deprecated 처리를 해주면 되겠죠.

더 중요한 사실은 중간에 필드가 빠졌음에도
기존 처럼 배열의 index를 일일히 재조정 해주지 않아도 된다는 것 입니다.

사실 이런 상황은 특수한 상황입니다.

저런 솔루션을 사용하는 그런 상황에서나 필요한 내용이지만
enum 클래스를 이런식으로도 사용하는구나.. 라는 뜻으로
봐주시면 좋을 것 같습니다.

Posted by 용식
TAG enum, Mapper