2013 슈퍼앱 코리아 개발자 예선 참가 후기

2013 슈퍼앱 코리아(2013 Super App Korea)

슈퍼 앱 코리아

공식 페이스북에 있는 소개글에 따르면 슈퍼앱 코리아는 국내외 시장을 타켓으로 경쟁력있는 앱서비스 개발과 스타트업을 지원하고자 하는 대회입니다.
특정 기간 동안 학생, 또는 일반인들로 구성된 기획자, 개발자, 디자이너가 모여 팀을 이루어 앱을 개발합니다.
개발 기간이 끝난 뒤에는 앱을 심사하여 우수한 앱을 개발한 팀에게 시상하게 됩니다.

이 대회는 2011년 1차 대회를 시작으로 2012년에 2차, 3차 대회가 열렸고, 올해에는 슈퍼앱 코리아 4차 대회가 열리게 되었습니다.
공식 명칭은 2013 슈퍼앱 코리아입니다.
이번 대회에서 특이한 점은 이전과 달리 별도의 기획자 모집이 없고, 개발자와 디자이너만 각각 모집한다는 점입니다.

예선을 통과한 개발자와 디자이너들은 9월 13일과 14일에 열리는 본선 기간에 팀을 결성합니다.
팀 결성 후 약 6주 동안 앱을 개발합니다.
심사는 10월 24일 ~ 11월 8일에 이루어질 예정입니다.
시상은 11월 12일에 할 예정입니다.
총 23팀이 수상하게 되고, 특별히 우승팀에게는 미래창조과학부 장관상 및 상금이 수여됩니다.

보다 자세한 내용은 아래의 링크들을 참조하세요.

AppCenter | (사)앱센터
슈퍼앱 코리아 공식 페이스북

8월 12일

SNS에서 우연히 슈퍼앱 코리아에 대해 알게 되었습니다.
개발자 참가 신청 기간이 7월 29일부터 8월 29일까지였기 때문에 당장 참가 신청이 가능했습니다.
개발자와 디자이너를 각각 모집하고 있었는데, 저는 개발자로 참가 신청을 했습니다.
앱센터에서 제공하는 구글 양식에 연락처, 앱개발 경험, 참가동기 등의 다양한 정보를 입력한 뒤에 신청서를 제출했습니다.
신청이 완료된 이후에는 다음과 같은 확인 메일을 받았습니다.

신청 접수

8월 30일

참가 신청 기간이 마감되자 개발자 예선의 시작을 알리는 이메일이 도착했습니다.

예선 안내

8월 31일에서 9월 3일까지의 기간 중 언제든 2시간 동안 예선에 참가할 수 있다는 것을 알 수 있었습니다.
마음 같아서는 예선 기간이 시작하자마자 당장이라도 참가하고 싶었지만, 당장은 바빴기 때문에 시간이 날 때까지 미루어 두었습니다.

9월 1일

시간의 여유가 생겨 이전에 받은 메일을 확인하여 codility.com의 링크에 접속했습니다.
총 3문제가 주어졌고, 주어진 문제의 답을 구하는 함수를 작성하는 것이었습니다.
두 문제는 난이도 낮음에 해당하고, 나머지 한 문제는 난이도 중간에 해당한다고 하였습니다.
C, C++, C#, Java, Python, PHP 등 다양한 언어를 선택할 수 있었는데, 저는 Python을 선택했습니다.

1시간 동안에 3문제를 다 풀고 제출하였습니다.

코딩 테스트

각각의 문제마다 답을 한 번씩만 제출할 수 있습니다.
한 번 답을 제출한 이후에는 수정할 수 없습니다.

코딩 테스트

제출한 답을 검증합니다.

코딩 테스트

답 검증이 끝난 뒤에는 남아 있는 다른 문제들을 풀 수 있습니다.

코딩 테스트

3문제를 모두 풀고 난 뒤에는 세션이 종료됩니다.
그러면 codility에서 제공하는 설문조사에 참여할 수 있습니다.

코딩 테스트

평가가 적절한지, 문제를 제대로 이해했는지, 시간이 충분했는지, codility 환경이 쓰기 편리했는지를 1점에서 10점 사이로 평가할 수 있습니다.

코딩 테스트

설문조사까지 끝나면 모든 과정이 종료됩니다.

코딩 테스트

9월 9일

오후에 낯선 번호에서 전화가 걸려왔습니다.
발신자는 슈퍼앱코리아였고, 제가 개발자 예선을 통과했다고 안내해 주었고, 이번 주말에 열리는 본선에 참석 가능한지를 물었습니다.
대회는 금요일 오후 2시부터 토요일 오후 2시까지 경기도 이천에서 진행될 예정이라고 하였습니다.
여기에 참석하지 않을 경우 중도 포기로 간주된다고 하였습니다.
저는 사정상 그 시간대에 참석할 수 없었기에 아쉬운 마음으로 대회 참가를 포기하겠다고 답했습니다.

대회를 통해서 다른 사람들과 함께 팀을 이루어 앱 개발을 해 볼 수 있는 기회였지만 사정상 참가하지 못하게 되어 아쉽습니다.
다음에는 슈퍼 앱 코리아 대회 본선에 참여할 여건이 생겼으면 좋겠습니다.

관련 포스트

SNUH 인턴 도우미

소개

서울대학교 병원 인턴의 생활을 돕기 위한 어플을 개발했습니다.
배달 밥집, 술기 요령 등에 관한 정보를 제공합니다.

구글 플레이 스토어에 있는 링크는 아래와 같습니다.

SNUH 인턴 도우미

용량은 382KB이고, 안드로이드 2.3.3(진저브레드) 이상에서 작동합니다.

밥집

배달 밥집을 서울대병원 본원(대학로), 분당서울대병원, 보라매병원, 국립암센터로 나누어 보여줍니다.

밥집

한 병원을 선택하면 그 주변에 있는 배달 밥집 목록을 볼 수 있습니다.
한식, 중식, 일식, 패스트푸드 등으로 분류하여 나열합니다.

대학로

밥집을 선택하면 기본적인 정보와 함께 배달 가능한 메뉴의 목록과 가격을 볼 수 있습니다.
각각의 메뉴를 터치하면 붉은색으로 강조됩니다.
화면 아래에는 해당 밥집의 전화번호 버튼이 있어서 전화를 걸 수 있습니다.

감골집

