저자: 한동훈(
traxacun@unitel.co.kr)
9장까지 살펴봤습니다. 이번 시간과 다음 시간은 XML과 HTML에 대한 것입니다. 그러니 아직 9장까지 못 따라오신 분들은 분발하면서 따라오시기 바랍니다. 다음 시간에는 10장에서 12장까지 설명할 겁니다. 솔직히 12장은 조금 어렵습니다. 제가 제대로 설명할 수 있을는지 모르겠군요. 이미 9장까지 공부하신 분들은 12장을 보시면서 이러한 형태가 있다는 것을 아시면 됩니다. 나머지 설명은 제가 하겠습니다.
지난 시간에는 여러분이 사용하고 있는 PC의 드라이브 목록을 출력하는 프로그램을 작성하는 과제가 있었습니다. 하신 분 있나요? 네, 한 절반 정도만 하신 것 같군요. 못하셨다고 해도 괜찮습니다. 이제부터 단순히 Environment 클래스의 GetLogicalDrives() 메소드를 이용해서 프로그램을 어떻게 작성하는지 알아보도록 하겠습니다.
먼저 MSDN을 실행합니다. 왼쪽 창 아래에 있는 탭들중에 "색인" 탭을 선택합니다.
색인 탭에 Environment.GetLogicalDrives를 입력해 봅니다. 그러면 여러분이 찾아보길 원하는 항목이 나타납니다. 항목을 마우스로 클릭하거나 단순히 엔터키를 누르는 것으로 다음과 같은 화면이 나타납니다.
창의 오른쪽에 Environment.GetLogicalDrives() 메소드에 대한 자세한 설명이 나옵니다. (참고로, MSDN에서는 메서드(method)라는 이름을 사용합니다. 하지만 저는 메소드가 익숙한 관계로 메소드라고 쓰겠습니다. 원래 영어로는 메서드가 맞다고 합니다.) MSDN의 오른쪽에 나타난 정보를 보세요. 먼저 파란색으로 된 부분을 살펴보면 .NET Framework 클래스 라이브러리라고 되어 있습니다. 이것은 닷넷 프레임워크에서 제공하는 것이지 C#이나 VB.NET과 같은 특정 언어에서 제공하는 고유의 기능은 아니라는 것입니다. 만약 C#이나 VB.NET에 대한 것이라면 C#이나 VB.NET이라고 표시됩니다. 메소드 서명에 대한 정보가 나옵니다. 이 중에서 C#에 대한 것을 찾아보세요.
[C#]으로 되어 있는 부분을 보면 public static string[] GetLogicalDrives();와 같이 되어 있는 것을 볼 수 있습니다. 이것이 의미하는 것은 문자열 배열(string[])을 반환해 준다는 것으로 다음과 같이 코드를 작성해야 합니다.
string[] driveLists;
driveLists = Environment.GetLogicalDrives();
static이라는 키워드가 붙어있으면 위와 같이 "클래스명.메소드명"으로 바로 사용할 수 있습니다. 조금 더 많은 것을 할 수 있게 되면 그 때 static에 대해서 자세히 설명하겠습니다. 익숙하지 않은 상태에서 설명해봤자 오히려 여러분에게 혼란만 주니까요. 이제 밑으로 내려가 보면 다음과 같은 정보가 나타납니다.
반환값에 대한 설명을 보면 첫 번째 논리 드라이브일 경우, 반환값은 "C:\"가 된다는 것을 알 수 있습니다. 따라서 D 드라이브가 있다면 두 번째 반환값은 "D:\"가 됩니다. 그 밑에 있는 예외에 대한 설명은 이 메소드를 사용할 경우 발생할 수 있는 예외에 대한 설명입니다. 사실, 이런 경우가 흔한 것은 아니지만, 알 수 없는 이유로 프로그램이 종료되는 것을 방지하기 위해 위와 같은 예외에 대한 처리 코드를 넣어주는 것이 좋습니다. 즉, 위에 작성한 코드에 try...catch...finally를 사용해서 적절히 프로그래밍해야 합니다.
IOException은 디스크에 하드웨어 적인 문제가 있어서 읽어들일 수 없는 경우에도 발생할 수 있으며, 스트림, 파일, 드라이브에 액세스할 수 없는 경우에 발생합니다. SecurityException은 드라이브에 대한 정보를 볼 수 있는 권한이 없는 사용자가 프로그램을 실행할 경우 발생합니다. 여러분이 사용하고 있는 PC에서는 여러분이 관리자이기 때문에 모든 권한을 갖고 있지만, 실제 네트워크에 제공되는 응용 프로그램에서는 그렇지 않은 경우가 많습니다. 권한이 없는 사용자가 이 메소드를 호출할 경우 SecurityException이 발생하게 됩니다. 따라서 만일의 경우를 준비하고 싶다면 적절한 예외처리를 할 수 있어야 합니다. 다행히도 MSDN은 발생할 수 있는 예외 형식에 대한 정보를 제공합니다. 또한, 마지막으로 위 두 가지 예외를 제외한 다른 예외가 발생할 수도 있기 때문에 Exception 클래스를 추가하는 것도 잊지 않아야 합니다. 따라서 처음 두 줄의 코드는 다음과 같이 작성해야 한다는 것을 알 수 있습니다.
string[] driveLists;
try
{
driveLists = Environment.GetLogicalDrives();
}
catch ( IOException eIO )
{
Console.WriteLine("입출력 작업을 할 수 없습니다");
}
catch ( SecurityException eSecurity )
{
Console.WriteLine("프로그램을 실행하는 데 필요한 권한이 없습니다");
}
catch ( Exception e )
{
Console.WriteLine("알 수 없는 오류가 발생했습니다");
}
지금쯤 여러분은 이 코드를 보고 짐짓 당황하셨을 겁니다. 사실 잘 작성된 응용 프로그램은 위와 같이 예외 처리가 잘된 코드를 뜻합니다. 개발자들이 하는 이야기 중에는 프로그램 로직을 위한 코드와 예외 처리 코드의 비율은 50:50이 되거나 40:60이 되어야 한다고 얘기합니다. 저는 50:50에 손을 들어주고 싶지만 Peter Norton과 같은 분은 40:60이라고 얘기한 것을 기억하고 있습니다. 이제 MSDN에서 우리가 살펴보지 않은 마지막 항목은 다음과 같습니다.
요구사항은 이 메소드를 사용할 수 있는 운영체제에 대한 정보를 제공합니다. 대부분의 경우에 요구 사항은 전혀 신경쓰지 않습니다. 그외 .NET Framework 보안 항목은 SecurityException과 관련하여 필요한 정보를 제공합니다. 참고 항목을 보면 세 가지 항목이 있는 것을 알 수 있습니다. 첫번째 Environment 클래스는 이 클래스에 대한 설명과 개요에 대해서 설명합니다. Environment 멤버는 Environment 클래스에서 이용할 수 있는 생성자, 속성, 메소드에 대한 완전한 목록을 제공합니다. 따라서 어떤 기능들이 있고, 어떻게 이용할 수 있는가 알아보기 위해 Environment 멤버를 사용합니다. 다음으로 System 네임스페이스라는 항목이 있습니다. 실제로 우리는 이 항목을 거의 살펴보지 않습니다. 여기에 System 네임스페이스라는 항목이 있다는 것은 Environment 클래스가 System 네임스페이스에 속해있다는 것입니다. 따라서 Environment 클래스를 사용하려면 C# 소스 코드의 첫번째 줄에 다음과 같은 코드를 작성해야 한다는 것을 의미합니다.
using System;
만약, 이 위치에 System 네임스페이스 대신에 System.Net.Sockets 네임스페이스라고 되어 있다면 C# 소스 코드의 처음에 다음과 같은 코드를 작성해야 한다는 것을 의미합니다.
using System.Net.Sockets;
이제 드라이브 목록을 문자열(string) 배열에 가져왔으므로, 배열에 있는 모든 항목을 출력하도록 해야합니다. 여기서는 다음과 같은 코드를 사용합니다.
foreach ( string drive in driveLists )
{
Console.WriteLine(drive);
}
나머지는 지금까지 엄청나게 많이 사용했던 class 문장과 Main 문장만 넣어주면 프로그램은 끝납니다. 이제, 다른 분들이 작성한 코드를 한 번 살펴볼까요?
using System;
public class Tester
{
public static void Main()
{
string [] drives = Environment.GetLogicalDrives();
Drives dv = new Drives();
dv.myDrives(drives);
}
}//End of Tester
public class Drives
{
public void myDrives(object [] drives)
{
foreach(object num in drives)
{
Console.WriteLine("{0}",num);
}
}
}//End of Drives
Tester와 Drives 클래스로 나누어서 작성을 했습니다. 하지만 코드가 조금 이상하군요. 틀린 것도 없고 제대로 실행되지만 몇 가지 문제점이 있습니다.
string [] drives = Environment.GetLogicalDrives();
이와 같이 문자열 배열을 사용하고 있으며, 메소드에 drives라는 문자열 배열을 넘겨주는 것을 알 수 있습니다.
dv.myDrives(drives);
위 코드를 보면 문자열 배열을 넘겨주고 있습니다. 그러나 Drives 클래스의 myDrives 메소드를 보면 object [] drives와 같이 되어 있는 것을 볼 수 있습니다. 이것은 적절하지 않습니다. 문자열 배열을 전달한다면, 메소드에서도 문자열 배열을 넘겨받도록 명시해야 합니다. string 보다 상위 클래스에 있는 object로 선언하는 것은 string 뿐만 아니라 다른 배열들도 넘겨받아서 처리하는 경우에나 사용할 수 있는 방법입니다. 여기서는 그러한 방법이 전혀 필요하지 않습니다. 그러니 object[] 대신에 string[]를 사용해야 합니다. 메소드 myDrives의 역할은 단순히 foreach 루프를 실행하면서 배열에 있는 드라이브 목록을 출력하는 것입니다. 따라서 적절한 메소드 이름이 아닙니다. 메소드 이름은 무엇을 하는 것인지 알 수 있도록 명확하게 작성해야 합니다. 그렇지 않으면 의미가 없습니다.
또한, 메소드 이름은 MyDrives와 같이 대문자로 시작해야 합니다. 첫 글자를 소문자로 시작하는 것은 변수 이름입니다. 이것은 C#에서 사용하고 있는 관례입니다. 이러한 관례를 따르지 않고 자신만의 규칙을 만들어 쓰는 것은 상관없지만 다른 프로그래머가 여러분이 작성한 코드를 이해하는 데 혼란을 줄 수 있습니다. 결과적으로 다른 프로그래머를 골탕먹이게 되지요. 모든 사람이 "사과"라는 단어에 대해서 공통된 의미로 약속을 했는데, 누군가가 "사과"에 대해서 "수박"이라고 하는 것과 같습니다. 언어라는 것은 사람과 사람 사이의 약속입니다. 따라서 C#이라는 언어를 사용할 때도 이러한 약속을 지켜주는 것이 좋겠습니다.
따라서 myDrives 메소드 이름은 PrintDriveList와 같이 구체적인 이름으로 바꾸는 것이 좋습니다. 또한, 드라이브 목록을 가져오는 것을 PrintDriveList에 포함시켜도 되며, 명시적으로 하나의 메소드처럼 분리하고 싶다면 GetDriveList와 같은 메소드로 포장해야합니다. 단, 한 줄의 코드라도 이와 같이 함으로써 의미를 보다 명확하게 할 수 있습니다. 실제로, 위에서는 GetDriveList와 같은 메소드를 정의할 필요가 없습니다. 이 기능은 이미 단 한 줄의 Environment.GetLogicalDrives()로 제공되고 있기 때문입니다. 제가 여러분의 코드에 대해서 이러니 저러니 이야기하는 것은, 이것은 매우 중요하기 때문입니다. 어떤 분은 띄어쓰기를 무시하고 모든 단어를 붙여쓰는 것을 봤습니다.
double thruputWrite=((double)orgTestSize/(((double)endWrite-(double)beginWrite)));
사실, 이런 코드는 알아보기 매우 어렵습니다. 여러분은 이것이 어떤 의미인지 단 번에 알 수 있습니까? 저는 이렇게 많은 괄호들을 바라보는 것만으로도 질려버립니다. 게다가 띄어쓰기가 전혀되어 있지 않기 때문에, 무슨 암호와 같습니다. 간혹, 똑똑하고, 오만하고, 거만한 천재들은 "저딴게 뭐가 어렵다고!"라고 할지도 모릅니다. 전, 그런 분들을 많이 봤습니다. 이런 분들은 차근차근 설명하는 것이 아니라, 잠시 머릿속으로 생각하고, 갑자기 모든 정리가 끝난 결과를 쓰십니다. 그리고는 잠시 생각하시고는 결과만을 써나가는 모습을 본적이 있습니다. 나름대로는 설명을 잘 하셨지만, 결과적으로 저는 단 하나도 알아들을 수 없었으며, 천재와의 차이는 저렇구나! 라는 사실에 좌절한 적이 있습니다. 저나 여러분은 결코 천재가 아닙니다. 그러니 저희는 저희가 알아볼 수 있는 방법으로 코드를 작성해야 합니다. 위와 같은 코드는 조금 번거롭더라도 다음과 같이 나누어 쓰는 것이 좋습니다. 또한 thruputWrite 라는 이름이 무엇을 의미하는지 알기 어렵다고 생각해서 transferRate로 변경하려 합니다. thruputWrite 보다는 transferRate가 보다 더 명확하다고 생각합니다.
double diffData = (double) endWrite - (double) beginWrite;
double transferRate = (double)orgTestSize / diffData;
double 형식으로 데이터 형식을 변환하기 때문에 어쩔 수 없이 코드가 지저분해 보이는 느낌은 있습니다. 사실, 저는 위 두 줄의 코드도 필요하다면 더 많이 고칠 수 있다는 것을 알고 있습니만, 지금은 이 정도에서 만족하도록 하지요. (사실, 지금 이 코드를 마구 뜯어고치고 싶다는 충동을 느끼고 있습니다.) 코드는 최대한 알아보기 쉽게 작성하는 것이 필요합니다. 이것은 매우 중요합니다. 초보자들이 가장 많이 하는 실수가, 자기도 알아볼 수 없도록 가장 어렵게 코드를 작성하는 것입니다. 소스 코드는 익숙하지 않은데, 더 이상하게 지저분한 소스 코드를 작성하는 것은 문제를 더 복잡하게만 만들뿐입니다. 어떻게든 예제를 빨리 입력하고, 실행하려고 하다보니 이런 문제가 발생하는데, 이는 결코 올바르지 않습니다. 이런 습관 때문에 오류가 발생해도 어디서 잘못되었는지 알 수 없고, 왜 틀린 건지도 이해할 수 없게 됩니다. 따라서 코드는 최대한 아름답게 작성하는 습관을 들여야 합니다.
아마도 저는 코드를 통해서 위와 같은 이야기를 자주 해드릴 겁니다. 사실, 이러한 내용들을 확장해나가면 리팩토링에 대한 이야기가 됩니다. 그리고 객체 지향 또는 닷넷 프레임워크를 어떻게 바라볼 것인가에 대해서 제가 이야기하게 될 겁니다. 그러한 이야기들은 실제로 디자인 패턴에 대한 이야기로 결코 어렵지 않습니다. 하지만 책에서는 그저 사실을 나열하고, 예제들은 최대한 단순한 내용만 전달하기 때문에 오해의 소지가 있습니다. 사실 책의 모든 예제에 제가 작성한 것과 같은 코드를 보여드리고 싶지만 초보들은 이러한 코드를 매우 부담스러워 하기 때문에 그렇게 하지 못한다는 것을 알고 있습니다. 그래서 실제로 응용 프로그램에서는 그 누구도 사용하지 않는 방법으로 코드를 작성하게 되는 우를 범하게 되는 것입니다. 프로그래밍 책에서 가장 올바르게 작성된 예제는 고작해야 "예외 처리"와 관련된 장 뿐입니다. 그러니 완전한 형태를 보고 싶다면 디버깅과 예외 처리에 대한 부분에 관심을 가지시기 바랍니다.
XML
XML은 데이터에 대한 데이터를 기술하는 언어입니다. 이와 같이 데이터에 대한 데이터를 기술하는 것을 메타(meta)라고 합니다. 따라서 XML은 흔히들 메타 언어라고 이야기합니다. 사실, 이런 이야기를 해봤자 여러분의 관심을 끌지 못할 것이라는 것을 알고 있습니다. 게다가 책에도 XML에 대해서 설명하지 않습니다. 하지만 제가 XML을 주제로 정한 것은 닷넷은 모든 데이터를 XML 기반으로 주고 받을 뿐만 아니라 모든 데이터를 XML로 보관하기 때문입니다. 나중에 ASP.NET 웹 프로그래밍을 시작할 때쯤이면 본격적으로 XML로 된 코드들을 보게됩니다. 이 때쯤 되면 XML에 대한 지식이 없는 상태에서는 왜 이것이 오류인지 알 수 없게 됩니다. 그래서 XML에 대해서 소개합니다. 이제, XML은 어떻게 쓰는가 간단한 예제를 보지요. XML은 보통 다음과 같은 형식으로 작성합니다. 메모장에서 다음과 같이 입력하면 됩니다.
Programming C#
Jesse Liberty
49
ASP.NET In a Nutshell
me
50
XML은 보통 이와 같이 작성합니다. 첫번째
와 마지막에 있는 를 루트 요소라고 합니다. 즉, 이 두 태그 사이에 모든 내용이 들어가 있어야 합니다. 또한 알 수 있는 것처럼
과 이 하나의 쌍을 이루면서 완전한 하나의 항목을 설명합니다. 따라서 두 번째 태그
에 해당하는 위치에 과 같은 태그가 섞인다면 오류가 발생합니다. 즉 다음과 같이 작성하는 경우입니다.
Programming C#
Jesse Liberty
49
Catz
Elliot
100
이와 같은 형태로는 XML 문서를 작성할 수 없습니다. 반드시 그 밑에 있는 과 으로 되어야 합니다. 그러나 뮤지컬에 대한 데이터를 XML로 작성하고 싶다면 다른 XML 파일을 만들어야 합니다. 위와 같이 각각의 태그들이 있고, 태그 안에 중첩된 형태로 데이터를 포함시킵니다. 이러한 중첩 구조에는 제한이 없습니다. 태그 부분도 사실은 다음과 같이 확장시킬 수 있습니다.
Programming C#
Jesse Liberty
Chris Carter
49
이와 같이 태그를 확장하여 작성하는 것도 가능합니다. 위 경우는 공동 저자가 있는 경우를 위한 것이지요. 즉, XML은 여러분이 원하는 태그만 만들어 쓸 수 있습니다. 이와 같이 XML로 데이터를 작성하면, 데이터는 어떻게 해석할까요? 여러분이 직접 데이터 해석을 작성할 수도 있겠지만 대부분 구문 분석기(파서,parser)로 XML 데이터를 해석할 수 있습니다. 자바의 경우에는 JDK 1.4에 XML 파서가 포함되어 있고, JDK 1.3까지는 XML4J와 같이 IBM에서 개발하여 배포한 XML 툴킷을 사용했습니다. 마찬가지로 C#은 닷넷 프레임워크에서 제공하는 System.Xml 네임 스페이스에 있는 클래스들을 사용해서 XML 데이터를 해석할 수 있습니다. 이처럼 단지 XML 데이터를 작성하고 그것을 이용하는 방법만 알면 됩니다. 하지만, 여기서는 파서에 대해서 좀더 알아보도록 하지요. XML을 사용하면 어떤 데이터가 들어 있는지 몰라도 데이터를 교환할 수 있다고 얘기합니다. 사실, 이게 무슨 소리인가 싶을 겁니다. 모르는데 어떻게 데이터를 교환할 수 있다는 것인지... 예제를 간단히 하기 위해 다음과 같은 XML 태그에 대해서 생각해 봅시다.
Programming C#
Jesse Liberty
49
ASP.NET In a Nutshell
me
50
제가 XML 파서라면 위 데이터를 읽고 다음과 같이 처리합니다.
1. 태그를 만나다. 첫번째 태그는 무조건 루트 요소이므로, 이것을 간단히 제목으로 표시하자. 따라서 다음과 같이 표시합니다.
Books
2. 그 다음 태그 을 읽어들입니다. 두 번째는 이제 루트 요소에 속한 첫 번째 항목이라는 것을 압니다. 따라서 을 만날 때 까지 차례대로 해석하여 보여주면 됩니다. 화면에 이제 순서를 붙여서 1. Book이라고 표시해주기로 했습니다.
Books
1. Book
3. 태그를 만나고 닫기 태그 사이에 있는 내용이 데이터라는 사실을 알고 있습니다. 따라서 태그는 제목으로 표시하고 태그 사이에 있는 내용을 보여준다고 하면 다음과 같이 됩니다.
Books
1. Book
Title: Programming C#
4. 와 태그에 대해서도 동일한 처리를 수행합니다. 따라서 표현은 다음과 같이 됩니다.
Books
1. Book
Title: Programming C#
Author: Jesse Liberty
Price: 49
이제 닫기 태그 을 만나고, 첫 번째 항목에 대한 해석이 완전히 끝났음을 알게 됩니다. 이제 두 번째 항목에 대한 해석을 시작합니다. 2번부터 4번까지의 과정을 다시 반복하게 되고 최종적인 결과는 다음과 같이 됩니다.
Books
1. Book
Title: Programming C#
Author: Jesse Liberty
Price: 49
2. Book
Title: ASP.NET In a Nutshell
Author: me
Price: 50
어떤가요? 조금은 그럴듯해 보이지 않나요? 이와 같이 XML을 사용하면 어떤 데이터가 있는지 몰라도 적절하게 데이터를 해석할 수 있습니다. "그러면 수도 없이 중첩될텐데... 그것은 어떻게 해석할 겁니까?"라고 질문하실지도 모르겠군요. 간단하지 않나요? 가장 하위에 더 이상 열기 태그가 없을 때 까지 재귀호출을 반복하면 됩니다. XML에서 몇 개의 태그가 중첩될지 모른다고 얘기했었지요? 그것은 바꿔말하면 다음과 같은 겁니다.
2 9
2 4 ... 1
2 2 ... 0
1 ... 0
결과 : 1 0 0 1
위 계산은 십진수 9를 이진수로 변환하는 것입니다. 마찬가지로 36라는 숫자를 2진수로 변환하면 어떻게 될까요?
2 36
2 18 ... 0
2 9 ... 0
2 4 ... 1
2 2 ... 0
1 ... 0
결과 : 1 0 0 1 0 0
이와 같이 이진수로 변환하는 경우에 과연 계산을 몇 번이나 반복할지 알 수 있을까요? 그러면 우리는 프로그래밍을 할 때 0과 1에 대해서는 아무 계산도 하지 않고, 2와 3에 대해서는 계산을 한 번 수행하고, 4, 5, 6, 7에 대해서는 계산을 두 번 수행하고, 8, 9, 10, 11, 12, 13, 14, 15에 대해서는 계산을 세 번 수행하는 식으로 모든 경우의 수에 대해서 프로그래밍을 하는 걸까요? 아니죠! 이런 경우에 재귀호출을 사용하는 겁니다. 재귀호출은 메소드 자신이 자신을 호출하는 겁니다. 언제까지 호출한다?? 네, 더 이상 필요가 없을 때 까지 반복하는 겁니다. 이진수 계산에서는 숫자가 2보다 작아질 때 까지 계속해서 반복하는 겁니다. 간단히 의사 코드를 쓰면 다음과 같습니다.
class AppMain
{
public void PrintBinary(int decimal)
{
while ( decimal > 2 )
{
Console.WriteLine("{0}", decimal % 2 );
decimal = Convert.ToInt32(decimal / 2);
this.PrintBinary(decimal);
}
}
}
이것은 간단한 의사코드입니다. 실제로 돌아가는지는 저도 모르겠네요. 뭐, 컴파일이 안된다면 조금 수정하면 되겠지요. 먼저 로직을 살펴보세요. while을 사용했습니다. While의 의미는 decimal이 2 보다 크면 계속해서 실행됩니다. Console.WriteLine에 사용한 decimal % 2의 의미는 모듈러스 연산을 의미합니다. 2로 나눠서 남은 몫을 출력하라는 의미입니다. 5 % 2 = 1이 되고, 4 % 2 = 0이 되고, 3 % 2 = 1이 되는 식이지요. 나머지 연산을 종종 유용하게 사용할 겁니다. 위와 같이 하면 함수는 계속 반복해서 호출될 수 있습니다. 위와 같은 방법을 재귀호출이라고 합니다. 사실, 재귀호출은 모두 비재귀호출로 변환할 수 있습니다. 이에 대해서는 나중에 언젠가 살펴볼 일이 있겠지요.
재귀호출은 위에서 볼 수 있는 것처럼 구현하기가 매우 간단합니다. 또한 속도가 빠르지만, 매우 많은 메모리를 사용합니다. 따라서 위와 같이 이진수를 구하는 경우에 작은 수는 문제 없이 동작하지만, 매우 큰 수를 넣는 경우에는 많은 메모리를 사용하게 되는 단점이 있습니다. 이러한 이유 때문에 깊이가 깊지 않은 구조에는 재귀호출을 사용하지만, 깊이가 깊은 구조에는 비재귀호출을 사용합니다. 혹시나, 이러한 알고리즘에 관심있는 분들을 위해서 얘기하자면, 재귀호출에는 중첩 재귀호출이 있습니다. 재귀호출을 비재귀호출로 바꿀 수 있는 것처럼 중첩 재귀호출을 비재귀호출로 변환할 수 있습니다. 또한, 중첩 재귀호출은 비재귀호출로 변환할 수 있는 다양한 방법이 존재합니다. 관심있는 분들은 한 번 찾아보세요.
이진수를 구하는 메소드와 마찬가지로 XML 파서 역시 이와 같이 되어 있으며, 여러분은 그저 편하게 XML 데이터를 해석할 수 있습니다. XML 파서는 이미 각 업체에서 제공하고 있습니다. 그러니 여러분이 어떤 언어를 사용하든지 관계없습니다. C/C++에서도 쓸 수 있는 라이브러리가 있습니다. 위에서 느낄 수 있는 것처럼 XML에 있는 데이터 중에 특정 태그에 해당하는 데이터만 조작하는 것도 가능합니다. 위에서는 단순히 태그와 데이터를 읽어서 표현하는 순서를 보여줬지만 실제로는 다양한 방법으로 형식을 지정하는 것이 가능합니다. 워드 프로세서에서 서식을 적용하는 것과 같다고 생각하면 됩니다. 그 뿐만 아니라 XML 데이터를 다양한 포맷 즉, RTF, 워드 파일, PDF, HTML로 변환할 수 있습니다. 이와 같은 변환을 하려면 변환을 수행하는 API를 사용합니다. 즉, 이미 이렇게 변환해주는 라이브러리가 존재하므로 그저 사용하기만 하면 됩니다. XML 데이터를 적절한 형식으로 형식화하기 위해 제공되는 언어가 XSL입니다. XSL은 XML 스타일 시트입니다. 그리고 이러한 변환을 수행하는 해석기를 XSLT라고 합니다. XSL Translator라는 의미입니다. 하지만, XSL/XSLT는 자주 사용하지 않습니다. 대부분 XSLT를 사용해서 데이터를 보여주는 것 보다는 데이터를 특정 포맷으로 변환하거나, 데이터를 해석해서 적절한 처리를 하는 데 이용하기 때문에 프로그래밍 언어에서 API를 사용하여 처리를 하는 경우가 대부분입니다.
이제, 다른 이야기를 해볼까요? 사실, 인간 세상에 다양한 종류의 사람이 살고 있다는 사실은 프로그래밍을 하다 보면 절실하게 느낍니다. 사용자의 비만도를 측정하기 위해 설문을 하는 프로그램이 있다고 합시다. 이럴 때 보통은 몸무게를 입력하죠? 저는 "64"라고 입력하겠지만 어떤 사람은 "64kg", 또 어떤 사람은 "육십사"라고 입력할 수도 있지요. 그러니까 항상 숫자만 입력하게 하고, kg은 쓰지 않아도 된다고 안내를 하거나, kg를 입력하면 사용자를 약올리는 메시지를 출력하는 수고스러운 작업을 해야 합니다.
이러한 현실은 XML에서도 마찬가지입니다. 예제로 사용한 태그에서도 반드시 숫자로 입력해야 하는 부분이 있습니다.
에는 반드시 숫자만 입력해야 합니다. 더 복잡한 문제는 날짜 형식을 표현하는 것입니다. 사람마다 날짜를 표현하는 방법은 다양합니다. 보통은 다음과 같은 표현들 중에 하나를 사용하지요. "2002/7/10", "2002/07/10", "2002.7.10", "2002.07.10", "2002년 7월 10일", "2002-07-10", "2002-7-10", "07/10/2002", "단기 4335년 7월 10일" 등등... 사실, 위에 든 예는 아주 소수에 불과합니다. 사람들마다 개성이 달라서, 수 없이 많은 다양한 형식이 존재하니까요. 그래서 XML에서는 아래와 같이 태그에 사용할 데이터 형식을 지정할 수 있는 방법이 필요합니다
이 태그에는 "숫자"만 넣어야 해!
이 태그에는 "문자"만 넣어야 해!
이 태그에는 "문자 두 개"만 넣어야 해!
이 태그에는 "문자열"만 넣어야 해!
이 태그에는 "숫자 또는 문자"만 넣어야 해!
DTD(Data Type Definition)
사실, DTD는 두 가지가 있다고 합니다. Data Type Definition과 Data Type Declaration! 저는 어떤 것이든 별로 신경쓰고 싶지는 않군요. DTD에 대해서는 제가 설명하지 않습니다. 왜냐하면 저희는 DTD를 볼 일이 거의 없기 때문입니다. DTD는 XML 기반이 아니라 고유의 데이터 형식을 사용합니다. 따라서 XML에 기반한 XSD라는 것을 사용합니다.
XSD(XML Schema Definition)
XSD는 XML에 기반한 언어이고 XML 태그에 사용할 데이터 구조를 서술합니다. 이것은 크게 어렵지 않습니다. 마이크로소프트를 비롯한 기업들이 XSD를 지지하고 있기 때문에 닷넷에서도 DTD를 사용하지 않고 XSD를 사용합니다. 그러나 XSD 역시 편리한 편집 도구들을 제공하고 있기 때문에 직접 XSD를 사용하는 일도 거의 없습니다. 여러분은 그저 XML에 "64"라고 써야 할 곳에 "육십사"라고 쓰지 않도록 하기 위해, 즉 각 태그에 사용할 데이터 형식을 지정하기 위해 DTD와 XSD를 사용한다!는 사실만 알면 됩니다.
사실 제가 "육십사"에 대한 이야기를 꽤 길게 장황하게 했습니다. 그건, 그만큼 여러분의 기억속에 남았으면 하는 바람입니다. 붕어빵을 생각하면 여러분은 "클래스와 인스턴스"의 관계를 쉽사리 잊지 못할 겁니다. 마찬가지로 만병통치약을 기억하고 있다면 여러분은 "좋은 예외 처리를 작성하는 방법"을 잊지 않게 될 겁니다. 마찬가지로 "육십사"라는 것을 기억하고 있으면 XML과 DTD/XSD의 관계를 잊지 않을 것이며, 여러분이 작성하는 프로그램은 끊임없이 사용자를 의심해야 한다는 사실을 잊지 않을 거라 생각합니다. 어이~ 거기 너무 웃지 마세요. 남은 정말 심각했다구요. 저는 이 "육십사"를 결코 잊을 수 없답니다.
XML의 문제점
사실, XML은 많은 문제점을 갖고 있습니다. 예를 들어, 사용자가 매우 붐비는 사이트를 생각해 보세요. 이런 사이트는 트래픽이 매우 많습니다. 기존의 고유한 데이터 형식을 사용할 때는 Programming C#, Jesse, 49 라는 데이터만 저장하면 되는데, XML로 전달하려 한다면 앞에서 본 것처럼 매우 많은 데이터를 전송해야 합니다. 게다가 네트워크 장비는 XML이 나오기 이전에 나온 것들이기 때문에 XML 데이터 형식을 인식하지도 못하고, XML 데이터를 가속시켜주는 가속 기능도 없습니다. 즉, 무조건 적으로 XML을 도입하는 것은 적절하지 않다는 것입니다. 그리고 위와 같은 XML 데이터는 다음과 같이 간단하게 표현하여 전달하는 방법도 있습니다. 사실 이 방법을 가장 많이 씁니다. 일단 전달할 데이터의 크기가 많이 줄어들기 때문입니다.
어떤가요? 처음보다 분명히 데이터 크기가 많이 줄었습니다. Title="Programming C#"을 속성(attribute)라고 합니다. 우리가 흔히 얘기하는 property(속성)과는 다르지요. attribute라고 합니다. 이와 같이 하나의 레코드를 하나의 행(row)으로 표현하는 방법이 있습니다. 위에서 태그는 다음과 같이 변경할 수 있습니다.
태그를 삭제하고 끝에 "/>" 태그를 사용합니다. 이와 같은 태그를 "스스로 닫기" 태그라고 합니다. 사실, 저는 이 태그를 한국어로 뭐라고 하는지 모릅니다. "self-closing tag"라고 하지요. 따라서, XML 책에 어떻게 소개되어 있는지 저는 알 길이 없습니다.
XML 태그를 표현하는 방법이 태그로 표시하는 방법과 속성으로 표시하는 방법이 있는 것처럼 여러분이 사용하게 될 XML 구문 분석기(parser) 역시 이 두 가지 방법에 따라 다른 클래스 또는 다른 메소드를 제공할 겁니다. 그러니까, 제가 하고 싶은 얘기는 여러분이 어떤 프로그래밍 언어에서 XML을 처리하더라도 XML의 두 가지 표현 방법을 처리하게 될 것이고, 표현 방법에 따라 사용하는 메소드나 속성들이 다를 거라는 얘기입니다. 개념이 중요합니다. 그러한 메소드나 속성은 설명서만 있으면 직접 다 구현할 수 있습니다. 물론 처음부터 완벽하게 구현할 수는 없겠지요. 그러한 클래스 라이브러리에 익숙해져야 합니다. 그래서 개발자들은 이러한 라이브러리에 익숙해지는 작업을 "삽질"이라고 합니다. 조금 고치고, 컴파일하고, 되는지 확인해 보는 것이지요. 저는 이 과정을 수도 없이 반복했습니다. 많은 분들이 이 과정에서 포기하는 것을 저는 많이 봤습니다. 하지만 조급해할 필요도 없고, 이것은 그저 익숙해지는 과정입니다. 스스로 노력하지 않고 할 수 있을거라 생각하는 것은 너무 탐욕스러운 게 아닐까요?
비록 여기서 C#을 설명하고 있지만, 저는 특정 언어나 특정 라이브러리에 얽매이는 것을 매우 싫어합니다. 필요한 게 자바라면 자바를, C#이면 C#을, 펄이면 펄을, LiveWire가 필요하면 LiveWire를, NetBasic이 필요하면 NetBasic을 쓰면 되겠죠. 여러분이 프로그래밍을 못하는 것은 결코 우연이 아닙니다. 여러분이 이 책 조금, 저 책 조금 열어보면서 포기하는 것을 많이 봐 왔습니다. 이것은 어떤 언어가 우수한가, 어떤 언어가 훌륭한가 라는 것의 문제가 아닙니다. 여러분이 프로그래밍을 못하는 것은 개념이 없기 때문입니다. 단순히 프로그래밍 언어 책을 보면서 문법을 외우고, 클래스 라이브러리 사용법을 달달 외울 생각으로 쳐다보는 것은 프로그래밍에 아무런 도움도 되지 않습니다. 저는 강의 첫날부터 지금까지 계속해서 반복해서 말했습니다.
문법의 형식은 책에도 있고 MSDN에도 나와 있습니다. 문법 형식은 외우는 게 아닙니다. 책에 있고, MSDN에 있습니다. 시험을 보는 것도 아니죠. 그리고 많이 하다보면 그러한 형식은 익숙해 질 뿐입니다. 모든 프로그래머가 프로그래밍 언어의 기능을 100% 활용하는 것이 아니라고 얘기했습니다. 고작 10-20% 일 뿐입니다. 오늘 강의에서도 마찬가지입니다. 여러분은 XML이 단순히 이런 것이다라는 감을 잡으면 됩니다. 사실, 제가 상당히 함축적으로 이야기하고 있는 것은 사실입니다. XML에 대한 이야기를 제가 여러분에게 이야기하고 있는 속도로도 한 달 동안 이야기해도 못할만큼 많은 이야기가 있습니다. 참, 할 말 많은 저를 보고 누군가 그러더군요. "오디오인가~ 인간인가!"(썰렁)
XML 요약
마지막으로 XML에서 지켜야 할 몇 가지 기본적인 규칙을 소개할까 합니다. XML 문서의 시작은 XML 이라는 것을 나타내기 위해 파일의 처음에 다음과 같은 태그를 추가로 사용합니다.
"
규칙 1. XML 문서의 시작은 태그로 시작한다
XML은 반드시 루트 요소를 가져야 합니다. 우리가 살펴본 예제에서는 루트 요소가 였습니다. 또한 루트 요소는 반드시 하나만 있어야 합니다. 태그가 두 개 이상 있으면 오류가 발생합니다.
규칙 2. 루트 요소는 하나만 존재해야 한다
또한 모든 XML 태그는 소문자로 사용합니다. 속성도 마찬가지로 소문자를 사용합니다. 낙타혹처럼 대소문자를 섞어서 사용하지 않습니다.
규칙 3. 모든 태그와 속성은 소문자를 사용한다
XML 태그는 반드시 닫기 태그를 갖습니다. 닫기 태그를 사용하지 않고 스스로 닫기 태그를 사용하는 경우도 있습니다.
규칙 4. 모든 태그는 반드시 닫기 태그와 쌍을 이뤄야 한다
XML 태그는 다른 순서로 중첩될 수 없습니다. HTML에서 Hello World와 같이 각 태그가 쌍을 이루지 않고 순서가 바뀌는 것이 허용되지만 XML에서는 허용되지 않습니다. 조심해야 합니다.
사실, 이 외에도 규칙이 많았던 것으로 기억하지만 저는 이 정도만 기억하고 있습니다. 아마 제가 XML과 거리가 먼 탓도 있겠지요. 이제 HTML은 안 쓴다. XML의 시대가 온다. XHTML을 쓰는 시대가 올 거라고 해서 열심히 XHTML을 학습한 슬픈 기억이 있습니다만 지금은 아무도 XHTML을 사용하지 않더군요. "우리 회사도 이제는 XHTML을 써야 해요!" 라고 얘기해도, "저는 디자이너에요! 그런건 프로그래머인 당신이 알아서 하라구요!"라고 말하더군요. ASP.NET을 설명할 때도 얘기하겠지만 ASP.NET의 XML 기반의 ASP.NET 컨트롤도 크게 사용되지 못하는 이유가 바로 여기에 있습니다. 개발자가 직접 다 바꿔야 하는데, 디자인만 바꿔도 모든 노가다를 처음부터 다시해야 하죠. 그래서 아무리 좋은 기능이라 해도 현실성이 없는 것 같습니다. 옛날에 한 번은 게시판 목록을 디자인해 달라고 했더니 테이블을 20개나 쓴 것을 주더군요. 그 정도 화면 구성은 테이블 4-5개 정도에서 해결이 될텐데 말이지요. 덕분에 고생했습니다. 하여간 제게는 참으로 암울했던 시기였어요!! 이제는 컴포넌트만 만들면서 살아야겠어요.
오늘도 어김없이 과제가 있습니다.(I wish I had no homework!!) 어림도 없습니다. 저는 오늘도 힘차게 과제를 낼겁니다.
[과제 1] 배열을 만들고, 배열에 과일 이름을 임의로 추가한다음, 정렬한 결과를 출력하세요. MSDN에서 Array 클래스를 찾아보고, Sort 메소드를 찾아보세요. 문자열의 출력은 foreach를 사용합니다. 어허~ 웃지마세요. 아무리 간단해 보여도, 여러분 스스로 해보면 많은 난관(?)에 부딪칠 겁니다. 전 알고 있어요!!
[과제 2] 첫번째 배열은 오름차순 정렬이었습니다. 이번에는 역순으로 정렬해서 결과를 출력하는 프로그램을 작성해보세요.
[과제 3] Environment 클래스에 있는 속성과 메소드를 사용해서 다음 정보를 출력하는 프로그램을 작성하세요.
PC 이름, PC 사용자 이름, 버전, 메모리 사용량
PC 이름은 MachineName 속성, PC 사용자 이름은 UserName, 버전은 Version 메소드, 메모리 사용량은 WorkingSet 속성에서 알 수 있습니다. 먼저 PC 이름을 출력하는 것부터 해보세요. 작은 것 하나부터 시작해서 하나씩 살을 덧붙여 나가야 쉽게 프로그래밍할 수 있습니다.
네, 과제 3은 처음에 드라이브 목록을 출력하는 프로그램을 확장해서 작성할 수도 있습니다. 사실은 확장해서 작성하면 더욱 좋습니다.
9장까지 못하신 분들은 9장까지 하시고, 9장까지 하신 분들은 12장까지 보시면서 예제를 모두 해보세요.