IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  XML | Architecture | 자바  >

XML 구문을 분석할 때 발생하는 오류 처리하기

XML 구문을 분석할 때 발생하는 오류를 SAX로 처리해보자

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Brett McLaughlin, 필자 겸 편집자, O'Reilly Media Inc.

옮긴이: 박재호 이해영 dwkorea@kr.ibm.com

2008 년 12 월 02 일

JAXP에서 JAX-WS에 이르기까지 다양한 자바 API가 나오면서 자바(Java™)에서 XML 구문을 분석하기가 한층 쉬워졌습니다. 덕택에 XML 구문분석은 자바 프로그래밍에서 기본이 되었습니다. 하지만 고차원 API가 추상화를 제공하는 탓에 구문분석기와 XML 자료 사이에 일어나는 구문분석 절차를 세세히 제어하기 어려워졌습니다. 그러다 보니 조그만 문제에도 구문분석기가 완전히 멈춰버리는 등 전반적으로 오류가 발생할 가능성이 높아집니다. 다행스럽게도 SAX(Simple API for XML)는 오류를 처리하는 손쉬운 방법을 제공합니다. 심지어 응용 프로그램에서 직접 SAX를 사용하지 않아도 SAX가 제공하는 오류 처리 메커니즘을 사용할 수 있습니다.

JAXP, JAXB, JAX-WS 등 XML 구분을 무척이나 쉽게 분석하는 새로운 자바 API가 쏟아지면서 XML 구문분석은 자바 프로그래밍에서 기본이 됐다. 하지만 고차원 API는 사용이 편한 만큼 추상화를 제공하므로 구문분석기와 XML 자료 사이에 일어나는 구문분석 절차를 세세히 제어하기 어려워졌다. 이 기사에서는 SAX(Simple API for XML)가 제공하는 메커니즘을 이용하여 XML 구문분석 오류를 처리하는 손쉬운 방법을 살펴본다. 응용 프로그램에서 직접 SAX를 사용하지 않더라도 이 방법을 사용하여 오류를 처리할 수 있다.

프로그램 죽이기는 오류 처리가 아니다

자주 사용하는 약어
  • API: Application Programming Interface
  • DOM: Document Object Model
  • JAXB: Java Architecture for XML Binding
  • JAXP: Java API for XML Processing
  • JAX-WS: Java API for XML Web Services
  • SAX: Simple API for XML
  • URI: Uniform Resource Identifier
  • URL: Uniform Resource Locator
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language

모든 응용 프로그램 개발자는 무엇보다 먼저 응용 프로그램 사용자이기도 하다. Vi든, 이맥스(emacs)든, 드림위버(DreamWeaver®)든, 어도비 포토샵(Adobe® Photoshop®)이든, 프로그래머는 다른 응용 프로그램에서 경험한 방식을 토대로 자신의 응용 프로그램이 돌아가는 방식을 결정한다. 그러다 보니 대다수 응용 프로그램, 특히 웹 응용 프로그램은 해독이 불가능한 숫자나 사전에도 없는 낱말을 화면에 출력하는 방식으로 오류를 처리한다. 그나마 신경을 좀 썼다면 글꼴이 예쁜 사과말을 띄운다. 아주 서투르고 어설픈 방식이라 하겠다.

설상가상으로 오류가 발생할 온갖 시나리오까지 고려하면 문제는 더욱 복잡해진다. classpath가 다른 빌드 두 개를 동시에 시작하여 이클립스(Eclipse)를 죽이거나, 절대로 클린업되지 않는 스레드를 실행하거나, 올바른 변수에 값을 넘기지 않은 채 서블릿을 호출하거나 MySQL® 데이터베이스에 연결을 엄청나게 많이 맺어 부하를 거는 등 사용자는 온갖 시나리오로 오류를 유발한다.

특히 XML은 사용자가 필드에 잘못된 값을 집어넣거나 검증 단계로 잘못된 자료를 넘기기로 악명이 높다. 게다가 다른 회사에서 XML 자료를 가져온다면 오류가 발생할 가능성은 몇 배로 늘어난다. 그쪽 회사 프로그래머들도 우리처럼 열심히 일해서 자료 형식을 정확히 맞춰 보냈으리라 믿었다가 구문분석기가 멈춘다. 프로그램이 던지는 예외도 애매모호하기 짝이 없다. 아래와 같이 try...catch 블록 하나로 모든 예외를 처리하는 코드는 올바른 오류 처리 방식이 아니다!

try {
  // 흥미롭고 복잡한 XML 처리 코드
} catch (Exception e) {
  System.err.println(e.getMessage());
}

위와 같이 오류를 처리하면 사용자가 화내고 상사가 짜증낸다. 가장 중요한 고객이 CEO에게 불평하는 전자편지를 날린다. 그 때문에 문제를 찾느라 늦게까지 일해야 하는 모든 동료도 성낸다.

무엇보다 먼저, 개발자는 오류 처리 코드를 실제로 짜야 한다. 넓은 의미에서는 catch 문과 System.err.println() 문을 오류 처리라 간주하지만, 실상은 부실하기 짝이 없는 방법이다. 오류 처리 코드는 단순히 오류를 보고하는 이상을 수행한다. 우수한 오류 처리 코드는 다음 요건을 갖춘다.

  • 사용자를 우선한다.
  • (꼭 필요하지 않는 한) 프로그램 흐름을 방해하지 않는다.
  • 유용한 정보를 제공한다.

오류 처리는 사용자를 우선한다

오류 처리 코드는, 무엇보다도 응용 프로그램 사용자를 위한 코드다. 사실은 프로그램 코드 전부가 궁극적으로 사용자를 위한 코드다. 프로그래머가 사용하는 디버깅 코드도 사용자가 사용할 기능을 고치려고 짜니까 결국 사용자를 위한 코드인 셈이다. 오류 처리 코드도 마찬가지다.

물론 여기서 "사용자"라는 낱말은 의미가 다양하다. 특히 고객에게 판매하는 소프트웨어가 아니라면 반드시 사람이라는 보장도 없다. 예를 들어, 회사와 은행 사이에 재정 정보를 교환하는 백엔드 시스템을 개발한다면 사용자는 회사나 은행 내부 그룹이 된다. 즉, 다른 그룹에서 내 코드를 사용하므로 그들이 내 고객이다. 그러므로 무엇보다 먼저 ‘누가 내 고객인가?’를 파악해야 한다.

