개발 slecs

JSP 뷰 기능 플래그 Boolean 조건 분기 오류 수정

목차

featureMap에서 기능 플래그 값을 꺼내 JSP 뷰에서 조건 분기할 때 == 1 로 비교하던 코드가 문제였다. Java EL(Expression Language)에서 Boolean true 와 정수 1 은 같지 않다. C나 Python 에 익숙하면 true == 1 이 당연히 참일 것 같지만, JSP EL 스펙은 타입 강제 변환 규칙이 달라서 Boolean(true) == 1false 를 반환한다. 플래그가 true 로 세팅돼 있어도 비교에서 항상 false 가 되고, 해당 블록이 렌더링되지 않는 상황이었다.

특정 케이스에서만 발생했다는 건, featureMap 값이 Boolean 으로 들어오는 경우에만 터졌다는 뜻이다. 정수로 들어오는 경로라면 같은 코드가 우연히 동작했을 테니, 꽤 오래 묻혀 있을 수도 있는 버그였다. 언제부터 심어져 있었는지 정확히 알 수 없지만, featureMap 타입이 어느 순간 Map<String, Boolean> 으로 정착하는 과정에서 뷰 코드가 그대로 남았던 것 같다.

왜 이런 실수가 생기나

JSP/JSTL 환경에서 기능 플래그를 다루는 방법은 팀마다 다르다. DB 에서 TINYINT(1) 으로 관리하다가 어느 순간 Boolean 으로 바꾸거나, 백엔드가 Map<String, Object> 로 내려줄 때 값 타입이 혼재하기도 한다. 그 과정에서 뷰 쪽 비교 코드가 뒤처지는 경우가 꽤 흔하다.

EL 에서 타입 비교 규칙이 직관적이지 않다는 것도 한몫한다. EL 2.x 스펙 기준으로 == 연산자가 피연산자를 비교할 때 적용하는 type coercion 은 이렇다. 한쪽이 null 이면 null 끼리만 같고, 한쪽이 String 이면 다른 쪽을 String 으로 변환해서 비교한다. 둘 다 숫자 타입이면 숫자로 비교한다. Boolean 과 Integer 를 비교할 때는 따로 정의된 변환 규칙이 없어서 결국 false 가 나온다.

비교식 결과 이유
${true == 1} false Boolean vs Integer, 변환 규칙 없음
${true == true} true 동일 타입 비교
${'true' == true} true String 'true' -> Boolean coerce
${featureMap['flag']} true/false Boolean 직접 평가, 권장 패턴
${featureMap['flag'] == true} true/false 명시적 Boolean 비교

== 1 패턴이 생기는 배경은 대개 두 가지다. DB 컬럼이 정수형이라 백엔드가 Integer 를 내려줬던 시절 코드가 남아 있는 경우, 또는 다른 언어에서 넘어온 개발자가 truthy 개념을 EL 에도 적용한 경우다. 어느 쪽이든 뷰 단에서 터지고 나서야 발견하게 되고, 원인 찾는 데 불필요하게 시간을 쓴다.

수정 방향

재현 시나리오를 먼저 만든 건 맞는 순서였다. JSP 버그는 특정 데이터 조합에서만 나타나는 경우가 많아서, 재현 조건을 명확히 해두지 않으면 수정 후 검증이 애매해진다. "고쳤는데 왜 동작하는 거지?" 상태로 넘어가면 나중에 다시 터질 때 또 처음부터 파야 한다.

수정 자체는 간단하다.

<%-- Before: Boolean 플래그를 Integer  비교 --%>
<c:if test="${featureMap['someFlag'] == 1}">
  <%-- featureMap['someFlag'] Boolean true여도  블록은 렌더링되지 않는다 --%>
</c:if>

<%-- After: Boolean 직접 평가 --%>
<c:if test="${featureMap['someFlag']}">
  <%-- featureMap['someFlag'] true면 정상 렌더링 --%>
</c:if>

<%-- null 가능성 있는 경우 방어적으로 --%>
<c:if test="${not empty featureMap['someFlag'] and featureMap['someFlag']}">
  ...
</c:if>

Boolean 플래그라면 굳이 비교 연산자 없이 EL 표현식 자체가 조건이 된다. == true 를 명시적으로 붙이는 팀도 있는데, 타입이 Boolean 임을 코드에서 바로 드러낸다는 장점이 있다. 뷰 코드가 길어지거나 유지보수 주기가 긴 파일이라면 == true 쪽이 의도를 더 빨리 읽히기도 해서 팀 컨벤션으로 정해두는 게 낫다. 뭘 쓰든 일관성이 중요하지, 어느 쪽이 절대적으로 맞다는 건 없다.

null 처리도 같이 챙겼다. EL 에서 null 은 조건식에서 false 로 평가되지만, 맵에서 키를 꺼낼 때 예상 못 한 null 이 들어오면 예외가 터지는 경우가 있다. not empty 를 앞에 달아두면 null 과 빈 값 모두 걸러낼 수 있어서 방어 코드로 쓸 만하다.

5개 파일에 걸친 수정이라서 주변 코드에 같은 패턴이 있는지 같이 훑었다. 이게 사실 버그 픽스에서 자주 빠지는 단계다. 하나를 고치면 됐다 생각하고 넘어가는데, 비슷한 패턴이 인접 파일에 그대로 있으면 얼마 지나지 않아 다시 같은 이슈로 돌아온다. JSP 뷰 파일들은 서로 구조가 비슷한 경우가 많아서 복붙으로 번진 코드가 있을 가능성이 높다. 이번에 sub 파일까지 포함해서 5개를 한꺼번에 정리한 이유다.

같은 실수가 반복되지 않으려면

featureMap 을 다루는 EL 패턴을 팀 내에서 통일해두는 게 가장 현실적인 예방책이다. ${featureMap['flag']} 인지, ${featureMap['flag'] == true} 인지 - 뭘 쓰든 일관성이 있으면 나중에 코드 읽는 사람이 타입을 덜 헷갈린다. 특히 Boolean 플래그 Map 과 정수형 값 Map 이 뷰에서 혼용되는 구조라면, 네이밍이나 패턴으로 둘을 구분할 수 있게 정해두는 편이 낫다.

백엔드 계층에서 내려주는 Map 의 값 타입을 뷰 레이어에서 모르는 상황도 이런 버그를 만들기 쉽다. 컨트롤러가 Map<String, Boolean> 을 내려주는지, Map<String, Object> 로 혼재하는지를 뷰 담당자가 쉽게 알 수 있으면 좋다. 명시적인 타입 선언이 어렵다면 주석이라도 달아두는 게 실용적이다.

수정 범위가 뷰 한정이었고 컨트롤러나 SQL 쪽은 건드리지 않았으니 회귀 범위는 좁았다. 그래도 정상 경로를 함께 테스트한 건 맞는 선택이다. 뷰 단 변경은 화면 렌더링 전체에 영향을 줄 수 있어서, 고친 케이스만이 아니라 인접 기능이 그대로인지 눈으로 한 번 더 확인하는 게 기본이다. 버그 픽스 커밋치고 파일이 5개로 퍼졌지만, 그만큼 같은 패턴이 여러 곳에 분포해 있었다는 뜻이고, 한 번에 다 잡아둔 게 나중에 더 드는 비용을 아낀 거라고 본다.

댓글 0

첫 댓글 달아줘.