한빛출판네트워크

IT/모바일

비주얼 스튜디오 매크로를 이용한 리팩토링

한빛미디어

|

2005-09-22

by HANBIT

저자: James Avery / 한동훈 역
원문: http://www.ondotnet.com/pub/a/dotnet/2005/06/20/macrorefactor.html

리팩토링은 응용 프로그램의 외적인 기능을 수정하거나 손상시키지 않고 코드를 개선시키는 방법이다. 리팩토링은 XP 프로그래밍의 핵심 지침인 동시에 테스트 주도 개발(TDD, Test-Driven Development)과 맞물려 사용되기 때문에 지속적으로 인기를 얻고 있다. 리팩토링에서는 개발자가 사소한 변경(리팩토링)을 끊임없이 하는 것이다. 이러한 변화들은 사소한 변경이기 때문에 재빨리 테스트할 수 있고, 위험 정도가 낮지만 전체적으로 보면 결국 이러한 변화들이 개발자의 코드나 응용 프로그램의 전체 품질을 향상시킨다. 리팩토링에 대한 다양한 목록과 보다 자세한 정보를 얻으려면 www.refactoring.com을 방문하기 바란다.

IDE 개발사나 서드 파티 애드인 회사들은 IDE에 리팩토링에 대한 지원을 추가하기 시작했다. 비주얼 스튜디오 2005는 다양한 리팩토링 기능을 포함하고 있다. C# 기능은 IDE 안에 내장되었으며, VB.NET 기능은 무료 애드인 형태로 제공된다. 새로운 애드인과 기능들은 대중적으로 잘 알려진 리팩토링을 포함하고 있으며, 포함되지 않은 것도 있다. 뿐만 아니라 범용적인 도구로 사용하기에는 지나치게 작은 범위만을 다루고 있는 것들도 있다.

그래서 이런 부족한 부분을 매꿀 수 있는 방법이 매크로를 이용하는 것이다. 마이크로소프트나 서드 파티 회사들이 제공하지 않는 리팩토링을 응용 프로그램에서 제공하기 위해 매크로를 사용할 수 있다. 손으로 코드를 직접 수정하는 대신에 매크로를 작성해서 이러한 리팩토링을 보다 쉽고 빠르게 할 수 있다.

이 글에서는 리팩토링을 적용하기 위해 두 가지 예를 살펴볼 것이다. 먼저 문제를 정의하고, 문제에 대해 사용할 리팩토링을 정하고, 리팩토링를 보다 쉽게 완료할 수 있는 매크로를 작성해볼 것이다.

범용 리팩토링(General Purpose Refactoring)

먼저 매직 넘버를 상수로 바꾸는 범용 리팩토링 매크로를 작성하는 것으로 시작해보자. 매직 넘버는 응용 프로그램안에 직접적으로 쓰여진 숫자를 말한다. 예를 들어, 영상 등급과 같이 연령 제한이 있는 경우에 응용 프로그램안에 직접 쓴 숫자를 의미하며, 이와 같은 코드를 변경하기 얼마나 어려운지를 빗대어 "돌에 새겨넣기(set in stone)"라고 한다. 다음은 이러한 매직 넘버를 사용한 예제 코드다.

public bool CheckAge(Person person)
{
  if(person.Age > 21)
  {
    return true;
  }
  else
  {
    return false;
  }
}

이 코드에서는 사람의 나이를 매직 넘버 21과 비교하고 있다. 여기서는 리팩토링을 통해 매직 넘버를 대신할 상수를 만들고, 매직 넘버가 쓰인 곳을 상수로 대체할 것이다. 매크로를 작성하고 나면 대체할 매직 넘버를 선택하고 매크로를 실행하는 것 만으로 같은 클래스내에 숫자를 상수로 옮기고, 매직 넘버를 상수로 대체할 수 있다.

매크로를 만들기 위해 비주얼 스튜디오 2003에서 [Tools] -> [Macro] -> [Macro IDE] 메뉴를 선택한다. 매크로 IDE는 매크로를 위해 특별히 내장된 개발 환경으로 이를 이용해서 매크로를 빠르게
만들고 사용할 수 있다.

매크로 IDE에서 새 모듈을 생성하고, 모듈에 새로운 Sub(함수)를 추가한다.

역주: VS.NET부터는 매크로 언어로 VBA(Visual Basic for Application) 대신에 VB.NET 을 사용한다.

Imports EnvDTE
Imports System.Diagnostics

Public Module Module1

  Public Sub ExtractToConstant()

  End Sub

End Module

