자바스크립트 정규식에 대한 고찰..

오늘 미투머니 테스트 코드를 작성하다가 그동안 안개속에 쌓였던, 정규식의 맘을 헤아리게 되어 몇가지 공유합니다. 제가 말하고자 하는 내용은 사실 이 포스트에 다 있어요.

정규식 RegExp Vs String

정규식은 기본적으로 문자열 패턴을 응용한 놈입니다. 그러니 당연히 String 객체와는 뗄레야 뗄수가 없는 놈입니다. 생각해보면 너무나 당연한 사실을 그동안 크게 신경 안쓰고 있다가 이제야 깨달았네요.

정리하면,

정규식은 2가지 형태로 사용할수있습니다.
1. 정규표현식 객체(RegExp)를 사용하는 방법
2. 문자열 객체(String)의 정규식 메소드를 이용하는 방법

메소드만 정리하면 대강 이렇습니다.

RegExp.test() - Boolean 값을 리턴
RegExp.exec() - 매칭된 값을 Array로 리턴
String.split() -
String.match() -
String.replace() -
String.search() -


RegExp.test() Vs RegExp.exec() 의 성능 차이

일반적으로 두 메소드 중에서, test() 메소드의 성능이 더 좋다고 얘기합니다. 왜 그럴까요? 그냥 성능이 좋다고 하니까 그려려니 하나요? 여기엔 이유가 있습니다. 바로 캡쳐링이라는 기능 때문입니다. 캡쳐링은 패턴으로 찾은 놈을 따로 저장하는걸 얘기합니다.

앞의 두 메소드의 리턴값이 하나는 Boolean 이고 하나는 Array 인 점이 바로 여기에 있습니다. 당연히, test 메소드가 리턴값이 Boolean 이므로, 성능이 더 좋겠죠? 네! 맞습니다. 하지만 단순히 리턴값의 사이즈가 작다고 성능이 더 좋다고 얘기할수는 없습니다. 왜냐면, 결국 RegExp 객체가 찾은 패턴을 모두 가지고 있기 때문이죠. 따라서, test() 메소드만을 사용한다고 성능이 좋아지는 것은 아니랍니다. 정확히 얘기하면, 어떻게 패턴을 정의하냐에 따라 달라지겠죠!!

예를 들어보겠습니다.

var str = "테스트 테스트1 테스트2 테스트3 테스트4";
var regx = /테스트\d/;

regx.test(str); // true
regx.exec(str);  // ["테스트1"]

위와 같이 실행하면 test()는 true, exec()는 [“테스트1”] 배열을 반환합니다. 그리고 여기서 하나더! 위에서도 잠깐 언급했지만 두 메소드 모두 결국 RegExp 전역 객체를 사용하게 됩니다.

그러니까, 각 메소드를 실행하고 아래와 같이 RegExp 객체의 $_ 와 $1 값을 확인해보면,
모두 같음을 확인할수있습니다.

RegExp.$_  // 테스트 테스트1 테스트2 테스트3 테스트4"  - 테스트할 문자열
RegExp.$1  // ""  - 캡쳐링된 문자열

이 얘기는 결국, test() 메소드와 exec() 메소드의 내부 구현은 같되, 리턴값만 다름을 의미합니다. 즉, 현재 까진 리턴 사이즈 말고는 성능이 같다는 얘기죠~!!

이제 좀 변형해봅시다. RegExp 객체에 패턴을 저장하기 위한 캡쳐링 옵션을 줘보도록 하지요. regx = /(테스트)/ 로 바꿔서 해봅시다.

var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(테스트\d)/;

regx.test(str);  // true
regx.exec(str);  // ["테스트1", "테스트1"]
RegExp.$1;       //"테스트1"    - test(), exex() 모두 같음.

뭐가 다른지 감이 오나요? exec()가 뱉어내는 리턴값을 유심히 보세요. 아직 모르시겠나요?
캡쳐링은 매핑된 결과를 RegExp 객체가 내부에 저장한다고 앞서 말씀 드렸습니다.
바로 그 캡쳐링된 결과를 RegExp.$1, RegExp.$2 등으로 읽어올수있습니다.
물론 캡쳐링은 하나의 패턴안에서 괄호를 여러번 사용함으로써 저장할수 있습니다.

