정상혁정상혁

출판사 인사이트 블로그에 아래와 같은 퀴즈가 올라왔네요 ( 원문: http://www.insightbook.co.kr/post/3814 )

문제

배열 arr[]과 위치 s, t가 있을 때,arr[s], arr[s+1], … , arr[t-1]을 오른쪽으로 한 칸씩 이동하고,arr[t]는 arr[s]로 복사하는 것을 ’1만큼 오른쪽으로 회전시켰다’고 한다.

예를 들어 길이가 8인 배열에서 s=2, t=6이면 다음 그림처럼 바뀐다.

image

길이가 n인 배열의 위치는 0, 1, 2, … , n-1이다.

문제 :k를 인자로 받아서 k만큼 오른쪽으로 회전시키는 함수를 작성하라.단, 1만큼 오른쪽으로 이동시키는 과정을 k번 반복해서는 안 된다.

조건 1 : 작성하는 언어에는 제한이 없습니다. 조건 2 : 답안으로 작성하신 글 제목에는 ‘문제로 풀어보는 알고리즘 0.3 생각해보기 풀이’라는 문장이 들어가야 합니다. (저희 블로그에도 트랙백을 걸어주세요.)

(주의: 이 코딩 인터뷰는 인사이트 입사와는 무관합니다. ㅡㅁㅡ /)

풀이

밀린 일이 많아서 정신적인 여유가 없지만, 경품에 눈이 어두워서 급하게 풀어봤습니다

우선 테스트부터 먼저 작성하고

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;

public class ArrayHandlerTest {
    ArrayHandler handler = new ArrayHandler();

    @Test
    public void testShift1() {
        int[] array = {1,2,3,4,5};
        handler.shift(array,0,3,1);
        assertThat(array, is(new int[]{4,1,2,3,5}));
    }

    @Test
    public void testShift2() {
        int[] array = {1,2,3,4,5};
        handler.shift(array,1,3,1);
        assertThat(array, is(new int[]{1,4,2,3,5}));
    }

    @Test
    public void testShift3() {
        int[] array = {1,2,3,4,5};
        handler.shift(array,1,2,1);
        assertThat(array, is(new int[]{1,3,2,4,5}));
    }
....

그리고 이를 통과시키는 실행코드입니다.

import java.util.Arrays;public class ArrayHandler {
    public void shift(int[] array, int s, int t, int k) {
        if (s > t) return;
        if (t >= array.length) return;

        int[] rangeToShift = Arrays.copyOfRange(array, s, t + 1);
        int rangeLength = t-s + 1; for(int i=0; i< rangeLength; i++) {
            int offset = (i+k) % rangeLength;
            array[s + offset] = rangeToShift[i];
        }
    }
}

대용량 배열일 때 메모리를 더 적게 쓰는 방법들도 고민해볼만도 하지만, 우선은 쉽게 문제를 푸는데 집중했습니다. t,s, k 같은 한글자 변수명은 별로 좋아하지 않지만, 문제에 있는 변수명이라서 그대로 살렸습니다.

전체 소스는 Github에 올렸습니다.

참고로 java.util.Collections.roate()메소드를 쓰면 위 문제는 아래 두 줄로 풀 수 있기도 합니다.

[source,java

List<Integer> range = list.subList(s, t+1);
Collections.rotate(range, k);

그런데 이렇게 하는게 이 문제의 의도는 아닐듯해서 참고구현 정도로 남겨 두었습니다. 처음 풀이에서도 Arrays.copyOfRange 메소드를 쓰기는 했지만 이 부분은 특별한 알고리즘에 없는 단순한 배열복사라서 활용했습니다.

정상혁정상혁

https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%9C%A0%EB%9E%91%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EA%B3%A0-git-%EC%A0%81%EC%9D%91%EA%B8%B0버전 관리 시스템 유랑기, 그리고 Git 적응기

2011년 6월 9일11월 10일에 열린 세미나에서 두 번 발표했던 내용을 글로 정리했습니다.

지금까지 다양한 버전관리 시스템을 쓴 경험과 Git의 장점이라고 느낀 점에 대해서 발표했었습니다.

https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC%EB%A5%BC-%EA%B1%B0%EC%9D%98-%EC%95%88-%ED%95%98%EB%8D%98-%EC%8B%9C%EC%A0%88버전관리를 거의 안 하던 시절

https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B4%80%EB%A6%AC%EB%8A%94-%EB%A8%BC-%EB%82%98%EB%9D%BC%EC%9D%98-%EC%9D%B4%EC%95%BC%EA%B8%B0버전관리는 먼 나라의 이야기?

Git에 대해 이야기를 하기 전에 지금까지 어떻게 버전관리를 해왔는지 되돌아보겠습니다. 아마 비슷한 경험을 하시고 공감하실 분들이 많으실 듯합니다.

버전관리 시스템을 실무에서 쓰기 전에는 책에서 간단한 소개를 접한 정도였습니다. 김익환님이 지은 '대한민국에는 소프트웨어가 없다' 라는 책에는 아래와 같은 내용이 있습니다.

내가 아무리 소스코드관리 프로그램과 버그 관리 프로그램의 찬양자가 되어 조언을 해도 평생 사용하지 않을 회사들이 대부분일 것이다. 이건 진짜 좋은 건데. 안타깝다(153쪽)

이 책이 나온 때가 2003년 말이였는데, 그 당시에 국내에서 버전관리를 제대로 사용하는 개발 조직은 많지 않았나봅니다. 제 주변에서도 그랬습니다.

그때는 버전관리가 실무에서 필요하다기 보다 'CMMI' 같은 프로세스처럼 이론으로만 강조되는 분야라고 느꼈습니다. 아니면 엄격한 프로세스를 준수하고 그만큼 개발자에게 시간을 충분히 주는 소프트웨어 선진국에서나 가능한 기법이라고 생각했습니다.

김익환님이 미국에서 근무했던 'GTE’라는 조직에서는 개발자들마다 코딩하는 줄 수를 측정했더니 하루 평균 8줄이 나왔다는 내용이 있습니다.(171쪽). 그정도로 엄격한 리뷰와 관리를 거친다는데, 하루에 화면 4~5개씩 찍어내야하는 제 주변의 현실과는 동떨어진 이야기였습니다. 그래서 버전관리도 우리와는 다른 방식으로 개발을 하는 먼 나라의 일로, 우리와는 관계가 없는 절차로만 여겼습니다.

https://gist.github.com/benelog/2922437#%EA%B0%9C%EB%B0%9C%EC%84%9C%EB%B2%84%EC%97%90-%ED%8C%8C%EC%9D%BC%EC%9D%B4-%EC%9E%88%EB%8A%94%EB%8D%B0-%EC%99%9C-%EB%98%90-%ED%8C%8C%EC%9D%BC%EC%9D%84-%EB%94%B0%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%A0%EA%B9%8C개발서버에 파일이 있는데 왜 또 파일을 따로 관리할까?

신입 때 처음 투입되었던 프로젝트에서는 버전관리를 하기는 했지만, 공유폴더보다 더 불편하다는 느낌 뿐이였습니다.

그 프로젝트에서는 Local PC에 개발환경을 설치하지 않고, 개발서버에 FTP로 jsp 파일를 올려서 개발을 진행했었습니다. JSP로만 주로 개발을 하던 초기에는 흔한 방식이였습니다. 그래서 개발서버에 이미 최신 파일이 올라가 있는데, 따로 왜 버전관리 시스템이라는 것을 써야하는지 이해가 되지 않았습니다. 가끔 실수로 파일을 덮어쓸 때에는 백업 폴더에 가서 복사해서 복구했습니다.

그 조직에도 Merant Version Manager라는 버전관리 시스템이 설치되어 있기는 했습니다.그 도구를 개발자들이 필요해서 쓴다기보다는 본사에서 시키니까 쓰기는하는데, 뭔가 부가적인 작업을 한다고 느꼈습니다. 개발서버에 파일 올려서 테스트를 다 해본다음에, 버전관리 시스템에 로그인하고, PC에 있는 파일 찾아서 올리고, Local에서도 여러 군대의 파일을 같이 신경써줘야 했습니다. 귀찮고 그냥 빼먹고 싶은 작업이였습니다. 그때는 지금의 SVN처럼 Eclipse plugin이 있는 것도 아니어서 따로 클라언트 프로그램을 띄워야했기에 더욱 불편했었습니다. 그래서인지 두번 째 투입되었던 조직에서는 개발서버에만 파일을 올려놓고 아예 버전관리를 안 했었습니다.

https://gist.github.com/benelog/2922437#%EA%B3%B5%EC%9C%A0%ED%8F%B4%EB%8D%94%EC%97%90-%EC%98%AC%EB%A0%A4%EC%A3%BC%EC%8B%9C%EB%A9%B4-%EC%A0%80%EB%85%81-6%EC%8B%9C%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4-%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4공유폴더에 올려주시면 저녁 6시에 배포해 드립니다~

세번째 프로젝트에서는 요즘처럼 Local PC에 WAS를 띄우고 Eclipse를 써서 개발을 하기는 했지만, 윈도우즈의 공유 폴더로 소스 관리를 했었습니다. 그때 완성된 프로그램을 서버에 배포하는 절차는 다음과 같았습니다.

  • 개발이 끝난 파일은 공유폴더의 약속한 위치에 올린다.

  • 담당자 한명이 매일 그 폴더의 파일을 자기 PC로 복사하고, Eclipse에서 컴파일한다.

  • 컴파일 에러가 나는 것이 있으면 그 파일을 올렸을 것으로 추측되는 사람에게 이야기해서 고쳐달라고 한다.

  • 컴파일 에러 안 나면 Eclipse에서 컴파일된 class파일과 JSP등을 FTP로 개발서버에 올리고 서버를 재시작한다.

  • 서버 재시작이 끝나면 개발자들이 각자 자기가 만든 모듈을 확인해본다.

  • 개발서버에서 잘 안 되는 기능 중에 급한 것은 다시 고쳐서 공유폴더에 넣고, 미안하지만 한번더 올려달라고 담당자에게 부탁한다.

  • 안 급한 것은 그냥 내일 반영하기로 하고, 일단 Local PC에서만 고쳐둔다.

  • 파일서버의 backup 폴더 아래에 날짜별로 오늘 배포한 것은 복사해 둔다.

매일매일 위의 절차를 반복하는데 30분을 넘게 썼었습니다. 프로젝트 구성원들 모두 개선을 해보려는 생각은 있었지만 당장의 업무가 급하다보니 투자할 시간이 없었습니다. 돌이켜 보면 프로젝트 개발기간이 6개월이 넘었는데, 6개월동안 매일 30분이면 엄청난 시간이 들어가는 작업이였습니다.

이 프로젝트에서도 버전관리시스템인 Merant Version Manager를 본사의 담당자가 와서 설치해주고,고객와 개발자들을 다 모아서 따로 교육까지 해주었습니다.그러나 역시나 버전관리 시스템에 파일을 올리는 건 파일서버에 복사를 하는 것보다 번거로운 일이라고 생각했었습니다. 그래서 버전관리 서버에는 1차 프로젝트 종료 후에 몰아서 한번 파일을 올리기로 합의했고, 그 방식이 효율적이라고 모든 프로젝트 구성원들이 공감을 했습니다.

그나마 개발 영역별로 업무분담이 명확해서 파일 충돌은 잘 일어나지 않았습니다. 충돌보다는 작업한 내용을 실수로 날릴까봐 걱정하는 사람은 많았는데, 어떤 개발자는 매일 퇴근 전에 개인 PC에 날짜별로 복사해 놓기도 했었습니다. 다들 그 개발자를 보고 부지런하고 좋은 습관을 가지고 있다고 칭찬을 했었죠. 이 기간 동안에도 공유폴더에 만족을 하고 버전관리는 업무를 더 느리게 하는 작업이라고 인식했습니다.

https://gist.github.com/benelog/2922437#merant-version-manager%EC%9D%84-%EC%93%B0%EB%8D%98-%EC%8B%9C%EC%A0%88Merant Version Manager을 쓰던 시절

https://gist.github.com/benelog/2922437#%EB%B9%8C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-%EA%B7%B8%EB%A6%AC%EA%B3%A0-merant-version-manager%EB%A5%BC-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8B%A4빌드 자동화, 그리고 Merant Version Manager를 제대로 사용하기 시작하다.

그렇게 공유폴더를 쓰던 개발팀에서 고도화 성격의 2차프로젝트가 시작되었습니다.운영 중인 시스템에서 몇가지 업무가 추가되는 사업이였는데, 운영 중인 시스템에 당장 배포할 소스와 2차 프로젝트 오픈 때 배포할 소스를 따로 관리해야 했습니다. 유지보수성 요청 때문에 수정된 소스는 바로 운영 시스템에 배포를 하지만, 2차 사업에 추가요구사항으로 개발된 소스는 오픈 시점에 맞춰서 운영서버에 배포해야 했습니다. 그래서 유지보수와 신규개발 버전을 따로 브랜치를 분리할 필요성이 생겼습니다. 그리고 전에는 담당자별로 업무영역이 명확하게 나눠어져 있었는데, 새로운 프로젝트에서는 그런 경계가 명확하지 않은 부분이 많이 생겼습니다.그런 필요성 때문에 버전관리를 본격적으로 시작하게 되었습니다.

그 시점에서 빌드자동화 스크립트도 구성했습니다. 수동으로 해서 매일 30분이 걸리던 빌드절차를 개선하는 시도였었습니다. 파일서버에 따로 복사한 파일이 아닌, 버전관리에 올라온 최신 소스 파일을 읽어서 컴파일하고 서버에 FTP로 올리고, WAS를 재시작하는 스크립트를 ANT로 구성했습니다. 제가 한 3일정도 걸려서 그 작업을 했었는데, 처음 맨바닥에서 하는 일이라서 지금의 기준으로 보면 정말 많은 시간이 걸렸습니다. 지금은 Maven + Hudson으로 하면 몇십분 안에 가능한 작업이지만, 그래도 3일 투자해서 매일매일 30분의 작업을 없애서 정말 만족스러웠습니다.그때부터 다른 개발자들도 이제 공유폴더에 파일을 복사해 넣는 대신 매일매일 버전관리시스템인 Merant Version Manager에 파일을 올리기 시작했습니다.

https://gist.github.com/benelog/2922437#-%EC%9D%B4-%EB%8C%80%EB%B6%80%EB%B6%84%EC%9D%B8-commit-%EB%A1%9C%EA%B7%B8'.' 이 대부분인 commit 로그

이 단계부터는 단순하게 파일시스템으로 백업을 하는 시대는 지났지만, 버전관리를 깊이 있게 사용하지는 못했습니다.그때 대부분의 커밋로그를 보면 점하나 (.)만 찍은 것이 많았습니다. 아무런 내용을 안 쓰면 입력하라고 메시지가 나오니까 그 걸 피하려고 넣은 문자가 '.'점이였습니다. 시스템을 운영하고, 담당자가 바뀌고, 업무 규칙이 바뀌어갈수록 버전관리 시스템에 있는 커밋로그가 정말 소중한데, 그때는 그런 걸 알지 못했습니다. SI프로젝트이다 보니 빨리 개발 다 끝내고 도망갈 생각이 먼저이지, 운영할 사람에게 도움이 되는 정보를 남겨야한다는 생각은 잘 나지 않았습니다.한참 지난 다음의 이야기지만, 불행스럽게도 제가 그 프로젝트에서 마지막까지 남아서 유지보수를 했습니다. 물론 그 덕분에 중간에 나갔으면 몰랐을 것들을 많이 느끼긴했습니다.

https://gist.github.com/benelog/2922437#lock-%EA%B1%B8%EA%B3%A0-%ED%87%B4%EA%B7%BCLock 걸고 퇴근

제가 처음 버전관리 시스템으로 썼던 Merant Version Manager는 CVS처럼 배타적인 방식으로 파일의 소유권을 관리했었습니다. 즉, 고칠파일이 있으면 먼저 그 파일들을 'Checkout’해야 하는데, 그 상태에서는 다른 사람은 그 파일을 건드리지 못하게 됩니다. 수정이 끝난 후에 'Check in’을 하면 고친 파일이 올라가고, 다른 사람이 'Check out’을 받을 수 있는 상태로 변합니다. 그 제품의 최신 버전은 어떤지는 몰라도 당시에는 그렇게 썼었습니다.

몇번은 다른 개발자가 파일을 Checkout 해놓은 상태로 퇴근을 했는데, 급하게 파일을 고쳐야 할 때가 있었습니다. 전화해서 비밀번호를 물어보고 해결을 했는데, 나중에는 업무가 밀접하게 관련있는 사람들끼리는 비밀번호를 거의 공유하다시피 했었습니다. 그리고 Lock 걸린 파일을 수정해야 할 때도 급한 일이 아니라면 먼저 'Check out’한 사람이 다 고칠 때까지 기다렸었습니다.

그때는 그게 당연한 방식이였고, 그렇게 해야 질서가 있고 안전하다고 생각했습니다. 얼핏 SVN이 lock없이 파일을 고쳐도 된다고 듣기는 했지만, 그렇게 하면 소스가 엉켰을 때 해결이 되지 않을 위험한 방식이라고 추측했습니다. 당시에는 그 버전관리 방식에 역시나 만족을 했었습니다.

https://gist.github.com/benelog/2922437#svn%EC%9D%84-%EC%93%B0%EA%B8%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8B%A4SVN을 쓰기 시작하다

https://gist.github.com/benelog/2922437#svn-%EC%9E%85%EB%AC%B8SVN 입문

그러다가 직장이 바뀌어서 SVN을 쓰는 개발조직에서 일하게 되었습니다.

SVN을 특별히 공부한 적이 없었는데도 SubclipseSubversive같은 Eclipse plugin 덕분에 금방 적응을 했습니다. 그런 Plugin 덕분에 버전관리가 부가적인 작업이 아니고, 개발과정에 자연스럽게 녹아든다는 느낌이 들었습니다.배타적이지 않은 lock관리 방식과 'transactional’한 특성을 가진 commit 방식도 직접 써보니 더 편리한 개념이라는 것을 깨달았습니다. 가끔 Eclipse plugin의 버그나 뭔가 상태가 꼬였을 때 헤매기도 했지만, 얼마 지나지 않아 비슷한 상황에서의 문제해결에 익숙해지고, Plugin도 점점 안정화되어서 큰 불편없이 쓰게 되었습니다.

SVN을 쓰기 전까지는 이전의 버전관리 방식에 만족을 하고 있었는데, 막상 써보니 이전에는 그 불편함을 어떻게 견디었나하는 생각이 들었습니다.

https://gist.github.com/benelog/2922437#%EB%A8%B8%EC%A7%80%EB%8A%94-%EC%96%B8%EC%A0%9C%EB%82%98-%EB%91%90%EB%A0%A4%EC%9A%B4-%EC%9D%BC머지는 언제나 두려운 일

그러나 SVN에서는 브랜치 관리나 머지가 여전히 번거롭거나 두려운 일이였습니다.특히나 브랜치를 생성하고 오랫동안 유지하는 일은 정말 손이 많이 가는 일이였습니다.장기 프로젝트를 했을 때는 브랜치가 분기된 이후로 트렁크(trunk)에 반영된 사안들을 다시 브랜치로 옮기는 작업을 주기적으로 했는데, 자주 안하면 나중에 최종 머지가 두려워지고, 그렇다고 자주하기에는 시간이 많이 걸리는 작업이였습니다.

브랜치 관리와 머지를 잘하기 위해서 여러 개발팀에서 다양한 방법을 쓰고 있는 것을 보았습니다. 어떤 프로젝트에서는 일주일에 한번씩 트렁크 쪽의 이력을 가장 많이 알고 있는 사람과 브랜치를 가장 많이 수정한 사람이 같이 앉아서 작업을 했는데, 오래 걸리면 한번에 30~40분이 넘게 걸리기도 했습니다.팀에서 그런 머지작업을 많이 한 사람이 '머지 전문가’로 불리기도 했습니다. 당시 썼던 SVN버전에서는 머지한 후에 그동안의 이력이 남지 않기 때문에, 머지를 수정한 커밋 로그에는 "from revision 10244 to 10532" 와 같은 형식으로, 어디서부터 어디까지를 합쳤는지 로그를 적었습니다. 머지에 쓰는 도구도 사람마다 팀마다 다양했습니다. SubclipseSubversive의 메뉴를 활용하는 팀이 있었는데, 각자 개인마다 나름대로의 노하우가 있는 듯했습니다.어떤 팀에서는 그런 도구를 이용하면 오히려 실수가 많다면서 2개의 브랜치를 모두 Eclipse에서 띄운 후에 Diff로 하나하나 비교해가면서 파일도 하나하나 복사해서 가는 방식을 썼었습니다.시간이 상당히 많이걸리는 방식이였지만, 그 팀에서는 머지는 그렇게 하는게 최고라면서 상당히 만족하고 있었습니다.

오랜 노하우가 쌓인다고 해도 어떤 방식이든 여전히 실수할 여지는 많았습니다. 머지되어야할 이력이 빠져서 오류가 발생했던 경험도 있습니다.

그렇게 브랜치를 관리하는 부담이 크다보니, 규모가 큰 변경이 아니라면 왠만해서는 브랜치를 안 따고 Trunk만 사용하고 싶었습니다.그런데 개발서버에라도 언제 배포될지도 모르는 Trunk에 마음대로 커밋을 하는 것은 부담이 되는 일이였습니다. 부분적으로 잘 안 돌아가는 소스를 커밋할 수는 없으니,어느 정도 기능별로 완결된 것만 커밋을 하게 되었고, 그러다보면 커밋을 자주하지 않게 되기도 했습니다.

박재성님이 쓰신 Java 프로젝트 필수유틸리티라는 책에서도 아래와 같이 SVN merge의 어려움에 대해서 나와있습니다.

SVN에서 소스 코드 머지가 어려워 브랜치를 사용하지 않고 신규 메소드를 추가하는 방식으로 변경할 수 밖에 없는 상황도발생한다. 그렇지 않아도 VCS의 사용에 반대하는 개발자들이 있는 상황에서 머지의 어려움은 개발자들을 설득하기 어렵게 한다.(307쪽)

위의 문단을 포함해서 이 책의 306~307쪽에는 머지를 한후 커밋 로그에 리비전 번호를 기록하는 팁이라던지, SVN에서 merge를 하다가 문제를 겪으신 분의 이야기가 나와 있습니다.( 안영회님의 블로그 글에 달린 강수형님의 댓글 )

SVN의 최신버전에서는 머지를 하면 SVN에서 알아서 따로 추가적인 정보를 남기도록 개선이 되었다고는 합니다.

https://gist.github.com/benelog/2922437#git%EB%A5%BC-%EB%A7%9B%EB%B3%B4%EB%8B%A4Git를 맛보다

https://gist.github.com/benelog/2922437#dvcs%EC%97%90-%EB%8C%80%ED%95%9C-%EC%86%8C%EB%AC%B8%EB%A7%8C-%EB%93%A3%EB%8B%A4%EA%B0%80DVCS에 대한 소문만 듣다가..

그럭저럭 SVN에 만족했지만 브랜치 관리에는 다소 아쉬움이 있었던 차에, 여러 오픈소스 프로젝트들이 Git이나 Mercurial같은 분산버전관리시스템(DVCS)로 옮기어 간다는 이야기를 들었습니다.인터넷에도 분산버전관리 시스템에 대한 글들이 많이 올라왔는데, 그 중에서 조엘 스폴스키가 쓴 Mercurial 튜터리얼이 인상 깊었습니다.

그래도 직접 써본적이 없으니, 어떤 점이 좋을지 막연하게 상상한 하는 정도였습니다.그러던 중 팀내부에서 진행하는 프로젝트에서 Git으로 버전관리를 하게 되면서, Git의 좋은 점들을 직접 체험하게 되었습니다.

https://gist.github.com/benelog/2922437#%EC%B2%B4%EA%B0%90%ED%95%9C-%EC%9E%A5%EC%A0%90체감한 장점

아래에 정리한 Git의 장점들은 다른 누군가가 그렇다더라.. 해서 적은 것이 아니고, 실제로 프로젝트를 하면서 솔직하게 느껴졌던 것들입니다.오랫동안 쓰다보면 이것보다 더 많은 것을 느끼겠지만, 처음 써보는 프로젝트에서는 이정도가 개발하는데 도움이 된다고 생각되었습니다.

https://gist.github.com/benelog/2922437#ctrlz-%ED%8C%8C%EC%9D%BC-%EB%B3%B5%EC%82%AC%EB%B3%B4%EB%8B%A4-%EC%95%88%EC%8B%AC%EC%9D%B4-%EB%90%98%EB%8A%94-local-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%ACCtrl+Z, 파일 복사보다 안심이 되는 Local 버전 관리

개발자의 Local PC에서도 버전관리를 할 수 있다는 점은 DVCS의 대표적인 장점으로 꼽히고 있습니다.그런데 과연 그것이 꼭 필요한지, 오히려 버전관리 과정을 더 복잡하게 하지 않는지 우려하시는 분이 많고, 저도 그랬었습니다.

실제로 프로젝트를 해보니 지금까지 'Ctrl + Z’나 주석문으로 가리기, 파일복사로 해왔던 작업 중에 어떤 부분은 Local에서의 버전관리로 해결할 부분이 아니었나하는 생각이 듭니다.코드를 작성하다보면 성공의 확신이 없는 시도를 해볼 때도 있고, 시도한 것이 여의치 않았을 때 다시 돌아올 수 있는 지점을 기록하고 싶어지기도 합니다.물론 간단한 1~2줄의 수정 같은 정도는 Ctrl+Z, 주석문 등을 이용하는 것이 더 편리할 수도 있습니다. 그러나 파일의 여러 부분이 동시에 바뀌어야 하는데 나중에 되돌릴 가능성이 있다던지, 두가지 방식의 구현을 시험삼아서 같이 진행할 때는 Local에서 중간중간 commit을 하거나 브랜치를 생성하는 편이 훨씬 편리했습니다.

SVN에서는 따로 브랜치를 따고, 나중에 머지하는 일이 번거롭고 두렵기 때문에 통합할 가능성이 확실하지 않은 수정은 Local PC에서 원래의 파일을 복사본을 만들어서 수정하기도 했었습니다. 그러나 Git에서는 Local branch 생성과 이동이 빠르고 결과를 머지하는 것도 더 안정감이 있다는 느낌이 들기 때문에, Ctrl + Z,주석문 처리(//), 파일 복사로 때우던 소규모의 변경 지점 관리가 더 편해집니다.

Git을 썼던 프로젝트에서 XML파싱을 하는 모듈을 개발했었는데, JDOM과 DOM4J중에서 어떤 것이 해당 용도에 적합할지 확신이 없는 상태에서 2개의 Local branch를 만들어서 작업을 했었습니다.처음에 JDOM으로 갔다가, 마음에 안 드는 부분이 있어서 DOM4J로 구현했다가 다시 JDOM으로 돌아왔는데, 이렇게 결정을 번복하는 과정에서 Local의 버전관리가 없었다면 훨씬 번거롭고 다른 작업자들에게도 방해가 되었을 듯합니다.

https://gist.github.com/benelog/2922437#%EB%B2%84%EC%A0%84%EA%B8%B0%EB%A1%9D%EA%B3%BC-%ED%86%B5%ED%95%A9%EC%9D%98-%EA%B0%9C%EB%85%90-%EB%B6%84%EB%A6%AC'버전기록’과 '통합’의 개념 분리

앞에서 말했듯이, Local에서 버전관리가 가능하기 때문에 '버전기록’과 '통합’이 다른 개념이 됩니다.SVN에서 '커밋’은 '버전기록’이자 '소스 통합’을 의미합니다.. 즉, 작업한 내용을 기록하는 일과 다른 사람이 일한 소스와 합치는 일이 같은 일입니다.저도 지금까지 그것이 당연하다고 생각해왔기 때문에 두가지를 같이 취급하는 것이 전혀 어색하지 않았습니다.

그런데 엄밀히 따지면 2가지는 구분이 됩니다. 버전기록은 중간중간 의미 있는 단위의 작업이 끝날 때마다 할만한 일입니다. 그에 반해서 통합은 내가 작업한 소스를 다른 작업자들이 받아가도 괜찮은 시점에 하는 것이 바람직합니다. 예를 들어 컴파일도 되지 않는 소스를 커밋을 하고 그 에러를 고치는 커밋을 하기 전에 다른 동료가 소스를 받아간다면, 동료는 원래 하는 일에 방해를 받게 됩니다.SVN에서는 '커밋=통합’이라서 개인이 작업한 것을 전체 소스에 통합을 해도 괜찮은 시점에만 커밋을 해야 그런 부작용이 없습니다.만약 작업하는 브랜치가 언제라도 배포될 수 있는 브랜치라면 극단적으로는 개발작업에 완료된 시점에만 커밋을 해야 안전합니다. 이렇게 '커밋=통합’일 때는 버전관리의 모든 욕구를 다 충족시키지 못할 수도 있습니다. 오류가 있거나, 아직 확정되지 않은 인터페이스를 포함하고 있어서 다른 사람에게 전파되지 않았으면 하는 프로그램의 상태라도 버전기록은 하고 싶은 상황도 생길 수 있습니다.많은 경우, 통합보다 버전기록이 더 자주 필요한 일입니다.그리고 통합은 버전기록보다 더 신중하고 노력이 많이 들어가는 일입니다.예를 들면 안전한 통합을 위해서는 svn update → mvn test → commit의 절차를 거쳐야하는데, 버전기록이 필요할 때마다 위와 같이 안전한 통합을 위한 절차를 할지말지 고민해야 한다면 버전기록은 훨씬 무거운 일이됩니다.

물론 SVN으로도 별도의 브랜치를 딴다면 다른 작업자에게 영향없이 독립적으로 버전기록을 할 수 있지만, 굉장히 번거롭습니다.Git에서는 버전기록이 필요한 시점에서는 언제든지 commit을 하고, 통합할만한 상태가 되었을 때만 push를 하면 됩니다.

어떤 분은 통합 없는 버전기록을 할 수 있다면 통합을 자주 하지 않게 된다는 우려를 하시기도 합니다.그런데 SVN으로 작업할 때도 통합 가능한 상태를 염두에 두지 않고 버전기록이 필요할 때마다 자주 commit을 하다보면 CI서버의 빌드가 자주 깨지게 됩니다.빌드 실패는 빠른 피드백으로서 의미가 있지만, 너무 자주 실패하면 잡음이 되어서 다른 사람에게 방해가 되거나, 그 신호를 무시하게 됩니다.그런 부작용을 우려해서 오히려 commit을 자주 하지 않게 되는 경우를 보기도 했었습니다. 그래서 어떤 프로젝트에서는 '적어도 하루에 한번씩은 commit을 하자’라고 규칙을 정한 곳도 있었습니다.

버전기록과 통합을 강제로 묶어서 통합을 자주 하기를 바라기보다는 두 가지 개념을 구분하고 어떻게 활용할지는 상황에 따라서 선택하는 편이 좋다고 생각합니다.버전기록과 통합이 강제로 묶여있다면 개발자들이 오히려 둘 다 자주 하지 않게 될 수도 있습니다.통합을 자주하고 싶다면 필수적인 통합주기를 개발팀에서 약속을 하는 편이 더 실용적입니다. 기술 도메인의 특성이나 작업을 어떻게 분담하느냐에 따라서 통합과 버전관리의 주기는 다양할 것입니다.보편적인 버전관리 도구라면 상황에 따라서 다양한 정책을 적용할 수 있도록 개념이 섬세하고 유연한 편이 더 좋다고 생각합니다.

https://gist.github.com/benelog/2922437#%EB%B9%A0%EB%A5%B4%EA%B3%A0-%ED%8E%B8%ED%95%9C-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%9D%B4%EB%8F%99빠르고 편한 브랜치 이동

어느 정도 규모가 있는 프로젝트에 참여하다보면 여러 브랜치를 왔다갔다 하면서 작업할 일이 많습니다. SVN에서도 Local에 받은 소스 폴더를 다른 브랜치로 이동을 할 수 있고, Eclipse plugin으로 편하게 지원됩니다.

Switch to branchhttps://camo.githubusercontent.com/14111eced471ead7d87404b86e3d337f404dce42/687474703a2f2f646c2e64726f70626f782e636f6d2f752f31333936303330302f6769742f73766e5f7377746963685f6272616e63682e706e67[SVN switch branch]

그런데, 저는 프로젝트를 하면서 저 기능을 거의 쓴 적이 없습니다. 네트워크로 다른 브랜치의 파일을 받아오기 때문에 이동하는 속도가 굉장히 느리기 때문입니다.그리고 원래 있던 브랜치에서 커밋을 아직 안 한 파일이 남아 있어도 이동이 안 됩니다.

그런 불편함 때문에 보통 여러 branch를 Eclipse에서 별도의 프로젝트로 받아서 한꺼번에 열어두는 일이 많습니다.

SVN branches

그러다보면 파일을 수정할때 어느 브랜치의 파일을 수정하는 건지 헷갈릴 때가 많습니다. 보통 저는 Ctrl + Shift + R로 파일을 찾는데, 여러 브랜치를 동시에 열고 있을 때는 같은 파일이름이 동시에 떠서 불편합니다.

Open Resources

이렇게 여러 브랜치가 열려있는 상황에서 의도하지 않은 브랜치의 파일을 고치는 실수는 주변에서 굉장히 흔하게 보였습니다.

Git에서는 이런 브랜치 전환이 굉장히 빠릅니다. 네트워크를 타지 않고 Local에서 branch를 전환을 하니 당연한 일입니다.Git을 처음 써본 프로젝트에서도 몇번 브랜치를 전환하면서 작업을 했었는데, 하나의 프로젝트만 열고만 있으면 되니 훨씬 편리하고 빨랐습니다.

https://gist.github.com/benelog/2922437#%EB%8D%94-%EC%A0%95%EA%B5%90%ED%95%9C-%EC%BB%A4%EB%B0%8B-%EB%A1%9C%EA%B7%B8더 정교한 커밋 로그

SVN에서는 브랜치에 있던 파일을 트렁크에 머지하면 그동안 브랜치에 커밋한 이력은 옮겨지지 않았습니다. 트렁크와 브랜치 사용 정책을 정하느냐에 따라서 다르지만, 어떤 프로젝트에서는 트렁크에는 아래와 같이 머지를 했다는 기록 밖에 없고, 머지를 담당한 사람의 이름밖에는 남아 있지 않았었습니다.

SVN history

Git에서는 원한다면 모든 이력을 남길 수 있습니다. Eclipse plugin으로 보니 머지한 이력을 그래프로 이쁘게 보여줬습니다.

Git history

Git에서는 커밋 로그를 정교하게 수정할 수도 있습니다. 몇개의 커밋을 하나로 합칠 수도 있고, 앞에 커밋한 이력을 수정할 수도 있습니다.파일을 하나 빠뜨리고 커밋을 해서 불필요하게 커밋이 나누어지거나, 설명을 대충 적은 것 같아서 아쉬울 때가 많았는데 Git에서는 commit을 한 이후에도 그걸 보완하는 작업이 가능합니다. 어쩌면 한 번에 완벽한 코드를 짜는 것이 불가능한 것처럼, 한번에 깔끔한 커밋 이력을 남기고 논리적으로 균일한 커밋 단위를 관리하기는 어렵습니다.오래 가는 코드를 위해서는 커밋 로그는 깔끔하고 친절하게 정리되어야 합니다. Git을 쓰면 commit 로그도 리팩토링의 대상이 되어서 더 정교한 commit 관리를 원하는 사람에게 도움이 됩니다.

https://gist.github.com/benelog/2922437#%EA%B7%B8%EB%A6%AC%EA%B3%A0-gitflow-gerrit그리고 Gitflow, Gerrit

지금까지는 직접 경험한 장점을 이야기했는데, GitflowGerrit은 직접 써본 도구는 아닙니다. 그래도 Git의 매력중의 중요한 부분이라고 생각되어서 간단하게 언급하고 넘어갑니다.

Gitflow는 Best Practice라고 할만한 버전관리 절차를 Git으로 편하게 쓸 수 있게 도와주는 도구입니다.

Gitflow model

Git이 워낙 기능이 많기 때문에 어떤 버전관리 정책을 써야할지 처음에는 막막하게 느껴질 수도 있을 것 같습니다. GitFlow는 'Feature' ,'Hotfix' ,'Release' 등과 같이 전형적인 역할의 브랜치들을 기본적으로 녹여내고 있습니다. 즉 좀 더 정형화되고 추상화된 개념들을 더 짧은 명령어로 쓸 수 있는 것입니다. 'git flow hotfix start’와 같이 명령어를 치면, hotfix를 위한 branch 생성을 해주는 식입니다.

Gerrit은 Git바탕의 코드 리뷰도구입니다.

어떤 블로거는 Getrrit을 소개하면서 'Someday, all software will be built this way.'라고 주장하기도 했습니다.

아래에 Gerrit과 Jenkins를 연동한 인상적인 Demo도 있습니다.

9분 40초부터 1분동안의 데모가 핵심적인 장면입니다. 코드를 수정하고 commit, push를 하고 Gerrit에 들어가보면 CI서버지인 Jenkins에서는 'verified’되었따는 표시가 나옵니다.그리고 그 코드를 리뷰해서 'Great’라는 메시지를 달아주고 +2점으로 점수를 부여해주는 장면이 나옵니다.

https://gist.github.com/benelog/2922437#%EB%85%BC%EC%9F%81%EA%B1%B0%EB%A6%AC%EB%A5%BC논쟁거리를

앞서서 Git을 쓰면 통합을 자주 안 하게 된다는 주장에 대해서 말씀드렸지만, 그외에도 Git에 대한 이런저런 불평들은 많이 있습니다.

https://gist.github.com/benelog/2922437#%EC%96%B4%EB%A0%A4%EC%9B%8C%EC%9A%94-%EB%B3%B5%EC%9E%A1%ED%95%B4%EC%9A%94어려워요~ 복잡해요~

우선 기능이 많고 어렵다는 것입니다. 이 부분은 저도 어느 정도 동감을 합니다.저도 프로젝트를 끝낸 후에 오랜 만에 다시 Git를 쓰려니 명령어가 잘 기억이 나지 않아서 헤메었던 기억이 있습니다.그리고 분명히 SVN보다 더 많은 개념과 기능을 제공하니 제대로 쓰려면 배워야할 것이 많기도 합니다. 중간에 많은 시행착오를 거치거나 실수로 소스를 날려먹은 사람도 몇번 봤습니다.

그렇지만 처음부터 그 많은 기능을 모두 알 필요는 없다고 생각합니다.간단히 SVN으로 하던 주요 사용법만 Git으로 배우는 것은 얼마 걸리지 않습니다.그리고 차근차근 기능을 익혀나가면 Git으로 더 편리하게 할 수 있는 일들이 늘어가고 앞에서 언급한 Git의 장점로 개발이 더 편해질 것이라 생각합니다.

Git의 많은 기능들은 Git 자체의 오버엔지니어링이라기보다는, 버전관리 업무 자체의 복잡함을 보여준다고 생각합니다.수많은 작업자가 같이 작업하고, 여러 배포 버전을 동시에 살려나가야 하는 대규모 소프트웨어는 버전관리 자체가 워낙 어려운 과제입니다.그런 문제 상황을 정교하게 분류하다보니, 점점 새로운 개념들이 생겨나서 처음 접하는 사람에게는 논리적으로 어렵다고 느껴질 듯합니다.그런데 그런 개념을 어떻게 응용할지 고민하면서 쓰다보면, 지금까지의 버전관리가 충분하지 않았음을 느끼게 됩니다.저도 파일 공유 서버로 버전관리가 충분했다고 생각하는 시절에는 SVN에서 제공하는 transactional한 commit 같은 기능이 필요하다고 생각하지 못했었습니다.마찬가지로 DVCS가 없이도 큰 아쉬움은 없었는데, Git등을 써보고 나니 이전에는 SVN에서 그냥 넘어갔던 부분이 더 불편하게 느껴졌습니다.

https://gist.github.com/benelog/2922437#eclipse-plugin%EC%9D%B4-%EB%B6%88%ED%8E%B8%ED%95%B4%EC%9A%94Eclipse Plugin이 불편해요

또 하나는 Eclipse plugin이 아직도 불안정하고 불편하다는 점입니다. 저는 Eclipse plugin은 history를 보는 용도로만 쓰고 모든 작업은 명령행으로 해서 특별히 불편을 겪은 적은 없습니다.다른 써보신 분들에 의견에 따르면 Eclipse plugin만으로는 Git의 모든 기능을 원활히 쓸 수 없다고 합니다.그래서 Eclipse plugin은 덤정도로 여기면서 큰 기대를 안 하고, 명령행 방식을 주로 쓴다고 생각하면 오히려 시행착오가 적을 것 같기도 합니다.명령어를 외우는 것이 처음에는 부담될지 몰라도, 나중에는 메뉴를 찾아해메는 것보다 작업속도가 더 빨리질수도 있습니다. 그리고 정보공유나 인수인계에서는 GUI보다 명령행이 더 유리하기도 하고, 더 재미있어할 개발자들도 많을 것입니다.

https://gist.github.com/benelog/2922437#%EB%A7%88%EC%B9%98%EB%A9%B0마치며

버전관리는 새로운 시도를 하기에는 워낙 두려운 분야이기는 합니다. 현재 방식의 버전관리로 충분히 만족하는 조직이 많고, 조직의 관습이나 변화에 필요한 비용등을 생각하면 그것이 어떤 조직에서는 정답일수도 있습니다.저도 돌이켜보면 버전관리의 필요성을 못 느끼던 시절부터 시작해서, Merant Version manager, SVN, Git을 거쳤고, 그 사이에 생각도 많이 바뀌었습니다.원래 하던 방식이 마음이 편했기에 제가 처한 환경의 특수성을 과대평가해서 새로운 방식이 필요하지 않다고 생각했던 시절이 많았었습니다.

그래도 발전한 도구들 덕분에 개발은 훨씬 편해진 것 같기도 하지만, 더 어려워지기도 했습니다. 단순히 소프트웨어 개발만 할 줄 알던 시절에서 빌드도구, 버전관리 도구에 대해서도 전에보다 더 많은 지식을 알아야 코드 한 줄을 쓸 수 있게 되었습니다.어떤 분들은 이런 흐름이 필요이상의 복잡함을 더했다고 느끼겠고, 새로 업계에 들어오는 개발자들에게는 점점 숙제거리가 늘어만 가는듯합니다.

저는 이런 변화는 발전이고, 여러 조직에서 비슷한 고민들이 있어서 공유하다가 약간씩 더 나은 해결방식이 나오고 있다고 봅니다. 저는 Git을 쓰는 프로젝트를 처음 해 보면서 다른 개발자들이 어떤 고민을 했을지 좀 더 많이 이해했고, 그런 고민들을 저는 지나쳤음을 깨달았습니다.그 점이 Git으로 얻은 가장 큰 선물이였습니다.

정상혁정상혁

스레드 안전성을 어떻게 문서에 표기할까?

Javadoc에서는 메소드 선언에 synchronized가 있는지 표시하지 않습니다. 그 이유는 동기화 여부는 세부 구현 방식이기 때문에 문서에까지 그것을 노출하지 하는 것은 적절하지 않기 때문이라고 합니다.

선언부에 synchronized가 있는 메소드도 메소드 안의 모든 블럭이 synchronized(this)와 감싸진 코드와 의미입니다. 즉 아래의 코드는

synchronized void run() {
//do something
}

다음과 똑같은 역할을 하는 코드입니다.

void run(){
  synchronized(this) {
  // do something
  }
}

구현방식을 개선하면서 동기화 구간을 메소드의 일부 구간으로 좁힐 수도 있고, this 객체 말고 다른 객체로 lock을 바꿀 수도 있습니다. 이런 세부구현 상황은 외부 인터페이스보다 더 쉽게 바뀔 여지가 있습니다. 그리고 lock으로 보호되는 모든 구간을 javadoc의 메소드 선언부에 표시하기도 어렵고, this로 전체 메소드를 lock으로 잡는 메소드인 synchronized 메소드 여부를 표시하는 것만으로 충분히 문서화가 되었다고 생각하기도 어렵습니다.내부적으로 더 효율적인 방식으로 동기화가 되어 있을 수도 있으니,Javadoc에서 synchronized가 있느냐 없느냐가 스레드 안정성을 판단할 수 있는 일관적인 기준도 되지 못합니다.

따라서 현재 javadoc의 정책은 적절하다고 생각됩니다. Effective Java 2nd Edition의 Item 70에서도 그렇게 언급되었습니다.

결국 현재 javadoc의 규약으로는 어떤 메소드가 멀티스레드에서의 안전한지 여부를 표시해야 하는 강제성은 없다는 이야기입니다.

Effective Java 2nd Edition에서는 스레드 안정성을 다음 4자리 분류로 구분해서 문서화하라고 권장합니다.

  • Immutable

    • 상태가 없습니다. 당연히 Thread-safe합니다.

    • 예) String, Long, Integer, BigDecimal

  • 무조건 Thread-safe

    • 상태가 있으나 내부적으로 알아서 동기화되어 있습니다.

    • 예) ConcurrentHashMap

  • 조건부 Thread-safe

    • 어떤 메소드는 외부에서 sync가 필요한 클래스입니다.

    • 예) Collections.synchronizedList로 받은 List의 iterator는 ConcurrentModificationException을 발생시킬수 있습니다.

  • Not Thread-safe

    • 외부에서 synchronized를 해야 함

    • 예) HashMap, ArrayList

  • Thread-hostile

    • 외부에서 synchronized를 해도 멀티쓰레드에서는 못 쓰는 클래스입니다. 다행히 Java에는 거의 없습니다.

    • 예) System.runFinalizersOnExit

