천객만래 [千客萬來] (It has an interminable succession of visitors)

[개발/컬럼] 조금만 멀리 보면

흔히들 "인사가 만사"라고 한다. 어떤 일을 하건, 사람이 가장 중요하다는 뜻이다. 정치도 그렇고, 경영도 그렇듯이, 제품 개발도 결국은 사람들이 하는 일이다. 따라서 좋은 사람들을 잘 써야 한다.

일을 하고 다니다보면, 하드웨어나 소프트웨어를 다루는 엔지니어들을 많이 만나게 된다. 평소에 별일 없을때야 뭐 하던일 계속 하면서, 개발 작업도 여유있게 하고 그런다. 문제는, 일이 터졌을때다. 개발 중인 제품에 문제가 생겼을때도 그렇고, 더 심각하게는, 개발을 완료하고 납품을 한 제품에 하자가 생겼고, 이로 인해서 회사가 엄청난 재정적 손실을 입게 될 상황이 생겼을때, 개발자들은 몇날 며칠씩 날밤을 새가며 디버깅을 해야 하고, 회사로서도 눈에 보이는 혹은 눈에 보이지 않는 손실을 입게 된다.

아주 간단한 예를 들어보자. 개발을 완료하고 납품을 한 제품에 치명적인 결함이 발견되었다고 해보자. 이로 인해서 회사가 입게되는 손실이 금전적으로 따졌을때 100억원이라고 한다면, 이를 최 단시간안에 해결해 줄 수 있는 디버깅 도구를 1억원 주고 구매를 해서, 손실을 50억원으로 줄여줄 수 있다면 이 1억원짜리 개발 도구를 안살 사람이 있겠는가?

그런데, 문제는, 사고가 터진 시점에 그런 개발 도구를 살 경우는 보통 좀 늦은 경우가 많다는 것이다. 구매 절차도 그렇거니와, 그 도구를 잘 쓰도록 배우는 시간도 필요하고 해서, 당장 필요한 문제에 적용을 하기에는 좀 무리가 있다. 그러면, 개발 기간중에, 이런 문제점을 예상하고, 미리미리 장비나 개발 도구를 사 놓고 불의의 사태에 대비할 수도 있을 것이다. 뭐 일종의 보험같은 것이라고 할 수 있을 것이다. 물론 당장 사용하는 1억원이 좀 아깝기는 하겠지만, 나중에 문제가 생겼을때 그 진가를 발휘할것이다.

자 그러면, 아예 그런 문제가 발생하지 않도록, 개발 과정 자체를 완전하게 만들면 어떤가? 방법은 간단하다. 개발자에게 그 1억원을 주고 완전한 개발을 독려하는 것이다... :)

위의 첫번째 경우처럼 사고가 터진 뒤에도 개발자들에게 노가다만을 강요하는 회사가 매우 많은 지금의 상황에서 말도 안되는 소리일 수도 있다. 개발에 매우 큰 도움이 될 것 같지만, 눈에 보이는 비용이 증가한다는 이유로 매우 열악한 하드웨어나 소프트웨어 환경에서 작업을 해야 하는 상황도 늘 보게 된다. 그러니, "인사가 만사"라고 하며 개발자에게 투자를 해야 한다는 소리는 허황된 소리일지도 모른다. 나는 아직도 믿어지지가 않지만, 어쩌면 내가 주변에서 보는 이런 불합리한 일들이 소위 "이공계 기피현상"의 원인인지도 모르겠다.

그러나 난 믿고 싶다. 좋은 개발 환경을 갖춘 상태에서 여유있는 일정으로 개발한 제품이 결국 그 가치를 발휘하듯이, 최고의 대우를 받는 최고의 개발자들을 보유한 회사가 결국 최고가 된다는걸 믿고, 또 그렇게 회사를 이끌어 가는 분들이 많이 계시다는걸...
Posted by SB패밀리

Written by 안재우(Jaewoo Ahn), 닷넷엑스퍼트(.netXpert)

 

예전 Voice of .NETXPERT 2003 행사 때, 저희 회사의 김유철 책임이 발표했던 내용인 '아무도 가르쳐 주지 않는 .NET 애플리케이션 개발 Tips 18가지'를 정리해서 올립니다.

 먼저 첫번째로 Visual Studio .NET 관련 팁으로 시작합니다.

 Tip 1. 참조 추가 대화상자에 나의 어셈블리나 컴포넌트를 보이게 할 수 있는 방법은?

 다음과 같이 나타나게 하는 것을 의미합니다.

 

사실 처음에는 GAC(Global Assembly Cache)에 올라가면 나오지 않을까 생각했었는데, 막상 그렇지 않다는 것을 알게 되었습니다.

어쨌든 참조 대화상자에 표시하는 방법은 크게 2가지 방법이 있습니다.

 첫번째 방법은 폴더를 이용하는 방법으로서, 다음 폴더에 위치하면 됩니다.

C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\PublicAssemblies

 두번째 방법은 레지스트리를 이용하는 방법으로서, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.0\AssemblyFolders의 PublicAssemblies 하위키를 이용하면 됩니다.

 Tip 2. 디버깅 빌드 속도 개선?

기본적으로 VS.NET은 빌드 수행 시 솔루션 내의 모든 프로젝트에 대해 빌드를 수행합니다. 그런데, 프로젝트의 갯수가 많아지다보면 디버깅을 위해 빌드를 수행하는데 걸리는 시간이 점점 많이 걸리게 됩니다.

이를 개선하기 위해서는 다음과 같이 옵션을 지정하여, 빌드 속도를 개선할 수 있습니다.

 

Tip 3. 인텔리센스 동적 도움말

내가 직접 만든 컴포넌트에 다음과 같이 인텔리센스 도움말이 나타나게 하려면?

 

일단 기본적으로 다음과 같은 XML 주석을 달면 된다는 것은 대부분 알고 있을 것입니다.

 

그런데 이 컴포넌트를 담고 있는 프로젝트와 이 컴포넌트를 사용하는 프로젝트가 동일 솔루션 내에 있는 경우에는 자동적으로 인텔리센스 도움말이 표시되지만, 문제는 이 컴포넌트의 DLL만 배포해서, DLL 참조를 수행할 경우입니다.

이러한 경우, 다음과 같이 프로젝트 속성에서 구성속성/빌드/XML 문서 파일에서 XML 파일의 이름을 지정해서 빌드를 수행할 때 XML 파일을 생성하게 합니다.

이제 DLL과 XML을 같이 배포하고, 이 DLL을 다른 프로젝트에서 참조하면 자동적으로 해당 XML 파일이 로드되어 인텔리센스 도움말을 지원하게 됩니다.

 

나머지 팁은 나중에 이어서 올리도록 하겠습니다.

 두번째로 .NET Framework 관련 팁 2가지입니다.

 

Tip 4. StringBuilder의 잘못된 사용

문자열(System.String)의 경우, 소위 말하는 Immutable 패턴을 따르고 있기 때문에 독특한 특성을 가지는데, 간략하게 설명하면 다음과 같습니다. 우선 다음 코드를 봅시다.

 string s = "Hello";
s = s + ", ";
s = s + "World !";

이렇게 했을 때, 실제 메모리 상에서는 다음과 같이 문자열에 해당하는 메모리 공간이 3번 점유됩니다.

 

Memory #0 : [Hello]
Memory #1 : [Hello, ]
Memory #2 : [Hello, World!]

