[리팩토링]리팩토링 사례 공유 모임 후기

저번달에 범균님과 함께 리팩토링했던 스타크래프트 프로리그 앱에  대한 내용을

6월 27일 저녁에 오프라인에서 공유하는 자리를 가졌다.

해당 내용은 범균님 블로그에서 확인할 수 있다.

20130627_200654

리팩토링 내용은 크게 아래와 같다.

  • 클래스/인터페이스/메서드/필드등을 의미있는 이름으로 변경
  • 객체생성을 빌더패턴으로 전환함으로서 중복코드 제거
  • 역할과 책임을 명확히 하기 위해 HTML파싱과 관련된 일부기능 분리
  • 각 액티비티에서 직접적으로 의존하고 있던 데이터공급객체를 Factory클래스를  사용해서 의존성 제거함
  • 여기저기 흩어져있던 파일관련기능을 한 곳으로 모아 응집도 높임

모든 내용을 발표하는데 1시간 반정도 걸릴 것으로 예상했지만, 의외로 1시간도 되지 않아

발표가 끝났다. 그리고, 짧게나마 발표내용에 대한 토론이 이어졌다.

  • 왜 set메서드를 쓰면 좋지 않은가? 꼭 써야될 경우도 있지 않을까?
  • Lombok을 쓴 도메인 클래스는 왜 Entity클래스가 아니고 DTO에 가까울까?
  • Entity라고 부를만한 도메인클래스의 조건은 무엇인가?

개인적으로, 토론이 너무 짧은시간 동안 진행되서 아쉬웠다.

뭔가 서로간의 피드백을 주고 받으면서 깊은 대화들이 오갔으면 하는 바램이었지만,

아쉬움을 뒤로 하고 차근차근 한걸음씩 내딛는 것도 의미있다고 생각했다.

범균님 블로그 자료 맨 마지막 장에 보면 리팩토링을 하고 싶어도 같이 할 선후배 동료가

없을 때, “흔쾌히 같이 해보자“라고 하는 반가운 광고가 실려있다.

생각있으신 분은 연락해서 한번 직접 리팩토링을 같이 해보는 것도 좋은 경험이 되리라

생각한다. 내가 이번에 리팩토링을 하면서 많이 느끼고 배운것처럼 말이다.

그렇게 해서 조직에 있는 한사람 한사람이 조금씩 변화하고 성장해서, 그 조직에

긍정적인 영향을 주고 함께 발전해 나가는 범균님의 바램이 꼭 현실로 이뤄졌으면

하는 바램이다! ^^

Advertisements

[프로세스/관리]개인프로젝트를 위한 개발환경 구축 – 1

전부터 개인적으로 만들어보고 싶은 프로그램들이 있었지만 차일피일 미루고 있던 것을

이제서야 “천리길도 한걸음부터..!“인것처럼 본격적으로 시작하기 전에

개발환경구축을 먼저 진행했다.

(1) IDE(Integrated Development Environment) 

통합개발환경은 STS(Spring Tool Suite) 3.2.0.RELEASE 버전을 사용했다.

스프링 프레임워크 기반의 웹 프로젝트로 진행할 것이기 때문에 프로젝트를 만들기 위해

[Spring Template Project => Spring MVC Project => Project name, 기본 패키지

세팅] 을 진행해 간단하게 웹기반의 MVC 프로젝트를 만들었다.

sample6 sample7 sample8

프로젝트가 생성되면 Maven이 자동으로 빌드도구로 선택된다.

난 이번에 Maven대신 Gradle을 사용할 것이기 때문에 .project 파일에 Maven관련

설정을 모두 제거했다. 그러면 프로젝트에서 Maven관련 아이콘이 사라진다.

sample12

(2) JDK/TOMCAT(Servlet/JSP container) 버전

사실 IDE선정에 앞서 JDK버전 명시를 해야 하는데 순서가 뒤바뀌었다.

JDK는 1.6(1.7을 공부하지 않아서인지 아직까진 딱히 필요성을 느끼지 못함),

Tomcat은 7.0.34를 사용했다. 그리고 STS에 Tomcat을 추가했다.

sample9

(3) 빌드도구 선정

