평소에 내가 코드개발을 할때 신경을 쓰면서 하던 부분들이 잘 정리되어있다.

4번 부분은 지금 내가 요즘 가장 고민하는 것중의 하나이다. 도메인 모델들이 get을 통해서 깊은 곳까지 접근이 가능한거 이거 짜증하는 일이다.

또, 서로 잘 모르는 도메인의 연결을 위해서 get을 포기하고 매번 연결하는 메소드를 추가하기도 찜찜하고...이 문제는 정답은 없다. 상황에 맞게 잘 써야 한다.

하지만, get을 덜 쓰려는 노력은 꾸준히 항상하자.

5번은 잘 지키려고 하고, 지난주 코드리뷰에서도 프로젝트원들에게 언급한 내용이다.
 
6번은 대체적으로 잘 지키고 있는데, 패키지부분은 아직 부족함이 있다. 좀더 연습이 필요하다.

Perfecting OO's Small Classes and Short
http://binstock.blogspot.com/2008/04/perfecting-oos-small-classes-and-short.html


실제상황에서 조금의 압박은 있었지만, 옳은 부분이라고 생각하는 부분이라 꾸준히 표현을 했고, 그 부분들이 최근에 팀스타일에 많이 반영이 되어서, 나름 편안하게 구현을 하고 있다.

자! 코드구현시 머릿속으로 한번씩만 더 생각을 하고 표현을 하자. 나는 주로 구현을 하면서 refactoring을 하면서 위와 같은 스타일로 발전을 하고는 한다.
2개의 dependency를 가지고 있는 클래스가 있다.

비즈니스 로직을 처리를 하려면 2개의 dependency를 이용해서 하나의 비즈니스 로직을 처리한다.

개발을 하다보니, dependency가 같고, 호출하는 메소드가 같은 경우가 종종 발생을 한다.
XXXXBO.java와 YYYYBO.java에 들어가는 코드가 다음과 같다.
Travel travel;
TravelCount travelCount;
public void setTravel(Travel travel){
      .......................
}
public void setTravelCount(TravelCount travelCount){
     ........................
}

public void processAndInsert(){
     .........................
     travel.insert();
     count.plus();

}
public void processAndDelete(){
     ......................................
     travel.delete();
     count.minus();
}


같은 패턴의 dependency와 메소드 실행을 보면서, 2개의 dependency를 가지는 클래스를 만들어서서 클라이언트 클래스에서 하나의 dependency만을 가지게 만들었다.
TravelUpdater.java라는 클래스를 만들어서 다음과 같이 구현을 하였다.
Travel travel;
TravelCount travelCount;
public void setTravel(Travel travel){
      .......................
}
public void setTravelCount(TravelCount travelCount){
     ........................
}

public void insert(){
    
travel.insert();
     count.plus();
}
public void delete(){
    
travel.delete();
     count.minus();
}

그러면, XXXXBO와 YYYYBO클래스에는 다음과 같이 코드가 들어간다.
TravelUpdater travelUpdater;

public void setTravelUpdater(TravelUpdater travelUpdater){
     ........................
}

public void processAndInsert(){
     .........................
    
travelUpdater.insert();
}
public void processAndDelete(){
     ......................................
    
travelUpdater.delete();
}
Travel과 TravelCount가 테스트가 된 클래스라면 TravelUpdater는 테스트도 필요가 없다. XXXXBO와 YYYYBO에서는 TravelUpdater만 mock으로 넣어서 테스트를 하면, dependency도 한개만 추가를 하면 된다.

클래스가 하나 늘어나긴 했지만, 중복을 제거할 수 있었고, 테스트 할때도 dependency의 감소로 코드를 만들기도 쉬웠고, 이해도 쉬웠다.

 만약 dependency가 2개가 아니라 3개,4개...점점 많아질 수록 위임받는 클래스를 사용하면 클라이언트 클래스에서 코드가 변경이 되지 않을 뿐만 아니라, XXXXBO와 YYYYBO 클래스 테스트는 깨지지 않고 강건하게 작성이 될 것이다.
기존에 있던 로직에 새로운 코드를 추가를 해야 할때 어떻게 추가를 할지에 대해서 고민이 많다.

