openlang

빈 셀은 0과도 같고 빈 문자열과도 같다

물려받은 매크로가 수량이 0인 주문 행을 표시하는데, 빈 행까지 전부 표시하더라고요. 검사 코드는 If ws.Cells(r, 4).Value = 0 Then이었어요. 빈 셀은 Empty가 든 Variant를 돌려주고, Empty는 0과 같다고 나와요(반대로 검사를 쓰면 ""과도 같다고 나오니까 그쪽으로도 못 이겨요). 당연히 아무도 변수를 선언하지 않았으니 매크로 전체가 Variant로 돌아갔고, 비교는 그냥 Variant가 하는 대로 했을 뿐이에요.

급한 불은 비교 전에 IsEmpty를 넣는 걸로 껐어요. 그건 됐어요. 그런데 그러다 보니 VBA에서 '여기 아무것도 없음'을 뜻하는 값이 몇 가지인지 세어 보게 됐어요. 빈 셀의 Empty, Access 필드의 Null, 객체 변수의 Nothing, "", 0, 그리고 셀에 #N/A가 보이면 오류 값까지 있어요. 표기만 여섯 가지고, 검사 방법도 다 달라요.

다들 VBA에서 '값 없음'을 어떻게 모델링하세요? 입구에서 타입 있는 변수와 플래그로 바꾸나요, 아니면 Variant를 그대로 두고 곳곳에서 VarType으로 검사하나요?

이어지는 대화 7

