2016년 1월 31일 일요일

소프트웨어 개발에 있어서 믿지 못할 3가지 표현

소프트웨어 개발을 하다보니 자연스럽게 절대로 믿지 않게된 3가지

1. 절대 안바껴요
모든 것은 바뀔 수 있다. 왜냐하면 오늘날의 현실 세계가 그만큼 빠르게 변하고 있고, 그에 따른 요구사항도 빠르게 변하기 때문이다. 특히 소프트웨어 세계에서는 다른 영역에 비해서 그 빈도가 더 잦고, 그 범위가 더 넓다. 왜냐하면 흔히 사람들은 소프트웨어는 하드웨어와는 다르게 간단한 작업만으로도 쉽게 바꿀 수 있다고 생각하기 때문이다. 
프로그램을 설계하거나 개발할 때, 특정 요건이 절대 바뀌지 않는다는 가정은 하지 말자. 머지 않아 그 가정은 틀어질 수 있고, 그 가정에 기반한 설계와 소스코드는 수정하기 어려운 지경에 이르렀을 가능성도 높다. 변경될 부분을 고려하여 OCP스럽게 설계해야 할 것이다.

2. 한방에 다 되요
현실 세계가 매우 복잡해졌기 때문에, 그런 현실세계를 투영한 소프트웨어도 매우 복잡한 구성을 가지고 있는 경우가 대부분이다. 구성뿐만 아니라 절차마저도 복잡해졌다. 의미있는 결과물을 만들어 내기 위해서는 여러 단계의 작업을 거쳐야 한다. 일반적으로 데이터를 추출하고, 추출한 데이터들을 연결하고, 의미있는 계산을 수행한 다음, 결과를 보기 좋게 표시하는 작업이 이어지게 된다.
그런데 이런 일련의 작업을 한방에 다 되게끔 만드려는 시도가 있다. "한방쿼리"와 같은 단어가 대표적이다. 이는 중간 단계의 한 작업의 일부요소가 변경되는 경우에도 그 여파가 전체로 이어질 수 있는 위험한 설계이다. SRP를 유념하여, 프로그램을 나누어 가면서 작업할 필요가 있다.

3. 테스트 다했어요
사람은 불완전하기 때문에 모든 경우를 다 고려할 수 없다. 그런데 테스트를 다 했다고 표현하는 것은 상당히 오만한 자만심을 가진 경우이다. 1+1=2 수준의 프로그램을 만드는 경우조차 불완전한입력, 문자입력, null입력, overflow 등의 다양한 경우에 대해 신경을 써야 하고, 이보다 복잡한 프로그램은 그 경우가 더 많아질 것이다. 그만큼 요즘의 소프트웨어는 단순하게 테스트하기에는 복잡도와 난이도가 너무 높다.
아무리 테스트케이스를 다양하게 만든다고 하여도, 테스트케이스가 부족할 수 있다는 것을 언제나 잊지 말아야 한다. 또한 아직 발견되지 않은 결함이 존재한다는 것을 인정하고 부끄러워하지 말아야 할 것이다. 대신 그 결함을 발견할 수 있는 테스트케이스를 어떻게 만들 것인지를 고민해야 할 것이다.

누군가 위의 3가지 중에 한가지라도 말한다면, 그의 말을 믿지 말아라. 대신 언제든지 바뀔수 있는 요구사항과, 생각보다 복잡하고 어려운 현실세계와, 생각하지 못한 경우의 테스트가 있음을 잊지 말고 준비해라.

2016년 1월 29일 금요일

파일이름 변경 끝판왕 DarkNamer

자주 쓰는 작은 프로그램에서 두번째는 DarkNamer이다. 파일이름을 일괄로 변경하는 프로그램인데, 작지만 강력한 기능들을 가지고 있어서 자주 사용한다.

1. 무설치
별다른 설치 과정없이 exe 실행파일 하나로 바로 실행이 가능하다.

2. 일괄 파일 변경
문자열바꾸기, 앞이름붙이기, 뒷이름붙이기, 이름지우기, 위치지우기, 묶인곳지우기, 숫자만남기기, 자리수맞추기, 번호붙이기, 경로명앞에, 경로명뒤에, 경로통일, 확장자삭제, 확장자추가, 확장자변경이 가능하다.