저렇게 잘 구분해서 Javadoc 문서의 제일 앞에 설명을 해준다면 좋겠지만, 강제적인 표준이 없다보니 기존의 문서들은 클래스 설명의 중간에서 스레드 안정성을 설명합니다.

몇가지 클래스의 사례

그렇다면 현재 Java 클래스가 스레드 안정성을 문서화하고 있는지 몇가지 예를 살펴보겠습니다.

java.util.LinkedList

LinkedList의 javadoc에서는 클래스 설명의 중간 쯤에 볼드체로 강조해서 이 구현은 'not synchronized’되었다고 적혀 있습니다.

LinkedList

java.text.SimpleDateFormat

SimpleDateFormat의 javadoc에서는클래스 설명의 마지막 즈음에 synchronization이라는 제목의 절에서 'not synchronized’라고 설명합니다.

SimpleDateFormat

org.springframework.batch.item.file.FlatFileItemWriter

스프링배치의 클래스인 FlatFileItemWriter는 not의 앞 뒤로 별표를 붙여서 'not thread-safe’라고 적어놨습니다.

FlatFileItemWriter

이렇게 클래스가 스레드 안정성을 표시하는 방법은 제 각각이고, 나름대로는 강조하고 있지만 주의깊게 API 문서를 주의 깊게 본 사람이 아니라면 지나치기 쉽습니다. 차라리 '스레드 안정성은 클래스별 설명 제일 윗줄에 넣도록 하고, 반드시 빨간색 Bold로 표시한다' 같은 규칙이 있었으면 얼마나 좋을까하는 생각까지도 듭니다.