뉴저지 주에 사는 컴퓨터 사용자든, 3층에 있는 웹 개발자든, 뉴욕 증권 거래소 소장이든, 일단 내 고객이 누구인지 파악하고 나면, 해당 고객이나 고객층을 고려하여 코드를 작성한다. 내 고객이 일반 컴퓨터 사용자라면 프로그래밍 용어를 배제하고 이해하기 쉬운 오류 메시지를 제공한다. 내 고객이 사내 웹 개발자라면 우리 부서 정보나 시스템 관리 그룹 정보를 제공한다. 내 고객이 뉴욕 증권 거래소 소장이라면…, 흠…, 오류를 처리하느라 프로그램을 중단하는 사태는 없어야 하겠다. 사실, 구체적인 오류 메시지를 걱정하기 전에, 모든 오류 처리 코드에서 오류를 보고할 필요는 없다는 점부터 숙지한다.

오류 처리는 (꼭 필요하지 않는 한) 프로그램 흐름을 방해하지 않는다.

회사로 출근하는 중 고속도로에서 공사 현장을 만났다면, 차를 갓길에 세우고, 엔진을 끄고, 고개를 빼 주변을 살피면서, 마음 속으로 이력서를 다듬지 않을 것이다. 바보 같은 짓이니까. 대신 다음 출구로 빠져서 다른 길을 찾아야 한다. 시간이 더 걸릴지도 모르고, 8시 회의에 늦겠다고 연락해야 할지도 모른다. 어쨌거나 어떻게 해서든지 길을 찾아 출근한다.

문제가 생겼을 때 최고의 오류 처리 방식이 정확히 이러하다. 문제를 돌아가는 길을 찾는다. 프로그램을 죽이거나, “죄송합니다. 운이 없군요”라는 문구를 내놓거나, 호출 스택 내용을 쏟아내는 방식은 오류 처리가 아니다. 이런 행위는 오류 보고다. 프로그래머의 역할은 공사 현장을 만났더라도 최선을 다해 사용자를 출근시키는 일이다.

XML 세상에서 이 말은 자료에 오류가 있더라도 어떻게든 처리하려고 애쓰라는 뜻이다. 때로는 (프로그램을 죽이지 말고) 파일에 오류를 기록하거나 일부 저차원 오류를 무시하라는 뜻이며, 때로는 프로그램 사용자에게 추가 정보나 다른 정보를 요청하라는 뜻이다. 여기서 프로그램 사용자가 반드시 사람은 아니라는 사실을 기억한다. 때로는 다른 프로그램이 프로그램 사용자인 경우도 있다. 어떤 경우든 자료를 계속 처리하면서 앞으로 나가려고 노력하라는 뜻이다.

진짜로 어쩌지 못하는 최악의 상황에서만 프로그램 흐름을 완전히 중단한다. 파일이 완전히 누락되었거나, 자료가 복구하지 못할 정도로 손상되었거나, XML 문서에서 필수 엘리먼트가 빠졌을 때만 사용자 작업을 중단한다. 고속도로 네다섯 곳이 홍수로 끊기고 뒷골목이 완전히 수몰되었을 경우에만 가는 길을 중단하라는 뜻이다. 즉, 진정한 재난이 아니라면 프로그램을 중단하지 말라는 뜻이다.

여기서 SAX가 아주 유용하다. 프로그램을 중단하기 전에 잘못된 자료나 오류 조건을 파악하여 다른 길로 돌아갈 기회를 제공하기 때문이다.

오류 처리는 유용한 정보를 제공한다

오류를 어떻게 처리하든 오류 처리 코드는 유용한 정보를 제공해야 한다. 프로그램 사용자가 원했거나 요청한 정보를 제공하거나 애초에 프로그램이 제공하려던 정보를 제공한다면 더 이상 바랄 나위가 없다. 우아하게 복구하기 어렵거나 다른 방식으로 처리해야 한다면 프로그램 사용자에게 현재 상황을 알려준다. 드물게 프로그램을 완전히 중단해야 한다면 문제 진단에 도움이 될만한 정보를 제공한다.

여기서 핵심은 사용자가 누군지 파악하는 데 있다. 프로그램이 B2B 컴포넌트라면, 즉 다른 컴포넌트에서 내 프로그램을 호출한다면, 호출 스택을 로그 파일에 기록하고 기술적인 오류 메시지를 반환하면 완벽하다. 그래야 내 프로그램을 사용하는 다른 컴포넌트 프로그래머가 정황을 제대로 파악한다. 그러나 일반 고객이 사용하는 웹 응용 프로그램이라면 호출 스택과 오류 코드를 던져서는 안 된다. 대신 사용자가 이해할 정보를 제공해야 마땅하다(사용자가 이해하는 정보는 프로그래머가 이해하는 정보와 다르다). 기본적인 오류 정보와 더불어 도움을 요청할 담당자 정보도 제공한다. 이것이 불가능하거나 관련 정보가 없다면 호출 프로그램이 똑똑한 결정을 내리도록 최대한 유용한 정보를 담아 예외를 던진다.

대다수 XML API는 SAX에 기반을 둔다

XML 오류를 제대로 처리하려면 SAX API를 잘 알아야 한다. SAX가 다른 API보다 본질적으로 월등하다거나 오류 처리에 적합해서가 아니다. 거의 모든 XML API가 SAX에 기반하기 때문이다.

이렇게 하는 이유는 아주 간단하다. SAX는 아주 오래 전에 나온 데다 엄청나게 빠르기 때문이다. XML은 상당히 다루기 쉬운 언어지만 많은 측면에서 직관적인 언어라고 보기는 어렵다. 특이한 구문이나 문법이 많아 구문을 분석하기가 쉽지 않다. 대다수 구문분석기 제작업체와 프로세서 제작업체에게 (텍스트 자료를 읽어 엘리먼트, 이름 공간, 엔티티 참조까지 분석하는) XML 구문분석기 API를 직접 구현한다는 아이디어는 완전히 말이 안 되는 상황은 아니더라도 부담스럽기 그지 없다. 그래서 업체에서는 (심지어 API 개발자까지도) 이미 잘 돌아간다고 검증된 SAX를 사용한다. SAX도 나름대로 단점이 많지만 XML 구문분석 하나는 뛰어나기 때문이다. 이런 이유로 SAX에서 오류를 처리할 줄 안다면 거의 모든 XML API에서도 오류를 처리할 수 있다.

이제 자세한 코드 예제와 더불어 SAX 기초를 살펴보자. 먼저 각 API와 SAX 사이의 관계부터 이해하자.

