• 최초 작성일: 2023-01-06
  • 최종 수정일: 2023-01-06
  • 조회수: 3,180 회
  • 작성자: 권현욱 (엑셀러)
  • 강의 제목: VB0260 ㅡ 최장 연승 기록을 구해 주는 함수 만들기

엑셀러 권현욱

들어가기 전에 ㅡ 계속하기를 계속하라

25년 넘게 조직 생활을 하는 동안 "올해는 호황이었다. 내년 경기는 더 좋을 것이다"라고 말하는 것을 본 기억은 한 번도 없다.

불황은 그 정의 상 미리 알 수 없다. 전미경제연구소(NBER)에 따르면 "전년 같은 기간과 비교했을 때 GDP가 2분기 연속으로 하락해야 불황"으로 정의하고 있기 때문이다. 지금이 불황인지 아닌지 그 시점에서는 알 수 없고, 6개월이 지나고 나서야 비로소 '그 때가 불황'이었음을 후행적으로 판단할 수 있다는 뜻이다.​

로딩 중...

(이미지 출처: Unsplash.com)

예전부터 '엄살경영'으로 유명한 두 기업이 있다. 토요타와 삼성이다. 분야는 다르지만 글로벌 기업이자 자국을 대표하는 기업이다. 두 기업 모두 '위기경영'(전문 용어로 '엄살경영'이라고도 한다^^;)의 대가들이다. 토요타는 '끊임 없는 위기감 고취'가 경영전략 중 하나라 할 만큼 상시적으로 비상경영, 위기경영을 말했다. 토요타 경영진들은 사상 최대 실적을 거두어도 만족하지 않았다. 위기의식을 지속적으로 주입함으로써 현실 안주를 경계했다.

고 이건희 회장은 위기라는 말을 입에 달고 살았다. 2위 사업 영역에 대해서는 "1등만 살아남는 세상이라서 위기"라 했다. 1위 사업장에 가서는 "모든 기업들이 삼성을 노리고 있어서 위기"라 했다. 잘 되고 있으면 "자만심에 빠져 위기"라 했고, 문제가 생기면 "이대로 가면 망하기 때문에 위기"라고 했다. 그에게는 일상이 곧 위기였다.

작년 이맘 때와 지금의 분위기는 거의 정반대라 할 만큼 다르다. 작년 1월에는 코스피지수가 3000을 목전에 두고 있었고, 위드코로나 기대로 경기 전망도 나쁘지 않았다. 지금은 뭐 하나 좋은 게 없다. 호재는 질식해 버렸고 악재는 자기 강화의 과정을 거쳐 확대재생산 되고 있다. 경제 주체들은 비관 일색이다.

하지만 우리는 안다. 인간은 앞 일을 예측하는 데는 그리 소질이 없음을. 재작년에 했던 작년 전망이 보기 좋게 빗나갔듯, 올해 전망에 대해서도 그러하기를 기대해 본다. 거시 지표, 외부 변수를 보면서 '예측'하려 하기보다는 내가 할 수 있는 걸 하면서 '대응'에 주력하련다. 그런 의미에서 올 한해 나의 화두는 이렇게 잡아보았다.

"계속하기를 계속하라."



완성 예

지난 강의에서 어느 테니스 클럽의 회원별 승패 현황에서 연속 승리 기록을 구하는 코딩을 해보았습니다.

가독성을 높이기 위해 시각적 효과를 가미했었는데, 그렇게까지 할 필요는 없고 연승 기록만 필요하다면 사용자 정의 함수User-defined Function(줄여서 UDF)를 만들면 됩니다. 길고 복잡한 엑셀 수식(mega-formula)을 만들어 해결할 수도 있겠지만 VBA로 코딩 하는 것이 훨씬 효율적이고 편리합니다.

로딩 중...

완성 예 1

사용자 정의 함수를 이용하면 코드가 조금 더 간단해질 수 있고 다양한 조건으로 결과값을 구할 수 있습니다. 예를 들어 6회차 이후의 승부 결과에 대해서만 연승 기록을 구하고자 한다면 함수 인수를 지정할 때 범위만 살짝 바꿔주면 됩니다.

로딩 중...

완성 예 2

전체 코드 살펴보기

워크시트에서 ALT + F11 키를 눌러서 Visual Basic Editor 창을 엽니다. VB Editor에서 [삽입] - [모듈] 메뉴를 선택하여 모듈Module을 삽입하고 코드를 작성합니다. 자세한 설명은 이어서 나오니까 전체 구조만 일별하고 진도를 계속 나갑니다. 그동안 주로 사용하던 Sub 프로시저가 아니라 Function 프로시저라는 것만 눈여겨 봐 두세요.

Function winRecord(rRow As Range) As Integer
  Dim rAreas As Range
  Dim rArea As Range
  Dim rA As Range
  Dim rWin As Range
  Dim rX As Range
  
  On Error Resume Next
  
  For Each rX In rRow.Cells
      If rX = "O" Then
          If rArea Is Nothing Then
              Set rArea = rX
          Else
              Set rArea = rArea.Resize(, rArea.Cells.Count + 1)
          End If
      Else
KKK:
          If rAreas Is Nothing Then
              Set rAreas = rArea
          Else
              Set rAreas = Union(rAreas, rArea)
          End If
          Set rArea = Nothing
      End If
  Next
  If Not rArea Is Nothing Then GoTo KKK
  
  For Each rA In rAreas.Areas
      If rWin Is Nothing Then
          Set rWin = rA
      Else
          If rWin.Cells.Count < rA.Cells.Count Then Set rWin = rA
      End If
  Next

  winRecord = rWin.Cells.Count
