스마트폰 os의 현황을 보면 안드로이드와 아이폰이 쌍벽을 이루고 있는 실정.
그렇다면, 앱스토어의 20만개, 구글마켓의 7만5천개 어플들이 각각 늘어갈 것인가..
아마도 당연히 두 운영체제사이의 추상계층(Abstrac Layer)이 발생하게 될것이고, OS의 종류에 상관없는 개발이 가능해지겠죠.
벌써 준비해서 진행되고 있지 않을까?
구글링을 해보았지만 아직 그런것은 안보이는걸보면 누군가 몰래 준비하고 있나봅니다.
만일 오픈소스기반의 개발방법론이 정착된 환경의 기업이라면, 공짜입니다.
이런 경우라면 오픈소스라이선스에 대한 검토가 있을것이고,
참여과 기여를 통한 SW의 질적향상에 대한 경험, 오픈소스의 자유로운 배포정신 등을 이해하고 있을테니까.
좋은 SW를 무료로 사용하면서 비지니스를 창출할 수 있습니다.
자신이 좋은SW들을 패키징해서 제품으로 판매할 수도 있고(redhat, suse 등), IT서비스를 컨설팅할 수도 있으며(openlogic), 다른 IT서비스 카테고리를 선택할 수도 있고(blackduck), 하드웨어와 접목한 융합기기를 만들어 낼 수도 있습니다(embeded).
하지만, 오픈소스에 대한 이해와 학습이 없는 기업이라면 공짜가 아닙니다.
SW구매이후 발생될 여러가지 비용을 포함해야 하는 기업환경에서
소스코드의 자유로운 배포를 기본으로 하며(gpl,lgpl,mpl,bsd등), 적합한 라이센스를 검토해야 하고,
해당 SW에대한 기술지식, SW에 대한 유지보수 능력등이 필요한 오픈소스 SW를 공짜라고 판단하는것은 잘못된 생각입니다..
공개SW를 사용해야 할까요?
기업의 CTO가 오픈소스철학을 이해하고 있다는걸 전제한다면, 오픈소스는 기업의 EA에 많은 이익을 가져다 준다.
수 많은 SW의 리펙토링이 가능하고, 글로벌에 적용되는 SW개발방법론을 기업의 인적자원에게 숙련시킬수도 있으며
다양한 SW를 통해서 창의적인 아이디어를 획득할 기회도 증가합니다.
참여와 공유를 통해서 집단지성이 제공되는 최근의 흐름을 생각해볼때,
세상이 스마트해지면 해 질수록 오픈소스에 대한 이해는 더욱 필요합니다.
소프트웨어를 설계하고 코딩을 하다보면 프로그램을 최적화할 때가 있다.
대부분의 프로그램은 80:20 법칙을 따른다. 전체 코드의 20%가 전체 시간의 80%를 차지한다.
그래서 프로그래머가 해당 컴퓨터 아키텍쳐의 세세한 부분보다는 논리에 집중하여 프로그래밍 시간을 줄여주는
자바나 C#같은 언어가 인기를 얻었다.
프로그램의 실행시간은 길어지지만, 프로그래머의 시간은 절약된다.
그러나 프로그램을 더 빨리 실행하기위한 최적화가 필요하지 않다는 말은 아니다.
많은 컴파일러는 알아서 최적화를 한다.
예를 들어, GCC 컴파일러는 (대문자 주의) -O 옵션으로 최적화 수준을 지정한다.
프로파일링(profiling)은 프로그램의 성능을 높이기위해 최적화할 코드및 함수의 위치를 발견하도록 도와준다.
프로그램에서 10번만 호출하는 함수보다 1000번 호출하는 함수를 최적화하는게 당연하다고 생각하지 않는가.
프로그램을 프로파일링하면 코드의 어떤 부분을 자주 사용하고 어떤 함수가 CPU 시간을 많이 잡아먹는지 알 수 있다.
이 두 정보는 최적화할 대상을 정하는데 유용하다.
실제 프로그램을 실행하면서 정보를 모으기때문에 감춰진 버그를 찾는데도 유용하다.
실행중에 예기치않게 어떤 함수를 1000번 호출한다면 설계상 문제이거나 버그일 수 있다.
또, 크고 복잡한 프로젝트에서 코드를 살펴볼때도 유용하다.
프로파일링 정보에는 두가지 종류가 있다 :-
* Flat Profile
함수별로 사용하는 CPU 시간과 호출 횟수를 보여준다. 수집한 전체 프로파일링 정보의 간단한 요약이다. 성능을 높이기위해 어떤 함수를 다시 작성하거나 수정할지 알려준다.
* Call Graph
모든 함수에 대해 자신을 포함하여 다른 함수가 호출한 횟수를 보여준다. 그래서 어떤 함수 호출을 없애거나 다른 효율적인 함수로 대체할지 제안한다. 이 정보는 함수들간의 관계를 드러내고, 감춰진 버그를 알려주기도 한다. 호출그래프를 본 후에 특정 코드 경로를 최적화하고 싶을 것이다.
; xdebug
zend_extension = C:\xampp\php\ext\php_xdebug-2.1.0RC1-5.3-vc6.dll
xdebug.default_enable = On
xdebug.show_exception_trace = On
xdebug.show_local_vars = 1
xdebug.max_nesting_level = 50
xdebug.var_display_max_depth = 6
xdebug.dump_once = On
xdebug.dump_globals = On
xdebug.dump_undefined = On
xdebug.dump.REQUEST = *
xdebug.dump.SERVER = REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT,SCRIPT_NAME
mod_security 룰셋 추가설정 KISA에서 받은 룰셋중 fckeditor 를 사용하면서 오류가 나는 부분이 있어서 아래와 같이 수정
# dir|page 가 들어가 있으면서 https가 요청되는 경우가 fckeditor 에서 발생하기 때문에 수정
SecRule REQUEST_URI "(dir|page|)" chain
#SecRule REQUEST_URI "=(http|https|ftp)\:/" SecRule REQUEST_URI "=(https|ftp)\:/"
SecRule REQUEST_URI "shell_exec\(" "msg:'PHP Injection Attacks'"
우선 테스트 자동화 도구는 테스트 수행의 어떤 단계를 자동화 하느냐에 따라 크게
테스트의 관리/실행(execution)/생성(generation)/측정(measurement) 자동화 도구로 분류할 수 있습니다.
- 첫번째로, 테스트의 관리(management)를 자동화하는 도구는 테스트 과정에서 산출되는 테스트 케이스/슈트/스크립트 및 각종 문서들을 통합관리하는 기능을 제공합니다.
- 두번째로, 테스트의 실행(execution)을 자동화하는 도구는 테스트 대상 프로그램의 수행 및 제어를 자동화하는 기능을 제공합니다.
- 세번째로, 테스트의 생성(generation)을 자동화하는 도구는 테스트 대상 프로그램에 입력으로 주어지는 테스트 케이스 및 테스트 스텁(stub) 등을 자동으로 생성하는 기능을 제공합니다.
- 네번째로, 테스트의 측정(measurement)을 자동화하는 도구는 테스트 수행에 따른 커버리지(coverage) 측정 등의 과정을 자동화합니다.
위의 네 가지 분류는 테스트 자동화 도구를 상호 배타적으로 분류하는 기준은 아닙니다. 다시말해 하나의 테스트 도구가 위의 네 가지 분류에 모두 속할 수도 있다는 것입니다. 예를 들어, 어떤 GUI Record/Playback 자동화 도구가 테스트 대상 GUI 응용프로그램을 수행하고 사용자로부터 주어지는 모든 이벤트를 기록한 후 저장하였다가 회귀(regression) 시험과정에서 저장된 데이터를 다시 활용하는 기능을 제공하고 테스트 수행결과에 대해 다양한 커버리지 측정결과를 제공한다면, 이 자동화 도구는 위의 네 가지 분류에 모두 속할 수 있는 것이죠.
또한, 위의 분류에서 테스트의 실행(execution) 자동화 도구와 생성(generation) 자동화 도구는 테스트 케이스 및 테스트 하니스(harness) 등의 테스트 수행에 필요한 입력 및 환경 등을 자동으로 생성하느냐에 따라 중요한 차이점을 갖습니다.
function countdown() {
counter--;
var element = document.getElementById('counter');
element.innerHTML = counter;
if(counter === 0) clearInterval(handle);
}
간단한 코드이므로 설명은 생략합니다. 스팩을 먼저 만들고 구현을 하면(즉, BDD 혹은 TDD를 하면) 더 쉽겠지만, 실제 상황과 유사하게 하기 위해서 스팩 없이 구현을 먼저 했습니다. Michael Feather의 명저서 "Working Effectively with Legacy Code(레거시 코드 활용 전략이라는 제목으로 번역되었습니다)"에 의하면, 위 코드는 바로 레거시 코드 입니다.
To me, legacy code is simply code without tests. ... Code without tests is bad code. It doesn't matter how well written it is; it doesn't matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behavior of our code quickly and verifiably. Without them, we really don't know if our code is getting better or worse. --pxvi
저는 테스트 케이스가 없는 코드를 레거시 코드로 봅니다. ... 테스트 없는 코드는 나쁜 코드입니다. 얼마나 잘 짰는지는 중요치 않아요. 아무리 예뻐도, 아무리 객체지향적이어도, 아무리 캡슐화가 잘 되어 있어도 소용 없습니다. 테스트가 있으면 빠르고 검증가능한 방식으로 코드의 행위를 수정할 수 있습니다. 테스트가 없으면 코드가 좋아지고 있는지 나빠지고 있는지 알 방법이 없지요.
이제, 이 레거시 코드를 좋은 코드로 고쳐봅시다.
2. 안도감을 느낄 수 있을만큼의 스팩
너무 많지도 않고 너무 적지도 않은 딱 적절한 양의 스팩을 달고 싶습니다. 다른 말로 하자면 "안도감을 느끼며 개발을 할 수 있을만큼의 스팩"입니다.
뭐 정의 자체가 애매하다보니 사람마다 그 기준이 다를 수 있겠습니다만, 저는 아래 두 가지 로직을 커버할 수 있는 정도면 안도감이 느껴질 것 같습니다:
* countdown()이 호출되면 div#counter 의 값이 1 씩 감소하는가
* countdown()이 0.1초 간격으로 호출되는가
function countdown() {
var element = document.getElementById('counter');
var counter = Number(element.innerHTML) - 1;
element.innerHTML = counter;
if(counter === 0) clearInterval(handle);
}
한 편, div#counter 엘리먼트도 어디서든 접근할 수 있다는 점에서 전역 변수라고 볼 수 있겠습니다. countdown() 함수가 이 엘리먼트에 의존하고 있기 때문에 아래의 HTML이 테스트 코드와 프로덕션 코드 두 곳에 중복으로 나타나죠:
<div id="counter">10</div>
스팩을 아래와 같이 수정하면 좋겠습니다:
describe('Counter', {
'countdown()이 호출되면 카운터가 1 감소되어야 한다': function() {
var element = {innerHTML: '5'};
countdown(element);
value_of(element.innerHTML).should_be('4');
}
});
countdown() 함수가 element를 인자로 받도록 수정하고, 가짜 엘리먼트를 하나 만들어서 넘겨주었습니다. 이 방식은 countdown() 함수가 element의 innerHTML 속성을 이용할 것이라는 가정을 하고 있다는 점에서 좋지 않습니다만(테스트가 구현 방식에 종속됨), 기존 방식(전역 변수에 의존)보다는 좋다고 판단하였습니다. "best way"는 아니지만 "better way"인거죠.
위 스팩이 통과하려면 counter.js는 아래와 같이 바뀌어야 합니다:
function startCountdown() {
handle = setInterval(function() {
var element = document.getElementById('counter');
countdown(element);
}, 100);
}
function countdown(element) {
var counter = Number(element.innerHTML) - 1;
element.innerHTML = counter;
if(counter === 0) clearInterval(handle);
}
이 정도로 하고 다음 스팩을 추가하겠습니다.
4. countdown()이 0.1초 간격으로 호출되는가
"countdown()이 0.1초 간격으로 호출되는가"라는 질문은 달리 표현하자면, "0.1초가 지나면 countdown()이 호출되는가"입니다. 이제 드디어 본론에 해당하는 내용이 나왔습니다.
대체 "0.1초가 지나면"이라는 것을 어떻게 테스트하면 좋을까요? JSSpec에 아래와 같은 가상의 기능이 추가되어서 0.1초를 "기다릴 수 있으면" 될까요?
'countdown()이 0.1초 간격으로 호출되어야 한다': function() {
// 여기에서 뭔가를 수행하고
wait_for(100); // 0.1초(100msec)를 멈춰서 기다린 후
// 여기에서 결과를 확인한다
}
위와 같은 방식이 기술적으로 가능한지 여부를 떠나서, 이는 좋은 단위 테스트라고 볼 수 없습니다. 만약 요구사항이 0.1초가 아니라 한 시간이었다면, 위 테스트를 수행하는데 한 시간이 걸린다는 얘기인데, 이렇게 되면 원하는 때에 피드백(즉, 테스트 성공 여부에 대한 확인)을 받을 수가 없게 됩니다.
요약하자면, "0.1초가 지난 후 어떻게 되는가"를 테스트하기 위해 실제로 0.1초를 기다려야 하는 방식은 적절치 않습니다.
좀 더 적절한 방법은 0.1초를 가짜로 흘려보내는 것입니다. 자바스크립트의 Date 클래스를 다음과 같이 확장하고, 애플리케이션의 모든 코드에서 new Date() 대신에 Date.get()을 쓰도록 수정할 것을 권장합니다:
preset() 함수는 시간을 임의로 설정하여 멈추어놓기 위해 사용됩니다. pass() 함수는 시간을 가상으로 흘려보냅니다. get() 함수는 현재 시간을 얻어옵니다. get() 함수가 반환하는 값은 preset() 혹은 pass()가 사용되지 않았다면 실제 시스템 시간이지만, preset()이나 pass()가 사용되었다면 가짜 시간입니다. 위 코드는 실제 프로덕션에서도 사용될 코드이므로 스팩이 아닌 counter.js에 추가합니다. 이제 아래 스팩을 추가해봅시다:
'0.1초가 지나면 countdown()이 호출되어야 한다': function() {
// countdown 바꿔치기
var backup = countdown;
var called = false;
countdown = function() { called = true; };
try {
Date.preset(0); // 시간 고정
updateIfTimePassed();
value_of(called).should_be(false);
첫째, updateIfTimePassed() 함수가 추가되었습니다. 이 함수가 하는 일은 자신이 마지막으로 호출된 후로 0.1초 이상이 흘렀으면 countdown() 함수를 호출해주는 것입니다.
둘째, 이번에 작성한 스팩은 0.1초 간격으로 countdown() 함수가 호출되는가를 확인하기 위한 것이지 countdown() 함수가 제대로 작동되는가를 확인하는 것이 아닙니다. 다른 말로 하자면 countdown() 함수가 제대로 작동하건 안하건 0.1초 간격으로 호출만 된다면 이 스팩이 깨지지 않아야 합니다. 따라서, countdown() 함수를 가짜로 대체하고 다시 복구하는 코드(변수 backup이 사용되는 부분들)가 들어 있습니다.
셋째, 조금 전에 Date 클래스에 추가한 메서드들을 사용하여 시간을 고정시킨 후 가짜로 흘려보내고 있습니다. 이렇게 하면 이 테스트는 실제로 0.1초를 보내지 않고 순식간에 수행됩니다.
이번에는 이에 따른 프로덕션 코드의 수정입니다:
var updatedAt = null;
function startCountdown() {
handle = setInterval(updateIfTimePassed, 10);
}
function updateIfTimePassed() {
if(!updatedAt) updatedAt = Date.get();
var now = Date.get();
var timePassed = now - updatedAt >= 100;
if(timePassed) {
var element = document.getElementById('counter');
countdown(element);
updatedAt = now;
}
}
변화되지 않은 부분들 - main(), countdown() - 은 생략하였습니다. 변화된 부분은 다음과 같습니다.
첫째, startCountdown 내부의 setInterval이 updateIfTimePassed를 0.1초 간격이 아닌 0.01초 간격으로 호출하 고 있습니다. 0.01초는 대략 "시스템에 무리를 주지 않는 한도 내에서 최대한 빈번한 간격"입니다. 이렇게 수정하자 이 메서드는 말 그대로 카운트다운을 시작하는 일만 하게 되었고, 카운트다운의 간격 등에 대해서는 아무런 간섭도 하지 않게 되었습니다.
둘째, updateIfTimePassed() 함수가 새로 만들어졌습니다. 이 함수는 자신이 마지막으로 호출된 이후로 0.1초 이상이 지나면 countdown() 함수를 호출하도록 되어 있습니다.
셋째, 이 과정에서 전역 변수 updatedAt이 추가되었습니다. 좋지 않지만 잠시 참고 가보도록 하겠습니다.
Date 클래스를 확장하는 방식에 대해서 조금 더 부연설명이 필요하신 분은 유닛테스트에서 시각과 시간이라는 글을 참고해주세요. 자바를 기준으로 설명하고 있지만 결국 같은 방식입니다.
5. 리팩토링
원하는 스팩을 두 개 만들었으니 이제 좀 마음놓고 리팩토링을 해야 합니다. 마음에 안드는 부분들이 많습니다.
첫째, 0.1초 간격으로 무언가를 호출하는 로직(updateIfTimePassed 함수)이 카운트다운을 수행하는 로직(countdown 함수)과 엉켜있습니다. 게다가 countdown 함수에 필요한 인자를 넘겨주기 위해서 DOM에도 의존하고 있습니다.
둘째, 전역 변수가 두 개(handle, updatedAt) 있습니다. 그리고 DOM 이 숨은 전역 변수 역할을 하고 있기도 합니다. 이게 무슨 말이냐하면, 전역 변수라는게 나쁜 이유는 어디에서든 접근할 수 있기 때문에, 코드의 여러 지점이 이 전역 변수를 중심으로 엮인다는 점입니다. 그렇게 본다면 div#counter 라는 엘리먼트가 여기저기에서 쓰이면서 의존성을 만들어내고 있다는 점에서 숨은 전역 변수입니다. 예전에 박응주님이 자바의 ThreadLocal이나 WebWork의 ActionContext 등도 전역 변수이다라고 말씀하신 것과 같은 맥락입니다.
(저는 예전에 위 두 가지 문제를 해결하기 위해 Timer 혹은 Scheduler라는 클래스를 만들었었는데 아주 만족스러웠습니다.)
셋째, 스팩 자체도 깔끔하지가 않습니다. 가짜 객체(element, countdown)를 만들어서 이런저런 의존성을 끊어주어야만 테스트간 격리(test isolation)가 이루어진다는 것은 결국 좋지 못한 설계로 인해 생긴 문제에 다름 아닙니다. 위에서 언급한 두 가지 문제를 해결하면 스팩도 더 깔끔해지겠죠. 원래 테스트하기 쉬운 코드일수록 설계가 좋은 코드라는 말이 있습니다. 그 반대도 성립합니다. 설계가 좋은 코드일수록 테스트하기가 쉽기도 합니다. 역시나 가장 좋은 방법은 테스트(혹은 스팩)를 먼저 만들고나서 설계를 하는 것(즉, TDD 혹은 BDD를 하는 것)입니다.
이제 퇴근을 해야겠으니 이런 부분들은 미해결로 남겨놓겠습니다. ^^;
PS - 요약 및 일반화된 결론
setInterval 을 테스트하는 문제는 사실 더 큰 문제의 구체적 사례일 뿐입니다. 문제를 더 일반화하면 이렇게 됩니다:
제어하기 힘들거나 비용이 많이드는 외부 시스템(시스템 타이머, 디스크, 네트워크, 외부 라이브러리, 프레임워크의 노출된 인터페이스 등등)과 엮인 코드를 어떻게 테스트할 것인가.
이 문제에 대한 일반해는 InsertTestableLayer를 참고하시기 바랍니다.
그런데, TestableLayer라는 것을 넣으면 테스트가 가능해진다는 점 말고 대체 뭐가 좋아지는걸까요. 위에서 설계가 좋아진다고 쓰기는 했는데 구체적으로 뭐가 어떻게 좋아진다는 것일까요. 뭐 여러가지 주절주절 설명할 수 있겠지만... 자바스크립트의 경우는 이렇습니다:
비즈니스 로직(0.1초 간격으로 1씩 카운트다운)에 해당하는 코드를 전혀 수정하지 않고 다른 호스트 환경(host environment - Rhino, jslibs, ASP, JScript.NET, Silverlight, Flash/Flex 등)에서 그대로 사용할 수 있게 됩니다. 이것도 또한 반대로 얘기할 수 있는데, 코드를 전혀 수정하지 않고 다른 호스트 환경에서 사용할 수 있게 된다면 제어하기 힘든 외부 시스템에 대한 의존이 제거되었다고 볼 수 있습니다.
프로그램 그룹으로 등록해서 사용.(텍스트필터로 실행)
제 경우 인수는 2t D D D,script,?,?php $(CurSel) 사용
사용법
[들여쓰기][코드들여쓰기] [태그리스트1] [태그리스트2] [태그리스트3] ($CurSel)
---------------------------------------------------------------------------
ex) 2t D D D $(CurSel)
* 들여쓰기
0~9까지의 숫자, 탭문자를 이용하고 싶으면 t를 사용
* 코드들여쓰기
0~9까지의 숫자, 탭문자를 이용하고 싶으면 t를 사용
코드 정렬을 안하려면 n을 사용한다
* 태그리스트1
들여쓰기할 태그 리스트, D를 이용하면 기본 태그를 사용한다.
기본 목록은 tr,td,div,ol,ul,li 이다.
추가하고 싶다면, D뒤에 컴마(,)를 이용 추가하면 가능하다.
D를 없애고 직접 적어주는것도 가능하다.
* 태그리스트2
들여쓰기는 안하지만 한줄에 하나만 있어야할 태그 리스트.
D를 이용하면 기본 태그를 사용한다.
기본 목록은 html,head,body,title,meta,table,link,map,select 이다.
* 태그리스트3
정리를 안할 태그 리스트, D를 이용하면 기본 태그를 사용한다.
기본 목록은 pre,style 이다.
script, ?, ?php 를 넣어두면 php와 javasript가 html의 정렬과 상관없이 정렬된다.