SAX API는 (당연히) SAX를 사용한다

SAX에 익숙하다면 더 이상 신경쓸 필요가 없겠다. 이미 SAX API를 사용하고 있으니까. Listing 1과 같은 코드를 자주 접하리라. Listing 1은 SAX를 사용하여 XML 문서를 구문분석하는 코드 예제다.


Listing 1. SAX를 사용하여 XML 문서 구문분석하기
                
XMLReader reader = XMLReaderFactory.createXMLReader();
ContentHandler handler = new PrintingContentHandler();
reader.setContentHandler(handler);
reader.parse(new InputSource(new FileReader(xmlFilename)));

위와 같은 코드가 있다면 XML 오류를 처리하기가 어렵지 않다. 게다가 XMLReader를 사용할 줄 안다면 오류 처리를 부드럽게 만들기 위한 첫발을 떼었다.

대다수 DOM도 SAX를 사용한다

대다수 DOM 구문분석기는 사실상 SAX 구문분석기를 사용하여 DOM 트리를 만든다. 물론, DOM API 자체는 아래서 돌아가는 SAX 구문분석기를 노출하지 않는다. DOM API는 DOM 트리를 생성하는 용도보다 DOM 트리를 조작하는 용도로 주로 사용하기 때문이다. 하지만 대다수 DOM 구문분석기는 제조업체마다 나름대로 기반 SAX 구문분석기를 사용하는 수단을 제공한다.

예를 들어, Xerces에서 DOM 트리를 만들 때 사용하는 클래스는 org.apache.xerces.parsers.DOMParser다. 이 개체에서 parse() 메서드를 호출하면 Document 객체인 DOM 트리를 얻는다. 구체적인 방법은 Listing 2와 같다.


Listing 2. SAX를 사용하여 DOM 트리 만들기
                
DOMParser parser = new DOMParser();
parser.parse(new InputSource(xmlFilename));
Document doc = parser.getDocument();

코드를 훑어보면 구문을 분석하는 단계가 앞서 Listing 1과 비슷하다. 실제로 (DOMParser 인스턴스로 문서를 가져오는 몇 가지 방법 중 하나인) InputSource 클래스는 SAX에서 사용하는 클래스다. 하지만 겉보기보다 비슷한 점은 더욱 많다. Xerces-J API 문서와 원시 코드를 살펴보면 DOMParser가 다른 Xerces 클래스인 org.apache.xerces.framework.XMLParser 클래스를 확장한다는 사실을 발견할 것이다. 그런데 이 XMLParser 클래스는 DOMParser 클래스는 물론 (SAX 구문분석기 클래스인) SAXParser의 부모 클래스이기도 하다.

상당히 복잡하게 들리지만 요지는 이렇다. Xerces-J에서 SAX 구문분석을 실제로 수행하는 XMLParser 클래스는 DOM 구문분석을 실제로 수행하는 클래스이기도 하다는 말이다. 즉, DOMParser 클래스에서 SAX XMLReader 클래스를 추적하기는 불가능하지만, DOMParser 클래스가 최종적으로 실행하는 코드와 SAX XMLReader가 최종적으로 실행하는 코드가 똑같다는 뜻이다. 그래서 DOMParser에서 몇 가지 진짜로 중요한 메서드를 사용할 수 있다.

  • setEntityResolver(): SAX constructor인 EntityResolver를 인수로 받아 XML 문서에 있는 엔티티를 처리하는 메서드다.
  • setFeature(): SAX가 제공하는 메서드로, 구문분석기에서 DOM 기능과 SAX 기능을 설정할 수 있다.
  • setErrorHandler(): 오류 처리에 중요한 메서드로, SAX ErrorHandler 클래스를 인수로 받는다. 이 메서드를 사용하면 오류를 가로채 처리할 수 있다.

위에서 보듯이, SAX XMLReader 클래스를 직접 사용하지는 못하지만, 이 기사에서 다루는 오류 처리에 핵심적인 SAX 메서드를 사용할 수는 있다.

JAXP도 SAX를 사용한다

대다수 개발자는 더 이상 SAX나 DOM을 사용하지 않는다. 대신 자바 API인 JAXP API를 사용하여 XML을 처리한다. Listing 3은 JAXP를 이용하여 SAX 구문분석을 수행하는 코드 예제다.


Listing 3. JAXP를 이용한 SAX 구문분석
                
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(false);
SAXParser parser = factory.newSAXParser();
parser.parse(new File(args[0]), new MyHandler());

어디서 많이 본 모습이 아닌가? 클래스 이름이나 옵션은 다소 다르지만 Listing 1에서 보았던 코드와 별반 다르지 않다. 하지만 여기서 한 걸음 더 나가서 SAX에 더욱 가까이 다가가는 방법이 있다. 구체적으로 말하자면, JAXP SAXParser 클래스는 getXMLReader()라는 메서드를 제공한다. 이 메서드는 기반 SAX XMLReader 클래스를 반환한다.

SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(false);
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
            

일단 SAX XMLReader 클래스를 얻었으면 SAX API를 사용할 때와 마찬가지 방법으로 오류를 처리하면 된다.

JAXP를 사용하여 DOM 구문분석을 수행할 때도 마찬가지다. DOM에서 SAX로 돌아가기는 더욱 쉽다. Listing 4는 JAXP를 사용하여 DOM 구문분석을 수행하는 일반적인 절차를 보여준다.


Listing 4. JAXP를 이용한 DOM 구문분석
                
DocumentBuilderFactory factory = 
  DocumentBuilderFactory.newInstance();

factory.setValidating(true);
factory.setNamespaceAware(false);

DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File(args[0]));

위 코드는 확실히 DOM 기반 구문분석이지만 SAX로 돌아가기는 아주 쉽다. 아니, DOM에서 SAX로 돌아가는 코드를 논하기에 앞서, 좀 더 기본적인 사실부터 이해하자. Listing 3에서 보았듯이 JAXP를 사용한다면 이미 SAX 클래스도 사용하는 셈이다. 그러므로 JAXP를 사용한다면 DOM에서 SAX로 변환하는 방법을 걱정할 필요가 없다(구체적인 방법은 참고자료에서 제공하는 링크를 살펴본다). 코드를 살짝 고쳐 DOMBuilder 대신 SAXParser를 사용하면 SAX 오류 처리 코드를 추가할 준비가 갖춰진다.

JAXB와 같은 고차원 API도 SAX를 사용한다