잘못된 방식으로 코드를 추가를 하다가 버그나 에러를 만들고 기존에 잘 되던 기능들을 해칠수도 있다.

가능하면 원하는 기능을 기존에 있던 로직을 침범하지 않고, 구현을 해야 한다.

일반적으로 로직을 추가를 할때는 다음과 같은 방법을 사용한다.
1. 기존에 있던 메소드 안에 추가를 한다.
2. 기존에 있던 메소드를 확장한 메소드를 만든다.
3. 기존에 있던 클래스를 확장한 클래스를 만든다.
4. 기존에 있던 클래스를 가지고 있는 클래스를 만든다.

가장 많이 사용하는 방법으로 번호를 매기어 봤다. 대부분 1,2번만 생각을 해서 코드를 확장한다.

OOP를 조금 한다는 사람 중에 상속을 좋아하는 사람은 3번을 가장 많이 고려를 한다.

하지만, 4번을 사용하는 것은 더 많은 가치를 안겨준다. 클래스의 인터페이스만 을 가지고 외부에서 Dependency를 넣어주는 형태로 구현을 하면, 좀더 loosely한 구현도 가능하고 테스트도 쉬워진다.

이런 식으로 구현을 하는 것이다.
구현체 <-- 인터페이스 <-- 인터페이스 wiring 비즈니스 로직 --> 인터페이스 --> 구현체
가운데 있는 비즈니스 로직은 인터페이스하고만 계약을 하기때문에 테스트를 하기가 쉬워질뿐만 아니라 변경에 있어서도 간섭을 하지 않게 된다.

항상 4번만 좋다는 것은 아니다. 클래스 갯수도 많아지고, 불필요하게 디자인이 복잡해질 수가 있다. 확장할 때 영향끼치는 범위에 따라서 더 깊은 고민을 한 끝에 결정을 해야 한다.
상속을 통해서 클래스 갯수를 줄이고 있던 디자인이 있었다. 거의 7개 가량의 갯수를 줄이기 위해서 디자인이 깨지는 것을 보면서도 이게 실용적(?)이라고 생각을 하면서 클래스 갯수를 줄였지만, 영 마음이 불편했다. OOP의 원칙을 깨가면서 만든 디자인이었기때문이다.

불편한 마음을 없애기 위해서 과감하게 클래스 갯수를 늘렸다. 일단, 원칙을 지키면서 점진적으로  refactoring을 하기로 결심을 했다. 대략적인 윤곽은 마음 속에 있었지만, 사실 목적은 분명하지 않은채 점진적으로 변경을 꾀하면서 변경된 코드 안에서 좀더 나은 디자인으로 개선을 해나갔다.

대략의 과정을 설명을 하자면,
구현 상속으로 되어 있던 디자인을 delegate를 통해서 데코레이션 패턴(나..이 패턴 너무 좋아한다..^^;)으로 변경하고 , 인터페이스만을 wiring하는 클래스를 만들고, dependency injection을 spring config에서 설정을 통해서 인스턴스 생성과 Factory에 등록을 했다.

디자인을 변경 후 3가지의 장점을 가질 수가 있었다.

1. 테스트가 쉬워졌다.
구현상속은 많은 dependency를 가지게 된다. dependency를 하나의 인터페이스로 추상화가 가능했고, 하나의 mock을 생성해서 테스트를 할 수가 있었고, 확장하는 클래스에서 필요한 클래스들만 mo필ck으로 만들어서 테스트를 하면 되었기때문에 테스트를 작성하기 쉬웠다.

2. 코드의 이해가 쉬워졌다.
구현에 대해서 집착을 하지 않아도 되고, 클래스 자체만 봐도 이해를 하기 쉬웠다. 다른 클래스들과의 관계는 이 클래스를 이해하는데, 별다른 장벽이 되지 않았다.

3. 객체지향원칙을 지킬 수 있었다.
부모 클래스에서 확장되어지는 자식 클래스때문에 로직이 추가가 되면 안된다. 비즈니스 로직 특성상 피하기 어려웠지만, 최소한의 변경으로 디자인이 깨지는 것을 피할 수 있었다. 즉, 자식과 관련되서 if문이 증가하지 않았다.