화면 상단의 술기 버튼을 터치하면 술기 메뉴로 이동합니다.
인턴에게 필요한 각종 술기들을 분류하여 나열합니다.

술기

혈관 및 주사 분류에 해당하는 술기들입니다.
터치하면 상세 보기로 이동합니다(2013년 5월 16일 현재 미완성).

혈관 및 주사

화면 상단의 설정 버튼을 터치하면 환경설정 메뉴로 이동합니다.
어플을 시작할 때에 처음으로 보여줄 화면을 지정할 수 있습니다.
그 이외에는 어플 및 개발자 정보를 볼 수 있습니다.

설정

개발에 관해

이 어플을 개발하려는 계획은 올해 초부터 있었습니다.
그러나 그 동안에는 바빠서 시간을 내기가 어려웠습니다.

본격적으로 개발을 시작한 것은 5월이 되어서입니다.
많은 시간을 투자하지 못하고 틈틈이 잠을 줄여가며 개발한 어플이기 때문에 완성도가 높지는 않습니다.

깔끔한 디자인을 제공하기 위해 하이브리드앱으로 구현했습니다.
하이브리드앱이지만 성능 최적화를 위해 폰갭이나 jQueryMobile 등은 사용하지 않았고 무료 모바일 웹 템플릿과 jQuery를 사용하였습니다.

웹사이트를 PHP+MySQL로 개발한 뒤에, 이를 htm 파일로 생성하여 안드로이드 어플의 assets 폴더에 넣었습니다.
안드로이드 어플에서는 해당 htm 파일을 불러오는 방식으로 작동합니다.

웹어플과 다른 점은 플레이스토어에서 어플을 다운로드하는 과정 이외에는 네트워크를 사용하지 않는다는 점입니다.
따라서 인터넷 접속을 포함하여 아무런 권한을 필요로 하지 않습니다.
또한 인터넷 속도가 느리거나 접속이 불안정한 상황에서도 제대로 작동한다는 장점이 있습니다.

웹뷰에 정의된 addJavascriptInterface 메서드를 사용하여 htm 파일과 어플간의 상호작용도 가능합니다.
개별 밥집의 htm 파일을 웹뷰에 불러온 경우에, 화면 아래에 전화번호 버튼이 생성되는 것이 한 가지 예입니다.

아직은 초기 단계이기 때문에 구현되지 않은 기능이 많고 불안정합니다.
앞으로 지속적으로 피드백을 받아 업데이트할 예정입니다.
현재는 안드로이드 버전으로만 개발된 상태이고, 반응이 좋을 경우 iOS용으로도 개발할 것입니다.

관련 포스트

구글 코드잼 2013 본선 1C 라운드 참가 후기

구글 코드잼

구글 코드잼에 대한 설명은 이전 포스팅을 참고하세요.(구글 코드잼 참가 신청)
지난 4월 13일 ~ 14일에 열린 코드잼 예선에 통과하여 본선 1라운드(1A, 1B, 1C)에 진출할 자격을 얻었습니다.
5월 12일 오후 6시부터 오후 8시 반까지 구글 코드잼 2013 본선 1C 라운드(Google Codejam Online Round 1C)가 열렸습니다.

대회 시작 24시간 전에 구글로부터 코드잼 본선 1C 라운드 시작을 알리는 메일이 도착했습니다.

메일

시간에 맞추어 다음 링크에 접속해서 문제를 풀었습니다.
http://code.google.com/codejam

문제

문제는 A~C로 총 3개가 주어졌습니다.

A. Consonants (8점+20점)
B. Pogo (10점+25점)
C. The Great Wall (9점+28점)

A, B, C 문제 모두 small input과 large input이 각각 채점되었습니다.
절대평가로 통과여부가 결정되는 코드잼 예선(Qualification round)와 달리, 본선의 경우 전세계 참가자 중에서 1000등 이내에 들어야 통과할 수 있습니다.

풀이

python을 사용하여 풀었습니다.

A. Consonants

알파벳 단어와 정수 n이 주어지고, 알파벳 단어로부터 연속된 자음이 n개 이상인 substring의 개수를 구하는 문제입니다.
주어진 단어에서 자음을 1, 모음(a, e, i, o, u)을 0으로 치환하고, 연속적인 1이 n개 이상 등장하는지 여부를 알아보는 방식으로 풀었습니다.
파이썬의 리스트를 사용하여 풀었는데, small input의 경우에는 쉽게 답이 얻어졌지만, large input에서는 단어의 글자수가 100만개 이상으로 길어지는 경우에 시간이 오래 걸렸습니다.
결국 시간이 초과되어 large input으로부터 답을 구하지는 못했습니다.

B. Pogo

(0, 0)으로부터 시작해서 1칸, 2칸, 3칸, …씩 차례대로 상하좌우로 이동해서 주어진 좌표에 도달하는 경로를 구하는 문제입니다.
목표 좌표로부터 최소이동횟수를 구한 뒤, 거꾸로 출발해서 (0, 0)까지 도달하는 경로를 찾았습니다.
상하좌우 중 어느 방향으로 이동할지는 현재 좌표와 (0, 0)을 비교해서 결정했습니다.
small, large input 모두 금세 답을 구했습니다.

C. The Great Wall

만리장성을 일정한 날짜 간격으로, 일정하게 이동하면서, 일정하게 변하는 강도로 공격하는 부족들의 침략 성공 횟수를 구하는 문제입니다.
침략이 성공한 뒤에는 해당 구간의 만리장성이 증축된다는 조건이 있습니다.
날짜별로, 부족별로 공격과 성공여부를 확인하는 코드를 작성했지만 시간이 부족해서 풀지 못했습니다.

성적

코드잼 1C

제 최종 성적은 A(8점) + B(10점+25점) + C(0점) = 43점입니다.
등수로는 전세계 672등입니다.
본선 통과의 커트라인인 1000등 이내에 들었기 때문에 다음 라운드에 진출하는 데에 성공했습니다.
이번 본선 1C 라운드에서의 1000등 커트라인은 38점이었습니다.
본선 1A 라운드에서의 36점, 본선 1B 라운드에서의 34점에 비해서는 커트라인 점수가 약간 높습니다.

다음 대회는 Online Round 2이고, 6월 1일 오후 11시에 시작됩니다.
본선 1A, 1B, 1C 라운드를 각각 통과한 총 3000명이 참가할 수 있고, 이 중에서 500등 이내의 성적을 받으면 Online Round 3로 진출합니다.