지금쯤이면 SAX가 상당히 널리 퍼져 있다는 사실을 눈치챘을 것이다. DOM API와 JAXP API가 SAX를 사용한다. 따라서 DOM을 사용하거나 JAXP를 본딴 고차원 API도 SAX를 사용할 이유가 충분하다. 고차원 API에서 SAX까지 내려가려면 앞서보다 많은 작업이 필요할지도 모르지만 대부분 크게 어렵지 않다.

SAX를 사용하는 또 하나 인기있는 API는 썬가 만든 자바 API인 JAXB(Java Architecture for XML Binding)다. JAXB를 사용하면 XML 문서를 자바 객체 모델로 변환하거나 객체 모델을 XML로 변환할 수 있다. XML에서 자바로 변환하는 프로세스는 언마샬링(unmarshalling)인데, 이는 두 단계로 이뤄진다.

  1. XML 문서를 구문분석한다. 문서 내 자료, 엘리먼트 이름, 속성 이름은 물론 엘리먼트와 속성과 자료 사이의 관계도 모두 보존한다.
  2. 구문분석한 자료를 자바 객체 모델의 클래스 변수와 인스턴스로 변환한다.

XML 구문을 분석할 때는 오류가 발생할 가능성이 항상 존재하며 여기에 SAX가 관련될 가능성은 거의 100%다. JAXB에서도 마찬가지며, JAXB에서 SAX XMLReader를 얻는 방법은 다소 복잡하다. Listing 5는 언마샬링을 수행하는 클래스인 Unmarshaller를 얻어서 SAX 기반 구문분석을 좀 더 직접적으로 통제하는 방법을 보여준다.


Listing 5. JAXB에서 SAX XMLReader 얻기
                
JAXBContext context = JAXBContext.newInstance("dw.ibm");
Unmarshaller unmarshaller = context.createUnmarshaller();

// Unmarshaller에서 저수준 처리기를 얻는다.
UnmarshallerHandler unmarshallerHandler = 
  unmarshaller.getUnmarshallerHandler();

// SAX 파서를 얻기 위해 JAXP를 사용한다.
SAXParserFactory factory = SAXParserFactory.newInstance();

// factory 옵션을 설정한 다음, 표준 JAXP 호출을 사용한다.
factory.setNamespaceAware(true);
 
XMLReader reader = factory.newSAXParser().getXMLReader();

// unmarshallerHandler로 reader 처리기를 설정한다.
reader.setContentHandler(unmarshallerHandler);

// 이제 JAXB에서 가져온 언마샬링 처리기를 사용해 구문분석에 들어간다.
reader.parse(new InputSource(new FileReader(xmlDocument)));

MyCustomObject topObject = (MyCustomObject)unmarshallerHandler.getResult();

앞서 살펴본 예제보다 다소 길지만 그래도 크게 복잡하지 않다. 위 코드는 JAXB 프레임워크를 사용하지 않고 Unmarshaller.unmarshal() 메서드를 이용하여 JAXB에서 (언마샬링 코드가 포함된) 핸들러를 얻는다. 위 코드에서 핸들러를 가져오는 부분은 다음과 같다.

// Unmarshaller에서 저수준 처리기를 얻는다.
UnmarshallerHandler unmarshallerHandler = 
  unmarshaller.getUnmarshallerHandler();

그런 다음, 반환된 핸들러로 직접 SAX 구문분석을 수행한다. 이 단계에서 필요한 오류 처리 코드를 삽입한다. 구체적으로 오류 처리 코드를 삽입하는 방법은 기사 후반에 설명한다.

구문분석을 마친 후에는 다음 코드를 실행하여 JAXB 프레임워크로 돌아간다.

MyCustomObject topObject = 
  (MyCustomObject)unmarshallerHandler.getResult();

그러면 언마샬링을 수행하는 과정에서 발생하는 오류를 원하는 방식으로 처리할 수 있다. 즉, 직접 오류를 살펴보면서 우회하거나 적절히 보고하는 등 사용자에게 좀 더 나은 경험을 선사할 수 있다.

다른 API도 마찬가지다...

여기서 모든 XML API를 소개하면서 각 최상위 API로부터 기반 SAX 구문분석기를 얻어내는 과정을 하나하나 설명하기란 불가능하다. 하지만 지금까지 소개한 API만으로도 전체적인 그림과 방식이 눈에 들어오리라 믿는다. API 문서를 읽어보거나, org.xml.sax.XMLReader 클래스를 확장하거나 구현하는 클래스를 찾거나, (org.xml.sax 패키지에 포함된) SAX ErrorHandler를 인수로 받는 클래스를 찾는다. “[API 이름] SAX”나 “[API 이름] ErrorHandler”라는 키워드로 구글을 검색해도 좋겠다. API와 SAX를 연결하기가 생각보다 훨씬 쉽다는 사실에 깜짝 놀랄 것이다.

ErrorHandler 인터페이스를 사용하여 오류 처리하기

SAX에서 오류를 처리하는 핵심 인터페이스는 org.xml.sax.ErrorHandler다. 간단한 인터페이스 세 개를 사용하면 모든 오류 유형을 구현하고 처리할 수 있다. SAX에 등록하기도 아주 쉬워서 오류 처리는 코드 몇 줄이면 끝난다.

오류 유형 세 가지와 처리 방법 세 가지

ErrorHandler 인터페이스 코드는 놀랍도록 단순하다. Listing 6이 인터페이스의 전부다(주석은 생략했다).


Listing 6. SAX ErrorHandler 인터페이스
                
package org.xml.sax;

public interface ErrorHandler {
  public void warning(SAXParseException exception) throws Exception {
  }

  public void error(SAXParseException exception) throws Exception {
  }

  public void fatalError(SAXParseException exception) throws Exception {
  }
}

이상이다. 구문분석 과정에서 오류가 발생하면 세 가지 메서드 중 하나가 호출된다. 즉, 자신만의 ErrorHandler를 구현하면 자신만의 오류 처리 방법을 구현하는 셈이다.

경고는 무시하라

XML 1.0 권고안에 따르면, SAX에서 경고(warning)는 오류(error)나 치명적인 오류(fatal error)가 아닌 문제를 뜻한다. 정의가 아주 애매모호하다. 좀 더 구체적으로 정의하자면, 경고는 구문분석기가 구문분석을 계속 수행하는 데 장애가 되지 않는 문제를 가리킨다. 일반적으로 경고는 (구문분석을 중단시키지 않으므로) 완전히 무시하거나 정보성 메시지만 출력한다.