즉 동일한 메모리 주소(#0)에 추가되는 것이 아니라, 문자열 연산을 수행한 결과의 문자열이 새로운 메모리 주소에 할당된다는 것입니다.

이에 비해 StringBuilder라는 클래스를 우리가 일반적으로 생각하는 방법처럼 동일 메모리 주소(#0)에 문자열을 추가함으로써 재할당의 오버헤드, 불필요한 메모리 공간의 낭비를 줄입니다. StringBuilder를 사용할 경우, 위 코드는 다음과 같이 됩니다.

StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append(", ");
sb.Append("World!");
 

이 때문에 상당수의 .NET 프로그래밍 팁에서는 문자열 연산을 여러번 수행하는 경우, StringBuilder를 사용하면 성능이 좋아진다고 설명해놓은 경우가 많습니다.

문제는 이를 맹신하고 남용하는 사람들이 많다는 것입니다. 실제로 다른 .NET 팁을 보고 나서 무조건 StringBuilder를 사용해서 코드를 작성하는 프로그래머를 본 적도 있습니다. 과연 그게 맞을까요? 위에서 본 두 가지 경우와 아래의 경우 중 성능이 가장 좋은 것은 어느 것일까요?

string s = "Hello" + ", " + "World!";

가장 바람직한 것은 바로 위, 즉 맨 마지막의 경우입니다. 바로 위처럼 작성한 경우, C# 컴파일러는 MSIL을 생성할 때 위 코드를 다음과 같이 최적화합니다.

string s = "Hello, World!";

그러므로 문자열 상수 연산이나 보통 5개 이하의 문자열 연산에는 StringBuilder를 사용하지 말고 + 연산자를 사용하는 것이 바람직합니다. StringBuilder를 써야 할 경우는 다수의 문자열 연산을 수행하거나, 반복문(for, while 루프 등) 내에서 문자열 연산을 수행할 경우입니다.

성능 뿐만 아니라 코드 가독성 측면에서도 + 연산을 사용하는 것이 StringBuilder.Append()를 사용하는 것보다 훨씬 나으므로, 코드 생성기나 HTML 렌더링과 같은 특수한 작업을 수행하지 않는 일반적인 경우라면 + 연산을 사용하는 것을 권장하고 싶습니다.

 

Tip 5. 콘솔 애플리케이션 출력 캡쳐

요즘은 대부분 웹이나 윈도우 애플리케이션으로 작성하긴 하지만, 간혹 가다 콘솔(커맨드라인) 애플리케이션을 작성해야 할 경우도 있습니다. 이럴 경우, 콘솔 애플리케이션의 출력 내용을 캡쳐하는 방법은 없을까요?

System.Diagnostics.ProcessStartInfo 클래스를 사용하면 이를 간단하게 수행할 수 있습니다. 다음 코드를 보죠.

Process cmd = new Process();
ProcessStartInfo procInfo = new procInfo();
procInfo.RedirectStandardOutput = true;  // 표준 출력을 Redirect
procInfo.UseShellExecute = false;             // 표준 입/출력 Redirect 시에는 false로 설정
procInfo.CreateNoWindow = true;            // 윈도우를 생성하지 않음
cmd.StartInfo = procInfo;
cmd.Start();
cmd.StandardOutput.Read();                    // StreamReader를 통해 출력을 얻음
 
다음 번엔 ADO.NET 관련 팁을 올리도록 하겠습니다.
 

세번째로 ADO.NET 관련 팁 2가지입니다.

 

Tip 6. Strict Typed Parameter 사용하기

데이터액세스 작업을 수행하다보면 Parameterized Query나 SP를 사용하는 경우, SqlParameter와 같은 Parameter Class를 사용하여 매개변수를 전송해야 하는 경우가 많습니다.

SqlParameter를 만드는 방법에는 여러가지가 있지만, 일반적으로 다음 생성자를 사용하는 경우가 많습니다.

SqlParameter(string parameterName, object value);

매개변수명/값 쌍으로만 전달하면 되므로, 다음과 같이 작성하기만 하면 되어서 전반적으로 매우 편리하기 때문입니다.

 SqlParameter param = new SqlParameter("@ProductID", "50");  // Case1
SqlParameter param = new SqlParameter("@ProductID", 50);    // Case2

둘 중 어느 것을 사용하더라도 상관은 없습니다. 그런데, 이 두개만 하더라도 실제 SQL 프로파일러로 찍어보면 결과가 서로 다른 것을 알 수 있습니다.

exec ...@ProductID', N'@ProductID nvarchar(4000)', @ProductID = N'50'  // Case1
exec ...@ProdcutID', N'@ProductID int', @ProductID = 50                         // Case2

이러한 결과가 나타나는 이유는 ADO.NET이 매개변수의 값으로부터 매개변수의 Type을 추론해내기 때문입니다. 명시적으로 지정을 하든, 지정을 하지 않든 Type은 어느 경우나 반드시 필요합니다. 위의 예에서는 Case2가 좀 낫긴 하지만, 만약 ProductID가 int가 아닌 다른 숫자형이었다면?

 결론적으로 가장 바람직한 것은 매개변수의 Type을 정확하게 지정해주는 것이 좋다는 것입니다. 그러므로 매개변수명/값 쌍 형태보다는 다음과 같이 매개변수명/Type 쌍으로 생성한 후, 값을 따로 지정해주는 것이 보다 바람직합니다. Type 외에도 Size, Precision 등이 있는 경우, 이를 지정해주면 더욱 더 좋습니다.

SqlParameter param = new SqlParameter("@ProductID", SqlDbType.Int);
param.Value = 50;

프로파일링을 해보면 알 수 있지만 보다 많은 사항을 명시적으로 지정해줄 수록 서버 측 리소스를 절감해서 쿼리 속도 및 성능이 개선되는 것을 알 수 있습니다. 정리하자면 개발자의 편의성과 성능은 반비례한다는 것이 되겠죠. ^^

 

Tip 7. ADO Recordset으로부터 DataSet 만들기

기존 시스템이 ASP로 되어 있었고, ADO Recordset을 반환하는 비즈니스 로직 컴포넌트를 호출해서 데이터를 가져오는 3-Tier 구조로 되어 있었다고 가정합시다.

그러던 어느날 프리젠테이션 부분을 ASP.NET으로 교체한다고 회사에서 결정을 내렸습니다. 윗 사람은 이미 비즈니스 로직은 다 작성되어 있어서 프리젠테이션 부분만 작성하면 되니깐 금방 하겠다고 하면서 프로젝트 일정을 말도 안되게 짧게 잡아 놓았습니다. 안된다고 말을 할려니, 이전에 3-Tier 구조의 장점을 역설했던 일이 거짓말이 되어버리게 생겼습니다. 그렇다고 기존 Recordset을 그대로 사용하려니 ASP.NET에서의 데이터바인딩과 같은 장점을 전혀 사용할 수가 없습니다. 어쩔수 없이 밤을 새서라도 기존 비즈니스 로직 컴포넌트를 .NET으로 재작성하려고 생각을 했는데, 엎칱데 덮친 격으로 기존 컴포넌트에 해당하는 소스가 현재 나한테 없습니다. 이러한 경우, 도대체 어떻게 해야 할까요?

이러한 경우, 기존의 Legacy 로직을 재사용하는 것으로 방향을 잡는 것이 바람직합니다. 처음부터 재작성을 하기에는 시간과 비용이 모자라기 때문입니다.

이때 유용한 것이 바로 OleDbDataAdapter의 Fill 메서드입니다. 다른 DataAdapter들과는 다르게, OleDbDataAdapter의 Fill 메서드에는 다음과 같은 오버로딩이 존재합니다.

public int Fill(DataSet dataSet, object ADODBRecordSet, string srcTable);

이 메서드는 ADODBRecordSet의 내용을 DataSet 내에 srcTable이라는 이름의 DataTable로 채워 넣는 역할을 수행합니다. 이렇게 하여 RecordSet의 내용을 DataSet으로 옮길 수 있게 됩니다.

이렇게 할 때 장점은 다음과 같습니다.

첫째, 기존 Legacy 로직을 수정/변경 없이 그대로 재사용이 가능합니다.

둘째, 기존 Recordset이 Connection 기반이더라도 이를 통해 Disconnected 모델로 전환이 가능합니다.

셋째, 기존 Recordset의 커서 타입에 관계없이 자유롭게 Disconnected 모델에서 사용이 가능합니다.

넷째, 기존 Legacy 로직을 XML 웹 서비스나 .NET 리모팅으로 쉽게 재사용할 수 있게 만들 수 있습니다.

 

단, 이렇게 할 경우, 재사용을 통해 시간과 비용을 절감할 수는 있겠지만, 성능적인 측면에서는 오히려 저하될 수도 있다는 것에 주의해야 합니다.

 

다음 번에는 ASP.NET 관련 팁일듯 합니다. ^^

 네번째로 ASP.NET 관련 팁 8가지 중 먼저 4가지를 올립니다.

 

Tip 8. 네트워크 드라이브에 있는 ASPX 실행하기

 

웹 서버가 여러 대 있는 경우, 웹 애플리케이션이 업데이트 되었을 때 이것을 각 웹 서버에 일일이 배포하는 일은 상당히 귀찮은 작업입니다. 별도의 배포 시스템(예 : AppCenter)을 사용하여 Rolling 업그레이드를 하는 것이 정석적입니다만, 어떤 회사에서는 배포 대상 파일을 한 곳(SAN과 같은 통합 스토리지, 파일 서버의 공유 폴더 등)에만 두고 이를 네트워크 드라이브로 공유해서 사용하는 경우도 있습니다. 이 경우, 공유된 경로에만 업데이트를 해주면 모든 웹 서버에 반영되므로 최소한 배포에 있어서는 훨씬 간편해 집니다.

 

 

우선 네트워크 경로로 IIS 홈 디렉터리를 설정하는 것은 쉽습니다.

 

문제는 ASP의 경우에는 별다른 문제가 없는데, ASP.NET의 경우, 위 그림과 같은 Parser 오류가 발생하게 됩니다. 무엇이 문제일까요?

바로 .NET Framework의 런타임 보안 정책과 관련된 문제입니다. ASP.NET이 ASPX 및 코드 비하인드 DLL을 처리하려면 FullTrust 권한이 필요합니다. 그런데, .NET Framework는 네트워크 드라이브, 즉 로컬 인트라넷에 대해서는 기본적으로 FullTrust를 하지 않기 때문에 이러한 문제가 발생하게 되는 것입니다.

해결책은 위의 글 중에 답이 있습니다. 런타임 보안 정책에서 로컬 인트라넷의 권한을 FullTrust로 변경해주면 됩니다.

 

Tip 9. 애플리케이션 간에 aspx, ascx 공유하기

 

예를 들어, 다음과 같이 동일 웹 애플리케이션 내에 있지 않는(즉, 다른 웹 애플리케이션에 속한) 웹 사용자 정의 컨트롤(*.ascx)을 페이지에서 사용하려고 하면, 다음과 같은 파서 오류가 발생되게 됩니다.

 

이 문제를 해결하려면, 다음과 같은 단계로 작업을 수행합니다.

1. 공유할 웹 애플리케이션을 동일한 물리적 위치로 지정합니다. 아래는 app1과 app2가 appshared를 공유할 경우입니다. (오타 수정 : 표에서 App1/control이 아니라 App1/appshared가 되어야 합니다)

 

2. 공유할 웹 애플리케이션(appshared)의 응용 프로그램 속성을 제거합니다.

3. 공유할 웹 애플리케이션(appshared)의 web.config, Global.asax를 제거합니다.

4. 이제 각 애플리케이션(App1, App2)의 페이지에서는 사용할 ASCX를 다음과 같이 지정합니다.

<%@ Register … Src="appshared/shareusercontrol.ascx" %>

 

Tip 10. 코드 비하인드의 긴급 수정

 

다음과 같은 시나리오를 가정해 보겠습니다.

ASP.NET 애플리케이션을 개발해서 개발 PC(또는 개발 서버)에 있던 내용을 실 서버에 배포했는데, 서버 관리자가 갑자기 긴급하게 수정해야 할 사항이 발견했습니다. 그런데 공교롭게도 이 내용은 매우 사소한 것이긴 한데, 코드 비하인드 클래스에 정의되어 있는 내용이라서 소스 파일을 수정해서 DLL을 재컴파일하여 올리지 않는 한 문제를 수정할 수 없는 상황이 되었습니다.

그런데, 이미 개발자는 퇴근을 했고, 관리자는 비주얼 스튜디오와 같은 개발 도구를 설치하지 않은 상황입니다. 비주얼 스튜디오가 있다고 하더라도 관리자는 사용법을 잘 알지 못합니다.

이러한 경우에 긴급하게 조치를 할 수 있는 방법이 없을까요?

 

우선 이 방법은 서버에 배포를 할 때, ASPX와 코드 비하인드 소스 파일(.aspx.cs 또는 .aspx.vb)이 같이 배포되어 있는 상황에서만 가능합니다. 또는 수정하려는 ASPX에 해당하는 코드 비하인드 소스 파일을 가지고 있어야 합니다.

이런 조건이 성립되어 있다고 가정하고, 긴급 조치 절차는 다음과 같습니다.

1. 수정할 .aspx의 코드 비하인드 소스 파일(.aspx.cs)를 편집기 등에서 열고, 문제점을 수정합니다.

2. 위의 코드 비하인드 클래스 이름을 임시로 변경합니다. 예를 들어 ProductList라면 ProductList_1 등으로 변경합니다.

3. 수정할 .aspx에서 <% @Page .. %> 내에서 CodeBehind를 Src로 바꾸고, Inherits 내의 클래스 이름을 1에서 변경한 이름으로 바꿉니다.

    <% @Page CodeBehind="productlist.aspx.cs" Inherits="Northwind.ProductList" %>

    <% @Page Src="productlist.aspx.cs" Inherits="Northwind.ProductList_1" %>

 

일단 이렇게 하면, 긴급 수정을 할 수 있게 됩니다. 그러나, 어디까지나 이 방법은 긴급 상황에서 사용할 수 있는 임시 방편일 뿐입니다. 정상적인 수정/업데이트의 경우에는 절대로 이 방법을 사용하지 않아야 합니다.

긴급 수정을 한 이후에는 개발자에게 해당 사항을 통지해주고, 문제점을 수정해서 컴파일된 버전으로 새로 업데이트를 하도록 합니다. 최소한 컴파일된 DLL을 업데이트해야 하고, 임시로 수정한 ASPX를 다시 원래로 돌려놓아야 한다는 것에 유의하십시오.

 

Tip 11. Postback 간 스크롤 위치 고정

 

웹 폼 페이지를 스크롤해서 내린 상태에서, 포스트 백이 일어나면 스크롤 위치가 다시 맨 상단으로 돌아가버리는 단점이 있습니다.

 

이 문제를 해결하기 위한 방법은 크게 3가지가 있습니다.

첫째, SmartNavigation을 사용하는 방법입니다. 다음과 같이 page directive에 SmartNavigation을 true로 지정해 줍니다.

<%@ Page ... SmartNavigation="true" %>

이 방법은 가장 간편하긴 하지만, 페이지 내에서 클라이언트 스크립트를 사용하는 경우 Smart Navigation의 스크립트와 충돌하여 문제가 발생할 수 있습니다.

 둘째, 다음과 같이 URL 뒤에 컨트롤ID를 앵커로 지정합니다.

http://localhost/.../....aspx#DataGrid1:_ctl72:_ctl0

위 코드는 DataGrid1의 72번째 줄을 기준으로 스크롤을 하게 됩니다.

 셋째, 클라이언트 스크립트의 scrollIntoView를 사용합니다.

<scirpt> document.all('DataGrid1').rows[72].scrollIntoView(true) </script>

 해결은 가능은 하지만, 보다시피 깔끔한 방법은 없네요. ^^ 

다음번엔 나머지 ASP.NET 팁 4가지를 올리겠습니다. 

네번째로 ASP.NET 관련 팁 8가지 중 나머지 4가지를 올립니다.

 

Tip 12. 폼 인증으로 모든 파일 보호하기

 

ASP.NET을 어느 정도 다뤄보신 분은 잘 아시겠지만, ASP.NET은 내부적으로 인증 메커니즘을 제공합니다. 특별한 코딩 작업 없이 web.config에 인증(Authentication)과 권한 부여(Authorization)만 지정하면 페이지 액세스 권한을 설정할 수 있습니다. 다음 예를 봅시다.

 

<authentication mode="Forms">
  <forms name="protectall" loginUrl="login.aspx" ... />
</authentication>

 

<authorization>
  <deny users="?" />
</authorization>

이렇게 web.config에 설정하면,

- 기본적으로 쿠키 기반의 폼 인증을 사용하여 사용자를 인증하며,

- 로그인 되지 않은 익명 사용자( = ?)에 대해서는 접근을 거부(deny)하며,

- login.aspx라는 페이지로 로그인하라고 리디렉션한다는 의미입니다.

 

그런데, 문제는 폼 인증의 경우, .aspx를 요청할 때만 동작한다는 것입니다. 만약 .gif와 같은 이미지 파일을 요청하면 어떻게 될까요?

해보면 알겠지만, 로그인을 하지 않았음에도 불구하고 .gif는 보이게 됩니다. 인증 자체를 Windows와 같은 다른 인증으로 바꾸면 .gif도 방어할 수 있지만, 폼 인증에서는 기본적으로 되지 않습니다.

폼 인증을 쓰면서 .gif에 대해서도 인증 및 권한부여 메커니즘을 동작시킬 수는 없을까요?

 

방법은 다음과 같이 간단하게 할 수 있습니다.

우선, IIS 관리자를 열고, 웹 애플리케이션(또는 사이트)의 홈 디렉터리 탭을 선택합니다. 하단의 응용 프로그램 설정에서 우측에 있는 구성 버튼을 누릅니다.

그러면, 아래 그림과 같이 응용 프로그램 구성 페이지가 나타납니다. 여기서 하단의 추가 버튼을 누르고 .gif에 대한 응용 프로그램 매핑을 추가하면 됩니다. 추가 시에는 .aspx와 동일한 실행 파일 경로(C:\WINDOWS\Microsoft.NET\Framework\[버전번호]\aspnet_isapi.dll)를 지정해주면 됩니다.

 

Tip 13. 스크롤 바를 가진 DataGrid 만들기

 

DataGrid를 페이지에서 표시하려고 하는데, DataGrid의 항목이 한 페이지에 표시하기에는 너무 많다고 가정합시다. 물론 페이징을 사용해서 처리하는 것도 방법이긴 하겠지만, 다음과 같이 스크롤을 하는 것이 보기 좋을 수도 있습니다..

 

일단 이걸 처리하는 방법은 간단합니다. <DIV> 내에 DataGrid를 집어넣고, <DIV>의 스타일에 overflow:auto를 지정해주면 됩니다. 물론 auto가 아닌 다른 값을 지정해줘도 됩니다.

그런데 이 방법의 문제는 DataGrid의 머리글(Header)까지 같이 스크롤되어 버린다는 문제점이 있습니다. 우리가 원하는 것은 머리글은 그대로 있고, DataGrid의 항목만 스크롤이 되는 다음과 같은 형태가 되겠죠.

 

사실 이렇게 만드는 방법은 민망할 정도의 꽁수에 불과합니다. 어떻게 하면 되느냐 하면..

DataGrid에서 머리글을 표시하지 않도록 지정하고, 머리글은 Table 태그를 사용해서 직접 그리는 것입니다. 그릴 때는 DataGrid 안의 테이블과 크기를 잘 조정해야 하고, DataGrid를 포함하고 있는 <DIV> 바로 위에 지정해야 한다는 것에 주의하십시오.

 

<!-- 머리글 내용 -->

<Table>...</Table>

<DIV style="overflow:auto">

   <asp:DataGrid ...>

       ...

   </asp:DataGrid>

</DIV>

 

Tip 14. 서버에서 클라이언트 스크립트 처리

서버 사이드 코드에서 클라이언트 스크립트를 처리하는데는 대략 다음과 같은 시나리오가 있습니다.

- 클라이언트측 스크립트 라이브러리를 제공하는 시나리오
- 페이지 상단이나 하단에 스크립트를 생성하는 시나리오
- (페이지에 여러 개의 컨트롤 인스턴스가 존재하더라도) 페이지에 스크립트 블록이 단 한번만 나타나도록 확인하는 시나리오
- 페이지에 폼이 포함된 경우, 폼의 클라이언트측 제출 이벤트와 이벤트 처리기를 연결하도록 컨트롤을 설정하는 시나리오
- 컨트롤에 의해 렌더링된 클라이언트측 요소를 클라이언트 상에서 선언된 배열 변수에 추가하는 시나리오
- 숨겨진 필드를 통한 값 전달

- 서버 컨트롤이 렌더링된 후 컨트롤의 클라이언트 이벤트와 이벤트 핸들러 연결 

 

Page 클래스는 다양한 Register* 계열의 메서드를 통해 이러한 시나리오를 가능하게 해줍니다. 관련된 내용은 MSDN의 다음 URL을 참조하기 바랍니다.

http://msdn.microsoft.com/library/KOR/cpguide/html/cpconClient-sideFunctionalityInServerControl.asp

 

위의 붉은색 부분에 대응되도록 작성한 코드는 다음과 같습니다.

 

 private void Page_Load(object sender, System.EventArgs e)
 {
      String scriptString = "\n";
      scriptString += "<script language=JavaScript>\n";
      scriptString += "<" + "!--\n";
      scriptString += "    function showIds() {\n";
      scriptString += "        for(var index=0;index < ids.length;index++)\n";
      scriptString += "        document.write(ids[index] + '<br>');\n";
      scriptString += "    }\n";
      scriptString += "//-->\n";
      scriptString += "<" + "/" + "script>\n";
   
      RegisterStartupScript("arrayScript", scriptString);
     
      string[] ids = {"111","112","the third one","114"};
      RegisterArrayDeclaration("ids","'" + String.Join("','",ids) + "'");

      RegisterHiddenField("myHidden", ".netXpert");
       
      btnArray.Attributes.Add("onClick", "showIds()");
      btnHidden.Attributes.Add("onClick", "alert(document.all.myHidden.value); return false;");
 }

 

이 코드를 사용해서 렌더링된 HTML은 다음과 같습니다.

 

Tip 15. 웹 팜(Web Farm) 환경에서 상태 관리 문제

 

L4나 NLB를 사용하여 여러 대의 웹 서버를 묶어서 동작시키는 웹 팜(Web Farm) 환경에서 ASP.NET을 사용하는 경우, 상태 관리 문제가 이슈가 됩니다. 대표적인 것이 ViewState와 Session, 폼 인증 쿠키 등이 있겠습니다.

 예를 들어 ViewState와 관련하여 PostBack 시에 다음과 같은 에러 메시지가 발생할 수 있습니다.

 HttpException (0x80004005): The View State is invalid for this page and might be corrupted.]
   System.Web.UI.Page.LoadPageStateFromPersistenceMedium() +150
   System.Web.UI.Page.LoadPageViewState() +16
   System.Web.UI.Page.ProcessRequestMain() +421
….

 

아마 한글 메시지로는 '이 페이지의 뷰 상태가 유효하지 않거나 훼손되었습니다.'라는 걸로 나오던 걸로 기억합니다.

 이 문제가 발생하는 원인을 살펴보겠습니다.

웹 팜 환경에서는 최초 페이지 접근은 A 서버로 하고, PostBack은 B 서버로 날라갈 수도 있습니다.

PostBack 동안 ViewState가 페이지에서 저장/로드될 때는 특정한 키를 사용하여 Encrypt/Decrypt 되는 과정을 거치게 됩니다. 그런데, 이때 사용하는 키 값이 A 서버와 B 서버가 서로 다르게 설정되어 있을 것이기 때문에 위와 같은 에러가 발생하게 됩니다.

폼 인증 사용 시 쿠키와 관련한 문제도 이것과 동일합니다.

 

이 문제를 해결할 수 있는 방법은 크게 2가지입니다.

우선 첫번째는 L4 장비에서 세션이 유지되도록(즉, 한 사용자는 최초 접근한 서버로 계속 접근하도록) 설정하는 방법이 있습니다. 이 방법을 사용하면 상태 관리 문제(세션변수, ViewState, 폼인증 쿠키)를 모두 잊어버려도 된다는 장점이 있는 대신에, 로드 밸런싱의 효율성이 떨어지게 됩니다.

그래서 권장되는 두번째 방법은 바로 모든 웹 서버의 키 값을 동일하게 설정해주는 것입니다. 키를 생성하고, machine.config를 수정하는 부분에 대해서는 다음 KB를 참고하십시오.

 http://support.microsoft.com/default.aspx?scid=312906

 그러나, 세션변수(Session)의 경우에는 두번째 방법으로 해결되지 않습니다. 세션 변수는 페이지에 저장되는 것이 아니라 웹 서버의 메모리에 저장되는 것이기 때문입니다. L4 설정을 하지 않고 세션 변수를 공유하는 방법은 없을까요? 바로 ASP.NET의 StateServer나 SqlServer 모드 등을 통해 세션을 별도로 관리하는 방법이 있습니다. 여기에 관해서는 다음 URL을 참조하십시오.

 http://msdn.microsoft.com/library/KOR/cpguide/html/cpconSessionState.asp

 웹 팜에서 StateServer나 SqlServer 모드를 사용할 때는 주의해야 할 사항이 하나 있습니다. 다음 URL을 반드시 참고하시기 바랍니다.

 http://support.microsoft.com/default.aspx?scid=325056

 마지막으로 Session 변수와 ViewState에 대해 참고할 만한 좋은 문서가 있습니다. ASP.NET Program Manager인 Susan Warren이 작성한 다음 문서를 보기 바랍니다.

 http://msdn.microsoft.com/library/en-us/dnaspnet/html/asp11222001.asp

 이 팁 시리즈도 이제 다음 번이 마지막이 될 것 같네요.

출처: http://blog.naver.com/saltynut


쌈꼬쪼려 소백촌닭
Posted by SB패밀리

Debug Basic

요약

.PDB 확장명은 "프로그램 데이터베이스"를 나타내며 .PDB 파일에는 Visual C++ 버전 1.0에 도입된 디버깅 정보를 저장하는 새로운 형식이 포함되어 있습니다. 앞으로 .PDB 파일에는 기타 프로젝트 상태 정보도 포함될 예정입니다. 형식을 변경한 가장 중요한 이유 중 하나는 점점 늘어나는 프로그램 디버그 버전의 연결을 가능하게 하기 위한 것으로 이러한 변경은 Visual C++ 버전 2.0에서 처음 도입되었습니다. -> .PDB 확장명을 가진 파일은 디버깅 정보를 가지고 있는 중요한 파일이다.

.DBG 확장명은 "디버그"를 나타냅니다. 32비트 NT 도구 집합으로 만든 .DBG 파일은 PE(Portable Executable) 파일 형식으로 되어 있고 COFF, FPO 및 경우에 따라 Codeview 정보가 있는 섹션을 포함합니다. Visual C++ 통합 디버거는 이 형식으로 된 .DBG 파일을 읽을 수 있지만 COFF 기호 섹션을 무시하고 Codeview 정보를 찾습니다.

.DBG 파일에 포함되어 있는 기호 정보를 확인하려면 명령 프롬프트에서 아래와 같이 입력합니다.
 
Dumpbin sample.dbg/symbol
 
참고 경로에 Dumpbin.exe와 MSdis100.dll의 디렉터리를 포함해야 할 수도 있습니다.
Path=%Path%;C:\Program Files\DevStudio\VC\bin;C:\Msssdk\bin
 
 
DUMPBIN에 대한 자세한 내용은 Microsoft 기술 자료의 다음 문서를 참조하십시오.
 
177429 (http://support.microsoft.com/kb/177429/) DUMPBIN 유틸리티에 대한 설명
 

추가 정보

.PDB 파일

이전의 16비트 버전 Visual C++에서 .PDB 파일을 사용할 때는 이 파일에 저장되어 있던 디버깅 정보가 링커에 의해 .EXE 파일이나 .DLL 파일의 끝에 추가되었습니다. 위에서 언급한 Visual C++ 버전에서는 디버깅 프로세스 동안 직접 .PDB 파일을 사용할 수 있도록 링커와 통합 디버거가 수정되어 링커 작업이 크게 줄었으며 번거로운 CVPACK 64K 형식 제한도 피할 수 있습니다.

CVPACK 제한에 대한 자세한 내용은 Microsoft 기술 자료의 다음 문서를 참조하십시오.
112335 (http://support.microsoft.com/kb/112335/) BUG: 형식 정보가 64K를 초과할 때 발생하는 CK1020 또는 CK4009
기본적으로 Visual Workbench로 생성된 프로젝트를 빌드할 때는 컴파일러 스위치 /Fd가 사용되어 .PDB 파일의 이름이 <project>.PDB로 변경됩니다. 따라서 전체 프로젝트에 대해 하나의 .PDB 파일만 있게 됩니다.

Visual Workbench로 생성되지 않은 메이크파일을 실행할 때 /Fd를 /Zi와 함께 사용하지 않으면 최종적으로 다음 두 개의 .PDB 파일이 있게 됩니다.
VCx0.PDB(여기서 "x"는 해당 Visual C++의 주요 버전을 나타내며 "2" 또는 "4"임) - .OBJ 파일 각각에 대한 디버깅 정보를 모두 저장하고 있습니다. 프로젝트 메이크파일이 있는 디렉터리에 있습니다.
<project>.PDB - 최종 .EXE 파일의 모든 디버깅 정보를 저장하고 있습니다. \WINDEBUG 하위 디렉터리에 있습니다.
두 개의 파일이 만들어진 이유는 다음과 같습니다. 실행 시 컴파일러는 .OBJ 파일이 연결될 .EXE 파일의 이름을 모르므로 <project>.PDB에 해당 정보를 넣을 수 없습니다. 두 파일은 서로 다른 정보를 저장합니다. 사용자가 .OBJ 파일을 컴파일할 때마다 컴파일러가 디버깅 정보를 VCX0.PDB에 병합합니다. 함수 정의 같은 기호 정보는 넣지 않고 형식 관련 정보만 넣습니다. 이 경우 한 가지 이점은 모든 소스 파일에 <windows.h> 같은 공통 헤더 파일이 있을 경우 이러한 헤더의 모든 형식 정의가 모든 .OBJ 파일에 저장되는 것이 아니라 한 번만 저장된다는 것입니다.

링커를 실행하면 해당 프로젝트의 .EXE 파일에 대한 디버깅 정보가 포함되어 있는 <project>.PDB가 생성됩니다. 함수 프로토타입과 기타 모든 사항을 포함하여 모든 디버깅 정보는 VCX0.PDB에 있는 형식 정보에만 저장되는 것이 아니라 <project>.PDB에도 저장됩니다. 두 종류의 .PDB 파일은 구조적으로 유사하므로 같은 확장명을 공유하며 둘 다 증분 업데이트를 허용하지만 실제로는 서로 다른 정보를 저장합니다.

새 Visual C++ 디버거는 링커에 의해 생성된 <project>.PDB 파일을 직접 사용하며 .PDB 파일의 절대 경로를 .EXE 또는 .DLL 파일에 포함합니다. 디버거가 해당 위치에서 .PDB 파일을 찾을 수 없거나 프로젝트가 다른 컴퓨터로 이동한 경우와 같이 경로가 잘못되면 디버거가 현재 디렉터리에서 .PDB 파일을 찾습니다.
 

.DBG 파일

Visual C++ 통합 디버거는 또한 .DBG 파일이 Codeview 형식 디버깅 출력을 포함하고 있는 이진 영역에서 만들어진 경우 .DBG 파일도 사용할 수 있습니다. .DBG 파일은 소스 코드를 사용할 수 없을 때 디버깅하는 데 유용합니다. 소스가 없어도 .DBG 파일을 사용하여 함수에 중단점을 설정하고 변수를 조사하며 호출 스택에서 함수를 볼 수 있습니다. 이 파일은 OLE RPC 디버깅에도 필요합니다.

한 가지 주의해야 할 점은 .DBG 파일의 기호로 작업할 때는 완전 데코레이팅된 이름을 사용해야 한다는 것입니다. 예를 들어 Windows sndPlaySound 함수 호출에 중단점을 설정하려면 _sndPlaySoundA@8을 위치로 지정합니다.

실제로 두 가지의 .DBG 파일 형식이 있습니다. 이전 형식은 16비트 환경에서 상당히 오래동안 사용되었습니다. .COM 파일의 형식은 메모리에 로드되는 단순 이진 이미지이므로 Codeview 디버깅 정보를 .COM 파일의 끝에 추가하면 파일 크기가 64K 제한을 초과할 수도 있기 때문에 파일 끝에 Codeview 디버깅 정보를 추가할 수 없습니다. 따라서 기호 정보는 Codeview 정보만 들어 있는 별도의 .DBG 파일에 대신 저장됩니다. .EXE 파일에서 /strip 옵션으로 CVPACK를 실행하여 .DBG 파일을 생성할 수도 있습니다.

32비트 .EXE의 경우 Visual C++ 버전 2.x 및 4.x 디버거의 기호 처리기가 이전 형식을 읽지 못하며 대신 Windows NT .DBG 파일에서 사용되는 형식을 읽습니다. 이 형식은 시스템 .DLL 파일과 함께 사용하도록 제공됩니다. 이러한 .DBG 파일은 PE 파일 형식으로 되어 있고 COFF, FPO 및 경우에 따라 Codeview 기호 정보가 있는 섹션을 포함합니다. 새로운 Visual C++ 디버거는 이 형식으로 된 .DBG 파일만 읽습니다. 또한 Codeview 정보만 사용하고 다른 기호 섹션은 무시합니다.

디버그 정보를 PE 파일에서 추출하여 .DBG 파일에 저장해 두었다가 디버거에서 사용할 수 있습니다. 이렇게 하려면 디버거가 별도의 파일에서 디버그 정보를 찾을지 또는 해당 정보를 파일에서 추출했는지 여부를 알고 있어야 합니다. 한 가지 방법은 디버거가 디버그 정보를 찾는 실행 파일을 검색하는 것입니다. 그러나 파일에서 추출했음을 나타내는 파일 특성 필드(IMAGE_FILE_DEBUG_STRIPPED)가 개발되어 디버거가 더 이상 파일을 검색하지 않아도 됩니다. 디버거는 PE 파일 헤더에서 이 필드를 찾아 디버그 정보가 해당 파일에 있는지 여부를 빠르게 확인할 수 있습니다.

Win32 SDK와 함께 제공되는 REBASE.EXE를 사용하여 이 형식으로 된 .DBG 파일을 생성할 수 있습니다. 자세한 내용은 Win32 SDK 설명서를 참조하십시오.

Windows NT 정품을 구축하는 동안 디버그 기호가 시스템 바이너리와 드라이버에서 추출되어 별개의 .DBG 파일에 저장됩니다. 이렇게 하는 이유는 Windows NT 커널 디버거가 이러한 .DBG 파일을 사용하여 최적화된 드라이버에 대해서도 디버깅 기호를 제공할 수 있기 때문입니다. 그러나 Visual C++ 통합 디버거는 보호 모드 커널 코드를 디버깅하기 위한 것이 아닙니다.

Windows NT 기호 파일은 Windows NT 정품 CD-ROM에 있는 \SUPPORT 디렉터리의 디버그 하위 디렉터리에 있습니다. 이 기호 파일은 CD-ROM에서 하드 드라이브로 복사해야 합니다. 대상 디버거 시스템에서 수행되는 사용자 모드 디버깅의 경우 .DBG 기호는 대상 시스템의 Windows NT \<winnt>\SYMBOLS 디렉터리에 있어야 합니다. 여기서 <winnt>는 Windows NT가 설치되어 있는 디렉터리입니다. 새로운 Visual C++ 설치 프로그램은 프로그램 그룹에 "NT 시스템 기호 설치" 아이콘을 설치합니다. 이 아이콘을 사용하면 Windows NT Workstation CD-ROM 디스크에서 하드 드라이브의 올바른 디렉터리 구조로 .DBG 파일을 자동 복사할 수 있습니다. Windows NT Server 4.0 CD-ROM에서는 .DBG 파일이 압축 형식으로 저장되어 있기 때문에 이 방법이 적용되지 않습니다.

커널 디버깅을 위해서는 _NT_SYMBOL_PATH 환경 변수에서 지정하는 디렉터리(예: C:\DEBUG\SYMBOLS) 아래의 기호 트리에 .DBG 파일을 넣어야 합니다. 커널 디버깅은 SYMBOLS\SYS 디렉터리의 모든 드라이버(*.SYS)에 대한 기호, SYMBOLS\EXE 디렉터리의 NTOSKRNL.EXE에 대한 기호 및 SYMBOLS\DLL 디렉터리의 HAL.DLL에 대한 기호로 구성된 최소한의 기호 집합으로 가능합니다. 커널 디버깅에 대한 자세한 내용은 Windows NT DDK Programmer's Guide에서 커널 디버깅 관련 설명을 참조하십시오.

.PDB 파일에서 다시 .DBG 파일로 변환하는 작업은 이론적으로 가능하지만 매우 복잡한 작업입니다. 이러한 도구에 대한 정보는 현재 없습니다. 이러한 도구에 대한 정보를 구하면 Microsoft 기술 자료에서 이 문서가 업데이트될 것입니다.
 
 
----------------------------------------------------------------------------------------
 솔직히 저도 위에 내용을 전부다 이해하지 못합니다. 다만. WinDBG를 사용하면서 정말 강력하고 좋다고 생각하던 중에 위 내용을 microsoft.com 페이지에서 찾아서 여러분들과 공유하고자 올려 놓았습니다. 그냥 참고 하시면 좋을것 같습니다.
 
출처 : support.microsoft.com (Another Developer's BIBLE)
Posted by SB패밀리

손쉬운 소프트웨어 스케줄 관리법


글 : Joel Spolsky
번역 :

AhnLab


2000년 3월 29일 수요일

지난해 10, 미국의 북동부 지역은 어딜 가나 아셀라(Acela) 광고 천지였다. 아셀라란 암트랙(Amtrak) 워싱턴-보스턴간 노선에서 운행할 새로운 초고속열차의 이름. 끊임없이 쏟아지는 TV 광고와 전광판 광고, 도처에 나붙은 포스터들을 보면서, 누구라도 정도의 물량 공세라면 새로운 초고속열차 서비스에 대한 수요 창출에 상당히 기여했으리라 생각했을 것이다.

글쎄, 그랬는지도 모르지. 하지만, 정작 암트랙사는 이를 확인해 기회가 없었다. 아셀라 프로젝트가 계속 지연되는 바람에 상용서비스가 개시되지도 않은 상태에서 광고 캠페인만 진행되고 있었던 것이다. 어떤 회사의 신제품 시판을 한달 앞두고 제품이 우수한 평가 등급을 받자 마케팅 책임자가 했다는 말이 떠오른다. “대단한 홍보 효과야! 이럴 시장에 물건만 있었으면 얼마나 좋았을까!”

남성호르몬이 철철 넘치는 게임 업체들은 자사 웹사이트에 제품 개발이 완료되는 대로 새로운 게임이 출시될 것이라고 떠들어대기를 좋아한다. 스케줄? 스케줄 같은 필요하지 않다. 그들은 나는 게임 개발자들이니까! 하지만, 대부분의 평범한 소프트웨어 업체들에게는 스케줄이 필요하다. 로터스의 경우가 좋은 예로, 로터스사가 처음 123 버전 3.0 개발을 완료했을 소프트웨어는 당시만 해도 아직 보급 대수가 많지 않던 80286 기종의 컴퓨터를 필요로 했다. 로터스는 제품 출시를 16개월이나 미뤘고 기간 동안 8086 기종의 메모리 한계인 640K 맞춰 제품의 용량을 줄였다. 수정된 제품이 완성되었을 때엔 이미 엑셀을 시판중인 마이크로소프트사가 시장에서 16개월 정도 앞서나가고 있었으며, 무슨 가혹한 운명의 장난인지, 8086 기종은 이미 시장에서 폐물 취급을 받고 있었다!

글을 쓰고 있는 지금도 넷스케이프의 인터넷 브라우저 5.0 경쟁사에 비해 거의 2가까이뒤쳐져있다. 개발된 코드를 모두 폐기하고 처음부터 코딩을 다시 시작하는 치명적인 실수를 저질렀기 때문이다. 애시톤-테이트, 로터스, 애플사의 MacOS 등도 똑같은 실수를 저지르는 바람에 소프트웨어 역사의 휴지통(Recycle-bin) 속으로 내던져지는 신세가 되고 말았다. 사이 넷스케이프사의 브라우저의 시장점유율은 80%대에서 20%대로 곤두박질쳤지만 회사는 아무것도 없었다. 주력 소프트웨어 제품이 산산조각으로 분해되어 있는 상태에서는 달리 손을 있는 방도가 없었기 때문이다. 한번의 잘못된 결정이 넷스케이프를 자폭시킨 핵폭탄이 셈이었다. ( 자세한 내용은 Jamie Zawinski world-famous tantrum 참조한다).

그래서, 스케줄관리가필요한것이다. 거의 모든 프로그래머들이 하기 싫어하는 일이 바로 스케줄 관리다. 필자의 경험에 따르면, 대다수의 프로그래머들은 어떻게 해서든 스케줄 같은 아예 짜지 않고 버텨 보려고 든다. 스케줄을 짜는 극소수의 프로그래머들도 대부분 상사가 하라고 하니까 억지로 하고 있을 , 스케줄대로 프로젝트가 진행되리라고 믿는 사람은 상사 밖에 없다. 상사는 스케줄을 믿는 한편으로, “기한 내에 완료되는 소프트웨어 프로젝트는 없다고도 믿으며 , UFO 존재도 믿는 사람이다.

그렇다면, 아무도 스케줄을 만들려 하지 않을까? 주된 이유는 가지다. 첫째는 스케줄을 짜고 관리하는 것이 매우 골치 아픈 일이기 때문이고, 번째 이유는 아무도 그것이 그만한 수고의 가치가 있는 일이라고 믿지 않기 때문이다. 스케줄이 지켜지지도 않을 거라면 도대체 그런 수고를 해야 한단 말인가? 스케줄이란 애당초 지켜지는 법이 없고 시간이 지날수록 현실과의 괴리는 점점 커지기만 하는데, 하러 그런 헛수고를 한단 말인가?

여기, 정확하게지켜지는스케줄을만드는간단하고손쉬운방법이있다.

1) 엑셀프로그램을사용하라. MS Project 같은 거창한 프로그램을 사용하려 들지 말라. MS Project 사용자가 종속성의 문제를 해결하기 위해 많은 시간을 할애할 것이라는 가정하에 만들어진 프로그램이다. 종속성이란, 가지 작업을 진행해야 하는 상황에서 하나의 선행 작업이 먼저 완료된 후에만 다음 작업을 시작할 있는 경우를 말한다. 소프트웨어 프로젝트의 경우, 작업들간의 종속성은 너무나 당연한 것이기 때문에 굳이 수고스럽게 종속성을 추적할 필요가 없다.

MS Project 사용하는 경우에 생길 있는 다른 문제는 프로그램을 사용하다 보면 자꾸 스케줄 균형 조정 기능을 실행하고 싶어진다는 것이다. 스케줄 균형 조정에는 불가피하게 모든 작업을 재조정하여 다른 사람에게 재할당하는 과정이 포함되는데, 소프트웨어 프로젝트의 경우 이런 재조정은 말이 되지 않는다. 프로그래머들간의 호환이 불가능하기 때문이다. 가령, 리타가 작업한 코드의 버그를 존이 수정하려면 리타 자신이 수정할 때보다 시간이 일곱 배는 걸린다. , UI 담당 프로그래머에게 WinSock 관련된 문제를 해결하라고 시키면, WinSock 프로그래밍에 익숙해지는 데에만 일주일은 걸릴 것이다. 결론은 MS Project 소프트웨어 개발 프로젝트보다는 건축 공사 프로젝트에 적합한 프로그램이라는 것이다.

2) 단순하게만들라. 필자가 이용하는 스케줄 포맷은 너무나 단순해서 한번만 봐도 기억할 있을 정도다. (컬럼) 개수는 일곱 개면 된다.

개발자가 여러 명인 경우에는 개발자별로 시트를 각기 따로 만들 수도 있고, 아니면 작업을 담당하는 개발자의 이름으로 컬럼을 만들 수도 있다.

3) 하나의기능은여러개의작업으로분해하라. 여기서, 기능이란 프로그램에 추가되는 맞춤법 검사 기능 같은 것을 말한다. 실제로 맞춤법 검사 기능을 추가하려면, 프로그래머는 여러 단계의 작업을 수행하게 된다. 스케줄을 가장 중요한 부분은 바로 이와 같은 구체적인 작업들의 목록을 만드는 것이다. 따라서 다음 규칙을 명심해야 한다.

4) 코딩을직접담당할프로그래머만이스케줄을만들있다. 관리자가 임의로 만든 스케줄을 프로그래머에게 던져주고는 따르라고 말하는 프로젝트는 실패할 밖에 없다. 코딩을 직접 담당할 프로그래머만이 기능을 구현하기 위해 어떤 작업들이 필요한가를 구체적으로 예측할 있고 작업에 어느 정도의 시간이 소요될 것인가도 추정할 있다.