아마 spring configuration을 사용하지 않았다면, 클래스 갯수를 7개정도 늘려서 DI를 해주는 상황이었다. 원칙에 따라서 디자인을 개선을 하고 나니 나름 볼만한 코드가 되었다. 클래스 갯수가 총 8개가 늘어나야 할 상황이었지만, 7개에 해당하는 dependency부분을 spring IoC가 해결을 해주었고, 인터페이스를 wiring하는 심플한 하나의 클래스만 늘어나게 되었다. spring config를 통한 IoC가 아니었어도, 클래스 갯수를 늘려서 확장하는 것이 좋은 선택이었을 것이다. 그 좋은 선택 안에서 spring IoC는 좀더 나은 기쁨을 준 것이다.

사실, 서비스되고 있는 코드를 refactoring하는 것은 쉬운 일은 아니지만, 계속된 찜찜한 마음의 짐을 떨치고 싶었다. 좀더 자신감을 가지고 변경을 꾀할수 있었던 것은 테스트가 된 구현 클래스들을 인터페이스 기반으로 조립하는 일이었기때문에 가능했다. 구현을 다시한 부분은 거의 없고, 클래스들 간의 관계를 바꾸는 디자인 개선작업이어서 좀더 편하게 변경을 가할수 있었다.



클래스 갯수가 적을 수록 좋을 수도 있겠지만, 확실히 의미적으로 상태와 행위를 구별을 하려면 갯수를 늘리는 것도 나쁜 방법은 아니다.

클래스가 갯수가 많으면 코드량이 항상 증가하는 것은 아니기때문이다. 불필요하게 클래스 갯수를 늘리면 안되겠지만, 클래스 갯수가 늘어나는 것에 대해서 너무 부담을 많이 가질 필요는 없다. 원칙에 맞다면 좀더 의미를 전달하기가 쉽다면, 과감하게 클래스를 늘려서 쉽게 코드를 만들자.

코드가 오히려 엉뚱한 곳에 있는 것 보다는 더 보기 좋을 것이다.
OCP와  SRP를 위해서 분리할 것은 확실히 분리하자.

정말 실용적인 것은 원칙을 쉽게 잘 지킬 수 있게 해주는 것이다.


대표적인 것은 클래스의 멤버변수로 있는 Collection인터페이스이다.
어떤 책인지 기억이 잘 나지는 않지만, 이미 여러군데의 책에서 다룬 내용이기는 하다.

아래에서 다룰 내용은 추상화, 캡슐화에 대한 이야기다.

클래스에서 get을 써서 외부에 어떤 behvior나 자신의 상태를 expose를 할 때 주의를 해야 할 상황이기도 하다.

첫번째 코드

public class SimpleSet{

   private List<Simple> simpleList = new ArrayList<Simple>();
  
   public List<Simple> getSimpleList() {
       return simpleList ;
   }
}


위와 같은 코드의 문제점은 무엇인가? 우리가 쉽게 쓴 패턴이다. 하지만, 클라이언트 코드에서 simpleList의 레퍼런스를 받은 후 수많은 조작이 가능하다.

심지어 이런 코드도 가능하다.
simpleList.clear()

필요하다면 SimpleSet에 clear를 행위로 만들어줘야한다.
직접적인 호출은 수많은 버그들을 만들 가능성이 커진다. 의도하지 않는 곳에서의 조작으로 디버깅도 어렵게 만든다.

두번째 코드

public class SimpleSet{

   private List<Simple> simpleList = new ArrayList<Simple>();
  
   public List<Simple> getSimpleList() {
       return Collections.unmodifiableList(addedInfoList);
   }
}


위와 같은 코드의 문제점은 무엇인가? 리스트를 바꿀 수 없게 collection API는 위와 같은 proxy(decorator) pattern의 인터페이스를 제공한다. 이제는 외부에서 simpleList 를 readonly만 접근이 가능하다.

세번째 코드

public class SimpleSet{

   private List<Simple> simpleList = new ArrayList<Simple>();
  
   public Iterator<simple> iterator(){
       return this.simpleList .iterator();
   }
}

한번더 생각을 해보자. 외부로 제공할 때 iterator패턴으로 위와 같이 제공을 한다면, 어떨까? 내부에 collection의 인터페이스마져 숨길 수가 있다. 항상 리스트만이 내부적인 알고리즘이나 로직을 처리하기 위해서 최선이라고 얘기를 할수는 없다.