End Function

CODE

코드 해설

코드는 지난 강의와 크게 다르지 않습니다. 반복할 횟수가 줄어들므로 오히려 더 간단합니다. 사용자 정의 함수는 Function 함수이름(인수명) 반환 타입' 형태로 작성합니다. 뭔가 심오한 것 같지만 하나도 어려울 것이 없으니 계속 진도를 나가세요.

1. Function이라는 키워드로 시작합니다. 워크시트에서 함수를 사용할 때 '=함수명(인수)' 형태로 사용하죠? 따라서 여기서도 rRow라는 인수를 하나 사용했습니다. 워크시트 범위에 접근하므로 인수 타입은 Range로 지정합니다.

Function winRecord(rRow As Range) As Integer    
  Dim rAreas As Range         ''' 승리(O)한 영역에 접근할 변수
  Dim rArea As Range          ''' 승리한 영역에 개별적으로 접근할 변수
  Dim rA As Range             ''' 승리한 영역에 접근할 순환 변수
  Dim rWin As Range           ''' 최장 연승 영역에 접근할 변수
  Dim rX As Range             ''' 개별 셀에 접근할 변수

해설

2. 지난 강의에서 만들었던 succcessiveWin 프로시저는 전체 테이블에 대해 한꺼번에 처리했으므로 중첩 For Each 문을 사용했습니다만 이번에는 한 행씩 함수를 사용해 처리하므로 구조적으로 더 간단합니다.

  On Error Resume Next       ''' 실행 도중 오류가 발생하더라도 다음 명령 수행
    
  For Each rX In rRow.Cells   ''' 인수로 넘겨 받은 영역의 각 셀에 접근
      If rX = "O" Then        ''' 셀 값이 O(승리)라면...

          ''' rArea 변수에 아무 것도 할당되어 있지 않다면 rX를,
          ''' 이미 어떤 영역이 할당되어 있다면 다음 셀(오른쪽 셀)까지로 범위 확장
          If rArea Is Nothing Then
              Set rArea = rX
          Else
              Set rArea = rArea.Resize(, rArea.Cells.Count + 1)
          End If
      Else
  KKK:
          ''' rAreas 변수에 아무 것도 할당되어 있지 않다면 rArea 변수 값을,
          ''' 이미 어떤 영역이 할당되어 있다면 기존 rAreas 영역과 rArea 영역 통합
          If rAreas Is Nothing Then
              Set rAreas = rArea
          Else
              Set rAreas = Union(rAreas, rArea)
          End If
          Set rArea = Nothing                ''' rArea 변수 초기화
      End If
  Next
  If Not rArea Is Nothing Then GoTo KKK      ''' 마지막 rArea 영역을 처리하기 위해 KKK 레이블로 이동

  For Each rA In rAreas.Areas                ''' rAreas의 각 영역(Area)에 접근
      ''' rWin 변수에 아무 것도 할당되어 있지 않다면 rA 변수 값을,
      ''' 이미 어떤 영역이 할당되어 있다면 rWin과 rA 영역의 크기를 비교하여 큰 영역을 rWin 변수에 할당            
      If rWin Is Nothing Then
          Set rWin = rA
      Else
          If rWin.Cells.Count < rA.Cells.Count Then Set rWin = rA
      End If
  Next

해설

3. 마지막으로, 작업을 수행한 결과를 돌려줍니다. 어떤 작업을 한 다음 결과를 돌려준다는 것, 이것이 Sub 프로시저와 가장 크게 다른 점입니다. 사용자 정의 함수를 실행한 결과를 돌려줄 때리턴(Return)에는 '함수명 = 리턴 값' 형태로 사용한다는 점에 유의하세요. Function 다음에 사용한 함수명과 리턴할 때 사용하는 함수명은 반드시 같아야 합니다(여기서는 winRecord).

  winRecord = rWin.Cells.Count     ''' 연속 승리한 영역의 셀 수를 카운팅하여 리턴
End Function

해설

함수 설명 추가하기

사용자 정의 함수도 일반 워크시트 함수와 마찬가지로 [함수 마법사] 대화상자에 간단한 설명이 나타나도록 할 수 있습니다.

로딩 중...
  1. 워크시트 상태에서 [보기] 탭 - [매크로] 그룹 - [매크로] 명령을 선택합니다.
  2. [매크로 이름] 항목에 해당 함수(여기서는 winRecord) 이름을 입력합니다.
  3. 로딩 중...
  4. 함수 이름을 제대로 입력했다면 [함수 마법사] 대화상자의 [옵션] 버튼이 활성화됩니다. [옵션] 버튼을 클릭하면 [매크로 옵션] 대화상자가 나타납니다. [설명] 란에 함수에 대한 설명을 간략하게 입력합니다.
  5. 로딩 중...
  6. [확인] 버튼을 클릭하면 [매크로] 대화상자로 돌아갑니다. [취소] 버튼을 클릭하여 [매크로] 대화상자를 닫습니다.
  7. [수식 입력줄] 왼쪽에 있는 [함수 입력] 아이콘을 클릭합니다. [함수 마법사] 대화상자에서 [사용자 정의] 범주에 있는 'winRecord' 함수를 선택하면 03 단계에서 입력한 설명이 나타납니다.
  8. 로딩 중...

Sub 프로시저나 사용자 정의 함수Function 프로시저 중에서 업무 특성에 맞는 것을 사용하시면 되겠습니다. 훌륭한 목수는 연장을 탓하지 않는다지만, 여러 가지 도구를 알고 있을수록 다양한 상황에 대처할 수 있음은 불문가지겠지요.

#사용자정의함수 #UDF #Function #엑셀_함수