5) 작업단위를세분화하라. 실효성 있는 스케줄을 만들기 위해 반드시 유념해야 부분이다. 작업량은 날짜가 아니라 시간 단위로 계산해야 한다. ( , 또는 단위로 작업 기간이 계산되어 있는 스케줄은 처음부터 지킬 생각으로 만든 스케줄이 아니다). ‘작업 단위를 잘게 쪼개면 정밀한 스케줄이 나오겠지 정도로 생각하는 독자가 있을지도 모른다. 천만의 말씀! 대략적인 작업 개요를 바탕으로 스케줄을 세워보고, 다시 작업 단위를 보다 세분화하여 스케줄을 세워보면, 단순히 정밀한 스케줄이 얻어지는 것이 아니라 전혀다른결과 나온다. 완전히 다른 수치가 나오는 것이다. 어째서 이런 일이 생기는 걸까?

작업 단위를 세분화하기 위해서는 실제 코딩 과정에서 구체적으로 어떤 일들을 수행하게 것인가를 생각해보지 않으면 된다. 서브루틴을 저작하고, 이러저러한 대화상자들을 만들고, wawa 파일을 읽어내고 등등. 같은 각각의 단계에 소요되는 시간을 추정하는 것은 어렵지 않다. 프로그래머라면 서브루틴을 저작하고 대화상자를 만들고 wawa 파일을 읽어내는 작업들은 모두 해봤을 것이기 때문이다.