Round 2

본선 1C 라운드의 전체 성적은 아래 링크를 참조하세요.
Scoreboard – Round 1C 2013 – Google Code Jam

코드잼에 관한 전체 통계(참가자 국가, 사용한 언어 등)는 아래 링크를 참조하세요.
http://www.go-hero.net/jam/

요령

코드잼에서는 input 파일의 형식은 다양하지만 output 파일의 형식은 일정합니다.
따라서 output 파일을 생성하는 코드를 미리 작성해 두고, input을 처리하여 output을 생성하면 시간을 단축할 수 있습니다.

루프를 돌리면서 무작정 트리를 탐색해야만 풀리는 문제는 거의 등장하지 않습니다.
대개는 쉽게 답을 구하는 방법이 정해져 있고, 그 방법을 찾으면 의외로 짧은 코딩으로 답을 구할 수 있습니다.
무작정 트리를 탐색하는 방법으로도 small input의 답을 구할 수는 있지만, large input의 답을 구하기에는 역부족입니다.

코드잼의 문제는 영어로 주어지고, 문제를 정확히 이해해야만 답을 구할 수 있습니다.
문제가 화면 한가득이기 때문에 영어를 모국어로 사용하는 사람이 아닌 경우에는 문제를 이해하는 데에도 시간이 많이 걸립니다.
따라서 구글 번역기(http://translate.google.com/)에 문제를 붙여넣기해서 번역해서 대략적인 내용을 이해한 뒤에 상세한 조건을 파악하기 위해 원문을 참고하는 방법이 권장됩니다.

관련 포스트

홈페이지 연동 iOS 어플 개발

운영하고 있는 사이트 중 하나에서 안드로이드/iOS 어플 개발을 요청했습니다.
안드로이드 어플 개발은 다른 사람에게 맡기고, 저는 iOS 어플 개발을 맡았습니다.

안드로이드 어플 개발은 해 보았지만 iOS 어플 개발은 경험이 없었습니다.
그래서 두 가지 방법으로 공부를 시작했습니다.
1) iOS 어플 개발 카페 가입 – 맥부기 애플(iOS,Mac) 개발자모임
2) 관련 서적 구입 – 시작하세요! 아이폰 5 프로그래밍

iOS 어플 개발을 하기 위해서는 Mac OS가 설치된 기기가 필요했는데, 당장 맥북이나 아이맥을 구입할 형편이 못 되어서 기존 노트북 내에 VirtualBox를 설치하고 그 안에서 Mac OS 및 X Code를 설치하였습니다.
개발된 iOS 어플을 테스트하기 위해서는 iOS가 설치된 기기가 필요했는데, 아이폰보다는 저렴한 아이팟 터치 4세대를 선택했습니다.

개발이 완료된 후에는 99달러를 결제하고 iOS 개발자 등록을 신청했습니다.
안드로이드 개발자 등록에 비해 비용이 비싸고, 한 번 등록된 뒤에 평생 유지되는 것이 아니라 1년간만 유효하다는 점에서 차이가 있었습니다.
또한 개발자 등록이 즉시 이루어지는 것이 아니라 일주일 정도의 심사 기간을 거친 뒤에 허가되는 방식이었습니다.

개발자 등록을 마치고 어플 심사를 위해 binary 파일을 제출했습니다.
일 주일 정도 waiting for review 기간을 거친 뒤에 in review 상태로 전환되었고, 얼마 뒤에 어플 등록 승인을 받았습니다.

어플이 출시된 이후에 발견된 일부 버그를 수정하고, 기능들을 추가한 뒤에 다시 어플의 새 버전의 심사를 위해 binary 파일을 제출했습니다.
첫 어플 등록과 마찬가지의 기간을 거쳐 어플이 업데이트되었습니다.

어플을 개발하고 등록하는 과정에서 여러 가지 문제점들이 있었고, 이를 통해서 많은 것들을 배웠습니다.

문제점 #1. .xib 파일이 없는 문제

새 프로젝트를 생성하고 디자인을 하는 데까지는 기존 강좌들에 적혀 있는 방식대로 하면 되었지만, 프로젝트 탐색기에서 .xib 파일을 사용해야 하는 상황에서 .xib 파일을 찾지 못했고, .storyboard 파일만  찾을 수 있었습니다.

문제의 원인은 최신 버전의 iOS SDK를 사용하면 프로젝트를 생성할 떄에 xib 대신에 storyboard를 사용하는 것이 디폴트로 선택되어 있었기 때문입니다.
새 프로젝트를 생성할 때에, 이 옵션을 해지하여 기존 방식대로 개발을 할 수 있었습니다.

문제점 #2. 콜백 블럭에서 이벤트 처리 문제

와이파이 연결에는 아무 이상이 없는데 로그인을 하거나, 글을 불러오는 과정에서 시간이 너무 오래 걸리는 현상이 발생했습니다.
또한 로그인 버튼을 두 번 터치하면 어플이 예외를 발생시키는 상황도 발생했습니다.

문제의 원인은 안드로이드 어플에서와 마찬가지로, 어플의 메인 스레드가 아닌 스레드에서 메인 스레드를 건드리지 않아야 한다는 점 때문이었습니다.
보다 구체적으로는 로그인이나 글 읽기를 위해 HTTP Request를 보낼 떄에 sendAsynchronousRequest 메서드를 사용하였는데, 이 메서드의 콜백 블록에서 메인 스레드의 메서드를 직접 호출하면서 교착 상태에 빠지거나 예외가 발생한 것이었습니다.

이를 해결하기 위해서 처음에는 performSelectorOnMainThread라는 메서드를 사용하여 메인 스레드의 메서드를 호출했습니다.
그러나 이 경우에는 메인 스레드에 있는 메서드로 파라미터를 2개 이상 넘겨주기가 까다로웠습니다.
결국에는 dyspatch_sync라는 함수를 사용하여 해결하였습니다.

문제점 #3. 푸시 서비스 등록 문제

푸시 서비스는 시뮬레이터에서는 테스트해볼 수 없습니다.
실기기에서 테스트해 본 결과 푸시 서버로의 접속 자체가 이루어지지 않았습니다.
그러면서 콘솔에서는 다음과 같은 에러 메시지가 출력되었습니다.

no valid aps environment entitlement string found for application …