ExtractToConstant() 매크로를 만들었으면 [Macro Explorer]를 사용해서 매크로를 실행할 수 있다. 물론, 아직 아무 코드도 작성하지 않았으므로 아무것도 하지 않는다. 이제 실제로 클래스내에 새로운 상수를 추가하고 선택된 숫자를 상수로 바꾸는 코드를 추가해보자. 이 작업을 위해 TextSelection 객체를 사용할 것이다. 이 객체를 사용하면 VS.NET에서 현재 선택된 텍스트를 얻어올 수 있고, 해당 문서에 대한 변경사항을 적용할 수 있다. 먼저, 현재 TextSelection 객체에 대한 참조를 얻고, 참조를 통해 선택된 텍스트를 얻어와보자

"선택된 텍스트를 가져온다
Dim sel As TextSelection

sel = DTE.ActiveDocument.Selection

Dim selectedText As String

selectedText = sel.Text

이제 TextSelection 객체와 선택된 텍스트를 포함한 문자열을 얻었다. 이제, 사용자에게 새로운 상수 이름을 무엇으로 정할지 물어보는 대화상자를 띄워야한다. 이는 InputBox 메서드를 사용해서 처리할 수 있다.

"상수에 사용할 이름을 질문한다
Dim constName As String = InputBox("Enter a name for the constant")

"선택된 텍스트를 상수 이름으로 대체한다
sel.Text = constName

사용자로부터 새로운 상수 이름을 입력받았으면 선택된 텍스트를 이걸로 대체해야 한다. 매직 넘버를 대체하고 나면 상수를 현재 클래스 앞에 추가해주어야한다. 이렇게 하기 위해서는 텍스트를 일일이 탐색하면서 클래스가 시작하는 장소를 찾아야한다. 클래스 시작 위치를 찾는 가장 쉬운 방법은 첫번째 여는 중괄호 "{"를 찾고, 다음 여는 중괄호를 찾는 것이다. 이 위치가 우리가 다루는 코드에서는 클래스가 시작하는 위치가 된다.

역주: VS.NET에서 자동 생성되는 클래스 코드는 네임스페이스, 클래스 순서이기 때문에 첫번째 여는 중괄호는 네임 스페이스 영역의 시작이며, 두번째 중괄호는 클래스 영역의 시작을 의미한다.

"문서의 맨 앞으로 이동한다
sel.StartOfDocument()
sel.FindText("{")
sel.FindText("{")
sel.LineDown()
sel.NewLine()
sel.LineUp()
sel.Indent()

위 코드에서는 문서의 맨 앞으로 이동한 다음에 첫번째 여는 중괄호 "{"를 찾고, 두번째 중괄호를 찾고, 한 줄 아래도 이동한 다음에 새 라인을 삽입하고, 다시 위로 한 줄 이동해서 새 라인에 이동해서 들여쓰기를 한다.

이제, 상수를 추가할 정확한 위치에 도달했으면 텍스트를 생성하고, 문서에 추가한다.

sel.Text = "public const int " & constName & " = " & selectedText & ";"

이 코드는 파일에 새로운 상수 정의를 추가한다. 이 코드는 C# 버전의 매크로이지만 쉽게 VB.NET 버전의 매크로를 만들 수도 있다. 이제 숫자를 선택하고 이 매크로를 실행하면 매직 넘버를 상수 이름으로 대체하고, 매직 넘버 값을 가지는 상수를 클래스 앞에 선언해준다. 이것은 간단한 매크로이지만, 리팩토링 과정을 돕는데 매크로를 이용하는 것도 상당히 좋다는 것을 알 수 있다. 상당히 짧은 시간안에 만든 매크로이지만, 꽤 자주 이용하는 기본적인 매크로이며, 이를 사용할 때마다 약간의 시간을 절약할 수 있을 것이다.

응용프로그램을 위한 리팩토링(Application Specific Refactoring)

보다 복잡한 응용프로그램을 위한 자동화를 살펴보자. 다음 메서드를 살펴보자.

public Person GetPerson(int personID, SqlConnection sqlConn)
{
  //Do some work here
}

흔히 볼 수 있는 객체를 반환하는 메서드로 데이터 액세스 레이어에서 쉽게 볼 수 있는 종류의 메서드이다. 매개변수로 SqlConnection을 전달하는 것을 알 수 있다. 개발 과정중에 메서드에 인자로 SqlConnection을 전달할 필요가 없으며, 메서드 안에서 SqlConnection을 가져오기로 결정했다고 하자. 이런 결정에는 다양한 이유가 있을 것이다 - 예를 들어, 다른 데이터베이스도 지원하기로 했다거나 다른 데이터베이스를 지원하기로 했을 때 데이터 액세스 레이어만 변경하기를 원한다거나 데이터 액세스 레이어에서 연결을 결정하는 아키텍쳐가 더 좋다고 결정할 수도 있다.

