애들센스


(업데이트) VBA에서 본 이상한 코드들 오피스/VBA/Office.JS


VBA구문에 충실하게 FM대로 코딩하다가, 다른 사람들이 만든 코드를 보면 초보 입장에서 '이런 코드가 가능해?'하고 의문점을 가질 수 있는 걸 종종 보게 된다. VB/VBA는 c/c++, python, javascript 처럼 다양한 코드 스타일을 만들어 볼 수 있는 유연성이 떨어져 코딩하는 재미가 없는 편이다. 그러나 초보시절부터 현재까지 종종 생각하지 못한 코드를 보았는데, 오늘은 그걸 한번 정리해보려고 한다. 일단 생각나는 대로 적어 보자면:
(이 포스팅을 계속 업데이트 하려는 데, 최근 업데이트 내용을 선두에 두려고 한다. 예전 거는 그냥 두고...)

11. Join으로 배열의 값 한 번에 출력하기(2)(2019-07-01)
(앞서 팁10과 같은 연장선상의 내용이라 추가할까 하다가 아무래도 묻힐 것 같아 새로 번호를 붙입니다)
Join() 함수를 이용하여 배열을 내용을 여러 행으로 나누어 출력할 수 있다. 보통 For루프를 사용하여 출력을 하였다.
    arr = Array("문재인", "트럼프", "김정은", "남북미 정상회담", "토착왜구", "나베")
Dim i As Long

For i = LBound(arr) To UBound(arr)
Debug.Print arr(i)
Next
그러나 Join함수와 vbCrLf(캐리지-리턴,줄바꿈, 상수)를 사용하여 루프없이 출력할 수 있다.
arr = Array("문재인", "트럼프", "김정은", "남북미 정상회담", "토착왜구", "나베")
Debug.Print Join(arr, vbCrLf)


10. Join으로 배열의 값 한 번에 출력하기(1)(2019-05-26)
Join(배열, 구분자)

Join()함수는 주어진 구분자를 이용하여 문자들을 연결하는 함수이다. 가령 m이라는 배열을 다음과 같이 만들었다.
m = Array(1, 2, 3, 4)

이때 이를 출력하려면
Debug.Print m(0); m(1); m(2); m(3)

와 같이 할 것이다. 그러나 Join()함수를 다음과 같이 사용할 수 있다.
Debug.Print Join(m, ", ")

1. 고디바초코렛 사서 하나만 빼먹고 버림?
    Split("123,456,789", ",")(0)
위의 코드는 "123,456,789" 라는 문자열을 컴마(,)로 분리하는 split()함수 사용 예이다. 함수뒤에 (0)가 붙는 데, 분리한 결과의 첫 번째 항목을 가져오라는 의미이다. 보통은 그 결과를 변수로 받고 변수에서 인덱싱을 하여 결과를 가져오지만 이과정을 생략한 것이다. split()함수와 같이 배열을 돌려주는 함수라면 (0)와 같은 참조를 할 수 있다.
    Dim str As String
Dim splited

str = "123,456,789"

Debug.Print Split(str, ",")(0)

2. Dim은 개무시?
    ReDim arr(10) As Long
ReDim()은 크기가 정해져 있지 않는 배열을 Dim으로 선언한 후 크기가 정해진후 사용하는 것이 보통이다. 하지만 그럴 필요없이 배열을 ReDim()으로 크기와 데이터형을 최초 선언할 수 있다.

3. 산 채로 잡아 꿀꺽?
    With Worksheets.Add
.Name = "Sheet2"
End With
또는
   Worksheets.Add.Name = "Sheet2"
개체를 추가하는 경우 With~End With구문을 이용하여 새로 추가한 개체에 대한 조작을 할 수 있다. Worksheets.Add의 경우 워크시트를 하나 추가하는 구문으로 보통 Set sht =Worksheets.Add와 같이 추가한 개체를 개체변수 에 할당하여 사용한다. 그러나 이 경우 추가와 동시에 변수에 할당하지 않고 바로 name속성을 부여한다. Worksheets.Add를 통해 만들어진 결과는 메모리에 있으며 With를 사용하여 할당된 메모리를 잡아둔 채 개체의 속성을 수정하고 있는 것이다.
1)번 예도 알고 보면 이런 식의 메커니즘이 작동하는 거으로 생각된다.

4. 여러 개를 한 방에 되돌려줌
    Function returnArray()
returnArray = Array(123, 456, 789)
End Function
Function프로시저는 보통 값을 하나만 돌려주는 걸로 알지만 배열을 돌려줄 수 있다. 함수 선언이 Function returnArray() As Long()같이 하여 이 함수가 배열을 돌려주는 걸로 미리 말할 수 있다. 그러나 그럴 필요없이 Array()함수를 사용하여 여러 종류의 값을 여러 개 돌려줄 수 있다.

5. 일단 두루와~
    Dim blnA As Boolean
Dim blnB As Boolean

Select Case True
Case blnA = True
Debug.Print "blnA is True"
Case blnB = False
Debug.Print "blnB is False"
Case Else
Debug.Print "blnA and blnB is not True and not False"
End Select
Select Case 문은 하나의 변수를 상대로 하여 여러 가지 조건식을 검사하고 분기하는 명령이지만 Select Case True은 이상해 보인다. 무한루프를 돌리는 느낌처럼 변수의 자리에 'True'라는 불린(Boolean) 리터럴이 떡 하니 자리잡고 있다. 사실 중요한 판단분기 부분은 Case 부분이다. 보통 사용하는 Select Case가 하나의 변수만을 대상으로 조건판단을 하지만 이 경우 blnA 를 먼저 검사하고 이것이 통과되지 못하면 blnB를 다음 번에 검사하는 셈이 된다. 하나의 Select Case를 가지고 우선순위를 두고 여러 개의 변수를 차례로 검사하는 경우이다.