원인은 처음에 개발용으로 발급받은 프로비저닝 프로파일 때문이었습니다.
처음에 개발용으로 프로파일을 발급받을 당시에는 APNS 인증서를 발급받은 상태가 아니었기 때문에 프로파일 내에 푸시 서비스 등록에 대한 내용이 포함되어 있지 않았습니다.

이를 해결하기 위해서는 푸시 서비스 등록에 관한 내용이 포함된 프로비저닝 프로파일을 재발급 받아야 합니다.
처음에 발급받은 프로비저닝 프로파일은 재발급을 받더라도 푸시 서비스 등록 기능이 추가되지 않습니다.
따라서 처음 발급받은 개발용/배포용 프로비저닝 프로파일을 모두 파기하고 새로 프로비저닝 프로파일을 발급받아야 합니다.
그런 뒤 새 프로비저닝 프로파일을 등록하면 푸시 서비스가 잘 등록됩니다.

문제점 #4. Easy APNS를 사용한 푸시 알림 문제

애플의 개발자 센터를 통해서 APNS를 위한 인증서를 발급받아 서버에 설치하고 Easy APNS를 사용하여 푸시 메시지를 전송하도록 하였습니다.
그런데 서버에서 메시지를 전송해 보면 매번 실패했습니다.
처음에는 서버에서의 SSL 연결이 차단되어 있는 줄 알았지만, phpinfo를 사용해서 서버 환경을 조사해 보니 open ssl이 잘 작동하고 있었습니다.

나중에 원인을 알고 보니 인증서에 걸려 있는 암호 때문이었습니다.
Easy APNS에서는 기본적으로 인증서에 걸려 있는 암호를 따로 처리하지 않습니다.
따라서 암호가 없는 인증서를 설치하고 사용하거나, 또는 직접 Easy APNS의 소스를 수정하여 인증서에 걸려 있는 암호를 등록해 주어야 합니다.

문제점 #5. 검수 과정에서 접속 문제

검수를 위한 binary 파일을 제출하고 waiting for review 기간이 지나 in review 상태로 전환되었습니다.
그런데 얼마 지나지 않아 rejected 로 바뀌었고, 이유를 확인해 본 결과 demo 계정에서 어플 로그인이 이루어지지 않는다는 내용이었습니다.

원인을 확인해 보니 demo 계정에는 문제가 없었고, 검수하는 서버(해외)에서 기존 웹사이트 서버(국내)로의 접속이 이루어지지 않는 것이 문제였습니다.
다른 어플의 검수 과정에서도 이러한 경우가 종종 있다고 합니다.
이런 내용을 영어로 적어서 답장을 보내니, 유튜브에 어플의 모든 기능의 작동에 관한 영상을 찍어서 업로드하면 그것을 바탕으로 검수하겠다는 답변을 주었습니다.
그렇게 하여 검수를 통과하였습니다.

관련 포스트

가입형 블로그 개발

방학 중 알바를 하기 위해 알바천국 등에 구직 글을 올렸습니다.
대학생이고 PHP, MySQL을 다룬다고 하니까 여러 군데에서 연락이 왔습니다.
그러나 대개는 제가 원하는 웹 프로그래밍 단기 아르바이트를 구하는 게 아니라 수련직으로 채용하는 방식이었습니다.
나중에 알고 보니 수련 기간에는 원래 주기로 한 돈보다 적은 급여를 줘도 되기 때문에 기업들이 선호하는 채용방식이라고 합니다.
그런 연락들을 거절하던 중 제가 원하는 단기 아르바이트 방식을 원한다는 연락이 있어서 계약을 맺었습니다.
일주일에 한두 번씩 수업 끝난 뒤에 찾아가서 피드백, 회의하고 돌아와서 작업하였습니다.

계약상 자세한 내용은 밝힐 수 없지만 기본적으로는 블로그와 SNS를 결합한 방식의 웹사이트였습니다.
아이디어로부터 시작해 기획과 코딩이 함께 진행되었기 때문에 최종 결과물에 비해서 프로그래밍의 기간은 긴 편이었습니다.
PHP, MySQL 환경에서 작업하였습니다.
처음에는 가입, 게시물 관리 등의 기능을 사용하기 위해 그누보드4를 주로 사용할 게획이었으나, 의뢰인이 원하는 기능 중에 그누보드4의 기본 기능을 뛰어넘는 부분이 많이 있었기 때문에 그누보드는 포스팅을 작성하고 수정하는 데에만 제한적으로 사용하였습니다.
핵심적인 몇 가지 기능을 구현하기 위해서 고생을 했는데 계약상 그 내용은 자세히 적을 수 없습니다.

문제점 #1. 언어 문제

국제적으로 서비스하기 위해 만든 웹사이트이고, 모든 서비스를 영어로 제공하기로 기획되었습니다. 그러나 그누보드4를 기반으로 제작하였기 때문에 가입, 검색, 안내문 등의 모든 언어가 기본적으로 한국어였습니다. 따라서 영어로 서비스하기 위해서는 번역을 위한 추가적인 작업이 필요했습니다.

이를 해결하기 위해서는 그누보드의 스킨을 사용했습니다. 그누보드는 게시판 뿐 아니라, 최근글, 검색, 회원가입, 외부로그인 등에 관한 스킨을 제공합니다. 따라서 많은 경우에는 스킨을 수정하여 한국어를 영어로 바꿀 수 있습니다. 그렇지만 비밀번호 찾기 메일, 각종 에러 발생 시의 안내문 등은 스킨으로 처리할 수 없는 부분이었기 때문에 직접 그누보드의 소스를 수정해야 했습니다. 이는 유지관리를 어렵게 하는 원인이 되었습니다.

이 과정에서 얻은 결론은, 외국어/다국어로 서비스해야 하는 웹사이트의 경우에는 처음부터 그누보드 이외의 프레임워크를 사용해야 한다는 것이었습니다.

문제점 #2. 날씨 입력 문제

블로그의 포스팅에 날씨를 자동으로 입력하기로 기획하였습니다. 이를 위해서는 접속자의 위치를 알아야 하고, 그 위치에서의 현재 날씨를 알아야 합니다.

이를 해결하기 위해서는 두 종류의 open api를 사용했습니다.
즉, ip 주소를 통해서 국가와 도시 이름을 알려주는 api와, 국가와 도시 이름으로부터 현재 날씨를 알려주는 api를 찾아서 이용했습니다.

문제점 #3. 회원별 고유주소 할당 문제