서로의 근거를 따라
  1. 고유 링크

    셀은 한 번만 읽고, 그게 뭔지는 한 곳에서 정하세요. 그다음부터 매크로의 나머지 부분은 타입 있는 값만 받고 Variant는 다시 안 봐요.

    Dim v As Variant
    v = ws.Cells(r, 4).Value2
    
    Select Case VarType(v)
        Case vbEmpty
            hasQty = False
        Case vbDouble
            hasQty = True
            qty = CDbl(v)
        Case vbString
            hasQty = False
            LogOddCell ws, r, v
        Case Else
            hasQty = False
            LogOddCell ws, r, v
    End Select

    여기선 Value2가 중요해요. Value로 읽으면 통화나 날짜 서식이 붙은 셀이 vbCurrency나 vbDate로 와서 Case Else로 떨어져요. 오류 셀은 vbError로 나오고 역시 Case Else로 가니까, 세지 않고 로그로 남게 돼요. LogOddCell 자리에는 건너뛴 행을 처리하던 기존 방식을 아무거나 넣으면 돼요.

  2. 고유 링크

    그 시트에 입력하는 사람은 목록에 있는 것보다 '아무것도 없음'을 만드는 방법이 더 많아요. Delete를 누르면 Empty예요. 지우려고 스페이스바를 치면 화면에선 비어 보이는 한 글자짜리 문자열이 돼요. 어떤 서식에서 0이 자꾸 사라져서 '0을 치면 이제 텍스트 "0"이 되고, vbDouble 분기는 그걸 건너뛰어요. 스크롤하면서 보면 셋 다 똑같이 생겼어요. 매크로가 건너뛴 셀을 사용자에게 보여 주는 싼 방법, 채우기 색 같은 게 있을까요? 스페이스바로 만든 칸은 입력하는 쪽에서 고치게요.

  3. 고유 링크

    Empty가 자기 타입이 있는지 아니면 빌려 쓰는지 궁금해졌는데, 직접 실행 창에서 보면 주변 식이 원하는 걸 그냥 빌려 써요. Empty + 1은 1이고, Empty & "x"는 "x"이고, Empty = 0과 Empty = ""는 둘 다 True예요. 그러니까 0과 같은 값이라기보다, 누가 묻느냐에 따라 0이 되기도 하고 빈 문자열이 되기도 하는 값이에요. = 0 검사가 그렇게 오래 멀쩡해 보였던 이유가 그거예요. 재밌는 건 어떤 검사가 그 차이를 알아채느냐인데, 제가 아는 한 IsEmpty와 VarType뿐이에요.

  4. 고유 링크

    저라면 그 앞에 단계를 하나 더 둘 것 같아요. 먼저 열 전체를 배열로 읽고, 모든 셀을 분류한 다음에야 계산하는 거예요. 그러면 합계가 일부만 검사한 범위에서 시작하는 일이 없어요. data = rng.Value2는 셀마다 원소가 하나씩 있는 2차원 배열을 주고, 행마다 Cells를 호출하는 것보다 훨씬 빨라요.

    다만 걸리는 게 하나 있어요. 범위가 셀 하나뿐이면 Value2는 배열이 아니라 값 자체를 돌려줘서 data(1, 1)이 실패해요. 그래서 읽은 직후에 IsArray(data)를 확인하고, 분류 단계 전에 값 하나를 직접 배열로 감싸요. 그러면 뭔가를 더하기 전에 빈 셀, 숫자 셀, 이상한 셀이 각각 몇 개인지 이미 알고 있게 돼요.

  5. 고유 링크

    고치기 전에 세어 보세요. 그런 시트라면 저는 숫자 세 개를 보고 싶어요. IsEmpty가 참인 셀, 그건 아닌데 값이 ""인 셀, 그리고 나머지 전부예요. 사람들이 잊는 건 가운데 그룹이에요. =IF(B2="","",B2*C2) 같은 수식은 아무것도 안 보여 주지만, 셀에 수식이 있으니 IsEmpty는 False이고 VarType은 vbString이에요. Excel의 COUNTBLANK는 이런 셀을 빈 셀로 세기 때문에, 워크시트 쪽 개수와 매크로 쪽 검사가 딱 그 그룹만큼 어긋날 수 있어요.

  6. 고유 링크

    하, Tcl에는 '없음'이 빈 문자열 하나뿐이에요! 문제는 반대 방향이에요. 값이 없는 것과 정말로 빈 텍스트가 같은 문자열이라서, 값을 보는 대신 info exists나 dict exists로 확인해야 해요.

  7. 고유 링크

    뭘 고치기 전에 말씀하신 세 가지를 세어 봤는데, 그걸로 얘기가 달라졌어요. 매크로가 표시한 행 중에 정말로 빈 건 몇 개뿐이었어요. 나머지는 누가 추가한 보조 열의 수식 빈칸이었어요(머리글에 그냥 "x"라고만 적어 둔, 누군지 모를 그분께 감사를). 그러니까 제 IsEmpty 수정은 거의 하나도 못 잡았을 거고, 저는 그걸로 끝났다고 했을 거예요.

    결국 smallprint님 방식대로 Value2에 Select Case를 쓰고, Case vbString 분기에서 ""는 수량 없음으로, 나머지는 로그에 남길 셀로 처리했어요. 로그 시트엔 이제 항목이 열한 개 있는데, 전부 kim_dh님이 말한 스페이스바 종류예요.

같은 질문의 다른 갈래

  1. 측정 단위를 값싼 ID로 쓰기

    정수 ID에 단일 케이스 유니온 대신 측정 단위를 써 보고 있는데, 더 퍼지기 전에 다른 의견을 듣고 싶어요. CustomerId of int는 [<Struct>]를 붙이지 않으면 할당이 생기고, 쓸 때마다 생성자나 매치를…

    답글 1fsharp
  2. Java에서 옮긴 헬퍼가 Groovy에서 다른 오버로드를 부른다

    Java 헬퍼 클래스를 빌드 스크립트 쪽 Groovy로 옮겼더니 같은 호출이 다른 메서드로 갔습니다. 순서대로 정리하면 이렇습니다. Java에서는 x의 선언 타입이 Object이므로 컴파일 시점에 show(Object)가 정…

  3. 판별 유니온 전에 JSON 검증하기

    타입 단언은 컴파일러에게 이렇게 믿으라고 말할 뿐이에요. 외부 응답은 바이트로 시작하고, 누군가 확인하기 전까지는 그게 어떤 변형인지 알려 주는 게 하나도 없어요. 텍스트 디코딩과 JSON 파싱이 먼저 오고, 둘 다 각자 실…