3. 일괄 파일 추가
폴더를 드래그앤드롭하면 폴더를 프로그램에 넣고 이름을 바꿀 수도 있고, 폴더안의 파일들을 프로그램에 넣을 수 도 있다.

4. 이름이나 경로 복사
프로그램에 넣은 파일의 이름이나 경로를 복사해서 쓸 수 있다. 탐색기에서 여러가지 파일 이름을 텍스트로 뽑아내는게 의외로 간단한 일이 아닌데, 이 프로그램으로 간단하게 할 수 있다.

파일명의 형식을 맞추거나 할 때 이보다 편한 프로그램은 없었다.

2016년 1월 25일 월요일

spring file uplode size 다양하게설정하기

2011년 10월에 네이버블로그에 올렸던 내용을 이곳으로 모아서 게시한다. 최근에 업무에서도 요긴하게 사용했다.

1. 요구사항
첨부파일의 크기를 업무별로 다르게 설정해야 한다. 예를 들면 일반게시판에는 20메가연계게시판에는 10메가

2. 제약사항
- Spring에서 일반적으로 사용하는 CommonsMultipartResolver에서는 maxUploadSize를 한가지만 지정할 수 있다.
제한하고자 하는 크기는 자바 클래스에 상수로 선언하여 사용해야 한다.
제한된 크기를 초과하는 경우컨트롤러 레이어에서 에러메시지나 에러페이지 등을 제어해야한다. (, Spring에서 제공하는 ExceptionResolver 등을 사용하지 않는다)

3. 해결방법
CommonsMultipartResolver를 상속하여 resolveMultipart() 메서드를 오버라이딩하여 요청 URL에 따라서 각각의 업무클래스에 정의된 maxFileSize 상수로 maxUploadSize를 설정한다.
- super.resolveMultipart(request) 메서드 처리중에 발생한 MaxUploadSizeExceededException을 잡아서 Exception 객체를 attribute로 추가한 request를 리턴하여 컨트롤러 레이어까지 처리흐름이 이어지도록 한다.
public class CustomMultipartResolver extends CommonsMultipartResolver {