주석에 문제가 있거나, 예상치 못한 값이지만 처리에 문제가 없거나, 온갓 잡다하고 사소한 오류가 경고로 감지된다. 경고를 어떻게 처리할지 고민하기 전에, 앞서 언급한 '오류 처리 원칙'을 상기하기 바란다.

  • 사용자를 우선한다.
  • (꼭 필요하지 않는 한) 프로그램 흐름을 방해하지 않는다.
  • 유용한 정보를 제공한다.

위 원칙을 염두에 두고 경고를 고려해보면 무시하는 정책이 가장 현명하다는 사실을 이해하리라. 경고 메시지를 디버깅 모드에서 출력하거나 로그 파일에 기록하겠다면 상관없다. 하지만 이 때도 (정의 상) 중요하지 않은 사안으로 귀중한 CPU 시간을 낭비하므로 주의한다. 일반적으로 warning() 메서드를 구현하는 가장 좋은 방법은 Listing 7과 같다.


Listing 7. ErrorHandler에서 warning() 메서드 구현하기
                
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class DefaultErrorHandler implements ErrorHandler {
  public void warning(SAXParseException exception) 
    throws SAXException {

    // 아무 일도 하지 않는다.
  }

  public void error(SAXParseException exception) throws SAXException {
    // 나중에 채워넣는다.
  }

  public void fatalError(SAXParseException exception) 
    throws SAXException {

    // 나중에 채워넣는다.
  }
}

오류는 복구를 시도하라

오류(error)는 XML 문제 중에서도 가장 처리하기 어렵다. 경고는 무시하거나 기록하면 그만이다. (잠시 후에 살펴볼) 치명적인 오류는 구문분석을 중단하고 심각하게 대응해야 한다. (치명적인 오류와는 다른) 이런 오류는 처리하기가 어중간하다. SAX 오류는 XML 1.0 명세에서 정의하는 오류와 동일한데, XML 1.0 명세는 오류를 상당히 애매하게 정의한다.

명세를 위반하는 사항: 결과는 알지 못한다. 이 명세에서 must, required, must not, shall, shall not이라는 키워드로 지시하는 규정을 위반하는 사항이 오류다. 명세를 준수하는 소프트웨어는 오류를 감지해서 보고해도 무방하며 오류에서 복구해도 무방하다.

불행하게도, 위 정의만 봐서는 오류가 발생하는 원인이 분명치 않다. 여기서 유일하게 주목할 만한 낱말이 “복구”다. 다시 말해, 프로그램에 오류가 발생하면 구문분석을 중단하고 프로그램을 죽이기보다 오류에서 복구할 수 있어야 한다는 뜻이다.

실제 환경에서 오류는 XML 구조(structure)나 정형성(well-formedness) 문제가 아니라 내용(content) 문제로 발생한다. 그래서 대다수 오류는 예상치 못한 오류다. 즉, XML 문서를 분석하는 과정에서 오류가 발생했다면 십중팔구 문서가 불완전하거나, 분석한 문서에서 일부 자료가 누락되었거나, 손상되었거나, 부정확하다는 뜻이다.

하지만 오류가 발생할 즈음이면 복구는 쉽지 않다. SAX로 문서를 분석할 때는 문서 앞을 미리 내다보거나 뒤로 돌아가지 못한다. 기껏해야 SAXParseException에서 이전 내용을 가져오는 정도다. 하지만 그렇다고 구문분석을 중단할 필요는 없다. 즉, 이런저런 대처 방법을 강구하느라 프로그램을 중단할 필요가 없다는 뜻이다. (고객이든 다른 개발자든) 사용자에게 반드시 오류를 보여줄 필요는 없다. 대다수 경우는 오류가 발생해도 유용한 결과를 반환할 수 있다.

그렇다면 구체적으로 어떻게 대처할까? 우선, 어딘가에 정보를 기록해야 한다. 자료에 문제가 있다는 사실을 사용자에 알릴 필요는 없을지라도 개발자 자신은 구체적인 정황을 알아야 한다. System.out이나 System.err는 되도록 피한다. 로그 파일에 오류 정보를 출력하거나 Log4J와 같은 API(참고자료 참조)를 사용하는 방법을 권장한다. 그래야 나중에라도 문제를 파악하여 재발을 막을 수 있다.

다음으로, 응용 프로그램의 비즈니스 논리에 맞게 error() 메서드로 적절하게 오류를 처리하는 방법도 고려할 만하다. 예를 들어, 응용 프로그램이 contact-info 엘리먼트 내에 있는 email 엘리먼트에서 전자편지 주소를 읽어서 저장한다고 가정하자. error() 메서드에서 contact-info 엘리먼트나 email 엘리먼트에 오류가 있다는 사실을 감지한다면 십중팔구 전자편지 주소가 잘못되었을 가능성이 크다. 이 때 (프로그램 흐름을 중단하지 않는 방식으로) 전자편지 주소가 잘못되었거나 누락되었다고 호출 프로그램에 알려주는 방법을 고려한다. 사용자가 직접 값을 입력하는 응용 프로그램이라면 간단한 웹 폼으로 사용자에게 전자편지 주소를 다시 입력하라고 요청해도 되겠다.

올바른 오류 처리 방식을 논할 때는 구체적인 예제를 들기가 가장 힘들다. 비즈니스 분야와 논리에 따라서 올바른 방식이 달라지기 때문이다. 그러므로 자기 프로그램에 맞게 error() 메서드를 구현해야 마땅하다. 단, 절대로 프로그램을 죽여서는 안 되며, 프로그램 흐름을 중단하는 예외를 던져서도 안된 다. 정의 상 (치명적인 오류와는 달리) 오류는 프로그램 흐름을 중단해서 안 된다는 사실을 명심한다.

치명적인 오류는 보고하라

마지막으로 살펴볼 문제 유형은 치명적인 오류(fatal error)다. XML 1.0 명세에 따르면, 치명적인 오류란 구문분석을 불가피하게 중단시키는 문제를 뜻한다. 가장 일반적인 예가 형식이 잘못된 문서다. 다시 말해, firstName 엘리먼트를 열고서 닫지 않았거나, 왼쪽 꺽쇠 괄호(<)는 있는데 오른쪽 꺽쇠 괄호(>)가 없는 경우다. 구문분석기는 이러한 문제를 복구하지 못한다. 문서 구조 전체가 의심스럽기 때문이다. SAX API 문서에서는 일단 치명적인 오류가 발생할 경우 응용 프로그램은 문서를 사용하지 못한다고 가정하라고까지 말한다. 즉, 치명적인 오류는 아주 심각히 다뤄야 한다는 뜻이다.