가령, 이렇게 쓸수도 있다는거죠!!

var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(테스트)\s(테스트\d)/;

regx.exec(str);   // ["테스트 테스트1", "테스트", "테스트1"]

자 이제 exec()의 리턴값의 구성을 이해하시겠죠?

[매칭된 문자열, 캡쳐링된 첫번째값, 캡쳐링된 2번째값, 캡쳐링된 3번째 값, … , 캡쳐링된 N번째 값]

이젠 RegExp에 저장하지 않도록 비캡쳐링(?:xxx) 옵션을 주고 하나 더 해보죠.

var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(?:테스트\d)/;

regx.test(str);  // true
regx.exec(str);  // ["테스트1"]
RegExp.$1;       //  ""    - test(), exex() 모두 같음.

자~~ 이젠 어떤 차이가 있지는 아시나요? 여전히 모르시겠다구요? 비캡쳐링(?:) 옵션을 사용해서 RegExp 에 찾은 패턴값을 저장하지 않았습니다. 그러니까 exec() 리턴값에도 찾은 패턴값이 넘어오지 않게 되죠?

자 그럼, exec() 리턴값의 구성을  한번더 정리해보죠~

[매칭된 문자열, RegExp.$1, RegExp.$2, RegExp.$3, … , RegExp.$N]

마지막으로 그럼,

도대체 성능과는 무슨 관계가 있는거냐?

이 질문에 답할 시간입니다. 이미 눈치채고 계신분이라면, 조용히 닫기버튼을 누르셔도 됩니다. test()와 exec() 메소드의 성능차이는 간단합니다. 이렇게 정리하도록 하죠!

“간단한 패턴 문자가 있는지 없는지를 확인할 땐, 비캡쳐링과 test() 메소드 조합을 사용해라!

되셨나요? 이렇게 얘기 할수도 있습니다.

“test() 메소드를 사용할때는 반드시 비캡쳐링 패턴으로 정의해라!”

그 이유는 앞서 주구장창 설명한 캡쳐링되어 저장된 값 때문입니다.

패턴 플래그 g 에 대한 고찰

이제 오늘 깨달은 것의 하일라이트!! 바로 g 플래그 옵션입니다. 패턴 플래그는 총 3가지가 있죠. i, g, m 뭐 다 아실꺼라 생각하고 각각의 설명은 생략합니다. 모르면 검색해보아요~

이제 g에 일반적으로 알려진 사실을 흔히 쓰는 예제로 보면, 아래와 같습니다.

var str1 = "No pain, No gain!";
var str2= str1.replace(/ain/g, "XXX"); // str2 = "No pXXX, No gXXX!"

즉, “g 옵션을 쓰면, 모든 패턴을 찾게 된다.” Global 의 G 가 바로 그 g 옵션인거죠..

그러면, 얼핏 이런 사실을 알고 있을때, 지금껏 제가 착각해왔던 것은 아래와 같은 겁니다.
앞에서 해왔던 예제를 이어봅니다.

var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(테스트\d)/g;

regx.exec(str);  // ["테스트1", "테스트2", "테스트3"] 일까요???????

g는 글로벌 옵션이니까,.. 저렇게 나와야 하는게 아닐까?.. 하는거죠!! 결론부터 얘기하면 아닙니다! 왜인지는 아시겠죠? 아직도 그 이율 모르겠다면 앞에서 설명한 exec() 메소드의 리턴값 구성을 다시한번 보세요.

그러면 이렇게 해보죠..

var str = "테스트 테스트1 테스트2 테스트3";
var regx = /(테스트\d)/g;

regx.exec(str);  // 1번 실행, ["테스트1", "테스트1"]
regx.exec(str);  // 연속해서 두번 실행, ["테스트2", "테스트2"]
regx.exec(str);  // 연속해서 세번 실행, ["테스트3", "테스트3"]
RegExp.$1;  // "테스트3"