만일 어떤 스케줄이 적당히 얼버무린 덩어리 작업들로만 (가령, “문법 검사 기능 구현하기 ) 채워져 있다면, 이는 프로그래머가 구체적으로 어떤 일들을 하게 것인가에 대해 제대로 생각해보지 않았다는 증거다. 그리고, 어떤 일들을 하게 것인지 제대로 생각해보지 않았다면, 거기에 어느 정도의 시간이 소요될 것인지도 없다.

개별 작업에는 어림잡아 2시간에서 많게는 16시간 정도까지가 소요된다. 스케줄에 40시간(1주일)짜리 작업이 포함되어 있다면, 작업 단위를 충분히 세분화하지 않았다는 이야기다.

스케줄을 작업 단위를 세분화해야 하는 다른 이유는 그런 과정을 통해 해당 기능을 머리 속으로 설계해보게 된다는 것이다. 만일 인터넷 통합이라는 떨리는 기능에 대한 스케줄을 수립하면서 무턱대고 3주일을 배정했다면 프로젝트는 실패할 밖에 없다. 구체적으로 어떤서브루틴들을만들어내야것인가 생각해내다 보면, 기능을 명확하게규정하게 된다. 같은 단계에서 미리 계획을 세워봄으로써 소프트웨어 프로젝트와 관련된 불안정성을 크게 줄일 있다.

 

6) 최초추정시간과현재추정시간을지속적으로추적하라. 어떤 작업을 처음 스케줄에 추가할 때는 작업에 어느 정도의 시간이 소요될 것인가를 추산한 , 결과를 Orig Est (최초 추정시간) 컬럼과 Curr Est (현재 추정시간) 컬럼에 모두 입력한다. 시간이 흐름에 따라, 작업 진도가 당초의 예정보다 지연 (혹은 단축)되면, 현재 추정시간 컬럼을 필요에 따라 업데이트할 있다. 같은 방식으로 관리하다 보면, 특정 작업에 필요한 시간을 보다 정확하게 추정하는 방법을 스스로 터득할 있다. 대부분의 프로그래머들은 각각의 작업에 어느 정도의 시간이 소요되는지 모른다. 그래도 걱정할 필요는 없다. 스케줄을 지속적으로 관리하고 그에 따라 업데이트하는 , 결국 스케줄과 프로젝트 완료 시기는 일치하게 된다. (최종 납기를 맞추기 위해 일부 기능이나 항목을 잘라버려야만 하는 경우도 생길 있지만, 그와 같은 추려내기가 필요한 시점이 언제인가를 일관되게 보여준다는 점에서 스케줄의 정확성은 여전히 유지될 것이다). 필자의 경험으로 , 대부분의 프로그래머들은 일년 정도만 연습하면 스케줄 짜는 도사가 된다.

작업이 완료되면, 현재 추정시간 컬럼과 Elapsed (경과시간) 컬럼의 값이 똑같아지고 Remain (잔여시간) 컬럼의 값은 0 된다.

7) 경과시간을매일업데이트하라. 물론, 스톱워치를 들여다보며 코딩을 진행할 필요는 없다. 퇴근하기 직전에, 혹은 회사에서 밤을 새워가며 일하는 경우라면 책상 밑에 기어들어가 잠깐 눈을 붙이기 직전에, 하루에 한번만 경과시간을 업데이트하면 된다. 8시간 동안 근무했다고 가정하고 (하하!), 그날 진행한 작업들에 대한 경과시간 필드에 8시간을 쪼개서 입력한다. 잔여시간 컬럼의 값은 엑셀이 자동으로 계산해 것이다.

그와 동시에, 현재 추정시간 컬럼도 업데이트하여 변화된 현실을 반영한다. 스케줄을매일업데이트하는걸리는시간은고작2정도밖에되지않는다. 그래서 방법을 손쉬운 스케줄 관리법이라고 부르는 것이다. 빠르고 간편하기 때문에.

8) 휴가기간과휴일등도항목에포함시켜라. 1 정도 진행되는 스케줄이라면, 사이에 프로그래머들의 휴가일수가 대개 10일에서 15 정도 포함될 것이다. 스케줄의 기능 컬럼에는 휴가, 휴일 기타 시간을 잡아먹는 모든 것들에 대한 항목이 전부 포함되어야 한다. 잔여시간 컬럼의 값을 모두 더한 이를 40(1주일)으로 나누면, 휴가 모든 요소를 포함하여 작업 기간이 주일이나 남아있는지를 산출할 있고 제품 출시 일자도 계산할 있다.

9) 디버깅시간도고려하라! 디버깅은 소요시간을 추정하기가 가장 어려운 항목이다. 가장 최근에 작업했던 프로젝트를 떠올려보라. 아마도 코딩 단계에서 소요된 시간의 100% 내지 200% 해당하는 시간이 디버깅에 소요되었을 것이다. 스케줄의 기능 컬럼에는 반드시 디버깅 항목이 포함되어야 하며, 아마도 가장 값이 항목이 것이다.

스케줄 업데이트 방법은 다음과 같다. 가령, 어떤 개발자가 wawa 파일을 코딩한다고 하자. 처음 스케줄을 예상했던 최초 추정시간은 16시간이었지만, 현재까지 이미 20시간이 소요되었고 앞으로도 10시간 정도는 걸릴 것으로 예상된다. 경우, 개발자는 현재 추정시간 컬럼에 30 입력하고 경과시간 컬럼에 20 입력하면 된다.

주요 작업 단계가 완료된 , 모든 항목들의 값들을 더해보니 상당한 수치가 나왔다. 이론상으로 제품 납기를 맞추자면 몇몇 기능을 잘라내 버려야 한다. 이렇게 시간에 쫓길 프로그래머들이 가장 먼저 잘라낼 있는 기능이 바로 디버깅 항목이다. 다행히도, 처음 스케줄을 디버깅 항목에 많은 시간을 할당해 두었으니까.

원칙적으로, 개발자들은 코딩과 동시에 디버깅을 병행해야 한다. 절대로, 수정할 있는 버그를 뒤로 미뤄둔 새로운 코딩에 착수해서는 된다. 미해결 버그의 수는 가능한 최소한으로 유지해야 한다. 이유는 가지다.

1) 버그는 코딩 직후에 곧바로 수정하는 것이 가장 수월하다. 달쯤 지난 후에 버그를 수정하려고 하면, 정확한 코딩 방식을 기억하지 못하기 때문에 버그 수정도 어려워지고 시간도 많이 걸릴 있다.

2) 버그 수정은 과학적 연구와 비슷해서, 언제 새로운 과학적 사실을 발견할 있을지 정확히 예측하기 어려운 것처럼 언제 버그를 해결할 있을지 정확히 예측하는 것도 쉽지 않다. 수정해야 버그가 개나 개뿐이라면 제품 출시 시기를 예측하는 것은 어렵지 않겠지만, 미해결 버그가 수백 개나 수천 개쯤 되는 경우에는 이것들을 언제 전부 수정할 있을지 예측하기란 불가능하다.