SimpleSet의 로직을 처리할 때 List보다 더 나은 자료구조가 생겼을 때 아주 쉽게 갈아탈수가 있다. 위와 같은 코드는 가장 추상화가 잘된 클래스이다.

사실 첫번째 코드가 아니라면, 두번째나 세번째 코드를 권장하고 가능한 세번째 코드를 이야기를 하고 싶다. 두번째 코드도 collection자체가 워낙에 유연해서 쉽게 만들수 있는 인터페이스이기 떄문이다. 물론, 출력자체를 iterator로 하는 거 자체에 많은 이점도 있기때문에 개발자가 심사숙고해서 선택할 expose 인터페이스이다.

하지만, 첫번째 코드를 사용하지 말자!!

참고문서
http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html
  • They are easy to notice in the code
  • They can be used to break code down into smaller, more understandable piece
  • They provide more flexibility. Seams that you introduce for testing might be useful when you have to extend your software
Working Effectively With Legacy Code (Paperback)

Feathers, Michael C. / Prentice Hall

OO 랭귀지가 가져다 주는 이익에 대해서는 기본서에서 부터 자세히 잘 나와있습니다. 현재의 랭귀지 패러다임으로 가장 인기있고, 현실적인 모습이는 생각이 듭니다. Working Effectively with Legacy Code를 보다가 그 중에 나온 몇가지 얘기들을 위에 적어봤습니다.

가장 많이 사용하는 자바와 C++, 그리고, 신흥강자 Ruby까지 모두 OO기반의 랭귀지입니다. 위에 OO가 가져다주는 장점을 세 가지정도 나열을 했습니다. 그 중에서 포인트가 될만한 단어를 빨간색으로 처리를 했습니다.

너무나 많이 듣는 얘기라, 더 자세한 설명은 피하겠습니다. 너무나 익숙한 문장입니다. 그렇다고 OO랭귀지로 만든 모든 코드가 위에 이득을 가질 수는 없습니다.

코드를 작성할 때 위에 사항을 항상 유념해서 코드를 만들어야 겠습니다.

과연 내가 만들어낸 코드는 테스팅이 가능한가? TDD로 개발을 하면 자연스럽게 가능해지는 부분이지만, 그게 어렵다면 코드를 작성후 테스트를 만들면서 코드를 테스트가 가능하게 리팩토링을 하세요. 사실 그것도 TDD입니다.

너무 많은 클래스를 보지 않아도 하나의 클래스에서 의도를 분명하게 알수가 있고, 코드를 읽기가 쉬운가? 좁은 의미의 변수와 클래스를 통해서 가능한 작게 만들고 위임을 적극 활용하세요. heavy한 모습보다는 주위에 자꾸 도움을 청하는 도움을 청하는 객체들과 나 자신의 객체가  cohesive해질때 객채들이 협업을 하는 아름다운 모습이 나옵니다.

항상 긴장하세요. 이득을 얻으려면 개발을 하면서 생각을 하고 위에 사항들을 지켜줘야 합니다. 처음부터 쉽지 않을수 있습니다. production코드도 시간을 가지고 만들고 관심을 가져야만 legacy code를 만들지 않을 수 있습니다.

그 무엇보다고 자신의 코드를 자주 들여다보는 것이 더 큰 공부가 될수도 있습니다.

코드를 작성을 할때 CPDD가 자주 일어나면 그 원인을 분석을 해보시고, 재사용을 할수 있게 문제를 해결해보시길 바랍니다.

http://www.ologist.co.kr/tag/CPDD



코드의 의도
http://www.ologist.co.kr/507

위에 많은 덧글들이 달려서 덧글 및 트랙백을 통해서 느낌점을 얘기해보려 합니다.

cycle length calculator
http://cavin.egloos.com/3049122

이 이야기의 시작은 바로 위에 있는 링크입니다.

애시당초 number 라는 변수의 이름 자체가 모호해서 생긴 문제를 불필요하게 해결한 건 아닌가 하는 생각이 듭니다. - 이희승