작년에 오픈소스 프로젝트를 하면서, 이클립스를 사용할 수 없는 우분투OS를 사용하는

리눅스환경에서 빌드환경을 구축해놓지 않아 무척 고생한 기억이 있다.

같은 실수를 반복하지 않기 위해 이번엔 시작부터 빌드도구로 회사에서 사용하고 있는

Gradle 1.6버전을 사용했다.(압축파일을 풀고 시스템변수 Path에 Gradle bin경로를 입력함)

Gradle로 빌드뿐만 아니라 Test, 배포까지 모두 커버할 수 있다.

sample10

(4) build.gradle 기본설정하기

다음은 Gradle로 프로젝트를 빌드하기 위한 build.gradle파일에 기본설정을 추가했다.

우선, 단일 프로젝트로 프로젝트를 생성했기 때문에 멀티프로젝트 빌드의 경우는 제외하고

Gradle이 빌드 실행시 인식할 수 있게 프로젝트루트에 build.gradle 파일을 만들었다.

아래 목록은 프로젝트에 사용할 기술에 대한 의존성 설정에 대한 내용이다

  • Spring버전은 3.1.1.RELEASE
  • 프로젝트에서 데이터접근계층을 구현하기 위한 API로는 JPA(Java Persistence API)
  • JPA 구현체는 Hibernate
  • Spring Data를 사용해서 데이터접근계층의 구현에 대한 노력을 덜게끔 함
  • DataBase는 Mysql
  • DBCP(DataBase Connection Pooling)는 BoneCP 사용
  • 유닛테스트를 작성하기 위해 Junit과 Mockito 사용

사실, Gradle을 공부하는 중에 처음 빌드파일을 작성하면서 가장 고생한 것은

바로 “의존성(dependencies)“추가였다.

각 라이브러리가 의존해서 쓰고 있는 동일 라이브러리의 버전이 충돌하면서 계속

컴파일을 해가며 이 버전을 맞추는데 시간을 보냈다.

sample5

버전충돌을 모두 해결하고, 빌드파일 설정이 끝나면 eclipse 태스크를 실행시켜

이클립스에서 해당 프로젝트를 import 할 수 있게 한다.

sample4

(5) 간단한 유닛테스트 작성 : 트랜잭션 롤백 테스트

빌드파일 작성이 힘들긴 했지만, 예전에 WEB-INF/lib 폴더에 하나씩 jar파일을 넣어가며

세팅했을 때와 비교해서, 자동으로 의존성관리를 해주니 편하게 프로젝트 세팅을 마쳤다.

그럼 이제 세팅한 프로젝트가 잘 돌아가는지 간단하게 트랜잭션 롤백에 대해 간단한

유닛테스트를 작성했다.


package com.sameple.test.service;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.annotation.Transactional;

import com.sample.test.domain.Member;
import com.sample.test.repository.MemberRepository;
import com.sample.test.service.MemberService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:test-root-context.xml")
public class MemberServiceTest {
    @Autowired
    private MemberRepository memberRepository;

    private MemberService service = new MemberService();

    private Member member = new Member();

    @Before
    public void setup() {
        member.setNo(1L);
        member.setUserId("bluepoet12");
        member.setPassword("test1234");
        member.setNickName("파란시인");

        ReflectionTestUtils.setField(service, "memberRepository", memberRepository);
    }

    @Transactional
    @Rollback(false)
    @Test
    public void modifyMember() {
        service.modifyMember(member);
    }
}

소스는 간단하다. Member 도메인 클래스에 대한 CRUD중 transaction이 들어가는

수정에 대한 테스트케이스를 추가해서 실행했다.

iBatis같은 Sql Mapping 프레임워크를 사용하였다면 저렇게 간단한 Member수정도

별도의 sql문을 작성하고 데이터접근계층에 대한 구현도 복잡했겠지만,

JPA와 Spring Data를 사용하고 나선 아래 한줄로 Member 수정이 끝난다.

 service.modifyMember(member);

더군다나, 데이터접근계층 구현을 위해 별도의 클래스가 필요하지 않고 오직 필요한건

JpaRepository를 상속한 MemberRepository 인터페이스를 정의해주는건만으로 충분하다.

