만약 MySQL대신 다른 Store를 이용하고 싶다면 다른 Store 클래스로 객체를 만드시면 됩니다. Factory 패턴이기 때문에 따로 신경안써줘도 되는건 OO 공부하신 분들이면 다 아실테지요.
[php]
$store = new Auth_OpenID_FileStore($store_path);
[/php]
예를 들어 파일패스를 스토어로 쓰고 싶다면 이렇게 해주면 된다는 말씀.
이제 만들어진 스토어 객체로 Consumer 객체를 만듭시다.
[php]
$consumer = new Auth_OpenID_Consumer($store);
[/php]
일반적인 시나리오에서는, 사용자는 Consumer 사이트에 와서 자신의 OpenID를 Form으로 입력하겠죠. 어쨌거나 인증하고자 하는 OpenID값을 Consumer 객체에 던져주기만 하면 됩니다.
[php]
$openid = $_POST[’login_openid’]; // 뭐, 예를 들자면.. 이런 식이겠죠:)
$auth_request = $consumer->begin($openid); //오픈아이디 서버와 association을 시도하고 그 결과를 돌려줍니다.
if($auth_request) {
//success;
} else {
//fail;
echo “Authentication Fail”;
}
[/php]
$auth_request가 true이면 정상적으로 association이 이루어졌다는 뜻입니다. 만약 false라면 인증에 실패했다는 메시지를 사용자에게 보여주면 되겠죠?
association이 성공적으로 이루어졌다면 이제, 해당 openid에 대한 인증을 시도하면 됩니다.
[php]
$trust_root = ‘http://consumer.com’;
$process_url = ‘http://consumer.com/afterAuth’;
$redirect_url = $auth_request->redirectURL($trust_root, $process_url);
header(”Location: “.$redirect_url);
[/php]
$trust_root는 Provider에게 이 인증시도가 어떤 Consumer로부터 시도된 것인지 신뢰여부를 판단하게 해주는 일종의 키값이 됩니다. 완벽하진 않지만, trustroot와 인증시도 도메인간에 도메인이 다를 경우 피싱시도로 파악해서 인증을 거부하거나 할 수 있도록 하지요. 반대로, trustroot로 인정된 도메인의 경우에는 따로 지정하지 않는 한, 인증을 자동보장한다거나 하는데 쓰일 수 있겠지요.
$process_url은 OpenID Provider가 인증을 처리하고 난 후 그 결과값을 되돌려받을 Consumer 사이트의 url입니다.
위의 코드가 실행되면, $redirect_url이 되돌려집니다. $redirect_url은 Provider의 인증처리 페이지에 인증 Request를 Get 메쏘드로 전달하는 형태이지요. 즉, 이렇게 만들어진 $redirect_url로 redirect시켜주기만 하면 됩니다.
만약 단순한 인증으로 끝나지 않고, Simple Registration Extension를 이용해 사용자 정보를 얻어오길 원한다면
[php]
$auth_request->addExtensionArg(’sreg’, 요구필드타입, 요구필드이름리스트);
[/php]
를 redirecURL메쏘드 실행전에 실행시켜주면 됩니다.
요구필드타입은 required/optional이구요, 비록 required라고 해도 Provider에서 반드시 정보를 제공한다는 보장은 없으니(Simple Registration Extension를 지원하지 않거나, 사용자가 정보제공을 거부하거나…), required로 요구한 필드의 되돌아오는 값이 없다해도 에러는 아니라는 점.
그외에, policy_url을 이용해, 어떤 필드들을 왜 요구하는지 설명을 알려줄 수도 있지요.
요구필드로 현재 Simple Registration Extension 에 정의된 필드는,
의 8 가지인데요, 주의할 점은 각각의 필드의 리턴값은 RFC와 ISO에 정의된 표준 스펙이라는 겁니다. 그러니까, country를 알려달라고 요구해도, “Korea”라든가 “대한민국”같은 형식으로 돌아오지는 않는다는 거지요.
각각의 필드에 대한 스펙 및 Simple Registration Extension에 관한 더 자세한 설명은, 여기를 참고하세요.
이제 인증 요청이 끝났습니다. redirect를 통해 Provider에게 GET 메쏘드로 인증요청을 했으니 나머지는 Provider와 사용자간에 나름의 과정을 통해 인증여부가 회신되겠지요?
전에 지정해둔 $process_url로 회신이 돌아옵니다.
다음은 $process_url에서 GET으로 돌아온 회신에 대한 처리부분입니다.
[php]
$response = $consumer->complete($_GET);
switch($response->status) {
case Auth_OpenID_CANCEL :
// 사용자가 인증을 취소했을 때의 처리
break;
case Auth_OpenID_FAILURE :
// 무언가의 문제로 인해 인증이 실패했을 때의 처리(인증을 요구한 openid가 없다든가..)
break;
case Auth_OpenID_SUCCESS :
// 인증성공!!
break;
}
[/php]
인증은 모두 끝났습니다. 간단하죠?
만약 Simple Registration Extension을 사용했을 경우, 요구했던 필드값들을 구해야겠죠?
그러니까 OpenID 지원하는게 뭐 대단한 것도 아닌거에요. 무슨 코어모듈을 고쳐야 한다거나 하는 작업이 있을리도 없으니까, Consumer 지원하는 건 어려운 일이 아니에요. 물론 Provider가 되려면 좀 더 복잡한 작업이 필요하긴 합니다만, Provider는 많을 필요도 없고요.
OpenID의 의의를 이해하신다면, 별 특징이나 기능도 없는 Provider가 늘어나는 건 그저 사용자들에게 불편만 주고 OpenID의 장점을 전혀 못살리는 멍청한 짓이기도 하지요.
delegation을 이용하면 어떤 Provider를 쓰든 상관없는 거니까요. (오히려 Simple Registration Extension을 지원안하는 Provider라면 편의성이 떨어지는 셈이니 이용에 고민을 해봐야겠지요. 물론 보안문제가 아직 완벽하게 해결된 것은 아니므로 SRE를 믿느냐는 좀 다른 이야기긴 합니다만.)
국내에서 OpenID를 이야기하는 사람들이 많아지긴 했습니다만… 뭐랄까, 실제 개발자들에게는 그런 뜬구름잡는 이야기보다는 간단한 샘플소스에 대한 설명 하나가 더 필요한 시점아닐까 하는 생각이 드네요. 정말로 OpenID를 널리 퍼뜨리게 하고 싶다면요. 혹은, 그냥 기술적 우위를 뽐내는 걸로 만족하는 거라면 또 다른 이야기겠지만요.
약속했던 대로, OpenID 구현에 대한 이야기입니다.
이번은 JanRain의 라이브러리를 이용한 초간단 OpenID Provider 제작편입니다.
JanRain의 라이브러리는 원래 Python으로 구현되었습니다만, 현재는 여러 언어로 포팅되어있지요. Ruby, Perl, PHP, DotNet, Java, ColdFusion 등이 가능합니다.
여기에서는 PHP Library를 이용하겠습니다.
주의 : php 4.3 이상. Bcmath, GMP 패키지 등이 설치되어 있어야 합니다.
MySQL의 경우에는 innoDB를 사용합니다.
로 설치하면 간단 끝.
만약 경로를 따로 제어하고 싶다거나, 혹은 Pear를 쓸 수 없는 환경이라면, 그냥 다운로드 받아서 적당한 곳에 풀어주면 됩니다.
2) 구조
압축을 풀면 여러가지 디렉토리가 있는데, 실제적으로 중요한 곳은 /Auth입니다. 이 안에, OpenID 컨슈머와 프로바이더 모두를 위한 클래스들이 들어있습니다.
실제로 PHP4로 제작되어 있기 때문에 클래스 내부 코드들이 완벽한 OO 스타일을 유지하고 있다고 말하기는 어렵습니다. 코드들은 거의 미로수준. 게다가, 은근히 도큐먼트가 부실해서…
3) 구현
OpenID는 프로바이더와 컨슈머 사이에서 키를 발급하고 조회하는 메커니즘으로 운영됩니다. 발급된 키를 관리하는 방법은 여러가지가 있는데, 예를 들어, 메모리 세션을 이용하거나, 파일 세션, 혹은 DB 세션등을 이용하기도 하지요. 여기에서는 MySQL을 Store로 사용하겠습니다.
우선, 필요한 파일들을 include해야겠지요?
[php]
require_once(’Auth/OpenID.php’);
require_once(’Auth/OpenID/Server.php’);
require_once(’Auth/OpenID/MySQLStore.php’);
[/php]
내부적으로 다른 인클루드 파일들을 요구하기 때문에, include_path에 아까 설치한 Library위치를 포함시켜두면 좋겠습니다. (아니면 ini_set(’include_path’,) 를 설정하던가요.)
그리고 PEAR::DB를 이용한 DB커넥션을 Store로 사용할 것이므로 DB인스턴스를 만들어서 선언해줍니다.
도큐먼트에 보면, 2번째 파라미터부터는 optional하다고 되어 있고, null값일 경우 default 정의된 테이블 이름으로 생성된다고는 되어있습니다만, 무엇의 문제인지 null값을 넣으면 제대로 생성되지 않더군요. 그리고 createTables()메쏘드를 실행해줍니다. 위에서는 주석처리해놓았는데, 사실 한번 테이블이 생성되면 저 메쏘드는 필요없어지기 때문입니다. 물론 여러번 실행시켜도 문제가 되지는 않습니다. (내부적으로 rollback)
이제, 해당 Store를 사용하는 OpenID Server 객체를 만들어야겠지요. 만약 다른 DB Store나 혹은 File Store를 쓰고 싶다면 해당 Store 클래스로부터 만들어진 객체를 $store로 넣어주면 됩니다.
[php]
$server =& new Auth_OpenID_Server($store);
[/php]
이제, 이 만들어진 Server 객체에 OpenID Request를 넘겨주면 되는데, 이 Request는 대개 Get메쏘드로 넘어온 값을 가지고 Request객체를 만들어 주면 됩니다.
[php]
//$value는 대개 Get으로 넘겨받은 OpenID request문자열입니다. 서버 구현에 따라 이 부분은 달라질 수도 있으니 알아서…
$request = Auth_OpenID::fixArgs($value);
$request = $server->decodeRequest($request);
[/php]
이 Request 객체의 멤버중에 mode라는 멤버가 있습니다. 이에 따라 해당 작업을 해주면 됩니다.
[php]
switch($request->mode) {
case ‘checkid_immediate’:
…
break;
case ‘checkid_setup’:
…
break;
default:
…
break;
}
[/php]
특별한 문제가 없는 한, association mode는 라이브러리에서 알아서 처리해줍니다. 물론 발급되는 키값을 바꾸거나 하고 싶다면 이 부분을 고쳐줘야겠지요. 어쨌거나, 특별히 고칠 부분이 없다면, 위의 default부분에, 아래의 코드를 넣는 것만으로 충분합니다. 이것은 user정보와는 상관없는 기본적인 handshake 단계의 통신이기 때문에 사실 건드릴 것도 별로 없는 셈이죠.
[php]
$response =& $server->handleRequest($request);
$webresponse =& $server->encodeResponse($response);
foreach ($webresponse->headers as $k => $v) {
header(”$k: $v”);
}
header(header_connection_close);
print $webresponse->body;
exit(0);
[/php]
checkid_setup과 checkid_immediate의 경우에는 프로바이더의 사용자 시나리오에 따라 복잡한 과정들이 처리되어야 하는데, 예를 들자면, 사용자가 인증을 허용하는지 여부등에 따라 반환되어야 하는 값이 달라져야겠지요.
만약 모든 조건이 클리어하다면, (예를 들어, Consumer사이트가 trustRoot에 포함되어 있고, 사용자가 해당 Consumer사이트로의 인증을 허락했다면…)
[php]
$response =& $request->answer(true);
$webresponse =& $server->encodeResponse($response);
foreach ($webresponse->headers as $k => $v) {
header(”$k: $v”);
}
header(header_connection_close);
print $webresponse->body;
exit(0);
[/php]
로 끝.
만약 Simple Registration Extended를 지원한다면, openid.sreg.required / openid.sreg.optional 의 형태(php에서는 openid_sreg_required / openid_sreg_optional 이라는 해쉬값으로..)로, 필요한 사용자의 데이터를 요구하게 됩니다. 이 경우에는,
[php]
$response->addField(’sreg’, 요구필드이름, 해당 데이터);
[/php]
형태로 추가해주면 되지요.(물론 $server->encodeResponse()전에..)
Provider의 사용자 시나리오에 따라서는, 로그인처리, 인증여부처리, 요구필드에 대한 허가 처리 등이 필요한 경우도 있을 수 있습니다. 그런 경우에는 Ajax나 혹은 일반 Form 형태로 사용자 브라우저에서 처리를 받은 후에, 현재 호출된 URL로 다시 redirect해주면 됩니다. (OpenID Request는 Get값으로 이루어지므로… 물론 처리페이지까지 OpenID Request들을 전달해서 그 페이지에서 처리하는 방법도 있겠지요.)
만약 올바른 요구와 답변이 아닐 경우에는,
[php]
$response =& $request->answer(false);
$webresponse =& $server->encodeResponse($response);
foreach ($webresponse->headers as $k => $v) {
header(”$k: $v”);
}
header(header_connection_close);
print $webresponse->body;
exit(0);
[/php]
로 해주면 됩니다.
나머지는 라이브러리에서 다 알아서 해주니까, Provider 개발자가 고민할 부분은, 사용자 시나리오를 어떻게 운영할 것인가.. 하는 부분이겠죠?
이상으로 OpenID 그까이꺼 만드는 법 Provider편 초간단 설명.
물론, 남의 라이브러리를 의지하지 않고, 프로토콜만 가지고 직접 구현할 수도 있겠습니다만, 뭐 굳이 그럴 필요 있나요?
Consumer편은 더 간단합니다만, 다음 기회에. 사실 Provider는 굳이 여러 곳에서 만들 필요는 없어요. 공연히 사용자에게 불편만 줄 뿐. 실상 Cyworld나 Naver Blog같은데서 지원하면 깨끗이 정리될 문제입니다만, 그럴 생각들은 없는 것 같고… (OpenID Provider라면 당연히 자사의 서비스에 Consumer도 붙여야 체면이 서겠습니다만… 과연 N이나 S가 그럴 것인지? ^_^)
http://www.openid.ne.jp
근 한달여 삽질 끝에 오픈했습니다.
일본 서비스라 한국에 계신 분들이 사용할리는 없겠지만.
CNET과 니케이 등에 기사가 났더니 제법 많이 들어오는군요. 일본 최초의 OpenID 구현이라 관심들이 많은가 봅니다.
물론, 서버만 제공해서는 아무 의미도 없으니, choix에 OpenID 컨슈머 모듈을 붙여 가입과 로그인을 OpenID로 가능하게 했습니다. News 2.0에도 조만간 붙겠죠.
JanRain의 라이브러리를 많이 참고(실은 거의 베꼈…)했음에도 여전히 숨은 버그들이 있습니다. 안들키기만 조마조마하게.. ^^; 실은 아직까지도 livejournal.com에는 왜 연결이 안되는지 원인파악도 안되지만.
저는 웹 프레임워크를 설계하고 OpenID 인증 파트를 개발했습니다. HTML 코드에 손댈 수 있는 상황이 아니라서, 코드에 불만이 있으신 분들도 있겠지만 조만간 깔끔하게 웹표준이 가능하도록 바꿔놓겠습니다. 그래도 javascript 없이 동작하고, 마이크로포맷을 조금 응용해넣기까지는 했습니다. 조금씩 고쳐가야죠.
시간이 나면 OpenID 서버와 컨슈머 구현에 대해 조금씩 정리해볼까 합니다. 저 자신도 아직 완벽히 프로토콜을 이해하고 있지는 못해서 말이지요. 복습하는 마음으로…
ps. 오픈마루 분들이 일본에 오셔서 오픈아이디 이야기를 꺼내셨을 때는 회사 내에서 다들 뜨끔… 조금 빨리 준비했다면 한국에서도 먼저 오픈할 수 있었을 텐데… 뭐, 그래도 OpenID를 이용하는 서비스들이 늘어난다면 좋은 거지요. OpenID 컨슈머들이 많이 늘어났으면 좋겠습니다.
Operator는 현재 보고 있는 웹페이지의 마이크로포맷을 분석해서 다른 서비스와 연동시키는 익스텐션입니다.
Operator가 파악하는 마이크로포맷은 현재,
hCard - 누구
hCalendar - 언제, 무엇
geo - 어디서
hReview - 무엇에 대해
tag - 어떤 키워드
hResume - 어떤 이력
xFolk - 어떤 북마크
입니다.
페이지 내에 구성된 이러한 데이터를 가지고 다음과 같은 여러가지 액션들을 수행할 수 있습니다.
내 “주소록”에 연락처 추가하기
내 “iCal”에 이벤트 정보 추가하기(혹은 Outlook에, 혹은 Google Calendar에…)
GoogleMap으로 해당 페이지에 언급된 장소 찾아보기(혹은 YahooMap으로..)
GoogleMap으로 해당 페이지에 언급된 인물의 주소 찾아보기
del.icio.us에 해당 태그로 컨텐트 북마크하기
Flickr에서 이 컨텐트와 관련있는 이미지 찾기
Technorati에서 관련 블로그 찾아보기
Yedda에서 관련 정보 찾아보기
Upcoming.org에서 관련 이벤트 찾아보기
Ma.gnolia에 이벤트나 장소, 인물을 저장해두기
한국에서는 사용이 거의 힘든 서비스입니다. 하긴, 외국에서도 아직은 그다지 마이크로포맷 자체가 널리 퍼지진 않았습니다만, 그와는 별개로, 마인드 자체가 국내에서는 아직 성숙하지 않아서지요. Yahoo나 Google, Technorati같은 굵직굵직한 벤더들이 마이크로포맷을 적극적으로 활용하고 있는데 반해, 국내 IT서비스들은 아직 마이크로포맷이 무엇인지 조차 모르는 경우가 대부분이고, 당장 업무에 적용시켜야 할 HTML 퍼블리셔, 기획자, 개발자들이 시맨틱 마크업에 대해 익숙치 않아서입니다.
나름 개인적으로 비슷한 서비스를 만들어서 혼 쓰고 있었습니다만, 확실히 브라우저단으로 기능이 올라가니까 훨씬 사용하기 쉽군요. 웹용으로 만든 건 폐기처분하고 이 익스텐션을 적극 활용해야겠습니다.
1. 인쇄 전용 CSS
어떤 면에서 프린터는 브라우저보다 CSS를 더 잘 구현해내기도 한다.
이글루스의 경우 print.css를 붙이기는 했는데, 실상 상당히 단순한 속성들로 구성되어 있다. 프린트 전용 CSS라기 보다는, 마치 초단순 스킨이라고 불리워도 무방할 정도로.
기왕 프린트를 위한 CSS라면 프린트 전용 CSS를 맘껏 사용해보는 것도 좋았을 텐데.
(이 print.css가 이글루스 스킨 시스템에 포함되어 사용자가 마음껏 고칠 수 있는 종류의 것인지는 모르겠다. 그렇다면, 심플한 CSS만 제공하고, 기교를 부리는 건 사용자에게 맡기도록 하는 것은 좋은 방법일 수 있다.)
아무튼, 인쇄 전용으로 사용가능한 방법으로는,
셀렉터
@page : .페이지 단위의 셀렉터. 하나의 페이지로 이루어진 모니터와는 달리, 인쇄는 물리적인 여러개의 페이지로 이루어진다.
@bottom-left-corner, @bottom-left, @bottom-center, @bottom-right, @bottom-right-corner : 페이지의 푸터영역 셀렉터(bottom-margin이 있는 경우)
@top-left-corner, @top-left, @top-center, @top-right, @top-right-corner : 페이지의 헤더 영역 셀렉터 (top-margin이 있는 경우)
속성
page-break-after, page-break-before, page-break-inside : 보기좋은 인쇄를 위해 강제로 페이지 브레이크를 걸어야 할 때 사용.
content : 해당 셀렉터에 인쇄시 내용을 추가할 때 사용한다. (예를 들어 @page:first의 @top-left-corner에 저자 이름을 따로 적어둔다거나 하는 식.)
counter-increment, counter-reset : 인쇄물의 페이지 번호등을 조절할 때 사용.
이외에도 더 많은 테크닉들이 있으나 프린터 및 프린터 확장 호환장비들에 따라 미세하게 다르므로 대충 이정도로 소개만. 더 자세히 알고 싶으신 분은, 여기를 참고.
기타 주의사항 :
프린터는 최소한 16가지의 색상을 구별이 가능하도록 다른 형태로 인쇄할 수 있다-인쇄해야 한다.(흑백이라 하더라도 패턴등의 방법을 사용하여.)
프린터는 인쇄물에 대한 페이지 사이즈를 지정할 수 있어야 한다.
2. 링크
링크는 웹문서의 기본이며, 문서의 가장 중요한 컨텐트 요소중 하나이다.
그런데 인쇄를 하면 이러한 링크정보가 사라지게 된다.
링크가 사라진 문서에, ‘링크였던’ 부분에 밑줄만 쳐져 있어 봤자 무슨 소용이 있을까.
erehwon님의 포스팅으로 보아 아마도 이 문서를 참조한 것 같은데, 해당 문서 중에 좋은 팁이 들어있다.
[css]
#content a:link:after, #content a:visited:after {
content: ” (” attr(href) “) “;
font-size: 90%;
}
[/css]
못보고 지나쳤을리는 없을 텐데, 왜 안들어 있을까.
아마도 링크를 인쇄물에 포함시켰을 때 인쇄물이 너무 지저분해지기 때문이었으리라. 또, IE에서는 통용되지 않는 다는 점도 고려되었을테고.
그럼에도 불구하고, 링크의 인쇄는 미관적인 이유로 빼버리기에는 너무 중요한 요소이다.
좋은 방법이 있을까?
몇십줄의 자바스크립트의 추가로 더 유용한 인쇄물을 만들 수 있다. 지금 보고 있는 이 페이지를 인쇄하고 그 결과를 이글루스의 그것과 비교해보자. 대부분 거의 비슷하겠지만,
단 한군데 다른 점을 발견할 수 있을 것이다.
(예시로 들기에는 이 블로그의 디자인이 너무 심플한데다가, 본인은 디자이너도, 퍼블리셔도 아니기에. 더 좋은 예시를 들지 못함이 아쉽다.)
3. 더 생각해 볼 만한 것 - selective print.
개인적으로는 그다지 좋은 방법이라고는 할 수 없기에 간단히 링크만 걸어둔다.
프린터용 CSS를 만드는 간단한 방법.
1. 인쇄에서는 사용할 수 없는 기능이 담긴 엘리먼트(검색창, 메뉴등)를 display:none;으로 감춘다.
2. 폰트타입을 바꾼다. 인쇄시에는 작은 글씨의 경우 모니터와는 달리 serif 종류의 폰트들이 더 가독성이 높다. 인쇄물의 특성을 고려하여 폰트를 바꿔준다.
3. 장식용 이미지가 필요하다 해서 HTML코드를 다시 고치지 말고, :after, :before등의 pseudo-element를 사용해 인쇄용 이미지를 추가한다.
4. 대개의 경우, 공용 CSS등에서 bullet 스타일을 지워버렸을 수 있다. 인쇄시에는 까먹지 말고 다시 bullet스타일을 지정해주자.
5. position, float을 사용한 블록들은 인쇄시에 의도한 곳과는 다른 곳에 인쇄될 수 있다. 되도록이면 복잡하지 않은 인쇄레이아웃을 잡도록 한다.
다음은, W3C에서 제공하는 인쇄용 CSS 샘플이다. 이걸 그대로 쓰라는 것이 아니라, 일종의 초기화 가이드라인 삼아 제작하면 좋을 것이다.
[css]
th { font-weight: bolder; text-align: center }
caption { text-align: center }
body { line-height: 1.33 }
h1 { font-size: 2em; margin: .67em 0 }
h2 { font-size: 1.5em; margin: .83em 0 }
h3 { font-size: 1.17em; margin: 1em 0 }
h4, p,
blockquote, ul,
form,
ol, dl { margin: 1.33em 0 }
h5 { font-size: .83em; line-height: 1.17em; margin: 1.67em 0 }
h6 { font-size: .67em; margin: 2.33em 0 }
h1, h2, h3, h4,
h5, h6, b,
strong { font-weight: bolder }
blockquote { margin-left: 40px; margin-right: 40px }
i, cite, em,
var, address { font-style: italic }
pre, tt, code,
kbd, samp { font-family: monospace }
pre { white-space: pre }
big { font-size: 1.17em }
small, sub, sup { font-size: .83em }
ol, ul, dd { margin-left: 40px }
ol { list-style-type: decimal }
ol ul, ul ol,
ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
br { content: “\A” }
February 1, 2007 at 10:48 pm · Filed under WebDevelopment
오늘의 교훈: Assertion의 습관화.
Code Complete에 보면, Assert에 관해 한 장을 할애할 정도로 중요하게 여기고 있다. The Pragmatic Programmer에서도 중요하게 다루고 있는 주제.
원론적인 면에서 동의하면서도 현실에서 체감한 적은 별로 없었는데… (솔직히, 너무 뻔한 이야기 아닌가… 라는 생각까지 했었다.)
근 2주가까이 한가지 문제를 해결하기 위해 고민하고 있었는데, 정작 문제는 너무나 간단한 곳에 있었다.
[php]
private function doSomething($param=null) {
…
}
[/php]
이런 메쏘드가 있을 때, 이 코드로만 보아서는 $this->doSomething();처럼 호출해도 문제가 없을 것 처럼 보인다. 주석 및 도큐먼트에는 심지어, $param은 optional하다고 적혀있기까지 하다.
그런데 실제로, 코드를 추적해본 결과, 개발자의 의도와는 달리 이 코드는 특정조건 하에서는 $param값이 null이 되면 오작동하도록 만들어져 있었다.
일차적으로는 개발자가 주석과 도큐먼트를 잘못 작성한 셈이지만,
근본적으로는 Assertion을 제대로 하지 않은 코드의 문제. 선행조건을 엄밀히 명시해두지 않았기 때문에 발생했다.
최소한 다음처럼 코드가 작성되어 있었어야 했다. 오류를 해결하는 것보다, 보고하는 것이 먼저다.
(어디가 문제인지 알아야 고치기라도 할 것 아닌가.)
[php]
private function doSomething($param=null) {
assert(’$this->someCondition || $param’);
…
}
[/php]
물론 동적으로 생성되는 값들에 대해 미리 선행조건을 알고 있기가 불가능한 경우도 있겠다. 그런 경우에라도 크리티컬한 미션을 수행하기 직전에, 해당 미션이 클리어되기 위한 선행조건에 대한 assertion을 붙이는 방어적 프로그래밍을 습관화해야겠다.
* 좋은 Assertion을 위한 지침. (from Code Complete)
1. 여러분이 발생할 것이라고 예상하는 상황들에 대해서 오류처리코드를 사용하라. 즉, 절대로 발생해서는 안 되는 조건을 위해서 어설션을 사용하라.
2. 실행가능한 코드를 어설션내에 입력하지 않는다.
3. 선행 조건과 후행 조건을 문서화하고 검증하기 위하여 어설션을 사용하라.
4. 매우 견고한 코드를 작성하기 위해서는 어설트한 다음 오류를 처리하라.
* PHP를 위한 assert function
[php]
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_WARNING, 0);
assert_options(ASSERT_BAIL, 1);
assert_options(ASSERT_CALLBACK, ‘assertHandler’);
function assertHandler($file, $line, $message) {
echo “$file : $line : $message”;
}
개인적으로는 MS Project의 강력함에 흠뻑 취해있고, Windows환경만 생각한다면 Outlook만으로 충분히 프로젝트 일정 관리를 해낼 수 있지만…
어쩌다보니 그런 환경이 여의치 않을 때 소규모 프로젝트를 관리해야한다면 Google Calendar를 추천합니다. 물론 프로젝트 참여인원 모두가 Google 계정이 있어야 한다는 제약이 있긴 하지만요.
사실 많은 회사에서 인트라넷으로 프로젝트 관리 도구를 사용하고 있습니다만, 지금까지 경험해본 바, 그러한 솔루션들이 구글 캘린더보다 못하더군요.(Outlook보다 못함은 당연하구요.) 물론, ERP환경에서 전자문서결재…등등을 포함하는 기능들은 분명 훌륭합니다만, “일정관리/공유”라는 면에서 본다면 많이 미흡하지요.
하긴, 그나마 그런 인트라넷도 없어서 여전히 엑셀 시트에 달력 그려서 관리하는 곳도 있으니까, 그런 열악한 환경이라면 구글 캘린더를 일정관리에 사용해보는 것도 괜찮겠습니다.
[프로젝트 관리자]
1. 프로젝트 관리자는 프로젝트 이름으로 새로운 Google 계정을 하나 만듭니다. (ex:ourproject@gmail.com)
2. 만들어진 계정으로 접속해서 Google Calendar로 들어간 다음, 왼쪽 하단의 Calendars에 “My Calendar +” 버튼을 클릭해 새 캘린더를 만듭니다.
3. 프로젝트 전체와 관련된 일정 (예:Milestone)을 입력합니다.
4. 이제, 만들어진 캘린더를 프로젝트 멤버들과 공유해야겠지요. “이 캘린더 공유”로 들어가 멤버들의 Google 계정을 사용자로 추가해줍니다. 이 때 옵션은 “모든 이벤트 세부정보 보기”(멤버들이 보기만 하고 변경은 할 수 없습니다.) 로 놓는게 좋겠지요.
5. Trac등의 관리도구를 쓴다면 거기에서 만들어진 일정 데이터를 iCalendar형태로 입력받을 수 있습니다. “기타 캘린더 +” 버튼을 눌러 “공개 캘린더 주소”로 들어가 Trac등에서 만들어진 일정데이터의 iCalendar주소를 입력하면 됩니다.
[프로젝트 멤버]
1. 멤버들은 각각 자신의 구글 캘린더에 접속한 후, 역시 “내 캘린더”를 새로 만듭니다.
2. 캘린더 이름에 자신의 이름을 적어 새로운 개인 업무 일정 캘린더를 만든 후에,
3. 역시 캘린더 공유에서 프로젝트 관리자를 공유 사용자로 추가해줍니다. 이 때 옵션은 “변경 및 공유 관리”로 해두어야 프로젝트 관리자가 각 개별 멤버의 일정을 조정한다든가 하는게 가능해지니 주의.
4. 자신의 캘린더를 수정하면 프로젝트 관리자의 캘린더에도 수정사항이 반영됩니다. 물론 그 반대도 가능.
5. 만약 캘린더 중의 특정 이벤트가 “회의”나 “공동작업”처럼 다른 사람과 공유되어야 할 경우, 이벤트 수정 페이지에서 해당 멤버를 “손님”으로 추가함으로써 이벤트를 공유할 수 있습니다.
6. 프로젝트 관리자가 새 이벤트를 각각의 멤버에게 할당한다든가 커멘트를 남긴다든가 하는 활용법도 있겠지요.
7. Gmail을 통해 여러가지 notifying이 가능하므로 평소에 Gmail 을 볼 수 있는 Alarm기능을 데스크탑에 설치해두는 쪽이 좋겠습니다. (Google Desktop이나, GoogleTalk나… 혹은 Gmail을 자신의 메일 클라이언트에서 받아 볼 수 있게 한다든가…)
[자신의 PIMS와 싱크하기]
안타깝게도, 아직 Google Calendar와 양방향 싱크가 되는 PIMS는 없는 것 같네요.
일단 OSX의 iCal에서는 단방향 싱크는 가능합니다.(Outlook Vista에서도 가능할지도. 테스트는 안해봤습니다.)
사용법은 간단해서, 싱크를 원하는 캘린더를 선택해 설정으로 들어가면 “비공개 주소” 항목에 iCalendar URL이 있습니다. 이 주소를 복사해서 자신의 iCal에 일정 불러오기로 넣어주면, 구글 캘린더에서 변경된 내용이 자신의 iCal에서 확인할 수 있습니다. 마찬가지 방법으로 그 반대방향으로의 싱크도 가능은 합니다만, 일정의 작성은 한군데에서 통일하는 쪽이 좋기 때문에 프로젝트와 관련된 일정의 작성은 구글 캘린더에서, 보기는 iCal에서 보는 쪽을 권합니다.
[작은 팁]
1. “기타 캘린더”를 생성할 때, “캘린더 찾아보기”에 들어가면 휴일 데이터가 있습니다. 저같은 경우 일본 휴일도 필요해서 한국 휴일과 일본 휴일을 모두 추가해서 유용하게 쓰고 있습니다.
2. “비공개 주소”에서 XML이나 HTML 주소를 이용하면 Excel 등에서도 일정 데이터를 쉽게 불러올 수 있습니다. 포매팅이 필요하다면 간단한 프로그램 하나를 짜서 적절하게 리팩토링을 해주는 것도 한 방법이겠지요.
이러이러 해도 MS Project나 Outlook에 비하면 많은 기능이 불편하긴 합니다만…
무료이고, 간단하고, 무엇보다도 OSX에서 문제없이 쓸 수 있다는 점에 억지로(?) 구글 캘린더로 프로젝트 일정관리를 시도해보았습니다. ^_^
블로그 주인에게 블로그란, ‘자신’에 대한 표현수단이겠지만, 블로그를 방문하는 사람은 사실, 블로거 자체에는 관심이 없다.
그들이 관심있는 것은 Topic일뿐.
블로그 방문자들이 방문한 블로그의 모든 글을 읽기를 기대할 수도 없다. 애초에, 블로그란 형식 자체가 post단위의 archiving/logging이기 때문에, archive(또는 post)는 그 자체로 자기완결되어야만 한다.(물론 어려운 일이다.)
대개의 방문자는 링크를 타고 들어와 그 하나의 포스트를 읽고, 나가버린다. 아주 가끔, 댓글을 단다거나 하는 예외를 제외하곤.
그렇다면 방문자들을 위해 가장 필요한 인터페이스는 무엇일까?
두가지 층위에서 바라볼 수 있는데, inbound entry와 outbound exit 층위가 그것. 들어오는 방문자와, 나가는 방문자를 위한 인터페이스.
‘블로그 내 검색’은 방문자를 가장 손쉽게 유혹할 수 있는 인터페이스라 할 수 있다. 방문자들은 카테고리 메뉴를 애용하지 않는다. 일일이 카테고리를 찍어보고, 포스트 목록에서 자신이 관심있어할 글 제목을 찾는 것은 매우 귀찮은 일이기 때문이다. 만약 방문자가 이 블로그에서 무언가 정보를 더 얻기를 원한다면 검색창에 바로 쳐넣고 결과가 뜨기를 기다릴 것이다.
따라서 블로그의 검색기능은 방문자를 위한 가장 훌륭한 인터페이스이다.
‘관련글’ - 수동으로 만들었거나, 혹은 FullText Search를 통한 자동 연결이거나 - 을 제공하는 것도 좋다. ‘연예뉴스’를 보러 들어온 방문자에게 관련된 다른 글들을 보여주는 것은 방문자들이 수고스럽게 일일이 뒤져보거나 검색할 필요를 줄여주는 친절한 인터페이스라 할 수 있다.
한가지 더 추가한다면,
referer를 통해, 검색엔진으로부터 유입된 방문자에게 자신이 보려고 했던 컨텐트를 바로 보여주는 것이다. 어떤 방문자가 검색엔진으로부터 ‘Ajax’라는 키워드를 찾아 블로그로 유입되었다면, 그는 아마도 내 블로그의 다른 ‘Ajax’관련 글에도 관심이 있을 것이다. 어쩌면, 검색엔진에 노출된 페이지보다, 숨겨진 페이지 쪽에 더 유용한 정보가 있을 수도 있다. 그런 경우 검색엔진으로부터 들어온 사용자에게 해당 엔트리들을 자동으로 보여준다면 얼마나 유용할까. (물론 이러한 기능들은 이미 많은 곳에서 구현되어 있고, 예를 들어 방금 말한 기능같은 것은 wp 플러그인으로 있어서 내 블로그에도 붙여놓았다. google에서 ‘불친절한 금자씨’로 검색해보시라.)
현재까지 블로그 인터페이스는 블로그 주인을 위한 자기만족적 성격이 강하다. 그러나 방문자로서는 스킨이 드래그 앤 드롭으로 변경되든, BGM이 있든 말든, 주인장 맘대로 태그로 뭘 붙이든 신경쓰지 않는다. (방문자 맘대로 리모콘 조작이 가능하다면야 또 모를까.) 그런 블로그 주인의 자기만족적 인터페이스보다는, 방문자를 위한 배려가 더 좋은 인터페이스일텐데, 이런 것들이 아직도 많이 미비한 듯 하여 아쉽다. (심지어 내 블로그 조차도.)