네이밍을 잘 지으면, 구현이 단순해질수 있다는 교훈을 희승님이 남겨주셨습니다. 하지만, 3n+1 문제를 가지고 더 좋은 이름을 지어 보려고 하는데, 생각이 잘 안나더군요...OTL

그래서, 저도 cavin님과 똑같이 이름을 짓고, extract local varibable이나 extract method를 통해서 의도를 전달을 했을 것입니다.

일단, 여기까지 코드를 작성을 합니다. 그리고, 좀더 많은 부분을 구현을 하다가 보면, 또는 pair나 코드리뷰를 통해서 더 좋은 이름이 나오면 refactoring을 통해서 불필요한 코드들을 제거를 하면 됩니다. cavin님은 테스트 케이스가 있어서 더욱더 쉽게 refacotring을 할수가 있습니다.

희승님의 아름다운 작명을 기대를 했는데, 조금 바쁘신듯 합니다...^^

3n+1문제 관련하여..
http://cavin.egloos.com/3085039

오픈마인드는 많은 경험을 갖게 하는 듯하다. 자신이 만든 소스를 공개를 한다는 것은 쉬운 일이 아니지만, 공개를 하면 더욱더 많은 경험과 좋은 의견들을 들을수가 있다.

과거에 1999년도쯤 자바를 처음하면서 고수들의 코득다 무척 궁금했을 때가 있었지만, 이원영님을 제외하고는 대부분의 사람들이 소스를 공개를 잘 하지 않아서 실제 잘 만들어진 코드를 보기 어려웠다. 지금은 오픈소스도 많이 있고, 주위에 많은 분들이 소스를 공개를 하셔도 많은 도움이 된다.

팀내에서 코드리뷰를 통해서 내가 너무 형식적으로 구현한 것들에 대한 불편함을 알수도 있게 된다. 사실 구현을 해두고 시간이 지나면서 좀더 도메인 영역을 자세히 알면서 매일 꾸준히 리팩터링을 하고 있지만, 정말 꼭 맘에 드는 아름다운 코드를 만들어 내는 일은 쉬운일이 아니다.

API 및 코드가 오픈이 되고, 사용자들의 의견이 더해질때 좀더 의미있는 객체들이 탄생을 하게 된다고 생각을 합니다.

김창준임이 가독성에 대한 좋은 이야기를 해주셨습니다.
prefactoring을 책을 보다가 보면, 극단적인 가독성과 추상화에 대한 좋은 이야기들이 나온다.

저의 생각은 최소한 클라이언트가 사용하는 인터페이스에서는 극단적으로 가독성과 추상화를 지켜주었으면 하는 생각이다. 내가 만든 코드가 애매하게 보일때, 메타포에 대한 지식이 있는 사람과 없는 사람은 가독성에 대해 현저한 차이가 난다.

아래 이야기는 비슷한 관점에서 나온 것이 아닐까 생각을 해봅니다.

만약 코드가 비지니스 로직이 들어가고 그 로직이 domain-rich하다면 되도록 가독성을 추구하겠지만(가독성은 독자에 따른 상대적 개념이라는 것을 명심하면서), 저는 때로 가독성을 손해보면서까지 중복을 줄이기도 합니다. - 김창준

자바지기 아저씨도 그와 관련해서 좋은 이야기를 적어주었다.
http://wiki.javajigi.net/pages/viewpage.action?pageId=7565

프리팩토링

Ken Pugh / 한빛미디어



객체지향에서 그걸 하다 보면 흥미로운 객체들을 발견합니다. 함수형에서 그렇게 하다 보면 흥미로운 함수와 함수의 함수(functional)를 발견합니다. 이 "흥미로운 무엇"은 강력합니다. 내가 전에 모르던 것을 배우게 됩니다. 그리고 종종 이것은 프로그래머의 울타리를 넘어서 영향력을 끼치기도 합니다. 고객들의 대화가 바뀔 수도 있습니다.

객체지향을 하면서 흥미로운 객체들을 발견하지 못한다면 너무 고리타분한 코딩은 아닐까 생각합니다. - 김창준