6. 어서 와~ 이런 셀 참조는 처음이지...
    Debug.Print WorksheetFunction.Sum([A2:A8])

Dim rng As Range

For Each rng In [A2:A8]
Debug.Print rng.Address
Next
VBA가 엑셀의 셀 범위를 참조하는 방식은 보통 Cells(행, 열), Range("셀 주소")와 같은 방식으로 하지만, []를 사용하여 현재 활성화된 시트의 셀 주소를 참조할 수 있다. 이것은 과거 VBA이전 매크로 시절의 유산(legacy)이라고 하는데, 아직도 잘 작동하고 있다. 그러면 궁금한 게 하나 더 생긴다. 셀 영역에 이름을 부여하고, 그 이름을 사용할 수 있지 않을 까?
가령 A2:A8X라고 이름을 만들면, 다음의 코드는 에러 없이 작동할 까?
    For Each rng In [X]
Debug.Print rng.Address
Next
물론 마찬가지로 작동한다. 하지만 상당히 신기해보인다.

7. '='는 '='가 아니다
    bln = A = B
하나 건너 '='가 나오는 위의 식은 간단하지만 생소했다. VB에서 '='는 할당 연산자이자 비교연산자이다. A = B는 변수 A와 변수 B가 같은 지 아닌지 를 묻는 비교연산이다. 이 결과는 bln에 할당하면서 'bln ='를 통해 할당연산자 '='를 사용한다.

8. Mid()야!, 네가 왜 거기에 있는 거야?
Mid()함수는 문자열의 중간에 있는 부분 문자열을 주어진 갯수만큼 돌려주는 함수이다. 얘도 함수이므로 당연히 '='를 사이에 두고 왼쪽에 Mid() 함수가 자리를 잡는다. 그런데 아래와 같이 이넘이 왼쪽으로 가고 그 자리엔 문자열이 떡하니 서 있다. 이게 무슨 짓인가?
    str = "The dog jumps"

Mid(str, 5, 3) = "fox"

Debug.Print str
위의 코드는 문자열 "The dog jumps"의 5번째 위치에 3개의 문자를 "fox"로 바꾸라는 의미이다. 문자열을 바꾸는 Replace()함수가 있지만 Mid()함수도 문자열을 바꿀 수 있다. 이렇게 바꾸면 "The fox jumps"로 문자열이 바뀌게 된다. 문자열의 갯수를 지정하는 length 매개변수는 그리 중요하지 않다. 즉
Mid(str, 5, 0) = "fox" 또는 Mid(str, 5, 9) = "fox"로 해도 Mid(str, 5, 3) = "fox"와 동일한 결과를 돌려준다. 바꿀 문자열("fox")의 갯수(3)가 더 중요하다.
다음의 경우는 어떠할 까?
    str = "New York"

Mid(str, 5, 4) = "Mexico"

Debug.Print str
그러면 Mid()의 절친인 Left()와 Right(), 얘네들도 가능할 까? 궁금해요?
(언제적 아재개그를 아직도 ㅠㅠ..)


9. 여러 개의 함수호출을 한 줄에 쓰기
함수는 값을 돌려주는 프로시저인데, 경우에 따라 결과값이 전혀 안궁금한 경우도 있다. 가령 Range 개체의 Replace()함수는 True/False를 돌려주는 데, 어떤 경우 셀값 치환(치질 아님)이 있으면 좋고 없어도 상관없다. 가령 주식포지션을 표시하는 데, 'Long'은 'L'로, 'Short'은 'S'로 간략하게 적는 게 깔끔하다. 이를 위해서는 Replace() 함수를 두 번 호출해야 한다.
    With rngPosition    
Debug.Print .Replace("Long", "L"), .Replace("Short", "S")
End With
Debug.Print가 결과값을 받을 변수 역할을 하는 데, Debug.Print는 여러 개의 변수를 사용할 수 있으므로 Replace()함수를 여러 번 사용하는 게 가능하게 되는 것이다.

덧글

  • VBA초보자 2018/02/01 19:17 # 삭제 답글

    글 잘 보았습니다.
    재밌네요!
  • 우왕 2019/03/15 10:26 # 삭제 답글

    3.번의 경우는 with를 쓸 필요도 없이
    worksheets.add().name ="시트명" 으로도 쓸수 있네요 ㅎ
  • 타임버드 2019/03/15 11:25 #

    맞아요. 더 간단해졌네요
    우왕님의 팁도 추가할게요 그리고 이상한 넘 하나 추가합니다
  • 우왕 2019/03/15 13:21 # 삭제 답글

    Sheets.add.Parent.Sheets.Add.Parent.Sheets.Add....
    ㅎㅎㅎ 제이쿼리 같아요
  • 타임버드 2019/03/15 13:47 #

    스크립팅 언어로 파이썬 또는 자바스크립트를 도입할거란 소문도 있어요. 이미 vsto개발에서는 이미 쓰이고 있죠. 하지만 vsto는 망한 분위기라...
  • 우왕 2019/03/26 13:20 # 삭제 답글

    여기 글은 재미있어서 자주 들어와 보게 되는데, 이번에 함수 짜면서 알게 된게 Function의 리턴값으로 Array뿐만 아니라 Collection이나 Object도 돌려줄수 있던걸 알게 되었네요.
  • 우왕 2019/06/25 17:21 # 삭제 답글

    CreateObject("Scripting.FileSystemObject").getDrive("C:").DriveType 이런식으로 인스턴스용 개체 선언 없이 그냥 갖다 쓰는방법이 있었네요
  • 타임버드 2019/07/01 06:33 #

    원리를 알면 다른 개체도 마찬가지이죠. ㅎㅎ
댓글 입력 영역