언뜻 보기에는 치명적인 오류도 선별이 가능하다고 여길지 모르겠다. 예를 들어, </firstName>이 없다면 비슷한 뭔가를 찾아서 지능적으로 추측하면 어떨까? </girstName>이라는 엘리먼트가 있다면 </firstName>을 잘못 입력했다고 간주해도 좋지 않을까? 사람 눈에는 대다수 치명적인 오류가 아주 사소한 문제로 보인다.

하지만 SAX는 순차적인 읽기 전용 구문분석기다. 즉, XML 문서를 미리 읽지도 못하고 이미 읽은 내용을 되집지도 못한다. 그래서 잠재적인 철자 오류를 미리 내다보거나 되돌아가서 확인하기란 사실상 불가능하다. 앞서 읽은 내용을 되짚으려면 읽어 들인 내용을 메모리 버퍼에 저장하는 핸들러 코드를 직접 작성해야 한다. 또한 문서를 미리 내다보려면 문서를 읽어 들이는 코드도 직접 작성해야 한다. 개발자가 지는 부담은 상당히 큰 반면, 얻는 이익은 미미하다. 어떤 방법을 동원하든지 원래 문서 작성자가 의도한 바를 추측하는 정도에 그치기 때문이다. 그러므로 치명적인 오류는 두 번째 오류 처리 원칙인 "꼭 필요하지 않는 한 프로그램 흐름을 중단하지 않는다"에서 꼭 필요한 경우에 해당한다.

여기서 세 번째 원칙 “오류 처리는 유용한 정보를 제공한다”가 중요하다. 진짜 문제가 생겼다면 응용 프로그램은 사용자에게 유용한 정보를 제공해야 한다. 다음은 피해야 할 코드 예제다(좋은 예제라고 오해하지 않도록 Listing에서 뺐다).

  public void fatalError(SAXParseException exception) 
    throws SAXException {

    // 전형적이지만 끔찍한 오류 처리

    // 비정상 종료 결과를 출력한다.
    System.out.println("**Parsing Fatal Error**" + "\n" +
                       "  Line:    " + 
                          exception.getLineNumber() + "\n" +
                       "  URI:     " + 
                          exception.getSystemId() + "\n" +
                       "  Message: " + 
                          exception.getMessage());        
    throw new SAXException("Fatal Error encountered");
  }

프로그래머에게는 유용한 정보일지도 모르겠다. 하지만 다른 사람에게는 아무 쓸모가 없다. 응용 프로그램마다 구체적으로 오류를 처리하는 방식은 달라지지만, 상위 프로그램에 SAXException을 던져야 한다는 제약은 같다. 예를 들어, 프로그램을 곧바로 중단해서 제어권을 가져오는 대신 상위 클래스에 유용한 정보와 예외를 넘겨주는 편이 낫다.

유용한 오류는 예외 클래스를 정의하여 던지라

프로그램에서 의미 있는 오류 정보를 전달하는 가장 쉬운 방법이 SAXException으로 넘길 예외 클래스를 직접 정의하는 방법이다. SAXExceptionErrorHandler에 속한 세 메서드가 모두 던질 수 있으며, 따라서 프로그램이 자신을 호출한 프로그램과 통신하는 유일한 수단이다. 기본적으로 SAXException은 다음과 같은 몇 가지 메서드만 제공한다.

  • getException(): 나중에 사용할 목적으로 SAXException에 들어 있는 Exception을 반환한다. 응용 프로그램에서 예외 클래스를 별도로 정의한 후 호출 프로그램으로 넘길 수 있다.
  • getMessage(): 좋은 오류 정보를 전달하기에 매우 직접적인 방법이기는 하지만, 사용자가 이해하기 쉬운 오류 메시지를 여기에 저장한다.
  • getString(): 모 클래스의 getString()을 재정의하여 내장된 예외 정보를 출력한다.

예를 들어, 웹 프로그램에서 사용할 XML 구문분석 컴포넌트를 구현한다고 가정하자. 프로그램에서 사용할 예외 클래스를 Listing 8과 같이 정의해도 좋겠다.


Listing 8. 웹 응용 프로그램으로 오류를 보고하는 예외 클래스
                
public class WebException extends Exception {
  private int httpStatusCode = 400;
  private String redirectURL;

  public WebException(String message, int httpStatusCode, String redirectURL) {
    super(message);
    this.httpStatusCode = httpStatusCode;
    this.redirectURL = redirectURL;
  }

  public WebException(String message, Throwable cause, 
                      int httpStatusCode, String redirectURL) {
    super(message, cause);
    this.httpStatusCode = httpStatusCode;
    this.redirectURL = redirectURL;
  }

  public int getHttpStatusCode() {
    return httpStatusCode;
  }

  public String getRedirectURL() {
    return redirectURL;
  }
}

아주 기본적인 예외 클래스이지만 HTTP 상태 코드(404나 401)와 재지정 URL 등 웹 프로그램에서만 사용하는 정보를 저장한다. Listing 9는 ErrorHandler 클래스에서 위 예외 클래스를 사용하는 모습이다.


Listing 9. 프로그래머가 정의한 예외 클래스를 받아서 오류 보고하기
                
  public void fatalError(SAXParseException exception) throws SAXException {
    // 웹 관련 예외로 보고한다.
    WebException webException = new WebException("There was a problem converting your " +
      "response into a format our server could read. Please contact our customer " +
      "service team at 1-800-555-0972, and we'd love to help you in person.", 
      exception, 406, "/errorPages/xmlError.php?reason=" + exception.getMessage());
    throw new SAXException(webException);
  }

코드는 별로 복잡하지 않지만 몇 가지 아주 중요한 작업을 수행한다.

  1. 오류 메시지가 구체적이고 이해하기 쉬우며 유용하다. 또한 프로그래머가 아니라 실제 사용자에게 제공하는 정보다.
  2. 상태 코드와 오류 페이지 등 프로그램 관련 정보를 상위 응용 프로그램에 제공한다. 따라서 XML 구문분석 컴포넌트를 호출한 상위 프로그램이 판독 불가능한 호출 스택 대신 대처 가능한 정보를 얻는다.
  3. 심지어 재지정 페이지도 유용한 정보를 제공한다. 로그 파일에 기록하거나 나중에 프로그래머가 대응할 수 있도록 버그 보고서를 자동으로 생성할 때 필요한 정보를 포함한다.