페이스북, 트위터, 싸이월드와 같이 회원별로 고유주소를 할당하기로 기획되었습니다.
즉, http://www.nam.or.kr/member_id 로 접속하면 member_id라는 회원의 개인 페이지로 연결되어야 하는 것입니다.
대개의 MVC 패턴의 PHP 프레임워크의 경우에는 이와 같은 기능을 기본적으로 제공합니다. 그러나 그누보드4는 기본적으로 이러한 기능을 제공하지 않습니다.

이를 해결하기 위해서는 서버의 public_html 디렉토리에 .htaccess 파일을 작성하였습니다.
접속한 url이 루트의 폴더가 아닌 경경우에는 특정 페이지로 리다이렉트하도록 하였습니다.
예를 들면 다음과 같습니다.

RewriteEngine on
RewriteCond %{REQUEST_URI} !^(/$|/index\.php|/adm|/bbs|/lib|/skin)
RewriteRule ^(.*)$ http://www.nam.or.kr/profile.php?user=$1 [L]

관련 포스트

정보처리기사 실기시험 준비, 응시, 합격 후기

정보처리기사 시험에 관한 소개와 필기시험 준비, 응시 후기는 이 블로그에 있는 아래의 글을 참조하세요.

정보처리기사 원서접수

정보처리기사 필기시험 준비, 응시 후기

실기 시험 준비

실기시험 이틀 전에 시나공 책으로 공부를 시작했습니다.
실무 알고리즘 응용의 경우에는 정렬과 배치 위주로 공부했습니다. 그 동안 웹 프로그래밍을 해왔기 때문에 알고리즘을 작성하고 디버깅하는 것이 낯설지 않았습니다.
데이터베이스 실무 응용, 업무 프로세스 실무 응용, 신기술 동향, 전산 영어 실무의 경우에는 최근의 기출 문제 위주로 공부했습니다.

7월 8일

성동공업고등학교에서 시험을 쳤습니다. 의외로 응시자 중에 여자분들이 많아서 신기했습니다.

실무 알고리즘 응용의 경우에는 잘 알려진 두 가지 유형을 종합한 문제가 출제되었습니다.
데이터베이스 실무 응용의 경우에는 SQL 쿼리의 빈칸을 채우는 유형이었는데 주로 DDL에 관해 출제되었습니다.
업무 프로세스 실무 응용의 경우에는 기출 문제와 유사하게 출제되었고, 주어진 지문으로부터 답을 찾기가 수월했습니다.
신기술 동향의 경우에는 웹 분야 위주로 출제되었는데, 지문의 의미만 파악하면 대개 맞출 수 있었습니다.
전산 영어 실무의 경우에는 프로세서의 작업관리 분야 위주로 출제되었는데, 대개가 정보처리기사 필기시험을 준비하면서 배웠던 내용들이었습니다.

시험 시간이 끝나기 전에 퇴실이 가능해지자 다들 퇴실하는 것으로 보아 시험의 난이도를 가늠할 수 있었습니다.