오잉? 저렇게 나올껄 예상 하셨나요? 올~~ 예상했다면, 당신은 규식이를 잘 하는 분입니다.
아직도 모르겠다구요? 글로벌 옵션이긴 하지만, 적어도 제가 기대했던 것과는 사뭇 다릅니다.

자 그럼 g 옵션의 비밀을 정리해보죠.. 정리하면,

패턴 플래그 g 옵션은 연속해서 패턴을 찾을때
다음 패턴 검색을 위해 RegExp 객체에 패턴 검색의 시작 위치를 저장해 둔다.


이해되셨나요? 앞에 예제를 곱씹어보세요~ ^^

test() 메소드와 exec() 메소드는 리턴값이 다른 만큼 분명 그 쓰임도 다릅니다.
따라서, 패턴을 어떻게 정의하느냐에 따라서 RegExp 객체에 얼마나 많은 정보가 캡춰링 되어 저장되는지 그 비효율성도 고민하셔야 합니다. 여기까집니다.

도움이 되셨나요? 도움이 되셨다면, 리플이나 광고라도 한번 눌러주세요.

당신의 개발속도는 얼마나 됩니까?

이번에는 예전에 작성한 하루에 얼마나 일하고 있나요? 에 이어서 2번째 시리즈입니다. 본래는 타임로그관련 3번째 포스트네요.
첫번째 포스트는 여기에 실었습니다.

이전 포스트에서는 타임로그를 작성법과 더불어, 타임로그 통계 정보들을 공유 드렸는데요..
이번에는 개발속도에 대해서 이야기 하려 합니다.

개발 속도와 추정

개발 속도를 이야기 하기 앞서, 지난 Sprint 5 에 대한 번다운 차트부터 봅시다!
[[ 이 글의 이미지도 역시 날려먹었네요. ㅜㅜ ]]

이전 포스트에서도 밝혔지만, 전 위와같은 챠트를 이용해, 현재 내가 얼마나 일하고 있는지..
그리고 앞으로 또, 얼마나 일해야 맡은 일을 완료 할수있는지 추정하고 있습니다.

그래프를 보시면, 이번 스프린트5 에서는 지난 스프린트와는 다르게 기간을 3주로 잡았습니다.
물론 3주로 잡은 나름의 이유는 있었죠. 여하튼, 빨간 가이드 라인과 녹색 그래프가 거의 비슷하게
일치하는 매우 준수한 그래프입니다. 평균 개발속도는 4~5를 왔다갔다 하는군요.
( 실제 속도는 78/15 = 5.2 시간입니다. 그래프와 약간의 오차가 있군요. )

지난 스프린트4 와 비교했을때, 역시 비슷한 속도를 내고 있습니다. ( 이전 속도는 5.6 이었습니다. )
이제 어느정도 일정한 속도를 내고 있다고 얘기할수 있겠군요.

하지만, 최종 그래프가 찍은 시간을 보면, 초기 추정 68시간보다 10시간 많은 78시간을 일했다고 얘기하는군요. 그래프를 보아하니, 마지막 3주차에 아하… 뭔가가 이슈가 있었나 봅니다.
그래서 무엇이 문제 였는지 로그를 디벼봤습니다.

추정의 실패한 원인 찾기

이슈가 있었던것이라기 보다는 다른 개발자가 사전에 해주어야할 일정이 예상보다 늦어지면서,
전 다음 스프린트에 해야할일을 땡겨서 해버렸습니다. 왜냐면 놀수는 없는 거잖아요. ㅎㅎ
분위기상 띵가띵가 놀수도 없고.. ㅋㅋㅋ

그러다 보니, 받듯하게 가야할 노랑색 추정시간이 마지막 차수에 10시간이나 올라가 버렸습니다.
일단 그래프만 보아서는 추정에 실패한 문제가 있는 그래프 이지만, 로그를 확인해보니,
사연이 있었군요.. 리즈너블합니다. ( 뭐 저 혼자만의 생각일수도 있어요.. ㅋㅋㅋ)