JCIP annotation

JCIP annotation은 'Java concurrent in practice' 책에서 제안된 스레드 안정성을 표시하는 기법입니다.아래 4개의 종류의 애노테이션을 제공합니다.

  • @GuardedBy : 해당 객체가 어떤 Lock으로 보호되고 있는지 표시. 필드에 메소드에 사용 가능

  • @Immutable : 불변객체

  • @NotThreadSafe : 스레드 안전하지 않음

  • @ThreadSafe : 스레드 안전함

Apache Httpclient에서 활용 사례

실제 이 JCIP 애노테이션이 활용된 사례는 Apache Httpclient가 있습니다. 이 라이브러리의 클래스인 HttpGet, DefaultHttpClient, SingleClientConnManager는 아래와 같이 @NotThreadSafe, @ThreadSafe를 클래스 선언에 붙었습니다.

@NotThreadSafe
public class HttpGet extends HttpRequestBase {

@ThreadSafe
public class DefaultHttpClient extends AbstractHttpClient {

@ThreadSafe
public class SingleClientConnManager implements ClientConnectionManager {

이 애노테이션들은 원래의 JCIP 애노테이션의 패키지인 net.jcip.annotations 대신에 org.apache.http.annotation 패키지에 들어가 있기는 합니다.그런데 이 패키지에 있는 @ThreadSafe등의 javadoc를 봐도이 애노테이션들은 JCIP책에서 유례하였다고 설명합니다.

이 애노테이션이 붙어 있는 클래스의 avadoc에서는 클래스 설명 단락의 위 쪽에 이 애노테이션을 보여줍니다.

DefaultHttpClient

일관성 있는 위치에 표시되기 때문에 한 눈에 스레드 안정성 여부를 인식할 수 있습니다.

FindBugs에서의 지원

오픈소스 정적분석 도구인 Findbugs에서는 JCIP 애노테이션 중 @Immutable을 인식합니다. 버전 2.0부터 JCIP라는 버그 패턴으로 등록이 되어 있습니다. 아래 페이지에서 확인할 수 있습니다.

Findbugs Eclipse plugin를 설치하면 보다 편하게 FindBugs가 주는 경고를 확인할 수 있습니다..

만약 아래와 같이 @Immutable로 선언된 클래스에 final이 아닌 필드가 있다면

import net.jcip.annotations.Immutable;

@Immutable
public class Memo {
  private String content;
  public void setContent(String content) {
   this.content = content;
  }
  public String getContent() {
   return content;
  }
}

Eclipse에서 경고를 보여줍니다.

findbugs

그러나 FindBugs에서는 @Immutable외의 애너테이션은 특별히 확인하지는 않습니다.즉 @ThreadSafe 로 선언된 객체에서 @NotThreadSafe 로 선언된 멤버변수를 접근하더라구도 아무런 경고를 보내지는 않습니다. JCIP 애노테이션과 Findbugs를 동시에 쓰면서 많은 기대를 하지는 말아야 합니다.

Findbugs의 자세한 설명방법은 아래 포스트를 참고하시기 바랍니다.

정리

Java에서 어떤 클래스가 멀티스레드에서 의도하지 않게 사용될 때 그 부작용은 심각하지만, 문제가 되는 지점을 추적하기는 어렵습니다. 그렇게 때문에 스레드 안정성은 반드시 엄격하게 문서에 언급 되어야 합니다.그러나 기존의 클래스를 보면 제 각각의 표현 방식으로, 때로는 눈에 잘 띄지 않게 문서에 적혀 있습니다.

JCIP 애노테이션은 일관된 방식으로 스레드 안정성을 문서화하는데 도움이 됩니다. 이 애토테이션은 Apache httpclient 프로젝트에 적용되었고, 강력하지는 않지만 findbugs에서 JCIP annotation이 걸린 클래스를 정적 분석을 해주기도 합니다.

대부분의 웹프로젝트에서는 비지니스 레이어를 상태가 없는 클래스로 멀티스레드에서도 안전하게 만드는 방식이 권장됩니다. 그러나 때로는 스레드 안전하지 않은 클래스를 만들게 될 수도 있습니다. 예를 들면 멀티스레드에서 공유되면 안 되는 외부 라이브러리의 클래스를 사용하는데, 그 클래스를 생성자로 받아서 멤버변수에 할당해야 하는 경우입니다. 또, 배치나 데몬서버 등을 만드는 프로젝트에서는 스레드 안전한 클래스와 그렇지 않은 클래스가 혼재하게 될 때도 많습니다. 그런 클래스들을 명확하게 구분하고 싶다면 JCIP 애노테이션으로 표시하는 것을 고려해볼만 합니다.

참고자료

  • [EJ2] : Effective Java 2nd Edition - Item 70

  • [JCIP] : Java concurrency in practice