시험이 끝나고 가답안으로 채점한 결과는 100점이었습니다.(http://www.gisa79.com)
다음 날 공단에서 공개한 정답으로 채점한 결과에서도 점수는 같았습니다.

8월 17일

오전 9시에 공단 웹사이트에 접속하여 로그인하고 합격여부를 확인했습니다.(http://www.q-net.or.kr)
가답안으로 채점한 결과대로 점수를 얻어 합격했습니다.
인터넷으로 결제하고 자격증 발급을 신청했습니다. 수령방식은 등기로 했습니다.

8월 20일

등기로 전달된 자격증을 받았습니다.

관련 포스트

정보처리기사 필기시험 준비, 응시 후기

정보처리기사 시험에 대해서는 이 블로그에 있는 아래의 글을 참고하세요.

정보처리기사 원서접수

3월 30일

온라인으로 2012 시나공 정보처리기사 필기/실기 교재를 주문했습니다.
아직 공부는 시작하지 않았습니다.

4월 2일

배송된 시나공 문제집을 받아 4권으로 구성되어 있다는 것만 확인하고 그대로 책꽂이에 꽂아두었습니다.
아직 공부는 시작하지 않았습니다.

4월 27일

정보처리기사 필기시험 원서접수를 했습니다.
아직 공부는 시작하지 않았습니다.

5월 15일

시나공 문제집 1편의 첫 50페이지에 대해 개념공부와 문제풀이를 했습니다.

5월 19일

바쁜 일이 생겨서 5월 15일 이후로는 공부를 더 하지 못하다가 시험 전날 저녁이 되어서야 다시 공부를 시작했습니다.
하루만에 남은 분량의 개념공부와 문제풀이를 할 수 없을 것으로 판단하고 선택과 집중을 하기로 했습니다.
아직 공부하지 못한 나머지 부분을 버리고, 최근 5번의 정보처리기사 기출문제를 중심으로 공부하기로 했습니다.
물론 답만 외우고 넘어가는 방식이 아니라, 각각의 문제 밑에 달려 있는 전문가 조언과 병행학습을 완전히 이해하는 방식으로 공부했습니다.
병행학습에 기록된 내용만으로 이해가 되지 않을 경우 문제 위에 달려 있는 섹션과 필드를 찾아보고 개념공부를 해서 이해한 뒤에 넘어갔습니다.
이렇게 해서 문제집에 수록된 첫 번째 기출문제인 2010년 5월 시험문제 100개를 다 공부하는 데에도 꽤 오랜 시간이 걸렸습니다.
그런 뒤에 두 번째 기출문제인 2010년 9월 시험문제들을 공부했는데, 앞에서 풀었던 문제들과 비슷한 문제들이 있어서 처음보다는 금방 공부했습니다.
두 번째 기출문제까지 공부하고 나니 이미 새벽 3시가 되어서, 다음 날 아침에 나머지 내용을 공부하기로 하고 잠들었습니다.

5월 20일

새벽 5시부터 30분 간격으로 맞춰 둔 알람 중 6시 알람을 듣고 일어났습니다.
세 번째 기출문제인 2011년 3월 시험 문제를 전부 공부하는 데에는 1시간 정도 걸렸습니다.
고사장까지의 이동 시간을 고려하여, 2011년 6월과 8월 기출문제는 시험치기 전까지 다 보지 못할 것으로 판단했습니다.
그래서 2011년 6월과 8월의 기출문제 중에서는 앞에서 기출문제에서 비슷한 문제가 자주 출제되지 않았던 제2과목(전자계산기 구조)과 제5과목(데이터 통신)만 공부하면서 고사장으로 이동했습니다.
고사장에 있는 다른 수험생들이 기존에 푼 문제들을 복습하거나 정리본을 보면서 공부하고 있는 동안, 저는 아직 못 다 푼 문제들을 풀고 있다가 시험 시작을 맞이했습니다.

시험

필기시험은 9시 30분부터 시작이 되었습니다.
그 전에 OMR 카드를 받아 이름과 수험번호 등을 기록했고, 시험지를 받아 인쇄가 잘못된 부분은 없는지 확인했습니다.
낯 익은 문제들이 눈에 많이 띄어서 다행이라고 생각했습니다.

우선 처음에는 거의 답을 확신하는 문제들에만 답을 체크하면서 한 번 풀었습니다.
그런 뒤에 OMR 답안지에 답을 표시하고, 그 숫자를 세어 보니 70개 정도 되었습니다.
일단 평균 점수 미달로 불합격하지는 않겠다는 생각에 안도가 되었습니다.

그러나 각 과목별로 확인해 보았을 때에 제2과목과 제5과목에서는 답을 확신하는 문제들이 각각 10개가 안되었습니다.
답을 확신한다고 해도 맞는다는 보장은 없었기 때문에 과락이 걱정이 되어서 나머지 문제들을 신중하게 찍기 시작했습니다.
시험 공부를 할 때보다 더 집중해서 가능한 한 모든 상상력을 동원해서 문제를 풀었습니다(찍었습니다).
퇴실이 가능한 10시 45분이 지나 11시 즈음에 고사장을 나왔습니다.
OMR 카드는 제출하고 시험지는 가져왔습니다.

채점

아직 가채점 답안이 나오지 않은 상태에서 시나공 책의 개념설명과 기출문제 해설을 찾아보면서 채점을 했습니다.
맞았다고 생각했던 문제에서 착각을 해서 틀린 문제들이 있어서 안타까웠습니다.
특히 과락이 나오지 않을까 걱정했던 제2과목에서 반가산기 회로의 carry와 sum을 논리식으로 나타내는 문제를 잘못 체크해서 틀린 것을 발견했을 때에는 더욱 초조했습니다.

시험이 끝나고 가답안으로 채점한 결과는 과락 없이 평균 80점으로 예상되었습니다.(http://www.gisa79.com/)
다음 날 공단에서 공개한 정답으로 채점한 결과에서는 85/65/80/85/75점, 평균 78점으로 필기 시험을 통과했습니다.

실기 시험

이제는 실기 시험을 준비할 차례입니다.
실기 시험은 보다 미리 준비해 두어서 이번 필기 시험과 같이 아슬아슬한 상황을 만들지 않도록 해야겠습니다.

응시 후기는 이 블로그에 있는 아래의 글을 참조하세요.

정보처리기사 실기시험 준비, 응시, 합격 후기

관련 포스트

정보처리기사 원서접수

정보처리기사 자격증

한국산업인력공단(http://www.hrdkorea.or.kr/)에서 시행하는 국가기술자격 중에 정보처리기사가 있습니다.
정보처리기사는 우수한 프로그램을 개발하여 업무의 효율성을 높이고, 궁극적으로 국가발전에 이바지하기 위해서 컴퓨터에 관한 전문적인 지식과 기술을 갖춘 사람을 양성하기 위해 제작된 자격입니다.

실무경험이 있는 경우 대학을 다닌 기간에 따라서 응시자격이 주어지고, 실무경험이 없는 경우에는 대학졸업(예정)자의 경우에 응시가 가능합니다.
그 외에도 몇 가지 응시가능자격이 있지만 저와는 상관이 없기 때문에 자세히 소개하지는 않습니다.

시험의 구성

시험은 필기와 실기의 2단계로 나누어 치러집니다.
필기시험은 다음 5가지 과목에서 출제되고, 평락(60점)과 과락(40점)이 있습니다.

  • 데이터베이스
  • 전자계산기 구조
  • 운영체제
  • 소프트웨어 공학
  • 데이터 통신

실기시험은 실제로 프로그램을 작성하는 것이 아니라, 다음 5가지 과목에서 주관식 유형으로 출제됩니다(많은 보기들 중에서 괄호 안에 알맞은 보기를 하나 선택하는 방식).

  • 실무 알고리즘 응용
  • 데이터베이스 실무 응용
  • 업무 프로세스 실무 응용
  • 신기술 동향
  • 전산 영어 실무

실기시험의 경우 과락은 없고 평락(60점)이 있습니다.

올해(2012년)의 경우 3번의 필기, 3번의 실기 시험이 있습니다.
그 중에서 2회 정보처리기사 필기 시험 접수가 오늘부터 시작됐습니다.
큐넷 웹사이트(http://www.q-net.or.kr/)를 통한 인터넷 접수만 가능하고, 필기 원서 접수 비용은 18,800원입니다.

시험 일정

필기 시험 원서 접수 기간은 4월 27일(금)부터 5월 3일(목)까지이고,
필기 시험 날짜는 5월 20일(일)이고,
필기 시험 합격자 발표는 6월 8일(금)이고,
실기 시험 원서 접수 기간은 6월 11일(월)부터 6월 14일(목)까지이고,
실기 시험 기간은 7월 7일(토)부터 7월 20일(금)까지이고,
최종 합격 발표는 8월 17일(금)입니다.

합격률

작년(2011년)의 경우, 필기시험에 47,602명이 응시하여 18,919명이 합격했고(39.7%), 실기시험에는 29,167명이 응시하여 16,874명이 합격했습니다(57.9%).

원서접수

저는 올해 1회 정보처리기사 필기 접수 기간을 몰랐기 때문에 그냥 지나갔고, 이번 2회 시험에 응시하게 되었습니다.
지원 자격으로는 관련학과 대학졸업예정자를 선택했습니다.
따라서 필기 시험에 합격한다면 실기 시험 원서 접수 기간에 졸업 예정 증명서 또는 최종학년 재학(휴학) 증명서 또는 최종학년 제적 증명서를 추가로 제출해야 합니다.

저는 컴퓨터공학을 전공하지 않았기 때문에 이번에 치르는 과목들을 제대로 배운 적이 없고, 독학으로 준비해야 합니다.
교재는 시나공 시리즈(정보처리기사 필기, 실기)를 구입해서 공부할 예정입니다.

관련 포스트

구글 코드잼 2012 예선 참가 후기

구글 코드잼

구글 코드잼에 대한 설명은 이전 포스팅을 참고하세요.(구글 코드잼 참가 신청)
4월 14일 오전 8시부터 15일 오전 9시까지 구글 코드잼 2012 온라인 예선이 열렸습니다.
대회 시작 시각이 되자 구글로부터 코드잼 예선 시작을 알리는 메일이 도착했습니다.
다음 링크에 접속해서 문제를 풀었습니다.
http://code.google.com/codejam

문제

문제는 A~D로 총 4개가 주어졌습니다.

A. Speaking in Tongues (15점)
B. Dancing With the Googlers (10점+10점)
C. Recycled Numbers (10점+15점)
D. Hall of Mirrors (15점+25점)

A 문제는 large input이 없었고, 나머지 문제들은 small input과 large input이 각각 채점되었습니다.
100점 만점에 20점을 넘으면 통과였기 때문에 1-2문제만 풀면 통과할 수 있었습니다.
작년과 달라진 점은 large input의 경우의 제한시간이 8분으로 늘었다는 점입니다.

풀이

작년에는 PHP를 사용했으나, 올해는 얼마 전부터 배우기 시작한 python을 사용하였습니다.

A. Speaking in Tongues

알파벳 26글자를 일대일대응으로 치환해서 만든 Googlerese라는 언어를 제시하고, 대응관계를 찾아서 Googlerese를 English로 번역하는 문제였습니다.
예문으로 주어진 3 문장을 분석해 보면 알파벳 26글자 중에서 24글자의 대응관계를 찾을 수 있지만, q와 z의 대응관계는 주어지지 않았습니다.
그러나 문제에 주어진 힌트를 살펴보면 z->q로 대응되는 것을 알 수 있으므로 일대일대응의 원리에 따라 q는 z에 대응됩니다.
이를 이용하여 주어진 Googlerese 문장을 번역할 수 있습니다.
large input 문제가 없고 small input 문제만 있었고, 쉽게 풀었습니다.
아마 암호화/복호화를 염두에 두고 출제한 문제로 생각됩니다.

B. Dancing With the Googlers

댄서들을 3명의 심사위원들이 평가하는 상황을 제시하고, 댄서가 받은 점수의 총점과 심사위원별 점수 중 최대값과의 관계를 구하는 문제입니다.
총점을 3으로 나눈 나머지에 따라 경우를 나누어 풀면 쉽게 풀립니다.
small input과 large input이 큰 차이 없이 쉽게 처리되어 답을 얻었습니다.
무엇을 염두에 두고 출제한 문제인지는 모르겠습니다.

C. Recycled Numbers

경우의 수 문제입니다.
주어진 범위 내에 속한 n자리 숫자를 재배열한 새로운 숫자를 만들어서 다시 주어진 범위 내에 속하도록 하는 경우의 수를 구합니다.
python의 성능을 믿고 for 문을 2번 중첩해서 사용한 결과 답이 구해졌습니다.
small input의 경우에는 금방 구해졌지만, large input의 경우에는 답을 구하는 데에 6분 정도 소요되어 아슬아슬하게 통과했습니다.
정렬 관련 알고리즘을 묻는 문제로 생각되지만 제대로 된 방식으로 푼 것인지는 모르겠습니다.

D. Hall of Mirrors

배점이 가장 크지만 제대로 풀지 못한 문제입니다.
거울로 된 방 안에서 자신의 모습이 몇 번이나 반사되어 보이는지를 묻는 문제입니다.
small input의 경우에는 방 안에 벽면에만 거울이 있고, 내부에는 또다른 거울이 없기 때문에 좌표평면으로 옮겨서 대칭과 거리, 기울기를 이용하여 풀었습니다.
large input의 경우에는 방 안에 또다른 정육면체 거울들이 존재했기 때문에 아예 풀 엄두를 내지 못했습니다.

성적

최종 성적은 A(15점) + B(10점+10점) + C(10점+15점) + D(15점) = 75점입니다.
등수로는 전세계 374등입니다.
예선 통과의 커트라인인 20점을 넘겼기 때문에 다음 라운드에 진출하는 데에는 성공했지만 격자나 기하를 응용한 문제를 풀기 위한 공부가 필요할 것 같습니다.
다음 라운드는 4월 28일에 한 번, 5월 6일에 두 번 열리고, 1000등 이내의 성적을 받으면 됩니다.

관련 포스트

트위터 전광판 페이지 개발

예전에 사용하다가 방치해 둔 블로그에 새 댓글이 달렸다는 알림 메일이 왔습니다.
이전에 facebook open api를 다룬 포스팅을 남긴 적이 있는데, 그 글의 댓글로 open api를 사용한 단기 알바를 해 보겠냐는 제의가 들어왔던 것이었습니다.
작업할 시간은 있었지만 굳이 일을 해야 할 필요성을 느끼지 못해서 처음에는 그냥 지나쳐 버렸습니다.
그렇지만 처음으로 open api를 사용한 웹개발을 해볼 수 있다는 설렘을 이기지 못하고, 결국 댓글을 남긴 분께 연락을 드렸습니다.

작업할 내용은 ASP 환경에서 twitter open api를 사용하여 특정 사용자(들)의 트윗을 가져와서 웹브라우저 상에 출력하는 일이었습니다.
제가 주력으로 사용하는 언어인 PHP를 사용할 수 없다는 점이 걸리기는 했지만, javascript api를 사용하면 해결될 것이라고 생각하고 작업을 시작했습니다.

계획은 제가 ASP 프로그래밍을 배운 적이 없기 때문에 순수 javascript로 작업하는 것이었습니다.
thread, jQuery를 사용하여 일정 간격으로 트위터 서버로부터 트윗을 가져오고
템플릿 파일을 읽어 특정 문자열을 트윗 데이터로 치환하도록 구현했습니다.

문제점 #1. cross site 보안 문제
client(웹페이지)에서는 보안 문제로 인해 몇 가지 예외를 제외하고는 다른 도메인에 접근할 수 없습니다.
대표적인 예외가 img 태그입니다.
이런 원칙에 따라 javascript 상에서도 트위터 api 서버로(즉, 다른 도메인으로) ajax 호출을 할 수 없습니다.
이를 해결하기 위해 JSONP를 사용하였습니다.
JSONP는 client에서 다른 도메인으로 ajax 호출을 할 수 있도록 합니다.
다만 그 다른 도메인의 페이지에서 반환하는 값이 json 형식이어야 합니다.

문제점 #2. ajax error 처리 문제
JSONP를 사용하여 다른 도메인으로 ajax 호출은 할 경우, 호출이 success한 경우의 콜백 메서드는 잘 작동하지만, 호출이 실패한(error) 경우에는 콜백 메서드가 실행되지 않는 문제점이 있습니다.
이를 해결하기 위해 timer를 사용하였습니다.
ajax를 호출할 때에 setTimeout()으로 에러처리 콜백 메서드를 호출할 timer를 만들어 둔 뒤, 호출이 success 한 경우에는 clearTimeout()을 통해 timer를 해제하도록 만들었습니다.

문제점 #3. rate limiting 문제
트위터 서버에는 api 호출에 rate limiting이 있습니다.
인증받지 않은 호출은 1시간에 150회, 인증받은 호출은 1시간에 350회까지 가능합니다.
특정 트위터 계정들의 트윗을 가져오기 위해 각각의 특정 트위터 계정에 대해 api 호출을 하면 금세 인증 횟수 제한에 걸립니다.
이를 해결하기 위해 별도의 트위터 계정을 생성하고 그 계정이 특정 계정들을 follow하도록 하고, 웹페이지에서는 생성한 트위터 계정의 timeline을 가져오도록 하려면 oauth 인증이 필요하므로 javascript만으로는 구현하기 어렵거나 복잡해집니다.
이를 해결하기 위한 방법으로 별도의 트위터 계정을 생성하고, 그 계정에 list를 추가한 뒤, 특정 트위터 계정들을 그 list에 추가하는 방법을 택했습니다.
list의 timeline을 가져올 때에는 인증이 불필요한데다 list에 대해 한 번 api를 호출하면 list에 속한 모든 트위터 계정들의 트윗을 가져오므로 rate limiting에도 걸리지 않게 됩니다.

문제점 #4. 이미지 출력 문제
트위터에서는 얼마 전부터 트윗에 이미지 첨부 기능을 제공합니다.
트윗을 가져오는 api를 호출하면 entities라는 태그 내에 이미지에 대한 url을 포함시키고 있는 것을 볼 수 있습니다.
그러나 아직 많은 사용자들은 yfrog나 twitpic을 사용하고 있기 때문에 이런 경우에도 웹페이지에서 곧바로 이미지를 출력해주기 위해서는 다른 도메인에 접속해서 parsing하는 부분이 필요합니다.
앞서 언급한 대로 javascript에서는 다른 도메인에 접속할 수 없기 때문에 javascript로만 해결하기는 어렵습니다.
이를 해결하기 위해 어쩔 수 없이 asp.net를 사용해서 코딩해야 했습니다.
yfrog.com이나 twitpic에 대한 링크를 별도로 만든 .aspx 파일에 전달하면 이 파일에서 그 링크에 접속하여 parsing을 한 뒤에 image 파일의 경로를 반환하도록 했습니다.

문제점 #5. 날짜 인식 문제
트위터에서 api 호출에 대해 반환한 json 객체 내의 날짜 표기 형식을 javascript의 Date 객체 생성에 곧바로 이용할 수 없었습니다.
이를 해결하기 위해 substring() 메서드를 사용하여 Date 객체 생성에 이용했습니다.

문제점 #6. json 객체 내의 null 문제
json 객체 내에 entities나 media 태그가 없는지 판단하는 과정에서 자꾸만 null 에러가 발생하면서 작동이 멈추었습니다.
매번 객체가 null이나 undefined 인지 판단하기도 어려웠을 뿐 아니라 코드가 길어지고 어려워졌습니다.
이를 해결하기 위해 try~catch 를 사용하여, entities나 media 태그가 없는 경우 catch 에서 처리하도록 했습니다.

문제점 #7. 다른 환경에서 실행되지 않는 문제
제 컴퓨터에서 개발할 때에는 .aspx 파일 실행이 잘 되었지만, 의뢰인의 서버에서는 잘 실행되지 않았습니다.
이를 해결하기 위해 publish 과정에서 생성된 Web.config, bin 폴더를 웹주소 루트에 위치시켰습니다.
또한 웹서버에서 asp.net의 버전을 동일하게 맞추어 준 뒤에는 문제 없이 실행되었습니다

문제점 #8. Chrome에서 실행되지 않는 문제
인터넷 익스플로러에서는 잘 실행되었지만 Chrome 브라우저에서는 제대로 작동하지 않았습니다.
개발자 도구로 에러나는 부분을 찾아보니 Date 객체를 생성하는 부분에 오류가 있었습니다.
new Date(“0000-00-00 00:00:00”)으로 Date 객체를 생성하였더니 IE에서는 문제가 없었지만 Chrome에서는 에러를 발생시켰습니다.
이를 해결하기 위해 new Date(“2001-01-01 00:00:00”)으로 Date 객체를 생성하였더니 Chrome에서는 잘 인식했지만 인터넷 익스플로러에서는 에러를 발생시켰습니다.
결국 new Date(2001, 1, 1)을 사용하니 두 브라우저에서 모두 이상 없이 작동했습니다.

문제점 #9. javascript 정규식 패턴 문제
트윗을 출력할 때에 템플릿에 맞추어 출력하는 과정에서 replace() 메서드를 사용하였습니다.
템플릿 파일 내에는 {screen_name}, {text} 등이 포함되어 있고, 이 부분을 찾아서 적절한 값으로 치환하는 방식을 사용했습니다.
data.replace(“{name}”, twit_item[“name”]) 과 같은 구문을 사용했는데, 가끔 제대로 작동하지 않는 등의 문제가 있었습니다.
이를 해결하기 위해 몇 시간이나 매달렸지만 제대로 되지 않았고, 결국 .aspx 파일을 ajax로 호출하여 GET으로 전달하는 방식을 사용하게 되었습니다.
나중에 알게 된 사실은, javascript에서 정규식 패턴은 “를 사용하여 감싸는 것이 아니라는 것이었습니다.
즉, 패턴에 “/      /im”라고 입력하면 슬래시 안쪽을 찾은 것이 아니라 쌍따옴표 안쪽을 찾는 것입니다.
원하는 결과를 얻기 위해서는 쌍따옴표 없이 /     /im을 사용했어야 합니다.

관련 포스트