그렇다면, 개발자가 코딩 과정에서 버그를 발견할 때마다 곧바로 버그를 수정하는데 디버깅 스케줄을 따로 만들어야 하는가? 프로그래머가 모든 버그를 그때그때 수정한다 하더라도, 작업 단계가 완료되면 불가피하게 버그 수정 과정이 필요하다. 테스트 (내부 테스트 베타 테스트) 단계에서 정말로 어려운 버그들이 발견되기 때문이다.

10) 통합시간도고려하라. 여러 명의 프로그래머가 함께 작업하는 경우, 프로그래머들 간에 서로 일치되지 않아 조정이 필요한 부분이 생기게 마련이다. 가령, 유사한 기능의 대화상자를 명의 프로그래머가 서로 다른 방식으로 구현할 수도 있다. , 여러 명의 프로그래머들이 각자 마음대로 추가한 메뉴 항목이나 키보드 단축키, 툴바 등을 누군가는 전체적으로 정리하고 통일시켜야만 한다. 사람이 코드에 체크인하는 순간 컴파일러 에러가 발생할 수도 있다. 모든 것들을 수정할 시간이 필요하며, 이는 별도의 기능 항목으로 스케줄에 포함시켜야 한다.

11) 여분의시간을포함시켜라. 시간은 모자라게 마련이다. 스케줄을 고려해야 여분의 시간(버퍼) 가지 종류가 있다. 첫째는 당초 추정했던 것보다 시간이 많이 걸리는 작업을 고려한 버퍼이고, 둘째는 관리자가 wawa 구현이 너무나도 중요해서 다음 버전으로 미룰 없다고 결정하는 경우 등과 같이, 계획에 없이 갑자기 추가된 작업을 고려한 버퍼다.

혹시라도 휴가, 휴일, 디버깅, 통합 시간 버퍼 항목 등을 모두 더한 값이 실제 작업 시간을 모두 더한 값보다 크다는 사실을 알고는 놀랄 수도 있다. 이런 사실에 놀라는 독자라면 아마 프로그래밍 경력이 아직 그리 길지 않은 개발자일 것이다. 책임질 자신이 있거든, 이런 항목들을 빼버려도 좋다.

12) 절대로관리자가프로그래머에게스케줄단축을요구해서는된다. 풋내기 소프트웨어 관리자 중에는 프로그래머들에게 빡빡한” (비현실적으로 짧은) 스케줄을 던져주는 방법으로 그들을 자극하여 작업을 보다 빨리 진행시킬 있다고 생각하는 사람들이 많다. 필자의 생각으로는 이런 식의 자극은 멍청한 짓일 뿐이다. 필자의 경우, 스케줄에 뒤쳐지면 오히려 좌절감이 생기고 의욕이 저하되며, 스케줄보다 진도가 빠를 활기도 생기고 생산성도 높아진다. 관리자는 스케줄을 심리전의 도구로 이용해서는 된다.

그럼에도 불구하고, 관리자가 스케줄 단축을 요구하는 경우에는 이렇게 대처하라. 릭의추정시간이라는 새로운 컬럼을 하나 추가한다 (물론, 프로그래머의 이름이 릭이라고 가정하고.) 프로그래머 자신이 예상하는 추정시간을 컬럼에 입력한다. 관리자가 원하는 새로운 스케줄은 현재추정시간 컬럼에 업데이트 한다. 관리자의 추정시간은 무시하고 작업을 진행하라. 프로젝트가 완료된 후에 누구의 스케줄이 현실적이었는지 비교해 보라. 필자의 경험에 따르면, “누구 스케줄이 맞는지 한번 볼까요?” 라고 관리자를 위협하는것만으로도 놀랄만한 효과가 있다. 자칫하면 프로그래머들과 얼마나 느릿느릿 일할 있는지 두고 보자는 식의 내기를 하는 꼴이 되고 만다는 사실을 관리자도 깨닫게 테니까!

풋내기 관리자들은 프로그래머의 스케줄을 단축하려고 노력하는가?

프로젝트가 처음 시작되면, 기술 관리자들은 비즈니스 담당자들과 회의를 하고는 그들이 3개월 정도 소요될 것으로 생각하는, 하지만 실제로는 9개월이 걸리는 제품 기능들이 적힌 목록을 만들어 온다. 어떤 작업들을 수행해야 하는가를 구체적으로 생각해 보지 않고 스케줄을 짜는 경우, 항상 실제로는 3n 시간 이상 걸릴 일이n 시간 정도 걸릴만한 일인 것처럼 보이게 된다. 현실적인 스케줄을 짜다 보면, 모든 작업 시간을 전부 더하게 되고 프로젝트가 당초 생각했던 것보다 훨씬 시간이 많이 걸릴 것이라는 사실을 깨닫게 된다. 현실은 가혹한 것이다.

풋내기 관리자들은 같은 문제를 해결하기 위해, 사람들이 보다 빨리 일하도록 만들 있는 방안을 찾아내려 애쓴다. 하지만, 이런 식의 접근은 그다지 현실적인 방법이 아니다. 작업 인원을 늘릴 수는 있겠지만, 그렇더라도 신입 프로그래머들이 작업에 익숙해질 때까지 상당한 시간이 필요하기 때문에 정도는 작업 효율이 50% 정도 밖에 되지 않을 것이다 (게다가, 그들에게 일을 가르쳐야 하는 사람들의 작업 효율까지 떨어진다). 무엇보다, 소프트웨어 업계에서 쓸만한 프로그래머를 새로 뽑자면 6개월 정도는 시간이 걸린다.

모든 에너지가 일년 안에 100% 소진될 만큼 팀원들을 쥐어짠다면 혹시 일시적으로 코드 산출량이 10% 정도 늘어날지도 모른다. 하지만, 별로 남는 장사는 아니다. 무엇보다, 이것은 배가 고프다고 종자용 옥수수까지 먹어치우는 것과 비슷한 꼴이다.

모든 사람들에게 아무리 피곤하더라도 참고 초인적으로 열심히 일해달라고 애원한다면 혹시 코드 산출량이 20% 정도 늘어날 있을지도 모른다. 하지만, 디버깅 시간은 배로 늘어날 것이다. 이야말로 숙명적으로 실패가 예정된 악수(惡手) 있다.

하지만, 아무리 해도 절대로 n 가지고 3n 이룰 수는 없다. 만일 있다고 믿는 회사가 있다면, 필자에게 이메일로 회사의 나스닥 상장기호를 일러주기 바란다. 필자가 상장기호를 반으로 줄여줄 테니까.

13) 스케줄은나무블록과같다. 집짓기 놀이용 나무 블록이 꾸러미 있는데 이걸 주어진 상자에 전부 담을 수가 없다면, 방법은 가지다. 상자를 구해오거나, 아니면 나무 블록 중에서 개는 포기하는 것이다. 6개월이면 제품을 완성할 있을 거라고 생각했었는데 작업을 진행하다 보니 12개월짜리 스케줄이 됐다면, 제품 출시를 늦추든가 아니면 몇몇 기능을 포기해야 한다. 나무 블록이 작아지게 만들 수는 없으니까. 만일 작아지게 만들 있는 것처럼 행동한다면, 이는 지금 앞에 보이는 것에 대해 스스로에게 거짓말을 함으로써 장래를 예측할 있는 기회마저 스스로 박탈하는 일이다.

이와 같은 스케줄 관리를 통해 얻을 있는 다른 부수적 효과는 많은 기능들 중에서 일부를 포기하게끔 만든다는 것이다. 포기하게끔 만드는 것이 좋으냐고? 여기 가지의 기능이 있다고 가정하자. 하나는 진짜로 쓸모 있고 장차 제품을 빛내줄 기능이고 (예를 들면, 넷스케이프 2.0 테이블 기능), 다른 하나는 코딩하기도 아주 쉽고 프로그래머가 만들어보고 싶어하지만 쓸모는 별로 없는 기능이다 (예를 들면, BLINK 태그).

스케줄 없이 프로젝트를 진행할 경우, 프로그래머들은 쉽고 재미있는 기능 먼저 코딩하기 시작할 것이다. 그러다가 나중에 시간이 부족하게 되면, 없이 유용하고 중요한 기능을 포기하게 된다.

프로젝트를 개시하기 전에 미리 스케줄을 수립하는 경우, 무언가를 포기해야만 하는 상황이 되면, 쉽고 재미있는 기능을 포기하고 유용하고 중요한 기능을 작업하게 것이다. 스케줄을 관리하다 보면, 버려야 기능들을 프로그래머들이 스스로 추려내게 되기 때문에, 보다 나은 기능들로 구성된 강력하고 좋은 제품을 만들 있게 된다.

필자가 엑셀 5.0 개발 프로젝트에 참여했을 때의 일이다. 당시 우리 팀이 계획했던 기능들이 워낙 방대한 양이었기 때문에 스케줄을 맞출 수가 없게 되었다. 팀원들은 고민했다. 이를 어쩌나! 전부 너무나도 중요한 기능들인데. 매크로 편집 마법사는 절대로 포기할 없어!

시간이 촉박해지자 별다른 도리가 없었다. 제품 출시 기한을 맞추기 위해 우리 팀은 자식 같은 기능들을 잘라내야 했다. 포기하게 기능들에 대해 모두 마음 아파했다. 아픔을 달래기 위해 팀원들은 스스로에게 이야기했다. 기능들을 포기하는 것이 아니라 그저 조금 중요한 기능들을 위해 엑셀 6.0 버전으로 잠시미뤄두는 것뿐이라고.

엑셀 5 거의 완성되어갈 무렵, 필자는 동료인 Eric Michelman 함께 엑셀 6 버전에 대한 스펙을 만들기 시작했다. 사람은 머리를 맞대고 앉아 엑셀 5.0 스케줄을 조정하면서 엑셀 6”용으로 미뤄둔 기능들을 다시 살펴보았다. 우리는 기능들이라는 것이 너무나 조악한 것들 일색이라는 놀라지 않을 없었다. 쓸만한 기능이라고는 하나도 없었다. 물론, 기능들은 다음, 다음 버전에서도 제품화되지 않았다. 스케줄을 맞추기 위해 필요한 기능들만 추려낸 것이야말로 우리 팀이 중에서 가장 잘한 일이었다. 그렇게 하지 않았다면, 엑셀 5.0 개발 기간은 배쯤 길어졌을 것이고 아무짝에도 쓸모없는 쓰레기 같은 기능들로 50% 채워졌을 것이다. (넷스케이프 5.0이나 모질라(Mozilla) 경우에도 분명히 이와 같은 상황이 벌어졌을 것이다. 스케줄도 없고, 확정된 기능 목록도 없고, 쓸만한 기능만 추려내는 아무도 원치 않고, 그러다 보니 제품을 출시할 없게 것이다. 그들은 결국 IRC 클라이언트처럼 시간을 들일만한 가치라고는 없는 기능들만 잔뜩 들어있는 제품을 내놓게 것이다.)

부록: 엑셀을이용할알아두어야것들

엑셀이 소프트웨어 스케줄 관리에 편리한 제품이라고 주장하는 이유 중의 하나는 대부분의 엑셀 프로그래머들이 오로지 엑셀 하나만 가지고 스케줄을 관리한다는 사실이다 (what-if 시나리오 같은 것을 분석하는 사람은 별로 없다... 프로그래머들한테 그런 기대하면 되지!)

목록공유: 파일/목록 공유 명령을 이용하면 팀원 모두가 한꺼번에 파일을 열어두고 편집할 있다. 전체가 지속적으로 스케줄을 업데이트해야 하기 때문에, 기능은 아주 유용하다.

자동필터: 자동 필터 기능은 가령, 특정 프로그래머가 자신에게 할당된 작업만 전부 골라서 보고 싶을 스케줄을 필터링하는 편리하다. ‘자동 정렬 기능과 함께 사용하면, 자신에게 할당될 모든 작업을 우선순위에 따라 순서대로 조회할 있다. 자체로 효과적인 작업 계획서 되는 것이다. 끝내주지?

피벗테이블: 피벗 테이블 기능은 요약정보나 크로스테이블을 보고 싶을 매우 편리하다. 가령, 각각의 우선순위에 대해, 개발자에게 필요한 잔여시간을 보여주는 도표를 만들 있다. 피벗 테이블은 결코 어렵지 않다. 반드시 기능을 사용하는 익숙해져야 한다. 엑셀을 백만 배쯤 편리하게 써먹을 있기 때문이다.

작업일기능: 엑셀의 분석 툴팩에 포함된 작업일(Workday) 기능을 활용하면 스케줄에 해당하는 실제 달력상의 날짜를 손쉽게 확인할 있다.



이 기사는 영어로 Painless Software Schedules 라는 이름의 기사가 원본입니다
Posted by SB패밀리

버그 없는 깨끗한 프로그램 만들기
2007/02/28 01:18 출처 : http://www.winapiprogramming.com/

 필자에게 프로그래밍이 무엇이냐고 묻는다면 프로그래밍이란 "버그와의 끝없는 싸움"이라고 대답하고 싶습니다. 필자가 처음 프로그래밍을 접해본 것은 중학교 3학년 때인 1984년이었습니다. 친구 집에 놀러갔다가 접한 SPC-1000에서 베이직으로 간단하게 계산기를 만들어본 것이 처음이었습니다. 그 컴퓨터란 물건이 얼마나 부럽던지 반년 동안 아버지를 졸라서 고등학교 1학년 때 애플 II 컴퓨터를 샀고 몇몇 컴퓨터 잡지를 사서 소스를 아무 생각없이 입력한 것 말고는 입시 준비(?)에 시달리느라 제대로 프로그래밍을 해본 적은 없었습니다. 그러다가 대학에 들어와서 포트란, 파스칼, C등을 배우면서 좀더 본격적인 프로그래밍을 시작하게 되었습니다. 그 시절을 돌이켜보면 참 어떻게 그렇게 무식하게 (?) 프로그래밍을 할 수 있었는지 대단하다는 생각이 간혹 들기도 합니다.

그 시절을 잠깐 회상하자면 일단 무턱대고 컴퓨터 앞에 앉아서 프로그래밍을 시작하지만 넘치는 의욕에 비해 실력이 따르질 않아서 쉬운 숙제에도 쩔쩔매며 간신히 코드를 만들었습니다. 컴파일하면 에러가 잔뜩 !!. 간신히 에러를 잡고 나서 실행하면 프로그램이 죽던지 엉뚱한 결과가 나오고... 그러면 이제부터 디버깅이 시작됩니다. 일단 의심이 가는 곳을 대충 고친 다음에 먼저 돌려보고 안 되면 다시 의심가는 곳을 고치고... 코드가 누더기가 된 다음에야 간신히 코드는 돌아가고 원하는 결과가 나오기는 하는데 왜 동작하는지 본인도 알 수 없고...

아마 대부분의 프로그래머들이 비슷한 과정을 거쳤거나 앞으로 거치리라 생각됩니다. 100줄 이상의 프로그램이라면 프로그램에 버그가 없을 수는 없습니다 (아니 적어도 컴파일 에러라도 있습니다). 그렇기 때문에 좋은 프로그래머가 되려면 버그가 적은 프로그램을 작성할 수 있거나 버그를 빨리 발견할 줄 알아야 합니다. 이 글에서는 여러분들이 이러한 목표를 달성하는데 도움이 될 수 있도록 먼저 좋은 코딩 습관에 대해 살펴보겠습니다. 그리고나서 다음으로 실례를 중심으로 자주 접하게 되는 버그 상황과 디버깅 팁에 대해 알아보도록 하겠습니다. 참고로 여기서는 비주얼 C++을 바탕으로 설명을 진행하겠습니다.

 