        @Override
        public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {

               // 요청URL path에 따라서 결정된 세부업무별로 maxUploadSize 설정
               String url = request.getRequestURI();
               int maxFileSizeMB = 0;
              
               // 게시판
               if( url.startsWith("/au/signbbs/") ) {
                       maxFileSizeMB = AuBoardFileConstant.getMaxFileSize();
               }
               // 연계게시판
               else if( url.startsWith("/au/connect/") ) {
                       maxFileSizeMB = AuBoardFileConstant.getMaxFileSize();
               }
               else {
                       throw new MultipartException("예상치 못한 url에서 파일업로드 시도함. AuMultipartResolver.resolveMultipart()에 추가 필요");
               }
              
               setMaxUploadSize(maxFileSizeMB * 1024 * 1024);
               setMaxInMemorySize(maxFileSizeMB * 1024 * 1024);
              
               try {
                       return super.resolveMultipart(request);
               catch (MaxUploadSizeExceededException e) {
                       // 파일사이즈초과되어도 에러메시지를 위해서 request 리턴하여 컨트롤러 메서드 호출되도록 한다.
                       request.setAttribute("exception", e);
                       return new DefaultMultipartHttpServletRequest(request, new HashMap(), new HashMap());
               }
        }
}

새로 만든 MultipartResolver 클래스를 Spring 설정 xml에 CommonsMultipartResolver 대신 등록한다.
<!-- 세부업무별로 파일업로드 사이즈 제한 -->
<bean id="multipartResolver" class="common.CustomMultipartResolver" />

컨트롤러 레이어에서는 request에서 가져온 MultipartFile 객체가 null인 경우를 확인하여 예외처리를 해준다. reqeuest에 MaxUploadSizeExceededException 객체가 있다면 적당한 메시지를 생성하여 UI로 에러메시지를 보내도록 한다.
// request에서 파일을 찾아서 MultipartFile 객체로 생성한다.
MultipartFile file = multipartRequest.getFile("attachfile");

// 최대업로드사이즈 초과 체크
if( file == null ) {
    Exception e = (Exception)multipartRequest.getAttribute("exception");
    if( e instanceof MaxUploadSizeExceededException ) {
       long maxFileSizeMB = (((MaxUploadSizeExceededException)e).getMaxUploadSize() / 1024 / 1024);
       errorMsg = "첨부파일 사이즈는 " + maxFileSizeMB + "M 를 초과할 수 없습니다.";
    }
    Log.error(e, "파일생성 중 예외발생");
    return "/error";
}

4. 활용방안
로그인한 회원의 권한에 따라서 크기를 다르게 설정해야 하는 요건의 경우에도 활용가능하다이를 테면 관리자 권한이나 수퍼유저의 경우 일반유저보다 큰 파일을 업로드 할 수 있다.

5. 개선할 점
요청 URL에 따라서 다른 상수클래스를 사용하는 부분에서 요청 URL이 하드코딩 되어있다그런데 명명규칙이 표준화되어 있어서 요청 URL과 상수클래스간에 일정한 규칙이 존재한다면 하드코딩된 부분을 제거할 수 있고이후 추가되는 업무에 따른 URL에 대해서도 소스코드 수정없이 적용이 가능할 것이다.

6. 참고

2016년 1월 22일 금요일

임의의 중국어 문장 만들기

회사에서 만드는 시스템에 중국어 버전을 준비하는 중이다. 영어를 중국어로 번역하는 작업은 외부에 맡겨놓은 상태인데, 번역이 완료되서 올 때까지 테스트를 기다릴 수 없다. 다국어처리가 안된 영어 문장이 시스템에 남아있는 것을 찾아야 하고, 로케일 설정이 변경되었을 때 서버와 브라우저에서 변경되는 부분도 미리 테스트해야 하기 때문이다. 그래서 적당한 길이의 중국어 문장을 만들어서 테스트하기로 했다.

1.
일단 how to generate random chinese로 구글링하면 온라인으로 중국어를 만드는 것들만 나온다. 중국이름이나 중국어용 로렌 입섬같은 것들... 그래서 검색어 끝에 java를 붙이면 3번째 결과에 java code가 나온다.

2.
소스코드를 보면, 중국어는 유니코드 4E00-9FA5 영역을 사용한다. 중국어로 배정된 구간은 9FFF까지이지만, 뒷부분은 아직 실제 글자가 지정되지 않아서 9FA5까지만 영역으로 잡은 듯 하다. 실제로는 더 다양한 영역에 중국어가 존재하지만, 대부분의 글자가 이 영역에 있다고 한다. 그 사이의 랜덤값을 만들어서 중국어 글자를 만들어 내고 있다. 복사해서 적용하니 한글자씩만 만들길래 반복문을 붙여서 원하는 것은 얻어냈다.

3.
소스코드가 좀 불필요한 부분이 있는 듯 하여 리팩토링을 진행했다.
private static int chineseStart = Integer.parseInt(String.valueOf(0x4e00));
private static int chineseEnd = Integer.parseInt(String.valueOf(0x9FA5));
private static String randomChinese(){
Random random = new Random();
int position = random.nextInt(chineseEnd-chineseStart)+chineseStart;
System.out.println(position+"--"+chineseEnd);
String code = Integer.toHexString(position);
return decode("\\u"+code);
}
일단 sysout을 지우고, 상수는 대문자밑줄로 변경, String으로 바꿨다가 다시 int로 바꾸는 것도 제거, 함수명도 get스럽게 변경, 불필요한 decode함수호출을 제거하고 바로 char캐스팅을 넣고, 지역변수제거하면 이렇게 된다.
private static int CHINESE_START = 0x4e00;
private static int CHINESE_END = 0x9FA5;
private static char getRandomeChineseCharacter(){
return (char)(new Random().nextInt(CHINESE_END - CHINESE_START) + CHINESE_START);
}

3.
반복문을 돌면서 중국어문장을 얻어내는 함수는 이렇게 된다.
private static String getRandomChineseSentence(int length) {
StringBuilder ret = new StringBuilder();
for (int i = 0; i < length; i++) {
ret.append(getRandomeChineseCharacter());
}
return ret.toString();
}

4.
미리 등록해서 사용하던 영어문장과 비슷한 길이의 중국어문장을 만들어내기 위해 영어문장길이에 3을 더하고 다시 3으로 나눈 길이만큼의 중국어를 생성했다.
String randomChinese = getRandomChineseSentence((text.length() + 3) / 3);

5.
문장에 매개변수를 넣기위해 {0}, {1}, {2}스럽게 넣었던 것들은 일괄로 앞에 붙여주었다.
if( text.contains("{3}") ) {
randomChinese = "{3} " + randomChinese;
}
if( text.contains("{2}") ) {
randomChinese = "{2} " + randomChinese;
}
if( text.contains("{1}") ) {
randomChinese = "{1} " + randomChinese;
}
if( text.contains("{0}") ) {
randomChinese = "{0} " + randomChinese;
}

6.
영어문장있는 DB에서 읽어서 랜덤중국어문장을 만들어, 로케일만 다르게 해서 DB에 넣었다.


2016년 1월 19일 화요일

eclipse egit에서 특정 branch만 fetch되게 설정하기

eclipse의 egit을 이용해서 git을 사용할 때, 특정 git repo에 대해서 특정 branch만 fetch를 하는 방법. 대상이 되는 git repo에 branch가 매우 많은데, 나는 development branch만 최신으로 유지하면 되는 경우에 유용하다. git reference의 "CONFIGURED REMOTE-TRACKING BRANCHES" 섹션을 참고했음

1.
egit으로 처음 git repo를 clone 받을 때, 기본으로 모든 branch를 fetch받도록 체크되어 있는데, 이때 fetch받고 싶은 branch만 선택한다.

2.
git repo의 properties를 열어서 remote.origin.fetch의 값을 아래처럼 변경한다. 붉은색 부분을 fetch하고 싶은 branch의 이름으로 바꾼다.
+refs/heads/development:refs/remotes/origin/development

git repo의 Remote - Fetch... 메뉴에서 변경할 수 도 있다.

3.
git repo나 workspace에서 fetch를 해도 해당 branch만 fetch가 된다. 즉, 다른 branch가 local git repo에 생기지 않는다.

2016년 1월 16일 토요일

2016년 계획 - 8가지 키워드

2016년의 계획을 생각해봤다. 2015년 정리할 때처럼 키워드로 적어본다.

블로그
원래 작년 목표였는데 작년에는 잘 지키지 못 했었다. 1년동안 10개 정도의 게시물밖에는 올리지 못 했다. 올해 목표는 120개이다. 120개를 하려면 3일에 한번꼴로 올려야 이룰수 있어서 도전적이긴 하다. 하지만 신경써서 해 볼 생각이다. 일단 작년에 만들었던 daag.pe.kr을 만들었던 후기를 시리즈로 블로깅할 생각이고, 내가 자주 쓰는 소프트웨어도 시리즈로 생각하고 있다.

안드로이드
지금도 그렇지만 앞으로도 모바일 개발 능력이 필수 역량이 될 것 같아서, 당장 필요는 없지만 미리 익혀두고자 한다. 당장 다음주부터 사내 강의가 있어서 신청해놓은 상태이고, 개인적인 시간도 투자해야 할 것 같다. 일단 작년에 웹으로 잘 만들어 놓은 daag.pe.kr을 앱으로 만드는 작업부터 할 것 같다.

연구회
입사하고 처음에 디자인패턴 연구회에 꾸준히 참석했었고, 그 후속 연구회인 모던프로그래밍 연구회에도 작년까지 지속적으로 참석했다. 최근에 조금 못 나가고 있는데, 올해에는 다른 연구회를 하나 더 참석해서 지식과 경험과 인간관계를 조금 더 넓혀보고자 한다. 아마 자바스크립트, 데이터베이스, 알고리즘 중 1가지에 관련된 모임이 될 듯 하다.

스터디
작년 말에 예고하고 올해 초부터 회사 개발팀 내부적으로 자바 스크립트 스터디를 하고 있다. 월수금으로 2주동안 조금 빠르게 진행했는데, 나름 반응이 좋은 것 같다. 처음에 계획했던 부분은 다 끝나가지만 앞으로도 관련된 부분을 조금씩 이어서 올해 내내 꾸준히 진행해보고자 한다. 개발팀의 전체적인 실력도 올라갈 것이고, 나도 더 편해질 것이다. 당분간 자바스크립트에 대해서 계속 진행할 생각이다.

daag
작년에 만들었던 daag.pe.kr의 기능을 더 확대해서 만들어 갈 생각이다. 지난해는 주로 기능을 추가했었는데, 올해는 컨텐츠에 집중해봐야 겠다. 일단 다음 업데이트는 주간메뉴표와 다른 사업장 지원이다. 거기까지 되면 다른 회사도 한번 생각해봐야 겠다.

독서
역시 작년 목표(3주에 2권)에 비해 너무 진행한 게 없다. 그리고 무엇무엇을 읽었는지도 잘 모르겠다. 올해도 목표는 작년과 같게 하고, 대신 읽은 책은 꼭 짧게라도 블로그에 기록이나 감상을 남기도록 하겠다.

빌드자동화
지금도 빌드의 많은 부분이 자동화되어 있지만, 아직 자동화되지 않은 부분도 많이 있다. 특히 모 책임님께서 매번 수작업으로 진행하시는 부분을 올해 꼭 자동화할 것이다. 대략 난독화, minify, 다국어데이터 동기화, 데이터베이스 동기화, 분석모듈 배포, 프러퍼티 관리, 시각화 등이 키워드가 있고, 이를 위해 maven, grunt, gulp와 같은 빌드도구들도 익힐 생각이다.

테스트자동화
항상 하고자 했던 영역이지만, 실력과 시간과 관심부족으로 하지 못했던 부분이다. 하지만 지금은 그 어느 때보다도 실력이 갖추어져 있고, 시간이 확보될 것 같고, 관심도 받을 것 같다. JUnit과 GUI와 Javascript 모두 진행할 생각이다.

여기까지만 해도 상당히 도전적인 올해 계획일 수 있다. 2016년 더 나은 내가 되기 위해 노력하자.

2016년 1월 15일 금요일

오래된 친구 같은 에디터 AcroEdit

블로그에 내가 자주 쓰는 작은 프로그램들을 소개하기로 하겠다. 첫 번째는 가장 오래쓰기도 했고, 그만큼 나름의 정도 많이 쌓인 AcroEdit

1. 빠른 시작
프로그램 처음 시작 속도가 빠른 편이어서 쉽게 시작해서 쓰고, 닫고, 다시 열고 닫고 하면서 쓰기에 부담이 없다. 빠릿빠릿한 편이다. 빨리 시작하기 위해 "마지막에 편집할 파일 시작할 때 자동으로 열기"를 체크해제하고 사용하는 중이다.

2. 열선택모드
Ctrl + B로 블럭선택모드를 열선택모드로 변경할 수 있다. 열선택모드가 되면 화살표키는 물론이고, Ctrl, Alt를 조합한 PageUp, PageDown도 가능해서 열기준으로 많은 량의 텍스트를 선택하기가 편하다. 이건 sublime text에서는 불가능해서 불만인 부분이다.

3. 모두 찾기
linux의 grep 명령처럼 찾은 결과를 아래처럼 별도의 검색결과창에 표시해준다. 전체적인 검색결과를 한눈에 볼 수 있다. 그리고 검색결과를 복사해서 사용할 수 있다는 것이 장점이다. 기본 단축키 셋팅에서는 빠져있어서 개인적으로 Ctrl + D로 설정해서 사용한다.

4. 공백처리
블럭선택후에 Shift + Tab으로 선택된 줄의 앞 공백을 Tab크기만큼 줄일 수 있다. 여기서 좋은 점은 선택된 줄마다 왼쪽에 남아 있는 공백이 없는 줄이 포함되어 있어도 다른 줄의 공백이 쭉쭉 줄어든다는 점이다. Eclipse는 이게 안되서 좀 답답하다. 
문장뒤 공백삭제, 탭을 공백으로 변경, 공백을 탭으로 변경하는 기본적인 처리기능도 있다.

5. 빈줄, 정렬, 중복제거
빈줄제거는 Ctrl + Alt + Shift + E (Empty), 정렬은 Ctrl + Alt + Shift + S (Sort), 중복행제거는 Ctrl + Alt + Shift + D (Duplicate)로 단축키를 지정해서 쓰고 있다. Shift + Tab과 같이 쓰면 텍스트에서 중복을 제거한 목록을 뽑아내기가 수월하다. 

6. 다중 선택 불가
문자열을 모두 다중 선택해서 동일하게 타이핑하는 등의 수정은 불가능하다. 마우스로 여러 곳을 클릭하는 것도 불가능하다. 그래서 선택한 문자열과 같은 문자열 전체 선택도 안된다. 

7. SFTP 불가
SFTP가 안된다. 그냥 FTP는 원격 파일을 열어서 리프레쉬도 되고, 원격 저장도 잘 되는 편이서어 SFTP가 안되는 점이 더 아쉽다. 로그 파일 보기에 참 좋은 에디터인데... 2003년도부터 기능 추가가 예정되었었는데 아직 안되고 있어서 더 아쉬운 부분이다.

8. 아주 큰 파일 느림
기가바이트 단위의 큰 파일을 처리할 때는 한계가 있거나 느려지는 경향이 있다. 

위에 언급한 특징과 단점이 내가 AcroEdit를 사용하는 이유이고, AcriEdit만 사용하지는 않는 이유이다.

이외에도 사용자도구, 프로젝트관리 처럼 더 고급진 기능도 있다. 그런데 막상 내가 잘 사용하지 않는 중이다. 

요즘에는 Sublime Text나 Atom같이 최근의 트렌드한 기능을 갖춘 다기능의 텍스트에디터가 여럿 있지만, 단순 윈도우 메모장을 대체하는 빠른 텍스트에디터가 필요하다면 AcroEdit가 좋은 대안이 될 수 있을 것이다.

10년을 넘게 사용하고 있는 프로그램이다보니, AcroEdit를 실행할 때마다 마치 오래된 친구를 만나는 것처럼 편안함이 생겼다. 개인적으로는 작업표시줄에 고정시켜서, 빠르게 사용하고 쓰고 종료하는 방식으로 사용중이다.

오랜 기간동안 훌륭한 프로그램을 무료로 꾸준히 업데이트하고 계신 김성동 개발자님께 감사의 인사를 드립니다.

2016년 1월 9일 토요일

2015년 정리 - 10가지 키워드

2015년에 나에게 생긴 변화를 몇가지 키워드에 맞추어 정리해 본다.

책임
3월에 책임으로 진급을 했다. 흔히 말하는 발탁으로 진급한 경우여서 축하도 많이 받았고, 선임 4년동안 같이 프로젝트를 진행했던 선후배동기들에게 진급턱도 내면서 즐거운 3월을 보낼 수 있었다. 딱히 책임이 되고 나서 하는 일이, 되기 전과 달라지지는 않은 것 같다. 다만 나를 "책임"으로 처음 알게되는 사람들은 나를 대하는 자세가 조금은(?) 다름을 느낄 수 있었고, 윗분들이 안계실 때의 내가 스스로 갖게 되는 마음가짐이 조금 달라지는 부분이 있다. 그리고 내가 스스로 판단해서 결정하게 되는 것들의 영역이 조금 더 넓어진 느낌이다. 

git
4월부터 회사에서 구축한 전사표준개발환경을 적용하면서 기존 SVN에서 강제로 git을 적용하게 되었는데, 결과적으로 잘 된 일이었다. git을 적용하지 않고 SVN으로 현재 개발된 모습의 솔루션을 만들어 낼 수 있었을까? 같은 공수로는 절대 불가능했을 것 같다. 지난해부터 개인적으로 노력하던 "최신 개발 트렌트 따라잡기"로 생각해봐도 걸맞는 작업이었다. 이제 다시는 SVN으로 돌아가기 어려울 것 같다. 돌아갈 일도 없고.

알고리즘
회사에서 알고리즘 시험이 본격 도입되어서 2-3개월정도 잠시 공부를 했다. 개인적으로는 알고스팟에서 연습을 했고, 스터디에서는 백준알고리즘을 활용했다. 대학교 시절에 데이터구조와 알고리즘 시간에 배웠던 것들이 눈앞에 아른 거리는데, 이게 손을 놓고 살았더니 쉽지 않았다. 꾸준히 갈고 닦는 수 밖에 없는 듯 하다. 2016년에는 더 높은 등급을 따야 한다고 하던데 준비를 미리미리 해놔야 한다.

GTA5
8월에 노트북을 나름 최신형으로 바꾸면서 기다리던 GTA5를 플레이할 수 있었다. 2-3주 동안 밤을 지새면서 GTA5의 스토리라인을 클리어했는데, 너무 잘 만들어놔서, 게임에서 플레이했던 내용이 정말 현실에서 일어났던 일처럼 기억되는 착각이 있었다. 키보드로는 원활한 드라이브가 불가능해서 몇년동안 구석에 쳐박혀 있던 CYVOX 게임패드를 꺼내서 xbox 360 패드처럼 에뮬레이팅을 해야 했었다. 그리고 회사 VDI 접속 프로그램과 충돌이 있어서 게임이 실행안되서 고생도 좀 했었다. (결국 윈도우 백업으로 해결)

github, gitlab
공개해도 되는 소스는 github에, 공개하면 안될 소스는 gitlab을 저장소로 사용했다. 이제 github는 단순 저장소를 넘어 하나의 개발문화로 자리잡은 느낌이다. 무엇이든 github에 저장되는 세상에 나도 한발 동참한 느낌이다. 집, 회사, VDI 어디에서도 접근이 가능해서 편리하기도 했다.

delacourt at a glance
개인적인 욕구에 의해 만들었는데, 만드는 과정과 결과가 모두 맘에 들었던 작업이다. 4월부터 만들어서 바로 서비스를 오픈했고, 12월까지 꾸준하게 기능을 추가했다. github, AWS, nodejs, bootstrap, google analytics, 도메인연결 등을 경험해 볼 수 있었던 일련의 과정이 그동안 프로그래머로서 항상 하고 싶어 했던 작업이었다. 그리고 일일 페이지뷰 600정도를 만들 수 있는 결과도 그동안 느낄 수 없었던 성취감이었다. 아마도 이런 성격의 작업이 규모가 크고, 돈이 된다면 바로 스타트업이 아닐까란 생각까지 들었다. 2015년 나를 가장 즐겁게 만들어 준 것, 바로 daag이다.

갤럭시S6
내 인생에서 최신형 전자기기를 구매한 적이 없었는데, 이상하게 갤럭시S6는 욕심이 났었다. 우연히도 언팩행사를 라이브로 시청해서 더 애정이 갔을 수도 있다. 그동안 최신기기를 못 써본 설움(?)이 터진 것일 수도 있다. 아무튼 홈키 더블클릭으로 카메라를 바로 실행시킬 수 있는 것은 5살 딸아이를 키우는 아빠로서는 매우 큰 강점이었다. 그 어느 해보다도 딸래미 사진과 동영상을 많이 찍어줄 수 있어서 좋았다. 출퇴근 2시간을 오롯이 책임져 주는 고마운 존재이다. 그리고 고속충전과 무선충전으로 배터리를 하나 더 챙기지 않아도 되는 수고를 없에 준것도 고마운 일이다. 삼성페이는 아직 그닥 편한지 모르겠다. 캐셔에게 카드를 주는게 전화기를 주는 것보다 쉬운 일이다.

무선헤드셋
내 일상생활을 가장 많이 바꿔준 것은 8월에 구매한 무선헤드셋이다. 단돈 6만원에 (그것도 무이자 3개월 할부로) 출퇴근 움직임이 자유로워졌고, 통화할 때 두 손이 자유로워졌고, 추운 겨울에 내 두 손이 따뜻할 수 있었다. 특히 이사준비로 전화통화가 잦았던 10월에 유용하게 사용했다. 그리고 나를 보는 사람들에게는 항상 목에 걸고 있는 무선헤드셋이 기억에 남을 만한 특징이었을 것이다.

호핀
9월부터 와이프가 호핀에 무제한 이용권을 사용할 수 있게 되면서, 나의 출퇴근 시간이 완전히 바뀌게 되었다. 호핀에서 간편하게 다운로드 받은 응답하라 1988, 육룡이 나르샤, 썰전, 그것이 알고싶다를 월요일 아침부터 출퇴근 시간에만 보더라도 어느새 목요일이 되어 있다. 평소의 나보다 동영상을 많이 보고 있긴 한데, 그래도 재미있으니 어쩌랴.

남가좌동
4년간의 용인 생활을 정리하고 11월에 서울로 다시 들어왔다. 더구나 새로 입주하는 아파트라서 더 이사한 느낌이 크다. 출퇴근도 이전보다 덜 갈아타고, 한번에 길게 가고, 앉아갈 수 있어서 더 쾌적하다. 와이프 회사는 물론이고 처가와 가까워진 것도 좋은 점이다. 부천과 정릉이 가까워진 것도 좋은 점이다. 집도 따뜻하고 주차장도 넓고 방도 넓고 놀이터도 많고 다 좋다. 다만 엘리베이터 소음과 벽너머 소음은 아쉬운 부분이고, 층간 소음은 걱정이 된다.

2016년에도 더 좋은 변화가 나에게 있기를 기대해 본다.

2016년 1월 3일 일요일

Page Object

요즘 업무 시간에 UI테스트를 자동화하고 있다. Selenium을 이용해서 하고 있는데, 관련 서적이나 블로그를 보면 항상 Page Object를 사용하도록 추천하고 있다. 관련된 마틴 파울러의 블로그를 읽으면서 내용을 정리했다.


웹페이지에 대한 테스트를 작성할 때, 웹페이지 내부의 HTML 요소를 직접 제어하지 말자. UI가 변경되면 테스트코드도 변경되어야 하므로, HTML 페이지의 구성요소와 기능을 래핑한 Page Object를 사용해서 테스트코드를 만들자. [그림참고]

Page Object의 기본 규칙은 사람이 해당 페이지에 대해서 볼 수 있는 것은 그대로 보여야 하고, 할 수 있는 것도 그대로 할 수 있어야 한다는 것이다. 인터페이스를 제공하고, 페이지 내부의 구성요소를 제어하는 로직은 숨겨야 한다. 텍스트 필드에 접근하고자 할 때에는 문자열을 받거나 리턴하는 메서드를 통해야 한다. 체크박스는 boolean을 사용하고, 버튼은 해당 버튼의 기능을 표현하는 메서드를 사용해야 한다. Page Object는 UI 내부 데이터를 제어하는 로직을 캡슐화해야한다. 인터페이스는 바뀌지 않고 실제 구현 클래스만 바뀌는 경우가 그러할 것이다.

"page"라는 용어를 사용하지만, 모든 웹페이지의 Page Object를 만들 필요는 없다. 중요한 구성요소에 대해서만 만들어서, 여러 앨범을 표시하는 웹페이지가 있다면 앨범 Page Object의 목록을 구성해서 활용하면 될 일이다. 헤더나 푸터 Page Object도 비슷하게 웹페이지를 구조화할 수 있을 것이다.

비슷한 방식으로, 다른 웹페이지로 이동하는 경우에는 초기 Page Object가 이동하는 Page Object를 리턴하게 만들면 될 것이다.

Page Object가 assertion을 내부에 가지고 있어야 하는지 아닌지에 대한 여러가지 의견이 있을 수 있다. 내부에 가지고 있게 되면, 중복되는 assertion을 제거하고, 좀 더 좋은 에러 메시지를 외부에 제공할 수 있을 것이다. 그렇게 해서 TellDontAsk 스타일의 API를 구성할 수 있다. 내부에 assertion이 없다면, Page Object를 좀 더 간결하게 유지할 수 있어서 내부 데이터에 대한 접근과 assertion 로직을 분리하기가 쉬울 것이다.

나는(마틴 파울러) assertion이 없는 Page Object를 더 선호한다. 공통의 assertion library를 통해서 중복되는 assertion을 피할 수 있을 것이고, 간단하게 웹페이지에 대한 기본적인 검증을 쉽게 할 수 있을 것이다.

Page Object는 보통 테스트에 사용되지만, 스스로 assertion을 해서는 안될 것이다. 단순히 내부 데이터와 구성요소에 대한 접근을 제공하고, assertion에 대해서는 테스트코드에서 수행되도록 유도해야 할 것이다.

HTML에 대해서만 이 패턴을 설명했지만, 어떤 다른 UI에 대해서도 동일하게 적용할 수 있을 것이다. Java의 swing UI를 사용할 때 잘 사용되는 것을 실제로 봤었고, 다른 어떤 UI에서도 잘 될것이라 의심치 않는다.

동시성 문제로 Page Object가 캡슐화할 수 있는 영역이다. 사용자에게는 async로 보이지 않는 async 동작을 그렇지 않게 숨길 수가 있다. (무슨 말이지?) 스레드 이슈도 캡슐화할 수 있다. UI와 worker간의 동작을 신경써야 하는 UI 프레임웍을 쓴다면 아마 해결 가능할 것이다. (이거도 무슨 소리지?)

Page Object는 대부분 테스트 영역에서 사용되지만, 일반 애플리케이션에서 스크립트 형식의 인터페이스를 제공하는 것으로도 사용될 수 있다. 보통은 UI 내부에 스크립트 인터페이스를 구축하는 것이 일반적이고, 덜 복잡하고 빠르다. 그런데 UI에 기능이 너무 많은 경우에는 Page Object를 쓰는 것이 좋지 않을 것이다. (이런 경우라면 로직을 분리하는 방안을 고려해봐야 한다. 그게 스크립트를 만드는 데도 도움이 되고, UI 자체도 장기적인 관점에서 도움이 될 것이다.)