간단한 예외 클래스를 정의하고 fatalError() 함수에 코드 몇 줄만 추가하면 위와 같은 작업이 가능하다.

응용 프로그램에 맞게 예외를 던진다

XML 구문분석 컴포넌트를 호출하는 프로그램이 웹 응용 프로그램이 아니라면 당연히 WebException 클래스는 쓸모가 없다. 심지어 웹 응용 프로그램에서도 별로 쓸모가 없을지 모른다. 그래서 응용 프로그램이 속하는 분야와 응용 프로그램 자체에 대한 지식이 중요하다. 자신의 응용 프로그램이 무엇을 필요로 하는지 알아야 한다.

현재 사용자를 이해하고 그들이 필요로 하는 정보도 파악해야 한다. 밑단에서 사용할 컴포넌트를 구현하는 경우에도 문제가 생겼을 때 호출 프로그램이 요구할 정보를 파악해야 한다. 응용 프로그램에 맞는 예외 클래스를 구현한 후 오류 메서드에서 SAXException에 넣어 던지라. 문제에 관련한 정보와 유용한 정보를 직접 짠 예외 클래스에 넣어서 전달하라.

고객단에 놓일 컴포넌트를 구현하는 경우가 아니라면 내 코드를 사용할 개발자에게 (전화나 전자편지나 문서로) 연락하라. 인트라넷 자바 서블릿 개발자가 내 코드를 사용한다면 직접 짠 예외 클래스에 유용한 정보를 잔뜩 넣어 SAXException으로 던진다고 알려주라. 예외 클래스 소스 코드와 기본적인 정보를 보내주고 사용법도 알려준다. 간단한 대화만으로도 그들이 getMessage()를 남발하며 제각각 부실하게 오류를 처리하는 실수를 막을 수 있다.

SAXException은 확장하지 말라

개발자가 자주 저지르는 실수 하나가, 예외 클래스를 SAXException에 넣어서 던지는 대신, SAXException 클래스를 실제로 확장하는 실수다. 앞서 Listing 89에서 WebException 클래스를 SAXException에서 확장하여 fatalError()에서 바로 던져도 되기는 된다. 불가능하지는 않으나, 그랬다가는 몇 가지 미묘한 문제에 부딪힌다.

첫째, SAXException 클래스를 확장해 예외 클래스를 정의하면 이 클래스는 SAX에서만 사용할 수 있다. 즉, XML 구문분석을 수행하지 않는 컴포넌트나 SAX 기능을 전혀 사용하지 않는 컴포넌트에서 예외 클래스를 사용하지 못한다. 일반적인 예외 클래스로 정의한 후 다른 컴포넌트에서 재사용하는 편이 훨씬 낫다. SAXException 클래스가 다른 클래스를 내장하는 기능을 제공하는 이유가 바로 여기에 있다. 둘째, SAX 기능을 사용하는 기존 응용 프로그램 대다수가 자동으로 SAXException에 내장된 예외 클래스를 참조한다. 즉, 원래 설계된 의도대로 SAXException을 사용하면 다른 기존 컴포넌트와 코드를 통합하기 훨씬 쉬워진다.

ErrorHandler 구현 등록하기

ErrorHandler 클래스에 메서드 세 개를 구현했다면 오류 처리 코드를 사용할 준비가 거의 끝났다. 마지막으로 오류 처리기를 구문분석기에 등록하면 끝이다. 매우 간단하지만 중요한 단계다. 오류 처리 코드를 멋지게 짜놓고도 SAX 구문분석기에 알려주지 않는다면 코드는 무용지물이 된다.

XMLReader에서 setErrorHandler()를 호출하라

XML API나 구문분석 계층에서 XMLReader를 가져왔다면 할 일은 간단하다. XMLReadersetErrorHandler()라는 메서드를 제공하며, setErrorHandler() 메서드는 ErrorHandler를 인수로 받는다. 메서드를 호출하는 방법은 Listing 10을 참고한다.


Listing 10. XMLReader에 오류 처리기 설정하기
                
JAXBContext context = JAXBContext.newInstance("dw.ibm");
Unmarshaller unmarshaller = context.createUnmarshaller();

// Unmarshaller에서 저수준 처리기를 얻는다.
UnmarshallerHandler unmarshallerHandler = 
  unmarshaller.getUnmarshallerHandler();

// JAXP를 사용해 SAX 파서를 얻는다.
SAXParserFactory factory = SAXParserFactory.newInstance();

// factory에서 옵션을 설정해 표준 JAXP 호출을 활용하게 만든다.
factory.setNamespaceAware(true);
 
XMLReader reader = factory.newSAXParser().getXMLReader();

// 내용 처리를 위해 unmarshaller에서 얻은 처리기를 사용한다.
reader.setContentHandler(unmarshallerHandler);

// 직접 만든 ErrorHandler를 등록한다
reader.setErrorHandler(new MyJAXBErrorHandler("/logs/logfile.txt"));

// JAXB에서 얻은 언마샬링 처리기로 이제 구문분석을 시작한다.
reader.parse(new InputSource(new FileReader(xmlDocument)));

MyCustomObject topObject = (MyCustomObject)unmarshallerHandler.getResult();

Listing 10에서 예제로 사용한 고차원 API는 (Listing 5에서 소개했던) JAXB다. 먼저 XMLReader 인스턴스를 얻은 후 setErrorHandler() 메서드로 새로운 ErrorHandler를 등록한다. 위 예제에서 우리가 구현한 핸들러는 MyJAXBErrorHandler다. 핸들러는 로그 파일 경로를 인수로 받는다. 핸들러를 등록한 후에는 parse() 메서드를 호출하여 구문분석을 시작한다. 그러면 구문분석 도중에 발생하는 문제는 모두 MyJAXBErrorHandler에서 구현한 메서드로 넘어온다.

getErrorHandler로 현재 ErrorHandler를 확인하라

현재 XMLReader에 등록된 ErrorHandler를 확인하는 방법도 있다. getErrorHandler() 메서드를 호출하면 현재 등록된 ErrorHandler가 반환된다. 여기서 필요하다면 처리기 클래스 유형이나 필요한 정보를 출력한다. 예제는 Listing 11을 참고한다.


Listing 11. 현재 오류 처리기 확인하기
                