1. 바람직한 코딩 습관

버그 없는 프로그램을 작성하는 가장 좋은 방법은 바람직한 코딩 습관을 몸에 배게 하는 것입니다. 저도 지금 생각해보면 처음 프로그래밍을 배울 때 누군가 옆에서 이러한 것에 대해 이야기해주는 사람이 있었다면 고생을 좀 덜하지 않았을까 하는 생각을 간혹 하곤 합니다. 물론 자신이 직접 깨닫는 것만큼 확실히 배우는 것은 아니고 그 당시 그런 조언을 이해할만한 수준이 아니었기 때문에 그런 것들을 들었다 해도 얼마나 습득할 수 있었을지 의문이긴 합니다만. 참 이런 이야기를 하다보니 제가 이제는 무슨 완벽한 프로그래머가 된 것처럼 되어버렸는데 저도 이런 코딩 습관을 확립하기 위해 지금도 노력하고 있는 중입니다.

자 그럼 제가 생각하는 바람직한 코딩 습관에 대해 하나씩 알아보도록 하겠습니다. 이제 읽어보면 알겠지만 여기에 무슨 눈이 번쩍 뜨이는 그런 이야기들이 있는 것이 아닙니다. 항상 그렇지만 모든 성공은 작은 습관과 집중력으로부터 비롯합니다.

 

1> 사용하는 프로그래밍 언어를 제대로 이해하자.

이 것은 초보 프로그래머에게는 아주 중요한 사항입니다. 프로그래밍을 처음 배우는 사람이라면 그 개념을 제대로 이해하지 못하는 경우가 허다합니다. 특히 C 언어를 배우는 사람이라면 포인터의 개념을 제대로 이해하지 못하는 경우가 많고 C++/자바 등의 언어를 배우면 클래스의 상속(Inheritance)이나 가상 함수(Virtual function)와 같은 개념을 이해하지 못하는 경우가 허다합니다. 이런 것을 이해하지 못하고 일단 프로그래밍에 뛰어들게 되면 당연히 많은 시간을 소비하기 마련이고 결과물을 만들어내지 못하는 경우도 많습니다. 아무리 시간이 없어도 한걸음 한걸음 배워나가는 것이 결국에는 시간을 단축하기 마련입니다.

프로그래밍 서적을 보고 공부할 때도 모니터 앞에 앉아서 지금 보고 있는 부분에 대해 실습을 해보며 공부하는 것이 최선의 방법입니다. 그렇게 하면 보다 더 이해하기가 쉽고 개발환경의 사용법에 대해서도 배워볼 수 있기 때문입니다. 그렇기 때문에 초보 프로그래머라면 프로그래밍 언어 관련 서적을 고를 때 예제 코드가 많이 있는지 또한 따라하기식의 구성이 잘 되어있는지를 살펴보는 것이 중요합니다. 즉 프로그래밍 언어는 머리로 이해하는 것도 중요하지만 손으로도 익혀야 한다는 것입니다.

top

2> 프로그래밍 중에는 프로그래밍에만 집중하자.

이 것은 초보 프로그래머 뿐만 아니라 이 시대의 모든 프로그래머에게 해당되는 이야기라고 할 수 있는데 한마디로 자신의 일에 대한 집중력을 키우라는 이야기입니다. 요즘 웬만한 컴퓨터는 인터넷과 연결되어 있습니다. 인터넷 매체와 메일, 메신저 등이 워낙 발달해 있어서 한 시간이라도 중단 없이 일한다는 것이 쉬운 일이 아닙니다. 이런 상황은 사실 프로그래머 뿐만 아니라 컴퓨터를 사용하는 모든 사람들에게 해당된다고 할 수 있을 것 같습니다. 다음과 같은 비슷한 경험이 있는지 한번 생각해봅시다.

코딩이 좀 막히면 머리를 식히기 위해 인터넷의 바다에서 뛰어들고 코딩이 잘 되어도 기분이 좋아서 인터넷의 바다에 뛰어들고... 또 요즘 메일 클라이언트는 모두 알림 기능이 있어서 메일이 도착하면 이를 알려주게 되어있습니다. 메신저는 어떠한가요 ? 툭하면 친구로부터 채팅 요청이 들어오고... 수다 떠느라 시간가는 줄 모르고...

의식적으로 노력을 해서 이러한 중단이 없도록 해야 합니다. 인터넷의 바다에 뛰어드는 시간대를 정해두는 것이 제일 좋은 것 같습니다. 점심 시간 직후라든가 하는 식으로 말입니다. 메일의 경우에는 새 메일이 도착했다는 알림이 와도 바쁜 경우라면 일단 그 일을 끝낸 후에 보는 식으로 한번 바꿔보기 바랍니다. 메일의 경우 같이 일하는 사람들간 의사소통 수단이기도 하기 때문에 알림 기능 자체를 꺼버리는 것은 좀 무리가 있는 듯합니다. 메신저 같은 경우는 집중이 필요한 시간에는 아예 로그아웃해버리는 것입니다. 아니면 약간 치사(?)하지만 상태를 "자리 비움" 혹은 "다른 용무 중" 같은 걸로 해두세요.

약간 이야기가 옆길로 새는 것 같긴 하지만 집중력에 관해 얼마 전에 제가 읽은 글이 있어서 참고로 여러분들께 소개해 드리고자 합니다. ("고도원의 아침편지"란 사이트에서 읽은 글입니다.)

밥을 먹을 때에는 밥먹는 일에 집중하고
청소할 때에는 온전히 청소하는 행위만 있어야 합니다.
그렇게 생각하고 말하고 행동하는 것을
달리 말하면, 집중력 또는 통일성이라고 합니다.
이 집중하는 태도와 노력을 통해
우리는 스스로 정화되기도 하고
안정되기도 하며
또 문제의 본질을 통찰하는
힘을 얻기도 합니다.


- 도법스님의 <<내가 본 부처>> 중에서

프로그래밍을 할 때는 프로그래밍에만 집중합시다 !! 인터넷으로 인한 이러한 중단이외에도 회사 생활을 막 시작한 초보 프로그래머의 경우에는 다음과 같은 것을 조심해야 합니다. 긴 업무 시간으로 인한 집중력 상실입니다. 이건 사실 개인의 힘만으로 해결할 수 있는 문제는 아닙니다만 아주 한국적인 근무 환경 하에서는 늦게 퇴근하는 것이 하나의 미덕입니다. 따라서 오랜 시간을 회사에서 보내려니 업무의 강도가 느슨해지기 마련입니다. 하루 근무시간을 8시간으로 생각하지 않고 10시간에서 12시간으로 생각하니까 할 일이 있어도 "저녁에 하지 뭐!" 이런 생각을 갖게 되고 커피마시고 담배피우며 잡담하느라 보내는 시간이 더 많아지게 되는 것입니다. 사실 이는 매니저 역할을 맡고 있는 사람이 해결해야할 문제입니다만 이런 상황으로 인해 집중력을 잃는 일이 발생할 수도 있다는 점을 알아두고 자신이 이런 증상을 보이거든 회사를 옮기던가(?) 자신을 채찍질하여 다시 집중력을 되찾기 바랍니다.

 

3> 주석을 많이 달자

주석을 많이 다는 것도 굉장히 중요합니다. 아주 복잡하고 거창하게 코드 흐름도를 그리라는 이야기가 아닙니다. 소스 코드 중간 중간에 설명을 자세히 달아놓으라는 이야기입니다. 그것만으로도 다른 사람이나 코드의 원저자 자신이 나중에 소스를 볼 때 큰 도움을 얻을 수 있습니다. 제 아무리 자기가 작성한 코드라고 해도 복잡하고 사연 많은 코드의 세부적인 내용은 몇 개월만 지나도 잊어버리기 십상이기 때문입니다.

귀찮아서 주석을 안 다는 사람들도 많습니다. 심지어 주석이 코드의 미관을 해친다는 이색적인 주장(?)을 펼치며 주석달기를 거부하는 사람도 본 적 있었습니다. 오 마이 갓 !! 주석을 다는 일은 습관이 되면 그리 어려운 일이 아닙니다. 또 주석을 달면서 작성하는 소스의 구성이나 흐름이 보다 더 명확해질 수도 있습니다. 예를 들어 C/C++이나 자바로 코딩을 하는 중이라면 먼저 //부터 입력하고 밑에서 할 일을 간단히 적고 시작하면 자신이 해야할 일이 좀더 명확해지고 나중에 주석을 보고 코드를 기억하거나 이해하기도 쉽기 때문입니다.

소스 파일을 하나 새로 만들면 그 앞에 다음과 같은 식으로 주석을 달기 바랍니다.

// -------------------------------------------------------

// 파일이름 : NewModule.cpp

// 설명 : 전체 프로그램에서의 이 소스 파일의 역할을 기술합니다.

// 노트 : 기억할만한 점이 있으면 기록합니다.

// 히스토리 : 생성 - 한기용, 2002.03.31

// Abc 함수추가 - 두기용, 2002.04.01

// -------------------------------------------------------

함수의 경우에는 함수마다 앞에 다음과 같은 함수 주석을 답니다.

// -------------------------------------------------------

// 함수이름 : Abc

// 설명 : 이 함수의 역할에 대해 기록합니다.

// 노트 : 기억할만한 점이 있으면 기록합니다.

// 인자 : 인자를 하나씩 설명합니다. 인자의 값을 누가 채워주는지도 명시합니다.

// [IN] int nCount

// [IN] char *pString

// [OUT] int *pResultCount

// 리턴값 : 리턴값의 타입과 의미에 대해 설명합니다.

// -------------------------------------------------------

번거롭게 느껴질지 모르지만 한번 몸에 배면 아주 좋은 특히 같이 일하는 사람들이 좋아하는 습관이란 점을 분명히 기억해두기 바랍니다. 문서화를 잘 하는 사람들은 어디를 가든 사랑(?)받습니다.

 

4> 새로 작성한 코드는 항상 디버거로 따라가 보자

처음 작성했거나 잘 동작하던 코드를 수정한 경우라면 컴파일이 제대로 된 후에 한번 디버거로 흐름을 따라가 보면서 생각하는 대로 동작하는지 살펴보는 것이 아주아주 좋습니다. 좀 번거롭게 생각되어서 이런 이야기를 그냥 듣고 넘길 수도 있는데 이를 습관으로 만들면 여러모로 오히려 편리합니다.

그냥 일단 돌려보고 제대로 동작하지 않으면 디버거로 따라가 보는 전략을 택할 수도 있는데 그것보다는 처음 한번은 일단 디버거로 따라가 보는 것이 여러모로 좋습니다. 디버거로 따라가 보면 결과에 나타나지 않는 에러들도 찾아낼 가능성이 있고 다른 아이디어가 나올 가능성도 더 높기 때문입니다.

>

top

5> 테스트 코드를 만들자

훌륭한 프로그래머라면 누구나 버그없는 빠르고 깔끔한 프로그램을 만드는 사람이라고 생각할 것입니다. 하지만 앞서 제가 언급한 것처럼 복잡한 프로그램을 작성하다보면 버그 없는 프로그램을 작성하는 것은 거의 불가능한 일입니다. 그렇기 때문에 제가 생각하는 좋은 프로그래머의 요건은 버그를 어떻게 빨리 발견해서 없애느냐에 달려있다고 생각합니다. 참고문헌 3을 보면 "Debugging the Development Process"라는 책이 언급되어 있습니다. 이 책은 제가 아주 감명(?)깊게 읽은 책으로 제대로 된 개발을 하는 방법론에 대해 다루고 있습니다. 시간이 된다면 한번 꼭 읽어보기를 권합니다. 이 책에서는 버그를 발견하고 이를 없앨 때마다 항상 다음 두 가지를 생각해보기를 권하고 있습니다.

 어떻게 했으면 이 버그를 내가 미연에 방지할 수 있었을까 ?

 어떻게 했으면 이 버그를 내가 아주 쉽게 찾아낼 수 있었을까 ?

이를 좀더 확장해서 생각하면 코드를 작성할 때부터 이걸 어떻게 테스트를 할 것인지 염두에 항상 두고 있어야 한다는 말입니다. 그래서 저 같은 경우는 프로그래머의 수준을 측정할 때 한 가지 척도로 그 사람이 테스트 프로그램을 작성하는지를 일단 봅니다. 자신의 코드를 테스트할 프로그램을 별도로 작성할 정도의 사람이면 일단 기본기가 아주 잘 되어 있는 사람이라고 볼 수 있기 때문입니다.

코드에 따라서는 테스트하는 것이 아주 힘든 경우도 있을 수 있습니다. 하지만 그걸 핑계로 테스트를 건너뛰지는 말기 바랍니다. 테스트할 방법을 계속 생각해보면 결국 어떻게든 그 방법이 떠오르게 되어 있습니다. 테스트하기 힘든 상황에도 그 방법을 생각해낼 정도의 사람이라면 아주 긍정적인 사람으로 인식될 것입니다.

결론적으로 무슨 코드를 만들던지 테스트할 방법을 생각하기 바랍니다. 프로그램 수행의 결과로 파일을 만들어내는 프로그램이라면 그 파일의 내용이 맞는지 체크하는 프로그램을 따로 만들어 볼 수도 있을 것이고 뭔가를 수행해서 화면에 출력하는 간단한 프로그램이라면 그 부분을 별도의 함수로 만들어서 다양한 입력을 주어서 올바른 출력이 나오는지 확인해볼 수 있을 것입니다.

또 실행이 오래 걸리는 프로그램이라면 중간 중간마다 실행 상태를 파일등에 기록해두도록 하는 것도 아주 좋습니다. 이런 용도로 사용되는 파일을 흔히 로그 (log) 파일이라고 합니다. 이때 그 시각도 같이 기록해두면 도움이 많이 될 것입니다.

6> 생각하는 프로그래머가 되자

무슨 코드를 작성하던지 어떻게 할 것인지 먼저 생각하는 습관을 갖기 바랍니다. 그리고 프로그램이 정상적으로 동작하고 원하는 결과를 낸다면 이에 만족하지 말고 더 빠르고 간단하게 처리하도록 개선할 방법이 없는지 자꾸 생각해봐야 합니다. 그 당시에야 일단 실력이 모자라고 개선하는데 시간이 걸리니까 답답하게 여겨질지 모르지만 결국에는 프로그래밍 실력의 발전에 가속이 붙을 것입니다.

사용자 인터페이스 프로그램을 만들고 있는 중이라면 사용자 입장에서 더 편리하게 사용할 수 있는 방법을 자꾸 생각해봐야 합니다. 물론 이 과정은 끝이 없는 과정이 될 수도 있기 때문에 뭔가 데드라인이 있는 일을 작업 중이라면 적정선에서 타협을 봐야 합니다. 이러한 타협은 회사의 운명이 걸린 상용 프로그램을 만들 때는 아주 중요합니다. 자꾸 개선하다가 오히려 에러를 더 낼 수도 있도 그로 인해 결과적으로 주어진 시간 내에 작업을 못 끝낼 수도 있기 때문입니다.