런타임시 해당 인터페이스를 구현한 객체를 Spring Data가 동적으로 만들어주기 때문이다.

그리고 테스트시 @Transactional 어노테이션을 붙이면 테스트 실행시 자동으로

트랜잭션 관리를 해준다.

다만, 실제코드와 차이는 트랜잭션이 끝나는 시점에 자동으로 롤백된다는 점이다.

강제로 DB에 반영시키기 위해선 위의 예제코드처럼 @Rollback(false)를 넣어주면 된다.

sample11

(5) 마치며..

앞서 얘기한대로, 빌드파일의 의존성관리 부분을 제외하곤 비교적 순조롭게 세팅이

진행되었다.

앞으로 개인프로젝트를 진행하면서 쓰게될 포스팅은 아래 내용이 들어갈 것이다.

  • 추가될 다양한 라이브러리들, 그리고 그에 맞춰 수정될 빌드파일(의존성관리)
  • 의존성관리외 빌드와 배포에 필요한 Gradle Task 정의, 필요시 멀티프로젝트 고려
  • 서비스 설계시 작성한 UML과 지속적인 리팩토링, 이를 뒷받침하는 테스트코드 작성
  • 단, 테스트코드 작성은 위험성이 크고 꼭 체크가 필요한 지점에 한함
  • 개인서버에 배포시 필요한 배포관련툴(http://gant.codehaus.org/)
  • CI연동, 코드품질관리/테스트커버리지/단위테스트결과 리포팅

이제 내가 만들고 싶은 프로그램에 대한 어려운 첫 발자국을 내딛게 되었다^^

[프로세스/관리]코드품질관리를 위한 젠킨스+Sonar 연동과 유닛테스트결과 리포팅

사내 CI툴로 젠킨스를 쓰고 있던 중, 혹시 이걸로 전체 유닛테스트 결과도 볼 수 있을까

하는 의문으로 시작해 이것저것 찾아보던 중, 코드품질관리를 해주는 오픈소스 프로젝트인

Sonar를 알게 되었다.

처음으로 CI툴을 만져보는지라 이때부터 삽질은 시작되었다.

(1) 젠킨스 버전 업그레이드

1.4 초반버전대로 동작하고 있던 기존 젠킨스에선 Sonar 플러그인이 정상작동하지 않아

젠킨스를 최신버전(1.518)으로 업데이트 하는 작업부터 진행하였다.

업그레이드는 간단하다. Jenkins 관리메뉴에서 새 버전의 jenkins.war를 다운받아

톰켓 Deploy 경로에 풀어주면  된다. 기존 내용을 그대로 유지한채 버전업된다.

(2) Sonar 관련 설정하기

버전업을 하니 젠킨스에 Sonar 플러그인이 자동으로 설치되었다.

다음으로 프로젝트 분석을 담당할 Sonar 서버를 세팅할 차례이다.

젠킨스의 관리메뉴에서 Sonar 부분을 설정한다.

jenkins13

(3) 젠킨스에 분석할 프로젝트 설정하기

이제 젠킨스에서 분석할 프로젝트를 만들고, 설정에서 Build Environment의

Pre build script에서 빌드를 실행할 스크립트를 적는다.

그리고 Sonar 분석을 위해 Build의 Add build step에서 Invoke Standalone Sonar Analysis

를 선택해서 Project properties에 설정을 추가한다.

jenkins12


################# Project properties #############################
# required metadata
sonar.projectKey=test
sonar.projectName=test-web
sonar.projectVersion=1.0

# path to source directories (required)
sonar.sources=src/main/java

# path to test source directories (optional)
sonar.tests=src/test/java

# path to project binaries (optional), for example directory of Java bytecode
sonar.binaries=build/classes

# optional comma-separated list of paths to libraries. Only path to JAR file and path to directory of classes are supported.
#sonar.libraries=path/to/library.jar,path/to/classes/dir

# The value of the property must be the key of the language.
sonar.language=java

# Additional parameters
#sonar.my.property=value

(4) 프로젝트 소스 옮기기

이젠 분석할 프로젝트의 소스파일(원래자바파일+테스트코드+클래스파일)을

Project properties에 설정한대로, 젠킨스에서 생성한 해당 job의 workspace 밑에

아래와 같은 구조로 해당 위치에 복사해서 넣어둔다.

난, 이 작업을 배포스크립트를 수정해서 처리했다.

workspace/src/main/java/원래자바파일들

workspace/src/test/java/테스트코드들

workspace/build/classes/클래스파일들

(5) 분석할 프로젝트의 빌드 실행

분석할 프로젝트의 빌드와 Sonar분석관련한 설정을 모두 마쳤으니 빌드를

실행하는 일만 남았다.

빌드를 실행하면 빌드가 끝난 후,  Sonar가 해당 프로젝트를 분석하여

데이터를 DB에 저장하고, 설정한 Sonar서버에 그 결과를 예쁘게

뿌려준다.

Rules compliance(RCI)를 통해 해당 프로젝트가 얼마나 규칙을 지키고

있는지 백분율로 표시해준다. 

그 밖에, 상세화면으로 들어가면 Violations의 Severity를 다섯단계로 나누어

어떤 부분을 고치면 되는지 자세히 어드바이스 해준다(Blocker/Critical/Major/Minor/Info)

jenkins14

(6) 테스트커버리지 대신 유닛테스트 결과 리포팅 

원래 목적인 전체적인 유닛 테스트 결과를 보려고 했던 것이 너무 멀리 와버렸다.

이제 다시 본래 목적으로 돌아가서 유닛테스트 결과를 어떻게 볼까 방법을 찾던 중,

Gradle에서 Sonar Runner Plugin이라는 훌륭한 놈을 발견했다. 아싸!

젠킨스에서 테스트커버리지를 못 본 아쉬움을 뒤로하고 이 놈을 재빠르게 적용해보기로

했다. 일단 Gradle 빌드 스크립트에서 Sonar Runner 관련 설정을 추가한다.


apply plugin: 'sonar-runner'

sonarRunner {
sonarProperties {
property "sonar.host.url", "http://localhost:9000/"
property "sonar.jdbc.url", "jdbc:mysql://localhost:3306/sonar?          useUnicode=true&characterEncoding=utf8"
property "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver"
property "sonar.jdbc.username", "test"
property "sonar.jdbc.password", "test"
property "sonar.sourceEncoding", "UTF-8"
}
 }

이제 task 실행명령만 내리면 된다.

gradle sonarRunner

빌드가 완료되면 해당프로젝트 밑에 reports\tests 밑에 각각의 유닛테스트 결과와

함께 index.html에 전체적인 유닛테스트결과가 나온다.

정말 간단한 설정만으로, 전체적인 유닛테스트 결과를 볼 수 있게 되었다.

jenkins15

(7)  마치며..

“전체적인 유닛테스트결과를 보고싶다”라는 욕구(!)에서 시작해서 처음으로

CI툴인 젠킨스를 만지는 것에서부터 시작했지만, 역시나 쉽지는 않은 작업이었다.

젠킨스 버전업부터 시작해서 프로젝트 소스를 옮기기 위한 배치스크립트 수정작업,

그리고 테스트커버리지가 나오지 않아 낙담하고 있다가 우연히 Sonar Runner plugin을

보고 적용한 후 실행결과를 보며 희열을 느낀 것 등등..

Sonar의 분석결과도 여러모로 앞으로 지속적인 리팩토링을 통한 코드개선으로

최종적으로는 설계도 개선되는 효과를 얻는데 큰 몫을 할 것이지만, 그보다 더 좋은 것은

역시나 전체적인 유닛테스트 결과를 한 눈에 일목요연하게 볼 수 있게 되었다는 점이다.

꾸준하게 나쁜 냄새가 나는 기존 코드를 리팩토링하면서 이를 뒷받침해주는 테스트코드의

신뢰성은 백번 천번 강조해도 지나치지 않다고 생각한다.

이제, 더욱 강력해진 툴들을 가지고 기쁜 마음으로 리팩토링을 하고 테스트코드를 만들면서

다음 스텝에는 “테스트커버리지 리포팅“이라는 끝판왕에 한번 도전해 볼 생각이다.

[Android]스타크래프트 프로리그 소스 공개!

개인적으로 미루고 미뤄왔던 안드로이드 습작용 어플 “스타크래프트 프로리그” 앱

소스를 공개한다.

기존에 있던 소스만 살짝 다듬으려고 했는데, 아뿔싸~!

e스포츠협회사이트가 리뉴얼을 했다.

그래서 하는 수 없이 마지막으로 Jsoup 라이브러리를 사용해서  HTML파싱노가다를

한번 더 하기로 했다.

===========================================================

“스타크래프트 프로리그” 앱에서 제공되는 핵심기능은 아래와 같다.

  • 해당 날짜의 프로리그 전체 결과와 상세결과(달력검색 제공)
  • 팀랭킹
  • 개인랭킹은 현재 데이터를 제공하지 않으므로 제외함
  • 좋아하는 선수 응원하기, 응원랭킹순위 보기
  • 해당날짜의 경기가 있으면 경기결과를 알림으로 알려주기

주요부분을 살펴보면,  데이터는

  • HTML 파싱데이터
  • 파일캐시데이터

두가지 형태로 제공되며,  파일캐시데이터가 없을 때만 HTML파싱데이터를

사용하게 된다.

데이터는 비동기로 가져오며, 기본적인 구조는 최범균님 포스팅을 참고하여 만들었다.

구조를 UML로 살펴보면,

async_structure

프로리그 결과, 팀랭킹 액티비티별로 각각 비동기로 데이터를 가져와 처리하는

부분을 추상클래스인 AsyncDataViewer를 상속받아 처리했다.

그리고, 파일캐시데이터는 데이터를 제공하는 DataProvider 인터페이스를

구현한 FileCacheDataProvider 클래스가 처리하고, 파일캐시데이터가 없으면

HTML을 파싱하여 데이터를 제공하는 HtmlDataProvider가 데이터를 제공하게 된다.

dataprovider_structure

AsyncDataViewer 추상클래스를 구현한 각 구현클래스들은 DataProvider의 구현체를

팩토리패턴을 사용한 DataProviderFactory 클래스를 통해 제공받게 된다.

public class DataProviderFactory {
    private static DataProvider dataProvider = new HtmlDataProvider();

    public static DataProvider create(Context context) {
        return new FileCacheDataProvider(context, dataProvider);
    }
}

전에 DataProvider 인터페이스 없이 HTML파싱으로 데이터를 제공하는 HtmlDataProvider

클래스만 있던 것을 한단계 추상화해서 DataProvider 인터페이스를 만들고 그 구현체를

팩토리 클래스를 통해 FileCacheDataProvider 객체를 만들고 HtmlDataProvider의 객체를

파라미터로 가져와 파일캐시데이터가 없을 시 HtmlDataProvider 객체를 통해 데이터를

제공하는 것이다.

즉, FileCacheDataProvider객체가 HtmlDataProvider 객체의 “데코레이터 역할“을

하는 셈이다.

그 밖에, 비동기로 제공받은 데이터는 컬백메서드를 통해 각 액티비티의 메서드를 호출하여

결과를 보여주게 된다.

listadapter_structure

그 밖에, HTML을 파싱해서 결과를 검증하는데 필요한 테스트코드를 따로 만들었다.

public class JsoupParserTest {
    @Test
    public void getDelimiterCurrentDate() {
        String currentDelimiterDate = CalendarUtils.getDelimiterCurrentDate();
	    assertEquals("13-06-09", currentDelimiterDate);
	}

    @Test
    public void removedDelimeterSearchDate() {
        assertEquals("130609", CalendarUtils.getDelimiterCurrentDate().replace("-", ""));
    }
    ....
}

프로그램 소소는 압축하여 여기에 업로드 하였다.

===========================================================

많은 기능이 있다거나 혹은 소스가 보기에 그리 훌륭하진 않지만 2011년 6월,

처음 안드로이드로 앱을 만들었을 당시의 소스와 점진적으로 리팩토링을 거쳐

지금까지의 최종소스를 만들고 나니 나름 감회가 새롭다.

그리고, 비록 재미로 시작했지만 이 “스타크래프트 프로리그”앱은 나에게

많은 좋은 기회를 주어 더욱 각별하다.

안드로이드를 공부하거나 새롭게 어플을 만드는 분에게 작은 도움이나마

됐으면 하는 바램이다.