JAXBContext context = JAXBContext.newInstance("dw.ibm");
Unmarshaller unmarshaller = context.createUnmarshaller();

// Unmarshaller에서 저수준 처리기를 얻는다.
UnmarshallerHandler unmarshallerHandler = 
  unmarshaller.getUnmarshallerHandler();

// JAXP를 사용해 SAX 파서를 얻는다
SAXParserFactory factory = SAXParserFactory.newInstance();

// factory에서 옵션을 설정해 표준 JAXP 호출을 활용하게 만든다.
factory.setNamespaceAware(true);
 
XMLReader reader = factory.newSAXParser().getXMLReader();

// 내용 처리를 위해 unmarshaller에서 얻은 처리기를 사용한다.
reader.setContentHandler(unmarshallerHandler);

// 지금 당장 오류 처리를 확인하자.
ErrorHandler handler = reader.getErrorHandler();
System.out.println("Error handler is currently: " + 
  handler.getClass().getName());

// JAXB에서 얻은 언마샬링 처리기로 이제 구문분석을 시작한다.
reader.parse(new InputSource(new FileReader(xmlDocument)));

MyCustomObject topObject = (MyCustomObject)unmarshallerHandler.getResult();

이 정보는 순전히 참조용이다. 실제로 클래스 정보로 수행할 수 있는 작업은 거의 없다. 그래도 자주 사용하는 XML API가 어떤 오류 처리기를 사용하는지 궁금하다면 관련 정보를 찾아내기 좋은 방법이다.

결론

오류 처리는 응용 프로그램을 구현할 때 가장 우선으로 고려할 사항 중 하나다. 실제로 대다수 사용자는 응용 프로그램이나 사이트에서 어떤 기능보다 오류를 (그리고 오류가 처리되는 방식을) 가장 강력하게 기억한다. 멋진 기능은 멋지고 불쾌한 오류 처리는 불쾌하다. 아주 간단하게 들리지만, 10%만 시간을 더 들여서 오류를 우아하게 처리하거나 오류 발생률을 줄인다면 사용자 경험은 극적으로 향상된다.

XML 구문분석 과정에서 발생하는 오류를 올바로 처리하려면 해당 XML API를 이해하는 정도로는 부족하다. 자신이 사용하는 XML API가 XML 구문분석을 수행하는 기본 방식을 이해해야 한다. 대다수 API가 SAX를 사용한다는 사실을 깨닫는다면 SAX가 올바른 오류 처리를 위한 열쇠라는 사실을 납득할 것이다. 5년 후에 다른 XML API가 SAX를 대체한다면 그 API도 편하게 사용할 정도로 익혀야 하리라. API에서 SAX XMLReader를 가져오기는 쉽겠지만, 가져온 인터페이스를 올바로 사용하기는 쉽지 않다. 자신이 사용하는 시스템과 하위 단계를 이해하는 것, 바로 이것이 오류 처리의 핵심이다. 그렇다고 당장 어셈블리 언어로 푸시와 팝을 구현할 필요는 없지만, 오늘날 SAX가 핵심적인 XML API라는 사실은 알아두어야 한다.

그러면 오류 처리는 구현과 실행 문제다. SAX는 XMLReaderErrorHandler 인터페이스로 오류 정보를 전달한다. 오류를 즉시 대응하거나, 호출 프로그램으로 넘기거나, 직접 구현한 예외 개체에 넣고 정보를 추가하거나, 어떤 방식을 취하든 응용 프로그램에 맞게 처리하면 그만이다. ‘이렇게 오류를 처리하라’는 절대 공식은 없지만, 중대한 원칙은 하나 있다. 바로 '사용자를 괴롭히지 말라!'는 원칙이다. 이 원칙만 잘 따른다면 동료와 경쟁업체를 쉽게 능가하리라.

오류를 회피하지 말고 부딪히라. 사용자에게 해를 끼치게 버려두지 말라. 프로그램에 이익이 되도록 활용하라. 그러면 불평이 적어지고, 관리자가 좋아하고, 사용자도 기뻐할 것이다. 자신의 프로그램에서 흥미로운 해결책을 찾았다면 필자에게 알려주기 바란다. 자신의 프로그램에서 재난을 회피했다면 그 방법을 developerWorks 포럼에서 다른 전문가들과 공유해도 좋겠다.



참고자료

교육

제품 및 기술 얻기
  • 자바 5나 최신 자바 6 소프트웨어: 자바 프로그래밍을 처음 시작한다면 여기에서 JDK와 JAXP를 내려받을 수 있다.

  • Log4J: IBM에서 제공하는 오픈 소스 유틸리티로, 텍스트 파일에서 네트워크까지 다양한 위치로 로그를 기록할 수 있다. 오류를 제대로 처리하려면 로그는 기본이다.

  • Java and XML, Third Edition (Brett McLaughlin과 Justin Edelson, O'Reilly Media, Inc.): XML, XSL, XML 명세 등 XML을 처음부터 끝까지 종합적으로 다룬다.

  • IBM 평가판 소프트웨어: 다음번 프로젝트를 developerWorks에서 직접 내려받은 소프트웨어로 만들어보자. DB2®, Lotus®, Rational®, Tivoli®, WebSphere®를 포함한다.


토론


필자소개

Photo of Brett McLaughlin

Brett McLaughlin은 베스트셀러 논픽션 작가다. 컴퓨터 프로그래밍, 가정 극장 시스템, 분석과 설계에 관한 책을 여러 권 집필했으며, 그의 책은 10만 부 이상이나 팔렸다. 거의 10년 넘게 기술 서적을 집필하고 편집하고 출판했다. 그래서 기타 치기, 두 아들 쫓아다니기, 아내와 못말리는 패밀리(Arrested Development) 재방송을 보며 웃기 만큼이나 문서 편집기에도 능숙하다. 가장 최근에 출간한 책 Head First Object Oriented Analysis and Design은 2007년 Jolt Technical Book 상을 받았다. 고전인 Java and XML은 자바 언어에서 XML 기술을 사용하는 방법을 소개하는 가장 권위 있는 책 중 하나로 인정받는다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


Adobe, the Adobe logo, PostScript, and the PostScript logo are either registered trademarks or trademarks of Adobe Systems Incorporated in the United States, and/or other countries. IBM, the IBM logo, ibm.com, DB2, developerWorks, Lotus, Rational, Tivoli, WebSphere, and pureXML are trademarks of IBM Corporation in the United States, other countries, or both. Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. Other company, product, or service names may be trademarks or service marks of others. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의