자바로 개발 프로젝트를 하면 DB를 사용하지 않는 프로젝트는 거의 없다고 봐야 한다. 자바로 DB를 핸들링할 때 사용하는 JDBC는 DB에 종속적이지 않고 표준적인 방식의 인터페이스를 지원하므로 개발자에게 드라이버의 교체만으로 여러 DB를 연결해 개발할 수 있게 해 주었다. 하지만 이런 JDBC로 개발을 진행하다 마주치는 문제 중 두 가지를 이번 코너에서 다뤄보고 여기에 대한 다양한 해결책을 논의해 보고자 한다.
양수열 (로코즌 컨설팅 사업부, 마이크로소프트웨어) 2003/04/11
필자가 대학을 처음 들어가서 만졌던 컴퓨터에는 DOS가 설치되어 있었다. 메인 프레임에 연결된 터미널도 있었지만 이런 터미널은 실습 때와 학점과 씨름할 때를 제외하고는 큰 필요성을 못 느끼게 하는 존재였다. DOS가 설치된 PC는 필자에 한글을 두 가지 방식으로 보여줬다. 하나는 한글 BIOS라는 조그만 카드를 끼워서 지원하는 방법과 소프트웨어로만 구현된 한글 바이오스 프로그램을 이용하는 방법이 그것이다. 그 당시 한글 바이오스 프로그램을 실행하기 전에 한글로 된 프로그램을 실행하면 깨진 글자가 나타나 당황하는 사람도 여럿 있었다. 그만큼 PC의 저변이 확대된 상태는 아니었다.
하지만 윈도우를 접하면서 이런 게 필요 없구나 하는 생각에 정말 신기해했던 기억이 난다. 더구나 윈도우 95는 한글을 문제없이 쓸 수 있겠구나 싶은 생각을 절로 하게 했다. 하지만 개발자로 시각을 바꾼 후 의외로 ‘한글’과 관련된 이슈는 윈도우가 XP가 나오고 기술이 앞서감에도 불구하고 꾸준히 내 발목을 붙잡았다.
DB에 안 들어가는 한글?!
한 번은 모 통신회사에서 데이터베이스를 마이그레이션할 때였다. 몇 번의 시행착오와 테스트를 거친 상태에서 시스템에 접속되는 모든 클라이언트의 연결을 끊고 데이터베이스를 익스포트/임포트했다. 진행 상태는 정상적이었으며 임포트가 완료되자 프로젝트 팀은 기쁜 마음으로 자리를 뜨려고 했다. 하지만 테스트 페이지를 확인하니 한글이 전부 깨져 나오는 것이 아닌가. 그리하여 원래 운영중이던 데이터베이스로 운영에 들어갔고, 프로젝트 팀은 다른 스케쥴을 잡고 다시 데이터베이스를 이전해야만 했다.
원인은 엔지니어 중 누군가가 언어 설정을 바꿔 놓은 탓에 임포트시 문자 셋이 깨져 들어간 것으로 밝혀졌다. 이로 인해 다음 유지보수 일정까지 시스템 전환 일정이 지연되는 어려움을 겪어야만 했다. 이 사건은 사소한 것부터 모두 체크하고 신경쓰는 세심함을 배우기는 계기가 되기도 했다. 이렇듯 한글로 인한 문제는 데이터베이스뿐만 아니라 일상적인 개발 업무에서도 문제가 된다. 다음과 같이 간단한 테이블을 만들어 보자.
create table test
(no number(2),
sex varchar2(2),
name varchar2(20)
);
그리고 테이블에 한글을 정해진 글짜만큼 넣어보자. 자바로 간단한 JDBC 프로그램을 만들어 보겠다.
import java.sql.*;
import java.util.*;
class DBInsert{
public static void main(String[] args) throws Exception{
java.util.Date t1 = new java.util.Date();
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
Connection conn = DriverManager.getConnection(
"jdbc:oracle:thin:@123.123.122.11:1521:ora8i","scott","tiger");
Statement stmt = conn.createStatement();
ResultSet rs = null;
StringBuffer sql = new StringBuffer();
try
{
sql.append("insert into test values('1', 'xy', '가나다라마바사아자차')");
rs = stmt.executeQuery(sql.toString());
System.out.println("OK");
}
catch (Exception e){
e.printStackTrace();
}
finally{
if(rs != null) try{rs.close();} catch(Exception e){}
if(stmt != null) try{stmt.close();} catch(Exception e){}
if(conn != null) try{conn.close();} catch(Exception e){}
}
}
}
이 프로그램을 실행하면 다음과 같은 에러를 만들어 낸다.
[papabear@mock maso]$ java DBInsert
java.sql.SQLException: ORA-01401: inserted value too large for column
at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:114)
at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:208)
at oracle.jdbc.ttc7.Oall7.receive(Oall7.java:542)
at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1311)
at oracle.jdbc.ttc7.TTC7Protocol.parseExecuteFetch(TTC7Protocol.java:738)
at oracle.jdbc.driver.OracleStatement.executeNonQuery(OracleStatement.java:1313)
at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.java:1232)
at oracle.jdbc.driver.OracleStatement.doExecuteWithBatch(OracleStatement.java:1353)
at oracle.jdbc.driver.OracleStatement.doExecute(OracleStatement.java:1760)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1805)
at oracle.jdbc.driver.OracleStatement.executeQuery(OracleStatement.java:410)
at DBInsert.main(DBInsert.java:18)
한글 한 글자를 2바이트로 계산하면 분명 varchar2(20) 컬럼에 들어가야 하는데 안 들어가니 이상한 노릇이다. 더군다나 오라클에서 던지는 에러 내용은 더 가관이다. 글자 수가 10개라는 것은 세어보지 않아도 될 정도의 적은 텍스트인데 그 컬럼에 들어가기에 너무 크다는 것이다. 그렇다면 Sqlplus를 통해 넣어보자.
SQL*Plus: Release 8.1.7.0.0 - Production on Wed Mar 19 13:19:22 2003
(c) Copyright 2000 Oracle Corporation. All rights reserved.
Connected to:
Oracle8i Enterprise Edition Release 8.1.7.0.1 - Production
With the Partitioning option
JServer Release 8.1.7.0.1 - Production
SQL> insert into test values('1', 'xy', '가나다라마바사아자차');
1 row created.
SQL> desc test;
Name Null? Type
----------------------------------------------------------------
NO NUMBER(2)
SEX VARCHAR2(2)
NAME VARCHAR2(20)
SQL>
Sqlplus를 통해 정상적으로 데이터가 들어갔다. 테이블을 확인해 봐도 정상적이다. 어이가 없다. 왜 안 들어가는 것일까? 분명 코드나 다른 환경적인 영향은 없으며 의심스러운 곳은 JDBC 드라이버뿐이다. 짐작대로 JDBC 드라이버의 Globalization Support Conversion이라는 내용에서 찾을 수 있었는데, 데이터베이스의 문자 셋은 US7ASCII 또는 WE8ISO8859P1인 경우를 바로 UTF-16으로 변환해 데이터베이스에 반영했지만 이외의 문자 셋은 UTF-8로 변환한 후 UTF-16으로 변환한 것이다.
이럴 경우 해당 한글은 UTP-8로 인코딩하면서 크기가 많이 증가하고 이러한 문자를 받은 오라클이 에러를 만든 것이다. 이 경우 다음과 같이 문자 셋을 변환해 넣어주면 정상으로 작동된다.
String name = "가나다라마바사아자차";
String name_KS = new String(name.getBytes("8859_1"), "KSC5601");
이렇게 문자 셋을 맞추면 UTF-8로 변환되는 과정을 막으므로 해당 쿼리가 에러 없이 정상으로 실행된다. 하지만 한글이 1300자 내외를 넘을 경우에는 이런 방법으로도 불가능하다. 이런 문제점은 웹에서 장문의 텍스트 데이터(메모장 등)를 스트링으로 넣거나 XML 데이터 같은 많은 텍스트 데이터를 처리할 때 경험한다. 이에 대한 해결책은 웹에서 많이 구할 수 있다.
① varchar2 Column(2000)을 여러 개를 만들어 쪼개서 저장하고 가져올 때 합친다.
② varchar2의 한계인 4000자 이내면 pstmt.setCharacterStream()를 사용해 삽입한다.
③ 테이블 설계부터 고려한다면 CLOB을 사용한다.
우선 이런 방법에 대한 장단점을 살펴보자. 첫 번째 방법은 2000바이트를 잘라낼 때 해당 문자가 2000~2001바이트를 걸치면 깨지는 현상을 고려해야 한다. 물론 가져와서 데이터를 합할 부분도 고려해야 하는 불편이 따른다. 하지만 이 방법은 JDBC나 JDK를 고려하지 않아도 되고 애플릿(JDK 1.2 이하의 개발 환경) 등 다양한 곳에 적용 가능하다.
1996 1997 1998 1999 2000 2001 2002 2003
마 소 사 랑
두 번째 방법은 JDK 1.2 이상(JDBC 2.0 이상)에서 추가된 setCharacterStream()을 사용하지만 단점은 JDK 1.2 이상(JDBC 2.0 이상)의 환경을 요구하고 애플릿 같은 환경에선 사용이 힘들다는 점이다. 또한 일부 운영 시스템(회사에서 실험하는 테스트 환경 중에는 유닉스웨어 플랫폼이 있는데, 여기서 JDK 1.2 이상을 쓰고자 할 경우 OS 패치를 해줘야 한다. 운영 시스템을 패치해야 JDK를 업그레이드할 수 있는 환경이 이런 경우에 해당한다)의 제한으로 JDK 하위 버전으로 작업할 경우에는 사용할 수 없다. 따라서 이 방법을 이용하면 4000자까지 단일 컬럼으로 가능하고 가장 깔끔하게 적용할 수 있다.
String memo = "봄이 오니~ 꽃이 피네" // 1300자 이상의 한글 문자열
Pstmt.setCharacterStream(1, new java.io.StringReader(memo),meno.length());
세 번째 방법은 이런 대용량의 문자열 데이터를 넣는 방법으로 오라클에서는 대용량 컬럼 타입으로 CLOB를 향후 권장하는 추세이다. 오라클 8 이상에서는 인터미디어(intermedia)를 통해 검색도 지원하고 있다. 하지만 이 방법은 데이터베이스 환경이 오라클 8i 이상이고 해당 컬럼에 대해 초기 설계시 고려돼야 하는 등의 문제점이 있다. 또한 데이터베이스 I/O를 조회할 때 많은 리소스를 점유하는 문제도 발생한다. 이 경우 트랜잭션에 민감한 사이트에서는 관리자가 많이 꺼려 할 소지가 있다.
앞서 언급한 방법들은 제각기 장단점이 있고 웹에서도 많은 자료를 얻을 수 있는 방법이다. 하지만 이런 방법으로도 여의치 않을 경우 데이터베이스에서 지원하는 UTL_RAW 패키지를 이용하는 방법이 있다. 이 패키지는 raw 타입을 변환하거나 문자열 처리를 할 수 있으며, 길이를 구하는 등 raw 타입을 다루기에 용이하게 사용할 수 있다.
스트링을 다루는데 갑자기 raw 타입을 다루는 패키지를 얘기하는 것에 대해 의아해 하는 사람도 있을 것이다. 그렇다면 ‘변환’, ‘문자열 처리’라는 말을 잘 살펴볼 필요가 있다. 앞서 언급한 두 번째 방법에서 문자열을 스트림으로 데이터베이스에 넣는 방법을 얘기했다. 그렇다면 데이터베이스에 있는 스트림을 변환하는 기능을 이용해 문자를 넣지 못할 이유가 없다(원래 목적대로 쓰라는 법은 없다 자바가 가전용으로 처음 설계됐으나 인터넷에 크게 활용됐던 것처럼 말이다). UTL_RAW를 사용하기 위해서는 패키지 인스톨을 해야 하는데 안 되어 있는 경우 다음과 같이 스크립트를 실행해 사용할 수 있다.
$ svrmgrl
SVRMGR> connect internal
Connected.
SVRMGR> @?/rdbms/admin/utlraw.sql
Statement processed.
No errors for PACKAGE UTL_RAW
SVRMGR> @?/rdbms/admin/prvtrawb.plb
Statement processed.
No errors for PACKAGE BODY UTL_RAW
Statement processed.
Statement processed.
Statement processed.
인스톨이 완료되면 앞 부분에 넣었던 소스를 UTL_RAW 패키지를 이용한 SQL문으로 변경하고 JDK 1.2 이하에서도 지원하는 ByteArrayInputStream() 메쏘드를 이용해 다음과 같이 소스를 변경한다.
.
.
String name = "가나다라마바사아자차";
byte[] raw = name.getBytes();
try
{
PreparedStatement ps = conn.prepareStatement(
// 패키지에 스트림으로 들어온 걸 varchar2로 변환하는 insert sql
"insert into test values('2', 'yz', UTL_RAW.CAST_TO_VARCHAR2(?))");
InputStream is = new ByteArrayInputStream(raw);
ps.setBinaryStream(1, is, raw.length);
ps.executeUpdate();
.
.
.
이 방법은 JDK 1.2 이하 버전에서도 사용 가능하고 CLOB 같은 Large Object로 사용하지 않으므로 데이터베이스 I/O에 민감한 사이트도 적용할 수 있다. 또한 개발자가 문자 셋을 생각할 필요없이 코딩할 수 있다는 장점도 있다. 또한 패스워드 같이 암호화되어 있는 문자열을 가져와 다른 테이블에 넣는 작업을 할 경우 디코딩할 필요가 없다.
이런 기능은 다양한 문자 셋으로 저장된 테이블을 핸들링해야 하는 개발자가 문자 셋을 신경쓰지 않고 작업할 수 있다. 하지만 이런 UTL_RAW를 남용하지 말길 바란다. 이 방법은 select시 where절에 조건으로 쓸 경우 쿼리 플랜에서 인덱스를 조회하지 않는다. 따라서 풀 스캔(Full scan)을 하는 일이 발생한다.
앞서 소개한 방법 중 해당 사이트의 상황(OS, 트랜잭션, 리소스 등)을 면밀히 고려해 적용하기 바란다. 필자의 경우 가급적이면 SQL은 데이터베이스에 의존성 있게 구현하지 않으려고 하는 편이지만 대상 DB에서 최고의 성능을 내야 하는 경우, 다른 방법으로 접근이 불가능한 경우에는 해당 DB에 특화된 기능이라도 찾아서 써야 할 때가 있다. 여기 제시한 방법을 소개할 때 항상 솔루션의 장단점을 소개한 이유는 여러 가능성을 두고 면밀히 검토하길 바라는 마음에서다.
C/S 환경과 윈도우 한글의 합작품
컴퓨터 시스템의 시대별 변경에 관해 대다수의 사람들은 잘 알고 있을 것이다. 메인 프레임에서 C/S 환경으로 넘어가서 요즘 같은 웹 환경으로 변모한 이야기는 컴퓨터 관련 강좌의 단골 메뉴이다. 이런 IT 인프라의 변천을 그대로 겪어온 사이트 환경에 접한 개발자는 필자와 같은 고민을 했을 것이다. 또한 신규 구축을 들어간 사이트의 경우 C/S와 웹이 혼재되어 있었다면 같은 고민을 경험했을 것이다.
필자가 이런 이야기를 꺼내는 이유를 이해하기 위해서는 윈도우 95가 회자되던 때로 거슬러 올라가야 한다. 윈도우 95가 나왔을 때 한때 네티즌이 OS에 탑재된 한글 문자 셋 때문에 거세게 반발했던 일이 있다. 필자가 이런 해묵은 일을 다시 언급하는 이유는 특정 회사를 성토하고자 함이 아니다. 다만 이로 인해 파생된 문제들이 있음을 얘기하고자 함이다.
알다시피 그 당시 문제됐던 것은 윈도우 95에 탑재된 한글 셋이 ‘확장 완성형’이라는 국적 불명의 코드라는 점이었다. 국가 표준이 있음에도 불구하고 우리나라에서 가장 많은 컴퓨터에 사용하는 한글 코드이기에 많은 사람들이 이에 대해 강력히 항의했던 것이다. 하지만 많은 항의에도 불구하고 윈도우 95에 확장 완성형이라는 문자 셋이 올라가고 지금껏 사용되어 왔다.
물론 지금의 윈도우 환경은 유니코드를 지원하고 있지만 이런 코드가 OS에서 쓰여 짐으로 인해 C/S 환경으로 구축된 시스템을 가지고 있는 대다수 사이트의 데이터베이스에는 확장 완성형으로 들어간 문자가 존재하고, 이런 문자를 JDBC로 가져와 작업해야 하는 개발자들에게는 다음과 같은 대책 없는 에러를 던졌다.
SQLException : Protocol violation
java.sql.SQLException: Protocol violation
at java.lang.Throwable.fillInStackTrace(Native Method)
at java.lang.Throwable.fillInStackTrace(Compiled Code)
at java.lang.Throwable.(Compiled Code)
at java.lang.Exception.(Exception.java:35)
at java.sql.SQLException.(SQLException.java:75)
at oracle.jdbc.dbaccess.DBError.check_error(DBError.java)
at oracle.jdbc.ttc7.O3log.receive2nd(Compiled Code)
at oracle.jdbc.ttc7.TTC7Protocol.logon(TTC7Protocol.java)
at oracle.jdbc.driver.OracleConnection.(OracleConnection.java)
at oracle.jdbc.driver.OracleDriver.getConnectionInstance(OracleDriver.java)
at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java)
at java.sql.DriverManager.getConnection(Compiled Code)
at java.sql.DriverManager.getConnection(DriverManager.java:130)
at bbb.main(bbb.java:29)
웹만으로 운용되는 사이트에서는 좀처럼 보기 힘든 에러인데, 이런 사례가 발생되어 필자가 속한 팀에서 이 문제를 어떻게 풀어 나갔는지 그 진행 과정을 소개해 보겠다.
처음 이런 문제점을 접하게 된 곳은 모 통신회사로 C/S 환경 및 웹 환경, 다양한 언어 및 미들웨어가 혼재된 대단히 큰 시스템이었다. 이곳의 대다수 시스템은 턱시도를 미들웨어로, 클라이언트는 파워빌더로 구축되어 있었다. 이런 에러를 내는 곳은 일정하게 에러를 만들어 한때 고심하게 만들었다. 문제는 이런 에러를 낼 경우 해당 커넥션을 풀에 반환한다고 해도 사용을 못한다는 데 있었다. 이런 문제는 포럼도 형성되어 많은 이야기가 오가곤 했는데, 이런 문제점이 영어권에서 이슈되는 사례는 대체로 오라클 DBMS와 JDBC 드라이버의 버전 차이에 따른 문제가 많았고 2바이트 언어권에서의 사례는 드라이버를 맞춰줌으로 인해 해결하지 못한 의견들을 볼 수 있었다. 그래서 우선적으로 DBMS 버전과 JDBC 드라이버의 버전을 확인했다.
import java.io.*;
import java.util.*;
import java.sql.*;
public class JDBCVersionCheck {
public static void main(String[] args) throws SQLException, FileNotFoundException {
try{
Class.forName ("oracle.jdbc.driver.OracleDriver");
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
Connection conn = DriverManager.getConnection(
"jdbc:oracle:thin:@123.125.125.122:1521:ora8i","scott","tiger");
DatabaseMetaData dbmd= conn.getMetaData();
System.out.println("DBMS version :"+dbmd.getDatabaseProductVersion());
System.out.println("Driver Version :"+dbmd.getDriverVersion());
} catch (SQLException ex) {
ex.printStackTrace();
}
}
드라이버의 버전과 오라클 DB의 버전을 확인해 보니 맞는 버전을 쓰고 있으니 드라이버의 버전 차이에 의한 문제는 아니라는 게 증명됐고, 다른 문제가 무엇인지 찾는 단계에서 공공 부문에서 작업하던 개발자의 의견을 들을 수 있었다. 역시 필자가 개발하는 환경과 비슷한 운영 환경을 가지고 있었고, 윈도우 클라이언트 환경에서 데이터 입출력이 되고 다른 언어로 개발한 솔루션에서는 문제가 보고되지 않았다. 유독 JDBC를 이용하는 자바로 개발한 프로그램에서 이런 문제가 발생을 했다. 해당 개발자는 씬 드라이버를 OCI 드라이버로 교체해 운영했고 OCI를 사용하지 못해 씬 드라이버를 사용해야 하는 부분은 catch()절에서 해당 Exception(protocol violation)이 발생했을 경우 새 커넥션을 생성하는 방법을 취했다. 물론 이방법도 해결책이 될 수 있었지만 필자가 개발해야 할 시스템은 성능적인 요구사항이 1600만건의 트랜잭션을 시스템에서 처리해야만 하는 문제가 있어 이 방법으로는 힘들다고 생각됐다. 그래서 좀더 원인을 찾아보기로 하고 팀내에서 이 문제에 대한 분석이 들어갔다. 그 결과 특정 컬럼의 데이터에서 계속 에러가 발생한다는 것을 발견했다. 해당 컬럼에 대한 분석이 들어갔고, 몇 가지 사례가 나왔다.
그 중에서 한 가지가 특정 문자열이 들어가 있다는 것이었다. “?櫻?, “?脾? 등 KSC5601로 표현할 수 없는 문자열을 데이터베이스에서 발견할 수 있었다. 데이터가 들어온 소스는 파워빌더로 작성된 클라이언트를 통해 들어온 데이터였다. 해당 클라이언트를 통한 조회는 아주 정상적이었다. 하지만 자바로 이 데이터를 가져올 때는 ‘100% protocol violation’을 내는 것이다. 그래서 다음과 같이 해당 컬럼에 대해 덤프(dump)를 떠봤더니 정말 재미있는 걸 발견할 수 있었다.
Select dump(문제 컬럼명) from 테이블명 where 해당 행
분명 varchar2(100) 컬럼에 크기가 101짜리 데이터가 들어가 있는 것이다. 팀내에서는 이 문제를 쿼리 수준에서 처리하고 테스트하는 데 합의했다. 그리고 해당 컬럼을 조회하는 쿼리를 오라클 내부 함수 중 RPAD를 이용해 컬럼 크기만큼 가져오게 하고 나머지는 ‘ ’으로 채우는 쿼리를 이용했다. 물론 이런 방법은 가져오는 데이터와 실제 데이터를 비교해 JDBC 상에 문제만 해결하고 실제 사용할 데이터에 문제가 없는지 반드시 검증을 해야만 한다. 정리하면 필자가 이런 문제를 해결하면서 찾아낸 방법은 세 가지 관점에서 해결하기 위한 노력들이었다.
① 데이터베이스 단에서의 처리 : DB 내 함수를 이용한 방법
② 애플리케이션 수준에서의 처리 : 해당 에러 처리(새 커넥션 생성)
③ 개발 환경 수준에서의 처리 : JDBC 드라이버 교체(thic → OCI)
필자의 개발팀이 겪은 문제는 C/S가 혼재된 환경에서 클라이언트(윈도우)에서 확장 완성형 코드로 되어 있는 한글을 확장 완성형을 지원하지 못하는 데이터베이스에 넣어 발생한 문제였다. 특히 이런 문제는 대부분의 웹 환경이 EJB를 지원하는 WAS(Web Application Server) 환경임을 감안할 때 많은 사이트에서 발생 가능한 문제이고 실제 일어나고 있다. 기존의 C/S 환경이 웹 구축 때문에 고쳐지기 힘들고, 데이터베이스에 들어간 이런 특수한 문자들을 찾아서 일일이 바꿔준다는 것도 불가능한 일이다. 그렇다면 개발팀은 이런 에러를 피해가면서 개발해 나아가야 한다. 물론 이 과정에서 최적의 솔루션을 찾는 노력이 가장 중요하다. 필자가 속한 개발팀에서 해결했던 방법 외에도 이런 'protocol violation'을 피할 수 있는 방법은 참고자료를 보기 바란다.
Silver Bullet을 향한 노력
필자는 ‘솔루션’이라는 말을 많이 좋아한다. 이유는 어떤 문제점에 도달해 그 문제를 해결하기 위해 노력한 결과물이라는 생각이 들어서이다. 그리고 그런 솔루션을 스스로 고민하면서 찾아보고 본인이 이용하는 솔루션이 다른 사람의 노력에 의한 것임에 항상 감사하는 마음을 갖곤 한다(원고 작성에 도움이 된 안기현 씨 글과 강석민 씨에게 고마움을 전한다).
하나의 문제점에 많은 솔루션이 있지만 어느 하나 ‘Silver bullet’이라고 말할 수 없다. 정확한 문제점 파악으로 최적의 솔루션을 찾는 것이 중요하다. 이런 직관과 분석에 따라 선택한 해결책이 빛을 발할 것이다. 그리고 그런 힘든 노력으로 얻은 솔루션을 다양한 사람들의 의견을 수렴해 정재해 나간다면 좀더 좋은 개발 환경(다양한 솔루션을 선택할 수 있는)에서 일할 수 있지 않나 싶다. @