김창준님의 흥미로운 객체에 대한 이야기는 DDD책에서 나오는 이야기와 일맥상통한다. cohesive하고, 도메인 객체 자신이 많은 이야기를 할수 있는 객체는 내가 코딩을 할때 큰 기쁨을 줍니다. DDD에서는 RIch Domain을 만들어가는 것은 쉽지 않은 일이지만, 진정한 Rich한 Domain객체를 만드는 일은 정말 의미있는 일이라고 나옵니다.

객체지향을 하다가 보면, 흥미로운 객체, Rich한 Domain객체에 대해 만들고 싶은 마음이 많습니다. 하지만, 제 경우에는 한번에 만들어지지 않고, 이리저리 refactoring을 하다가 발견이 되더군요. 김창준님 애기처럼 발견이라는 말이 더 어울립니다.

김창준님, 이희승님, cavin님 덧글 고맙습니다...^^

일반적으로 abs를 하는 예제이다.

number = Math.abs(number) // Java code

number = number.abs // Ruby code


strlen(name) // C

name.length // Java, Ruby code


"이런 부분이 Ruby가 진정한 OOP언어라고 말할수 있다."
- Programming Ruby,Dave Thomas

랭귀지를 떠나서, OO적인 프로그래밍을 하려면 기본적으로 어떻게 코딩해야 하는가를 보여주는 예제로 볼수도 있다.

OO랭귀지를 아직도 procedural스타일로 코드를 짜는 사람이 많은데, 좀더 생각을 해보자. 복잡한 문제 해결을 위해서 어떤 방법이 정말 유지보수를 편하게 할수 있는지.

기본적으로 중복코드가 적고, 데이터가 적게 움직이면, 그 만큼 유지보수 비용이 절감할 수밖에 없다. 약간의 학습곡선이 필요한 부분(디자인 설계와 같은 부분)도 존재하겠지만, 학습곡선을 통해서 좀더 편해질수 있는 것은 불변의 진리다.

학습곡선이 싫다고, 구구단을 외우지 않고, 계속 더하기만 할것인가? 게으름때문에 학습을 피하는 것은 잘못된 생각이다. 학습을 통해서 게을러지자.

왜 객체지향인가?에 대한 질문과 대답들은 너무도 많은 책들과 페이퍼에 나와서 여기서는 생략한다. 그마져도 중복일수 있으므로....

원칙이라는 것은 대부분 많은 경험에서 나오는 관습과 같은 것이므로 스페셜한 케이스가 아니라면, 가능한 지켜주자.

난 요즘 SRP(Single Responsiblity Principle), LSP(Liskov Substitution Principle) 등등의 객체지향원칙을 당연한 이야기지만, 더 지키려고 노력한다.

그리고, 우리가 정말 테스트하기 쉬운 코드를 작성하고 있는지 다시 한번 생각을 해보자. 대부분의 경우 테스트가 어려우면 디자인이나 dependency관계에 문제가 있고 객체지향원칙들이 깨지고 있는 것이다.

OOP만큼 중요한 것은 OO-System이다. 가령, 단순하게 procedural한 수정(인터페이스 변경이 아닌 내부 로직이나 알고리즘 변경같은것)을 했는데, 부적절한 관계로 다른 시스템에서 영향을 받는다면, 진정한 OO-System이 아닐것이다.
 

잘 만들어진 Object를 어떻게 판단을 할까요?

  • Does it stick to its purpose?
  • Are its responsiblities clearly stated?
  • Do its reponsibilities match its role?
  • Is it of value to other objects in its neighborhood?

다들 알고 있으면서 막연했던 내용들이 잘 정리가 되어 있어서 적어봤습니다.

개발을 할때 Object를 만들고 나서, 수시로 refactoring을 하면서 위에 내용에 해당하는 Object들은 가차없이 제거를 하거나 위에 사항에 맞는 quality를 가질수 있게 책임을 만들어 주어야 겠습니다.



출처 :

Object Design

Rebecca Wirfs-Brock,Alan McKean / Addison-Wesley



사족~

내가 블로그에 글을 적는다는 것은 보통 정리를 하는 마음에서 하는데, 글을 길게 쓰면 나도 지치고, 보는 사람도 지칠거 같아서 앞으로는 가능한 간결하게 쓰도록 하겠습니다.

BLOG main image
OOP and Java by ologist

공지사항

카테고리

All (649)
private!! (106)
WEB & IT (140)
Developer (400)