요약하자면 무슨 프로그래밍을 하건 간에 먼저 생각하는 습관을 들이라는 것입니다. 그리고 결과로 만들어지는 코드의 질을 높이기 위해 노력하라는 것입니다. 이런 자세가 습관이 된다면 자신이 만들어낸 코드에 대한 안정성과 높은 성능을 보장해줄 것입니다. 이는 프로 프로그래머가 가져야 할 의식이며 이것이 바탕이 된다면 다른 사람들도 모두 여러분을 믿을만한 사람이라고 높게 평가해줄 것임에 틀림없습니다. 사실 필자도 프로그래밍을 배울 때 이렇게 하지 못했습니다. 프로그래밍을 오래 하다 보니까 그런 자세를 처음부터 갖는 것이 중요하다는 생각이 든 것입니다.

 

2. 예로 살펴보는 버그와 디버깅 팁

이제부터 실질적으로 코드를 통해 자주 접하게 되는 버그의 유형에 대해 예를 들어 알아보기로 하겠습니다.

 

1> 부호 숫자 타입과 무부호 숫자 타입

C/C++에는 부호 숫자 타입과 무부호 숫자 타입이 다음과 같이 존재합니다.

타입 크기

부호 숫자 타입 크기

무부호 숫자 타입 크기

8비트

char (-128 ~ 127)

unsigned char (0 ~ 255)

16비트

short (-32,768 ~ 32,767)

unsigned short (0 ~ 65,535)

32비트

int (-2,147,483,648 ~ 2,147,483,647)

unsigned int (0 ~ 4,294,967,295)

32비트

long (-2,147,483,648 ~ 2,147,483,647)

unsigned long (0 ~ 4,294,967,295)

비주얼 C++에서 사실 int와 long은 모두 32비트라는 점에 유의하기 바랍니다. 초보 프로그래머들이 많이 실수하는 분야 중의 하나가 바로 부적절한 정수 타입을 사용하는 것입니다.

 먼저 사용되는 데이터 값의 범위를 확인하고 그에 맞는 변수를 선택해야 합니다. 위의 표를 보고 적절한 변수를 선택하면 됩니다. 위의 표에는 없지만 사실 __int64라고 해서 64비트 짜리 정수 타입도 있습니다. 사실 대부분의 경우 int로 충분합니다.

 만일 사용되는 데이터의 값이 음수가 될 수 없는 것이 분명하면 무부호 정수 타입을 사용하기 바랍니다. 예를 들어 int 대신에 unsigned int를 사용하란 이야기입니다.

 그런데 무부호 정수 타입을 사용할 경우에는 비교 연산시에 아주 주의해야 합니다. 예를 들어 다음과 같은 코드를 보면

unsigned int dwNumber1 = 100, dwNumber2 = 200;

if (dwNumber1 - dwNumber2 > 0)

AfxMessageBox("dwNumber1 is larger");

else

AfxMessageBox("dwNumber2 is larger");

당연히 if 연산이 거짓이 되어 두 번째 메시지 박스가 출력될 것 같지만 그렇지 않습니다. 이 if 연산은 참이 됩니다. 무부호 정수간의 연산 결과는 다시 무부호 정수가 됩니다. 무부호 정수에는 말그대로 음수가 없습니다. 따라서 이 연산은 항상 참이 됩니다. 위의 코드는 다음과 같은 식으로 변경해야 합니다.

if (dwNumber1 > dwNumber2)

AfxMessageBox("dwNumber1 is larger");

else

AfxMessageBox("dwNumber2 is larger");

즉 무부호 정수끼리 뺄셈을 하는 경우에는 아주 조심해야 한다는 것입니다.

 

2> 포인터 사용하기

C/C++에 처음 입문한 프로그래머들이 가장 어려움을 느끼는 영역 중의 하나가 바로 포인터라는 개념을 익히는 것입니다. 포인터는 메모리 주소를 가리키는 변수입니다. 여기에 메모리를 할당해서 유효한 메모리 주소를 갖게 하기도 하고 다른 변수의 주소를 가리키게 하기도 합니다. 즉 포인터 변수는 선언한 후에 바로 사용하면 안 되고 무엇인가가 유효한 메모리 영역을 가리키게 만들어야만 한다는 것입니다. 전산을 4년 전공하고 회사에 막 들어온 사람들 중 상당수가 이를 제대로 이해하지 못한 상태임을 여러 번 보았습니다. 그 중의 한 예는 다음과 같습니다.

void DoSomething(char *lpstrSource)

{

char *lpstrString;

strcpy(lpstrString, lpstrSource);

...

}

위의 코드를 보면 pString이란 포인터 변수를 선언한 후에 그걸 그대로 strcpy라는 함수에 사용하고 있습니다. 이 상태에서 pString이란 변수는 메모리의 아무 영역이나 가리키고 있습니다. 여기에다가 인자로 넘어온 pSource가 가리키는 문자열을 복사하려고 하면 당연히 프로그램이 뻗어버립니다. 이런 코드가 나오는 이유는 포인터라는 것의 개념을 이해하지 못했기 때문입니다. 그런 상태에서 strcpy 함수의 원형을 보고 그대로 변수를 선언하고 인자로 지정한 것입니다. 올바른 코드는 다음과 같습니다. 포인터에 메모리를 할당한 다음에 이 것이 제대로 할당된 경우에만 복사를 시도하는 식으로 변경해야 합니다.

void DoSomething(char *lpstrSource)

{

char *lpstrString;

lpstrString = (char *)malloc(strlen(lpstrSource)+1);

if (lpstrString)

strcpy(lpstrString, lpstrSource);

...

}

다시 정리하자면 포인터란 메모리 영역을 가리키는 변수이기 때문에 무엇인가 유효한 영역을 가리키도록 초기화해야만 사용할 수 있습니다.

참고 1. 메모리 할당

컴퓨터 세계의 함수 중에는 쌍으로 사용되는 것이 무지하게 많습니다. 대표적인 것이 바로 메모리 할당을 할 때 사용되는 malloc 함수와 더 이상 사용할 일이 없을 때 이를 운영체제에 반환해주는 free 함수입니다. C++에는 new와 delete가 있지요. 이것들의 쌍이 제대로 맞지 않으면 메모리가 조금씩 조금씩 부족해지다가 결국에는 바닥나게 되어 있습니다.

그런데 화장실 들어갈 때와 나갈 때 마음이 다르다고 처음엔 메모리가 필요하니까 할당해서 사용하지만 사후처리를 잊기 십상입니다. 뭐 간단한 프로그램에서야 메모리할당과 반환이 그다지 복잡하지 않지만 정말로 복잡한 프로그램에서는 이게 쉽지 않습니다. 서버 프로그램의 경우 아주 작은 양의 메모리가 반환되지 않는 경우에는 프로그램이 한두달 돌아야 그게 밝혀지는 경우도 있습니다.

그래서 자바(Java)와 닷넷(.NET)에서는 이런 메모리 반환의 책임을 프로그래머에게 넘기지 않고 시스템 레벨에서 처리합니다. 즉 사용되지 않는 메모리가 있으면 알아서 반환시켜버리는 것입니다. 이를 가비지 컬렉션(Garbage Collection)이라고 부릅니다. 프로그래머 입장에서 두손 들고 환영할 일이지요. 하지만 이 가비지 컬렉션을 해주는 프로세스가 주기적으로 동작해야 하고 메모리가 바로 반환되는 것이 아니라 시간이 걸리기 때문에 프로그램의 실행 속도가 전체적으로 좀 느려지게 됩니다.

 

3> 함수의 리턴값 체크

초보 프로그래머이건 노련한 프로그래머이건 간에 또 많이 하는 실수가 바로 함수의 리턴값을 체크하지 않고 그대로 코드를 작성하는 경우입니다. 리턴 값을 체크하려면 if 문이 들어가야 하고 이게 좀 귀찮은 일입니다. 또한 이게 많아지면 코드를 한눈에 보기도 힘들어집니다. 그래서 많은 프로그래머들이 별일 있겠냐 하는 마음에 함수 리턴값 체크를 하지 않습니다. 예를 들어 파일 I/O를 한다고 하면 다음의 코드처럼 리턴 값을 체크하지 않는 경우가 허다합니다.

// strFilePath가 읽어들이고자 하는 파일의 경로를 가리킨다고 하자.

CFile file;

char strHeader[256];

file.Open(strFilePath, CFile::modeRead);

file.Read(strHeader, 255);

이런 코드는 strFilePath가 가리키는 파일이 없거나 파일은 있지만 파일의 크기가 부족한 경우에 에러를 내게 됩니다. 사용된 함수의 리턴 값을 모두 체크하도록 코드를 수정하면 다음과 같습니다.

// strFilePath가 읽어들이고자 하는 파일의 경로를 가리킨다고 하자.

CFile file;

char strHeader[256];

bool bError = FALSE;

// CFile::Open 함수는 오픈이 성공하면 TRUE를 리턴한다.

if (file.Open(strFilePath, CFile::modeRead))

{

// CFile::Read 함수는 읽어들인 바이트수를 리턴한다.

if (file.Read(strHeader, 255) != 255)

bError = TRUE;

}

else

bError = TRUE;

if (bError) // 에러가 있으면

{

CString strError;

strError.Format("에러가 발생했습니다 - %d", GetLastError());

AfxMessageBox(strError);

}

수정된 코드를 수정전의 코드와 비교하면 아마도 조금 더 코드의 흐름을 이해하기 힘들다는 느낌이 들 것입니다. 하지만 대신에 훨씬 더 코드가 안정적으로 동작하게 됩니다. 참고로 수정된 코드를 보면 에러가 발생했을 때 GetLastError 함수를 부르는데 이 함수는 윈도우 운영체제가 제공해주는 함수로 방금 발생한 에러에 해당하는 에러코드를 리턴해주는 역할을 수행합니다. 이 값에 해당하는 설명 문자열을 보고 싶다면 비주얼 C++의 도구(Tools) 메뉴의 오류 조회(Error Lookup) 명령을 실행한 다음에 거기에 에러코드를 입력해보면 됩니다. 다음은 "오류 조회" 다이얼로그의 실행 화면입니다.


< 그림 1. "오류 조회(Error Lookup)" 다이얼로그의 실행 화면 >

가끔 테스트 프로그램을 작성하거나 시간이 없을 경우 방금 코드처럼 리턴 값을 일일이 체크하는 것을 생략하는 사람들을 많이 봤습니다. 코드를 작성하는 당시에는 나중에 제대로 검사하게 고쳐야지 하지만 그럴 만큼 부지런한 사람은 드뭅니다. 수정할 기회가 언제 올지 알 수 없기 때문에 무슨 프로그램을 짜던지 항상 최선을 다하는 것이 좋습니다. 즉 되도록이면 "나중에 고치지"라는 생각은 접어 두시기 바랍니다.

참고 2. try/catch

함수의 리턴값 체크가 번거롭다면 그의 대안으로 사용할 수 있는 것이 바로 C++의 try/catch 문법입니다. 비주얼 C++에서는 MFC 클래스에 대해서만 적용가능하다는 단점을 갖고 있긴 하지만 이를 이용하면 if 문을 이용해 함수의 리턴 값을 검사할 필요가 없기 때문에 코딩도 간단해지고 코드를 읽기도 좀더 쉬워집니다. "3. 함수의 리턴값 체크"에서 본 코드를 try/catch를 이용하도록 변경해보면 다음과 같습니다.

// strFilePath가 읽어들이고자 하는 파일의 경로를 가리킨다고 하자.

CFile file;

char strHeader[256];

try

{

file.Open(strFilePath, CFile::modeRead);

file.Read(strHeader, 255);

}

catch(CException e)

{

e.ReportError();

e.Delete();

}

try로 둘러싸여진 블록 내에서 에러가 발생하면 실행이 바로 catch 블록으로 넘어갑니다. 단 모든 에러가 다 try 블록에 의해 감지되는 것은 아닙니다. 에러가 발생한 경우 그것을 throw 키워드를 이용해 리턴하는 함수들에서만 에러가 감지됩니다. MFC 클래스들은 대부분 에러가 발생하면 CException이란 클래스로부터 계승된 각자의 에러 클래스의 개체를 throw하도록 되어있습니다. CFile 클래스의 경우에는 CFileException이란 클래스가 에러 클래스에 해당하며 CException으로 계승된 클래스입니다. 또 클래스는 아니지만 new로 메모리를 할당하는 경우에도 메모리가 부족하면 예외를 발생시킵니다. 즉 ,new를 이용해서 메모리를 할당하는 경우에는 try/catch를 이용한다면 굳이 메모리 할당이 제대로 되었는지 일일이 검사할 필요가 없다는 이야기입니다.

사실 try/catch/throw는 그 자체만으로 상당한 지면을 통해 설명해야 하기 때문에 여기서는 이런 것이 있다는 것을 알리는 정도로 끝을 맺겠습니다. 참고로 비주얼 베이직이나 자바, C# 등의 대부분의 현대적인 프로그래밍 언어들은 이런 방식의 에러처리를 지원합니다.

>

top

4> 컴파일러 경고 메시지에 신경쓰자

대개의 프로그래머들은 컴파일러가 내주는 에러 메시지에만 신경을 쓰고 경고(Warning) 메시지에는 둔감합니다. 그런데 이 경고 메시지를 눈여겨 보면 간혹 모르고 지나쳤을 버그를 잡는 경우가 있습니다. 조사에 의하면 버그를 발견하는데 드는 시간이 90%이고 이를 수정하는데 걸리는 시간은 10%에 불과하다고 합니다. 경고 메시지를 눈여겨 보면 적은 노력으로 버그를 발견할 수 있는 셈입니다. 다양한 경고 메시지 중에서 네 가지를 살펴보도록 하겠습니다.

 비초기화 변수 사용 경고

그 중의 대표적인 것이 바로 변수를 선언은 했지만 그 변수를 초기화하지 않고 사용하는 경우입니다. 예를 들면 다음과 같은 경우가 있습니다.