그래도 그나마 다행인것은, 노랑색 추정그래프가 왔다갔다 하지 않고, 일정 하게 수평을 긋고 있다는 사실입니다. 노랑색 그래프가 한쪽 방향으로 올라가거나 내려간다면, 분명 초기 추정에 실패한것입니다.
따라서, 우리는 매순간순간 철저하게 일정을 추정해야하고, 성실하게 로그를 기록해야
아~~~ 내가 이쯤 달리고 있꾸나 를 넘어서,
아~~~ 우리가 이쯤 달리고 있구나 하는것을 알수가 있는 것이죠. ^^

정답은 역시 타임로그!

그렇다면, 역시 답은 하나밖에 없습니다.
앞서 언급했던 개개인의 타임로그를 부지런히 남기는 수밖에 없습니다.

“수신제가치국평천하” 라고, 개인의 타임로그를 부지런히 남겨야,
전체 프로젝트 관리도 잘 될수 있다는 사실 다시 한번 곱씹어 봅니다.

맥에 기본 설치된 Apache 활용팁

Mac OS X 에는 tiger 버전 부터 기본으로 설치된 Apache2가 있다.
현재 내가 쓰고 있는 Mac 버전은 스노우래퍼드고,  apache2 의 위치는 아래와 같다.

/etc/apache2/


아파치 서버 실행 방법
그리고 맥에 기본 설치된 아파치를 실행하기 위해선 아래와 같이

시스템환경설정 > 공유 > 웹공유 를 체크하면 된다.
사용자 삽입 이미지

가상 호스트 설정

일반적인 가상 호스트 설정하는 방법과 마찬가지로, vhost-httpd.conf 라는 파일을 생성해,
httpd.conf 에서 Include 하는 방법을 알아보자.
먼저 기본으로 설치된 아파치 폴더에서 /etc/apache2/httpd.conf 파일을 열고, 맨아랫줄을 보면, 아래와 같이 주석처리가 되어있는 부분에서 주석(#)을 제거하자.

#Include /private/etc/apache2/other/*.conf

그리고 other 폴더에 가보면, 맥은 참으로 친절하게 이미 vhost-httpd.conf 파일을 만들어놨다것을 알수있다. 이제 vhost-httpd.conf 파일을 열어서, 원하는대로 호스트 설정을 하면 되시겠다!

대충 샘플은 아래와 같으니, 적당히 수정해서 쓰면 끝~!!

<VirtualHost *:80>
    DocumentRoot “/Library/WebServer/Documents/Me2Money”
    ServerName local.me2day.net
    ErrorLog “/private/var/log/apache2/local.me2day.net-error_log”
CustomLog “/private/var/log/apache2/local.me2day.net-access_log” common
<Directory “/”>
Allow from all
Options +Indexes
</Directory>
ProxyRequests Off
<Proxy /*>
Order deny,allow
Allow from all
</Proxy>
ProxyPass /seleniumReport/ http://local.me2day.net:8088/seleniumReport/ retry=1
ProxyPreserveHost Off
</VirtualHost>
이제 테스트를 위한 /etc/hosts 파일을 열어서, 원하는 호스트를 설정을 한다.
#Me2Money
10.0.1.4 local.me2day.net
그리고, 브라우저에서 설정한 호스트(local.me2day.net)로 접속하면, 로컬 아파치로 접속하게 된다!

Apache + PHP 연동

맥에는 역시, php 모듈도 기본 설치 되어 있다. 연동은 너무나 간단하다.
사실상, 이미 연동되어 있다고 생각해보 무방하다.
기본으로 설치된 PHP 연동을 위해서는 httpd.conf 파일을 열면,
아래와 같이 주석으로 처리된, php 모듈만 로드해주면된다.

#LoadModule php5_module        libexec/apache2/libphp5.so

간단히 주석을 제거한후, 아파치를 다시 실행하자.
그리고 간단한 phpinfo(); 파일을 출력해보자.

사용자 삽입 이미지
올레~~ 성공~!!
차암~ 쉽죠~!!