Java 12
논리적 조건문을 꿰뚫는 단락 평가
비교 연산과 &&, ||, !가 조건문 안에서 섞이면 어떤 조건이 실제로 검사되는지 헷갈린다.
근거 · 교안 p83-p94
1장: 비교 연산자, ‘판단’을 내리다
솔라의 손가락이 노트북 화면 위를 불안하게 맴돌았다. 화면에는 코드 한 줄이 떠 있었다.
if (userAge >= 19 && hasTicket == true) {
// 성인 관람객 입장
}
분명 아는 단어들의 조합이었다. userAge가 19보다 크거나 같고, hasTicket이 true이면. 하지만 솔라의 머릿속에서는 이 조건문이 하나의 거대한 안갯속처럼 느껴졌다. 컴퓨터가 이 줄을 읽을 때, 정확히 무슨 일이 일어나는 걸까? 나이를 먼저 보고, 그 다음에 표가 있는지 보는 건가? 만약 나이가 미성년이면, 뒤에 있는 표 검사는 아예 하지도 않는 걸까? 아니면 일단 두 가지를 다 확인한 다음에 결과를 합치는 걸까? 생각의 갈래가 너무 많아지자, 솔라는 화면을 노려보는 것을 멈추고 등을 의자에 기댔다.
“언니.”
나지막한 부름에 책을 읽고 있던 루나가 고개를 들었다.
“이 조건문, 좀 이상해. 나이를 비교하는 거랑 표가 있는지 확인하는 거, 이 두 개가 어떻게 합쳐져서 하나의 결정을 내리는지 모르겠어. 그냥 순서대로 하나씩 검사하고 끝나는 건가?”
솔라는 자신의 질문이 횡설수설하다는 것을 알았지만, 머릿속의 혼란을 달리 표현할 길이 없었다. 비교하는 연산(>=)과 둘을 잇는 연산(&&)이 뒤섞여, 코드의 실행 흐름이 전혀 예측되지 않았다.
루나는 조용히 일어나 솔라의 옆으로 다가왔다. 화면 속 코드를 잠시 들여다보던 루나는, 말없이 손을 들어 코드의 오른쪽 절반을 가렸다. 솔라의 눈에는 이제 이것만 보였다.
userAge >= 19
“솔라, 다른 건 다 잊어버리고. 딱 이것만 봐봐.”
루나의 손가락이 userAge >= 19 부분을 가리켰다.
“만약에 userAge가 25라면, 이 부분은 뭐가 되지?”
“음… 25 >= 19이니까… 참(true)?”
너무 당연한 질문에 솔라는 멋쩍게 대답했다.
“좋아. 그럼 userAge가 15라면?”
“그럼 15 >= 19니까, 거짓(false)이겠지.”
“맞아.”
루나는 가리고 있던 손을 떼는 대신, 옆에 있던 작은 메모지를 가져와 무언가를 적었다.
10 > 5
“이건 어때? 이건 계산하면 뭐가 나와?”
“이것도 참(true)이지.”
솔라는 마치 쉬운 퀴즈를 푸는 기분이었다.
“정확히는, ‘참’이라는 값을 가진 boolean이 되지.”
루나가 덧붙였다.
“이 작은 표현식의 역할은 딱 하나야. 왼쪽과 오른쪽을 비교해서, 그 관계가 맞는지 틀리는지 ‘판단’하고, 그 판단의 결과를 true나 false라는 표딱지로 되돌려주는 것. 그 이상도 이하도 아니야.”
판단. 그 단어가 솔라의 머리에 박혔다. 10 > 5는 ‘10은 5보다 크다’는 주장에 대한 컴퓨터의 판단 결과였다.
“아…”
솔라는 짧은 탄성을 뱉었다. 루나는 솔라의 얼굴에서 무언가 깨달음의 빛이 스치는 것을 보고, 화면을 가리고 있던 손을 천천히 내렸다.
다시 코드 전체가 드러났다.
if (userAge >= 19 && hasTicket == true)
솔라는 아까와 다른 눈으로 코드의 첫 부분을 바라보았다. userAge >= 19는 더 이상 복잡한 조건문의 일부가 아니었다. 그냥 그 자체로 하나의 독립된 질문이었다. ‘사용자의 나이는 19세 이상입니까?’ 그리고 이 질문에 대한 대답은 오직 true 아니면 false 둘 중 하나였다.
“그럼 userAge >= 19 이 부분은… 그냥 하나의 판단을 내리는 거였구나. 이 덩어리가 통째로 true나 false로 바뀐다고 생각하면 되는 거네.”
“바로 그거야.”
솔라는 고개를 끄덕였다. 마치 복잡하게 얽힌 실타래에서 첫 번째 매듭 하나를 풀어낸 기분이었다. 비교 연산자는 그저 두 값을 저울질해서 참 또는 거짓이라는 하나의 판단을 내릴 뿐이다. 최종 결과를 내는 거창한 역할이 아니었다.
안개가 조금 걷히는 것 같았다. 하지만 솔라의 시선이 코드의 가운데에 있는 && 기호로 옮겨가자, 새로운 질문이 떠올랐다.
“알겠어. 그럼 첫 번째 덩어리 userAge >= 19가 하나의 ‘판단’을 만들고, 두 번째 덩어리인 hasTicket == true도 또 하나의 ‘판단’을 만들겠네. 그럼 내 앞엔 true랑 true 이렇게 두 개의 판단 결과가 놓이는 거잖아. 그런데 if문은 최종적으로 딱 하나의 대답만 원하는데… 이 두 개의 판단을 어떻게 합쳐서 마지막 하나로 만드는 거지?“
2장: 논리 연산자, ‘판단’들을 엮다
솔라의 질문이 끝나자, 루나는 대답 대신 솔라의 책상 위에 흩어져 있던 포스트잇 두 장을 가져왔다. 그리고는 펜을 들어, 첫 번째 포스트잇에 true라고 큼지막하게 썼다. 솔라가 userAge가 25세라고 가정했을 때 나올 첫 번째 ‘판단’의 결과였다. 루나는 두 번째 포스트잇에도 마찬가지로 true라고 적었다. 이것은 hasTicket이 true라고 했을 때의 두 번째 ‘판단’ 결과였다.
루나는 두 개의 포스트잇을 노트북 화면 아래에 나란히 놓았다.
true true
솔라는 그 모습을 말없이 지켜보았다. 자신의 머릿속에 떠다니던 두 개의 판단 결과가 눈앞에 물질처럼 놓여 있었다. 문제는 명확했다. if문은 이 두 개를 받는 것이 아니라, 단 하나의 최종 결론을 필요로 했다. 루나는 여기서 멈추지 않고, 더 작은 메모 조각을 떼어내 그 위에 && 기호를 그렸다. 그리고는 두 개의 true 포스트잇 사이에 조심스럽게 끼워 넣었다.
true && true
이제야 비로소 &&가 독립된 부품처럼 보였다. 두 개의 판단 덩어리 사이에서 무언가 역할을 기다리는 연결고리 같았다.
“자, 이제 다시 봐봐.”
루나가 나지막이 말했다.
“왼쪽엔 ‘참’이라는 판단이 있고, 오른쪽에도 ‘참’이라는 판단이 있어. 그리고 그 사이에 &&라는 연산자가 있네. 이건 마치 재료 두 개를 넣으면 새로운 결과 하나를 만들어내는 기계 같지 않아? 이 기계는 어떤 결과를 내놓을 것 같아?”
기계라는 비유에 솔라는 고개를 끄덕였다. ‘그리고’라는 뜻이니까…
“음… ‘나이가 성인이다’ 그리고 ‘표를 가지고 있다’는 두 조건이 모두 참이어야만 문이 열리는 상황이잖아. 그럼 둘 다 true일 때, 이 && 기계는 최종적으로 true라는 결과를 뱉어내야 말이 되지.”
“그럼 이건 어때?”
루나는 오른쪽의 true 포스트잇을 뒤집어, 뒷면에 false라고 적고 다시 내려놓았다.
true && false
“한쪽이라도 false가 되면… ‘그리고’ 조건을 만족할 수 없으니까, 최종 결과는 false가 되겠네.”
솔라는 자신도 모르게 규칙을 발견하고 있었다. 루나는 말없이 펜을 들어 새 메모지에 작은 표를 그리기 시작했다. 마치 && 기계의 사용 설명서 같았다.
| 입력 1 | 입력 2 | && 결과 |
|---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
“이게 && 연산자의 역할이야. 양쪽에 놓인 두 개의 boolean 판단을 ‘조합’해서, 단 하나의 최종 boolean 판단을 만들어내는 것. 마치 두 개의 재료를 받아서 하나의 요리를 완성하는 것처럼.”
조합. 그제야 솔라는 비교 연산자와 논리 연산자의 역할이 완전히 다르다는 것을 깨달았다. 비교 연산자(>=, ==)는 현실의 값(나이, 표의 유무)을 가지고 true나 false라는 첫 번째 ‘판단’을 만드는 역할을 한다. 그리고 && 같은 논리 연산자는, 이미 만들어진 그 ‘판단’들을 재료로 삼아 두 번째 가공을 하는 것이었다. 판단들을 조합해 최종 판단을 내리는 역할.
“아…”
솔라는 탄성을 내뱉었다. “완전히 다른 일을 하는 거였네. userAge >= 19가 true를 만드는 1차 공정이라면, &&는 그렇게 만들어진 true나 false들을 모아서 최종 제품을 만드는 2차 공정인 셈이구나.”
“맞아. 그럼 이런 기계도 있어.”
루나는 &&가 적힌 메모 조각을 치우고, 그 자리에 ||라고 적은 새 메모를 놓았다.
“이건 ‘또는’이라는 뜻이야. 이 기계는 규칙이 좀 달라. 둘 중 하나만 true여도 최종 결과를 true로 만들어줘.”
솔라는 잠시 생각에 잠겼다. ‘청소년 할인 대상: 19세 미만 또는 학생증 소지자’ 같은 조건이 떠올랐다.
“알겠다. 그럼 true || false는 true가 되겠네. false || true도 true고, 둘 다 true여도 당연히 true. 둘 다 false일 때만 유일하게 false가 되는 거구나.”
이제 if (userAge >= 19 && hasTicket == true)라는 코드는 더 이상 안갯속이 아니었다. 솔라의 눈에는 명확한 실행 흐름이 보였다.
- 컴퓨터는 먼저
userAge >= 19라는 비교 연산을 실행해서 하나의 판단(true)을 얻는다. - 다음으로
hasTicket == true라는 비교 연산을 실행해서 또 하나의 판단(true)을 얻는다. - 마지막으로
&&논리 연산자가 두 개의true를 조합하여 최종 판단(true)을 내린다. if문은 이 최종 판단true를 보고,{}안의 코드를 실행할지 결정한다.
판단을 만들고, 그 판단들을 조합한다. 두 단계로 분리해서 생각하니 모든 것이 명쾌했다. 하지만 명쾌해지자 새로운 의문이 고개를 들었다.
“조합하는 건 알겠어. 그런데 언니, 컴퓨터가 정말 이 모든 걸 다 할까? 예를 들어 && 연산에서, 첫 번째 판단이 false면 어차피 최종 결과는 false로 정해진 거잖아. 그런데도 굳이 두 번째 판단을 확인하러 갈까? 그냥 거기서 멈추는 게 더 효율적이지 않아?“
3장: 단락 평가, ‘실행’의 비밀
솔라의 질문은 허공에 잠시 머물렀다. 효율성. 컴퓨터가 정말 모든 조건을 꿋꿋이 확인할까, 아니면 더 똑똑한 길을 택할까? 솔라의 시선은 이전 장에서 루나가 만들어준 && 기계의 진리표에 닿았다. 첫 입력이 false일 경우, 두 번째 입력이 무엇이든 결과는 항상 false였다.
루나는 대답 대신, 솔라의 시선이 머무는 그 표를 손가락으로 가리켰다. 그리고는 조용히 새 포스트잇을 가져와, false라고 적힌 포스트잇 옆에 있던 true 포스트잇을 떼어내고 그 자리에 새것을 붙였다. 하지만 이번에 루나가 적은 것은 true나 false가 아니었다.
“헬로!”라고 소리치기
솔라는 눈을 동그랗게 떴다. 이건 참이나 거짓 같은 ‘판단’이 아니라, 어떤 ‘행동’에 대한 지시였다. 루나는 이전처럼 && 메모 조각을 그 사이에 놓았다. 솔라의 눈앞에는 기묘한 조건문이 조립되어 있었다.
false && “헬로!”라고 소리치기
“솔라, 네가 방금 한 질문에 대한 실험이야.”
루나의 목소리는 차분했다.
“컴퓨터가 네 생각처럼 효율적으로 움직인다면, 이 명령을 실행했을 때 어떤 일이 벌어질까? 우리는 ‘헬로!’라는 소리를 듣게 될까?”
솔라는 잠시 생각에 잠겼다. 질문의 핵심은 ‘실행’의 여부였다. && 연산의 규칙상, 첫 번째 판단이 false이면 최종 결과는 이미 false로 확정이다. 두 번째 판단이 무엇인지는 결과에 아무런 영향을 주지 못한다. 그렇다면… 컴퓨터가 굳이 두 번째 행동을 실행할 이유가 있을까?
“아니.” 솔라가 확신에 찬 목소리로 말했다. “첫 번째 판단에서 이미 끝났잖아. false라고. 그럼 뒤에 있는 ‘소리치기’는 아예 쳐다보지도 않을 거야. 우리는 아무 소리도 듣지 못해.”
“맞아. 아무 일도 일어나지 않아.”
루나는 고개를 끄덕였다.
“컴퓨터는 게으르다고 생각하면 편해. 최종 결과를 알 수 있는 최소한의 정보만 확인하고 싶어 하거든. 이렇게 앞선 조건만으로 결과가 확정될 때, 뒤따라오는 조건은 아예 실행조차 하지 않는 규칙을 ‘단락 평가(short-circuit evaluation)‘라고 불러.”
단락 평가. 단어의 뜻이 솔라의 머리에 선명하게 박혔다. 회로를 끊어버리듯, 불필요한 평가의 흐름을 중간에 끊어버리는 것. 모든 것을 다 확인하고 조합할 거라는 솔라의 초기 모델이 무너지는 순간이었다. 실행 흐름은 고정된 것이 아니라, 조건의 값에 따라 유동적으로 변할 수 있었다.
“그럼 || 연산자도 마찬가지겠네?”
솔라는 스스로 다음 단계로 나아갔다. 루나는 말없이 && 메모 조각을 치우고 ||를, false 포스트잇을 떼고 그 자리에 true를 놓았다.
true || “헬로!”라고 소리치기
“이것도… 소리가 안 나겠네.” 솔라가 빠르게 결론 내렸다. “|| 연산자는 둘 중 하나만 true여도 최종 결과가 true잖아. 첫 번째 판단이 true인 순간, 결과는 이미 true로 확정이야. 뒤는 볼 필요도 없어.”
비교 연산자가 ‘판단’을 만들고, 논리 연산자가 그 판단들을 ‘조합’한다는 것을 깨달은 것이 첫 번째 전환점이었다면, 단락 평가는 그 ‘조합’ 과정에 숨겨진 효율적인 ‘실행’의 비밀을 파헤친 두 번째 거대한 전환점이었다.
솔라는 다시 노트북 화면 속 코드로 시선을 돌렸다. 이제 이 한 줄은 살아 움직이는 생물처럼 느껴졌다.
if (userAge >= 19 && hasTicket == true) {
// 성인 관람객 입장
}
“만약 userAge가 15라면…”
솔라는 혼잣말처럼 중얼거리며 코드의 실행 흐름을 머릿속으로 그려보았다.
- 먼저
userAge >= 19라는 첫 번째 판단이 실행된다.15 >= 19는false다. - 그리고
&&연산자가 이false결과를 본다. - 순간, 단락 평가가 작동한다.
&&의 첫 부분이false이므로, 최종 결과는 이미false로 확정. - 컴퓨터는 두 번째 판단인
hasTicket == true를 아예 실행하지 않고 건너뛴다.
“아…! 그래서 그랬구나.”
솔라는 무릎을 탁 쳤다. 예전에 비슷한 코드를 디버깅할 때, 왜 특정 변수의 값은 확인조차 되지 않는지 의아했던 기억이 떠올랐다. 그저 모든 조건을 다 검사할 거라고만 막연히 생각했었다.
“이제 알겠어. 조건문을 볼 때 그냥 ‘참이냐 거짓이냐’만 볼 게 아니었어. 어떤 순서로, 그리고 어디까지 ‘실행’되는지를 예측해야 하는 거구나.”
솔라의 눈에는 더 이상 안개가 없었다. 복잡하게 얽혀 있던 비교 연산과 논리 연산, 그리고 그 실행의 흐름이 명확한 인과관계로 꿰뚫어 보였다. 판단을 만들고, 그 판단을 조합하며, 그 과정에서 효율적인 실행 규칙에 따라 건너뛸 것은 건너뛴다. 이제 어떤 복잡한 조건문이 눈앞에 나타나더라도, 그 안에서 컴퓨터가 어떤 길을 따라 걷고 어디서 멈출지 정확히 예측할 수 있을 것 같았다.