{

int nNumber1, nNumber2;

nNumber1 = 100;

nNumber1 += nNumber2;

nNumber2에는 어떤 값이 들어가 있을지 아무도 모릅니다. nNumber2에는 임의의 값이 들어가 있을 수 있고 이는 nNumber1의 값에도 영향을 주게 됩니다. 위의 코드를 컴파일하면 다음과 같은 컴파일러 경고 메시지가 나타납니다.

warning C4700: local variable 'nNumber2' used without having been initialized

컴파일러의 경고 메시지도 유의해서 보는 사람이라면 쉽게 nNumber2가 초기화되지 않고 사용된 사실을 알아차릴 수 있습니다. 사실 이런 종류의 에러는 컴파일러의 경고 메시지가 아니면 찾기가 그리 쉽지 않을 수도 있습니다. 그 이유는 위와 같은 결과가 프로그램의 실행 결과에 큰 영향을 끼치지 않을 수도 있기 때문입니다. 즉 모르고 넘어갈 수도 있다는 것입니다.

 함수 내에 값을 리턴하지 않는 플로우 존재

함수의 코드가 길어지고 그 안의 다양한 부분에서 값을 리턴해야 한다면 실수로 return문을 빼먹을 수가 있습니다. 이 경우에도 앞서 초기화 안 된 변수를 쓸 때와 마찬가지로 그 당시 스택에 있던 아무 값이나 리턴이 되고 또한 마찬가지로 프로그램이 실행되는데 별 지장이 없을 수 있습니다. 그래서 이 역시 좀 찾기 어려운 에러가 될 수 있습니다. 예를 들어 다음과 함수를 보겠습니다.

int ReturnCheckFunction(int i)

{

switch(iValue)

{

case 1: return 0;

case 0: return 1;

}

}

위의 함수를 보면 인자 iValue로 1,0이외의 값이 들어오는 경우 return 문이 존재하지 않습니다. 이 경우 스택에 있던 아무 값이나 리턴됩니다. 위의 코드를 컴파일하면 다음과 같은 경고 메시지가 발생합니다.

warning C4715: 'ReturnCheckFunction' : not all control paths return a value

 논리 판단문안에서의 치환문 사용

가끔들 많이 하는 실수 중의 하나는 if문이나 while 문과 같은 논리 판단문 안에서 == 대신에 실수로 =를 사용하는 것입니다. 이 에러도 경우에 따라서는 아주 찾기 힘든데 컴파일러의 경고 메시지를 눈여겨 보면 아주 쉽게 찾을 수 있습니다. 예를 들어 다음과 같은 코드가 있다고 합시다.

int iRet; iRet = CallSomeFunction(); if (iRet = 0) { ... }

위의 코드에서 if문을 보면 iRet의 값과 0을 비교한다는 것이 잘못 되어서 iRet에 0을 대입하고 있습니다. 이렇게 되면 이 if 문은 항상 거짓이 됩니다. 이 코드를 컴파일하면 다음과 같은 에러가 발생합니다. WARNING 4706: assignment withing conditional expression 반대의 경우로 =를 써서 변수에 어떤 값을 대입하는 경우에 잘못해서 등호 연산자인 ==를 사용하는 경우도 있습니다.

i == 0;

이 것도 컴파일이 됩니다. 단 컴파일러가 다음과 같은 경고 메시지를 내줍니다.

WARNING 4553: '==' : operator has no effect; did you intend '='?

항상 컴파일러가 내주는 경고 메시지에 꼭 신경쓰기 바랍니다. 몇십초 동안 잠깐 살펴보는 것으로 여러분이 몇 시간 혹은 며칠동안 고생하는 것을 방지해줄 수 있습니다.

 

5> BOOLEAN 타입

비주얼 C++에는 두 종류의 Boolean 타입이 존재합니다. 하나는 BOOL이고 다른 하나는 bool입니다. 이 두 타입은 모두 다른 타입으로부터 typedef를 이용해 재정의된 것인데 다음과 같이 정의되어 있습니다.

typedef int BOOL;
typedef byte bool;

이 두 가지 중에서 bool 타입을 항상 사용하기를 권합니다. 이 타입의 장점은 다음과 같습니다.

 크기가 작습니다. 위에서 볼 수 있듯이 BOOL 타입은 크기가 4바이트이고 bool 타입은 크기가 1바이트입니다. 즉, 커다란 배열을 사용할 일이 있으면 bool이 절대적으로 메모리의 낭비를 줄일 수 있습니다.

 안전합니다. BOOL은 사실 TRUE, FALSE이외에도 다른 값이 대입 가능합니다. 예를 들어 다음과 같은 코드를 보고 실행 결과를 예측해보기 바랍니다.

BOOL bRet = 100;

if (bRet == TRUE)

AfxMessageBox("bRet가 참입니다.");

else

AfxMessageBox("bRet가 거짓입니다.");

BOOL 타입은 원래 TRUE와 FALSE 둘 중의 한 값을 갖도록 되어 있지만 불행히도 위와 같이 bRet에 100을 대입하는 코드는 전혀 문제를 일으키지 않습니다. 그리고 FALSE는 0으로 정의되어 있고 TRUE는 -1로 정의되어 있습니다. 따라서 위의 if문에서 bRet의 값(여기서는 100이 됩니다)과 TRUE는 서로 다른 값이 되어버리기 때문에 두 번째 메시지 박스가 실행됩니다. 즉 BOOL 타입의 문제는 TRUE, FALSE 이외의 다른 값이 들어갈 수 있는 가능성이 있기 때문에 if 문을 어떻게 사용하느냐에 따라서 전혀 다른 실행 결과를 낳는다는 것입니다. 만일 위의 코드에서도 if (bRet)와 같이 사용했다면 if 문이 참이 될 것입니다.

반면에 bool 타입(혹은 BOOLEAN)은 true와 false라는 단 두 가지의 값만을 가질 수 있습니다. 만일 0이외의 값을 이 타입의 변수에 대입하여도 이 값은 true라는 값으로 바뀌어서 저장됩니다. 다음과 같은 코드를 보기 바랍니다.

bool bRet = 100;

if (bRet == true)

AfxMessageBox("bRet가 참입니다.");

else

AfxMessageBox("bRet가 거짓입니다.");

위의 if 문은 참이 됩니다. 앞서 설명한 것처럼 bool 타입에서는 0이외의 값은 true로 바뀌어 저장되기 때문입니다. 항상 bool 타입을 사용하기 바랍니다.

 

6> 함수 인자 유효성 체크

이것은 버그를 방지하기 위한 일종의 방법인데 자신이 만드는 함수의 선두 부분에서 주어진 인자가 맞는 범위에 있는지 항상 먼저 체크하는 것입니다. 이 방법은 특히 다른 사람과 사용할 함수를 만들거나 다른 사람이 만든 데이터를 사용하는 함수를 만들 때 아주 효율적인 방법입니다. 즉 자기가 예상하고 있는 데이터들이 들어오는지 사용에 앞서 한번 검사하는 것입니다.

방금 이야기한 것처럼 이 방법은 공동작업에 아주 쓸모가 많습니다. 사람마다 나름대로의 독특한 이해방식을 갖고 있기 때문에 한참을 이야기해서 함수 인자 및 데이터의 형식에 대해 토론을 하고 결론을 내려도 실제로 구현을 해놓고 보면 서로의 이해가 다른 경우가 많습니다. 이로인한 혼란을 막기 위한 방법 중의 하나가 바로 이 방법입니다.

예를 들어 어떤 소팅함수가 있는데 소팅 대상이 되는 것이 0부터 2048 사이의 정수라고 합시다. 그러면 그 함수의 선두 부분에 다음과 같은 검사 코드를 넣어두는 것입니다.

BOOL Sort(int *pnNumbers, int nCount)

{

// 검사 코드를 넣어둡니다.

if (nCount <= 0) // 소팅할 대상이 0보다 작으면 그냥 리턴합니다.

return FALSE;

for(int iIndex = 0;iIndex < nCount;iIndex++)

{

if (pnNumbers[iIndex] < 0||pnNumbers[iIndex] > 2048)

{

printf("%d 번째의 값이 이상합니다. - %d\n", iIndex, pnNumbers[iIndex]);

return FALSE;

}

}

// 실제 소팅 코드가 나온다.

....

}

위와 같은 식으로 코딩을 하면 많은 버그를 잡을 수 있습니다. 그런데 위의 코드를 보면서 속도가 느려지지 않을까 하는 걱정을 하는 사람도 있을 것입니다. 그런 걱정이 든다면 위의 코드를 디버그 모드에서 동작하도록 하면 됩니다. 원래 디버그 모드에서 개발을 하고 디버깅이 끝나고 나면 릴리스 모드로 컴파일해서 사용하는 것이 정석이니까요. 그렇게 할 수 없는 경우도 가끔 있습니다. 디버그 모드에서는 잘 동작하는 프로그램이 릴리스 모드에서는 잘 동작하지 않는 경우가 간혹 있습니다. 다시 본론으로 돌아와서 디버그 모드에서 위의 체크 코드를 활성화시키고 싶다면 #ifdef 조건부 컴파일 지시자와 _DEBUG 상수를 이용하면 됩니다. 이 상수는 디버그 모드로 컴파일되는 경우에 비주얼 C++ 컴파일러에 의해 정의가 됩니다.

bool Sort(int *pnNumbers, int nCount)

{

// 검사 코드를 넣어둡니다.

if (nCount <= 0) // 소팅할 대상이 0보다 작으면 그냥 리턴합니다.

return FALSE;

#ifdef _DEBUG

for(int iIndex = 0;iIndex < nCount;iIndex++)

{

if (pnNumbers[iIndex] < 0||pnNumbers[iIndex] > 2048)

{

printf("%d 번째의 값이 이상합니다. - %d\n", iIndex, pnNumbers[iIndex]);

return FALSE;

}

}

#endif

// 실제 소팅 코드가 나온다.

....

}

#ifdef 다음에 오는 상수가 #define문으로 정의된 것이면 여기서부터 #else 혹은 #endif까지의 코드는 포함되고 결과적으로 컴파일됩니다. 즉 디버그 모드인 경우에만 위의 검사 코드가 포함되는 것입니다. 릴리스 모드에서는 _DEBUG라는 상수가 정의되지 않기 때문에 위의 코드는 포함되지 않습니다. 참고로 #ifndef라는 것도 있습니다. 그 다음에 오는 상수가 정의되어 있지 않으면 참이 되는 것입니다.

다시 한번 말하지만 디버그 모드는 말그대로 개발시에 디버깅을 위한 컴파일 모드입니다. 이 모드에서는 컴파일러가 코드 최적화를 시도하지 않습니다. 그렇기 때문에 덩치도 크고 속도도 릴리스 모드에 비해 20-40% 가량 늦습니다. 따라서 상품화되거나 외부에 서비스를 하는 프로그램이라면 릴리스 모드로 컴파일되는 것이 필수적입니다. 참고로 비주얼 C++ 닷넷에서는 그림 2처럼 빌드(Build) 메뉴의 구성 관리자(Configuration Manager) 명령을 선택하면 이를 선택할 수 있습니다. 비주얼 C++ 6.0에서는 이 컴파일 모드를 결정하는 것은 Build 메뉴의 Set Active Configuration 명령에서 가능합니다.


그림 2. 디버그 모드와 릴리스 모드의 선택

7> 통일된 함수 실행 성공 리턴 값과 시작 인덱스 값

리턴 값이 있는 함수의 경우 성공을 나타내기 위해 사용하는 값을 통일하기 바랍니다. 예를 들어 어떤 함수에서는 성공을 나타내기 위해 0을 사용하고 다른 함수에서는 1을 사용하고 또 다른 함수에서는 -1을 사용한다고 하면 자신이 만든 함수라 해도 항상 코드를 다시 살펴보아야 하는 문제가 발생하며 이로 인해 버그가 발생할 수도 있습니다. 저는 항상 성공을 나타내는 리턴코드로 0을 사용합니다. 성공을 나타내는 함수의 리턴 값을 통일해서 사용하기 바랍니다.

시작 인덱스 값도 마찬가지입니다. 예를 들어 열명의 사람에 대한 정보가 있는데 이름이 주어졌을 때 그 사람에 해당하는 번호를 리턴하는 함수를 만든다고 합시다. 자 그럼 이 번호를 0부터 셀 것인지 아니면 1부터 셀 것인지 혼란이 오는 경우가 있습니다. 저는 이런 경우 항상 0부터 세며 꼭 주석이나 문서에 0부터 센다는 것을 명시합니다. C/C++의 배열 인덱스가 0부터 시작하기 때문에 C/C++에서는 무엇인가 숫자 정보의 시작을 0부터 하는 것이 자연스러운 경우가 많습니다. 자신만의 인덱스 시작값 시작 규칙을 만들어 두기 바랍니다. 만일 여러 사람이 같이 일을 한다면 다같이 통일하면 더욱 좋겠지요.


참고 3. 변수 표기법

프로그래머의 개인 취향에 따라 변수 이름을 만드는 방법은 참 다양합니다. 아무 생각없이 하는 사람도 많긴 합니다만. 윈도우 운영체제에서는 찰스 시모니(Charles Simony)란 사람이 만든 헝가리안 표기법이란 것을 사용합니다. 이런 이름이 붙은 이유는 이 사람이 헝가리 출신이었기 때문이었고 윈도우에서 이 사람의 표기법을 사용하게 된 것은 이 사람이 마이크로소프트의 초창기 멤버로 많은 일을 했기 때문입니다. 이 표기법은 변수 이름 자체에 그 변수의 타입에 대한 정보를 같이 주는 것이지요. 예를 들면 다음과 같습니다.

int nCount; // 정수 변수 앞에는 i나 n을 붙입니다.

DWORD dwCount; // DWORD 타입 앞에는 dw를 붙입니다.

bool bReturn; // Boolean 타입 앞에는 b를 붙입니다.

char *lpstrMessage; // 문자에 대한 포인터앞에는 lpstr 혹은 pstr을 붙입니다.

여기에다가 윈도우에서는 클래스 멤버 변수의 경우, 그 이름 앞에 m_를 붙이는 것이 일반적입니다. 저 같은 경우는 전역 변수의 경우 그 이름 앞에 g_를 붙입니다. 이렇게 하면 좋은 점은 변수를 보는 순간이 이게 어디 정의되었고 타입이 무엇인지를 알 수 있게 된다는 것입니다. 예를 들어 다음과 같은 코드를 보기 바랍니다.

m_nCount = nIndex +g_nMinimum;

위의 코드를 보면 m_nCount는 이 코드가 실행되고 있는 클래스내의 멤버 변수이고 nIndex는 로컬 변수 (흔히 말하는 auto 변수)이고 g_nMinimum은 전역 변수라는 것을 알 수 있습니다. 거기다가 모든 변수가 정수 타입이라는 것도 알 수 있습니다.


결론

지금까지 버그를 방지하거나 버그를 잡기 위한 여러 가지 방법들에 대해 알아보았습니다. 사실 제일 중요한 것은 빠르고 간결하며 정확한 코드를 만들겠다는 마음 자세가 아닌가 싶습니다. 이러한 자세가 바탕이 된다면 위의 방법들이 습관으로 만드는 것이 그리 어렵지 않을 것입니다. 사실 방법 하나하나는 대단한 것이 없습니다. 하지만 이것들이 합쳐지면 시너지 효과가 대단합니다. 이것들을 습관으로 만드는 것이 바로 버그를 없애는 프로그래밍의 시작이며 프로그래밍을 처음 배우는 단계에서 이를 습관으로 만들 수 있다면 여러분은 아주 뛰어난 프로그래머가 될 수 있을 것입니다.

참고문헌

  1. Steve Maguire, Writing Solid Code : Microsoft's Techniques for Developing Bug-Free C Programs, Microsoft Press, 1993
  2. Brian W. Kernighan and Rob Pike, The Practice of Programming, Addison-Wesley, 1999
  3. Steve Maguire, Debugging the Development Process : Practical Strategies for Staying Focused, Hitting Ship Dates, and Building Solid Teams, Microsoft Press, 1994
  4. Steve C McConnell, Code Complete : A Practical Handbook of Software Construction, Microsoft Press, 1993
Posted by keegan
Posted by SB패밀리

 방법은 아래 레지스트리에

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Main\ 이곳에
TabProcGrowth값을 DWORD값으로 생성하고 값은 0으로 해주면 됨.

LCIE라는 기술때문에, 디버깅이 안된다

출처 : http://aplus.pe.kr/zbxe/?mid=BoardDevWeb&document_srl=18368

Posted by SB패밀리