이런 변경사항의 이유가 무엇이든 간에 이런 작업은 많은 시간을 잡아먹는 작업임에는 틀림없다. Find & Replace 정책만으로는 이런 작업을 쉽게 할 수 없으며, 이런 작업은 기본적으로 많은 삭제와 붙여넣기 작업을 필요로 한다. 먼저, 우리가 원하는 변경사항을 살펴보자.

public Person GetPerson(int personID)
{
  SqlConnection sqlConn = GetSqlConnection();

  //Do some work here
}

여기서 원하는 것은 SqlConnection 매개변수를 제거하고, 같은 선언을 메서드 안으로 집어넣고, SqlConnection을 얻기 위해 응용 프로그램에서 사용하는 메서드를 호출하는 것이다. 이러한 종류의 매크로는 프로젝트 안에서 정의되고, 하나의 작업만을 위해 정의되는 것이 보통이지만, 이런 작업은 매크로를 이용하는 것이 사람이 직접 하는 것보다 훨씬 빠르고 쉽다.

첫번째 매크로 예제에서와 같이 선택된 텍스트를 가져와야한다. 매개변수와 변수 이름이 동일하기 때문에 선택된 텍스트를 선언으로 사용하고, 메서드의 다른 부분은 변경하지 않을 것이다.

"선택된 텍스트를 가져온다
Dim sel As TextSelection

sel = DTE.ActiveDocument.Selection

Dim selectedText As String

selectedText = sel.Text

매개변수 목록에서 변수를 정리해야한다. 여기서는 매개변수가 항상 매개변수 목록에서 두번째나 그 이후에 위치한다고 가정한다. 즉, 매개변수 앞에 여백과 콤마(,)를 삭제해야 한다고 가정한다. 즉, 현재 선택된 텍스트를 삭제하고, 텍스트의 왼쪽에 있는 문자들을 삭제한다고 가정한다.

"선택된 텍스트를 제거하고, 텍스트 앞에 위치한 여백과 콤마를 삭제한다.
sel.Delete()
sel.CharLeft()
sel.Delete()
sel.CharLeft()
sel.Delete()

다음으로 메서드 앞에 메서드를 호출하는 새로운 선언을 추가하기 위한 공간을 만든다.

sel.FindText("{")
sel.LineDown()
sel.NewLine()
sel.LineUp()
sel.Indent()
sel.Text = selectedText + " = GetSqlConnection();"

이 코드는 첫번째 매크로와 다소 비슷하다. 먼저 여는 중괄호 "{"를 찾고 새로운 라인을 만들고 들여쓰기를 한다. 원래 선언과 새로운 메서드를 추가한다.

사용자가 매개변수를 선택하고 매크로를 실행하면 매개변수는 메서드 앞에 새로운 메서드를 호출하는 같은 이름을 가진 변수로 대체된다. 모든 매개변수가 일관되게 선언되어 있는 경우에는 FindText 메서드를 사용해서 이 리팩토링을 보다 정교하게 자동화할 수 있다. 매개변수를 선택하는 대신에 활성 문서 전체를 검색해서 일치하는 매개변수를 검색할 때마다 이러한 리팩토링을 자동으로 적용할 수 있다.

Public Sub DemoteSQLConnectionForDoc()
"선택된 텍스트를 가져온다

Dim sel As TextSelection
sel = DTE.ActiveDocument.Selection
sel.StartOfDocument()
sel.FindText("SqlConnection sqlConn")

이 코드에서는 TextSelection 객체를 생성하고, 문서 시작 위치로 이동한 후에 FindText 메서드를 사용해서 매개변수 이름과 일치하는 부분을 검색한다.

  While (sel.Text <> "")
    Dim selectedText As String

    selectedText = sel.Text

    "선택된 텍스트를 삭제하고, 텍스트 앞의 여백과 콤마를 삭제한다
    sel.Delete()
    sel.CharLeft()
    sel.Delete()
    sel.CharLeft()
    sel.Delete()

    sel.FindText("{")
    sel.LineDown()
    sel.NewLine()
    sel.LineUp()
    sel.Indent()
    sel.Text = selectedText + " = GetSqlConnection();"
    sel.FindText("SqlConnection sqlConn")

  End While
End Sub

일치하는 부분이 있는지 알기 위해 while 루프를 사용한다. 일치하는 부분이 있으면 리팩토링을 수행하고, 문서내에 리팩토링을 수행할 다른 메서드가 있는지 알아보기 위해 다시 FindText()를 호출한다.

매크로와 리팩토링은 상호 보완하는 기술이다. 매크로를 사용하면 복잡한 응용 프로그램을 리팩토링하는 동안에 발생하는 반복적인 작업을 자동화할 수 있다. 이를 이용하면 리팩토링를 보다 쉽게 할 수 있고, 보다 적은 시간을 들일 수 있으며, 최종적으로 보다 나은 코드를 만들어 낼 수 있게 된다.
TAG :
댓글 입력