삼항연산자의 멋짐을 모르는 당신이 불쌍해

Nested Ternaries are Great

Posted by mido on 2018-04-24

이 글은 Eric Elliottmedium에서 연재하는 Composing Software 시리즈를 번역한 것입니다. [원문보기]

Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0)

참고 : 이 글은 JavaScript ES6+의 함수형 프로그래밍 및 소프트웨어 합성 방법론을 기초부터 다루는 "소프트웨어 합성"시리즈의 일부 입니다. 앞으로 계속하여 연재될 것입니다.
<이전 | << Part 1에서 다시 시작 | 다음>

보통 삼항연산자를 중첩시키는 것은 가독성을 떨어트리기 때문에 피해야 한다고 합니다.

Conventional wisdom is sometimes unwise.

사실, 삼항 연산자는 대개 if 문보다 훨씬 간단합니다. 사람들이 그 반대로 믿게되는 두 가지 이유가 있습니다.

  1. if 문에 익숙합니다. 친근성 편향Familiarity bias은 우리가 반대되는 증거를 제시 할지라도 사실이 아닌 것을 믿게합니다.
  2. 사람들은 삼항연산자를 if 문처럼 사용하려고합니다. 삼항연산자는 구문statement 아니라 표현식expression 이므로 이는 잘못된 용법입니다.

세부사항에 대해 알아보기 전에 삼항연산자 표현식을 정의해 보겠습니다.

삼항식은 값으로 평가되는 조건부 표현식입니다. 조건부, 진리 절 (조건부가 참인 경우 평가되는 값), 그리고 거짓 절(조건부가 거짓인 경우 평가되는 값)으로 구성됩니다.

다음 처럼 표현됩니다 :

1
2
3
(conditional)  
? truthyClause
: falsyClause

표현식 대 명령문

스몰 토크, 하스켈 및 대부분의 함수형 프로그래밍 언어를 포함한 몇 가지 프로그래밍 언어에는 if문이 전혀 없습니다. 대신에 if 표현식이 있습니다.

if 표현식은 값으로 평가되는 조건식입니다. 조건부, 진리 절 (조건부가 참인 경우 평가되는 값), 그리고 거짓 절(조건부가 거짓인 경우 평가되는 값)으로 구성됩니다.

비슷하지 않습니까? 대부분의 함수형 프로그래밍 언어에서는 if 키워드를 삼항식처럼 사용합니다. 어째서일까요?

표현식은 단일 값으로 평가되는 코드 덩어리입니다.

구문은 값으로 평가되지 않는 코드 덩어리입니다. JavaScript에서는 if문이 값으로 평가되지 않습니다. if문이 의미있는 일을 하게 하려면 부수효과를 발생시키거나 함수 안에서 값을 리턴해야 합니다.

함수형 프로그래밍에서는 변이와 부수효과를 피하려는 경향이 있습니다. JavaScript의 if는 자연스럽게 변이와 부수효과를 발생시키는 경우가 많으므로 저를 포함한 많은 함수형 프로그래머가 중첩 여부에 관계없이 삼항연산자를 사용합니다.

삼항식의 관점으로 생각하는 것은 if문의 관점으로 생각하는 것과는 조금 다릅니다. 그러나 2주 정도만 의식해서 사용하다보면 자연스럽게 삼항식에 끌리기 시작할 것입니다. 단순히 타이핑할게 적기 때문에라도 말입니다.

친숙함 편향 Familiarity Bias

제가 본 공통적인 의견중 하나는 중첩된 삼항 표현식은 "읽기가 어렵다"는 것입니다. 몇 가지 코드 예제로 그 미신을 산산조각 내보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
 const withIf = ({  
conditionA, conditionB
}) => {
if (conditionA) {
if (conditionB) {
return valueA;
}
return valueB;
}
return valueC;
};

이 중첩된 if문 코드는 falsy 절과 truthy 절이 시각적으로 분리되어 매우 단절된 느낌을줍니다. 사실 매우 간단한 코드지만 구문을 분석하려면 살짝 주의깊게 봐야합니다.

동일한 논리를 삼항 표현식으로 작성해보았습니다.

1
2
3
4
5
6
7
8
9
const withTernary = ({  
conditionA, conditionB
}) => (
(!conditionA)
? valueC
: (conditionB)
? valueA
: valueB
);

여기에 몇 가지 흥미로운 점이 있습니다.

데이지 체이닝[1] vs 네스팅

첫째로, 우리는 중첩을 평평하게 했습니다. “중첩된” 삼항식은 약간의 잘못된 표현입니다. 삼항식은 직선적으로 쓰기 쉽기 때문에 들여쓰기로 중첩할 필요가 없습니다. 따라서, 단순하게 위에서 아래로 읽으면서 조건에 부합하는 순간 즉시 값을 반환합니다.

올바르게 작성된 삼항식에는 해석해야될 중첩이 없습니다. 직선도로에서 길을 잃기란 꽤 어렵습니다.

따라서 우리는 이를 "삼항연산자 체이닝"이라고 부를 것입니다.

두 번째로 지적하고 싶은 것은 이 직선 연쇄를 단순화하기 위해 논리의 순서를 조금 바꿨다는 것입니다. 삼항 표현식의 끝으로 가서 두 개의 콜론 절 (:) 중, 마지막 절을 위로 이동시키고, 단순화하기 위해 첫 번째 조건의 논리를 역으로 바꿨습니다. 더 이상 복잡해질 이유가 없습니다.

사실, if 문을 단순화하기 위해 동일한 트릭을 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
const withIf = ({  
conditionA, conditionB
}) => {
if (!conditionA) return valueC;
if (conditionB) {
return valueA;
}
return valueB;
};

훨씬 나아졌습니다. 그러나 아직도 시각적으로 분리된 conditionB절이 혼란을 야기 할 수 있습니다. 저는 이러한 문제들이 논리적 버그로 이어지는 것을 보았습니다. 논리가 평평 해졌음에도 불구하고이 버전은 삼항연산자 버전보다 여전히 혼란스럽습니다.

어수선한 구문들

if 버전에는 잡동사니들이 포함되어 있습니다. if 키워드 , return 키워드, 여분의 세미콜론, 여분의 중괄호 등. 이 예제와 달리 대부분의 if 문장은 외부 상태를 변경하며 여분의 코드Extra Code와 복잡성이 더해집니다.

여분의 코드 가 좋지 않은 몇 가지 중요한 이유가 있습니다. 이미 몇번씩 언급했었지만, 모든 개발자의 뇌에 화상을 입힐 때까지 반복 할 가치가 있습니다.

단기 기억

인간 두뇌의 단기 기억력은 서로 다른 항목을 저장하기 위한 공간이 한정되어있으며, 함수의 인자와 변수는 잠재적으로 그 공간 중 하나를 소비합니다. 변수가 늘어날 수록 각 변수의 의미를 기억하기 힘들어 집니다. 단기 기억 모형에 따르면 우리의 두뇌는 일반적으로 4-7 개의 항목을 단기 기억 공간에 저장할 수 있으며 이 보다 크면 오류율이 급격히 증가합니다.

우리는 함수 파이프라이닝을 사용해 변수 3개를 소거하였고 다른 변수를 기억하는데 사용할 수 있게 됐습니다. 단기 기억 공간의 거의 절반을 확보한 것입니다. 이는 인지 부하를 상당히 줄여줍니다. 소프트웨어 개발자는 데이터를 묶어 기억하는 경향이 있지만 그렇다고 이 개념의 중요성이 약해지는 것은 아닙니다.

신호 대 잡음비

간결함은 코드의 신호 대 잡음 비율Signal to Noise Ratio을 향상시킵니다. 라디오를 듣는 것과 같습니다. 라디오의 주파수가 제대로 맞춰져 있지 않으면 소음이 발생하며 음악을 듣기가 어려워집니다. 방송국 주파수로 정확하게 튜닝하면 소음이 사라지고 음악 신호가 강해집니다.

코드도 마찬가지입니다. 간결한 표현은 이해력을 향상시킵니다. 일부 코드는 유용한 정보를 제공하고 일부 코드는 공간을 차지합니다. 전달되어야 할 의미를 변화시키지 않는 선에서 코드의 양을 줄이면 코드를 읽고 이해해야하는 다른 사람들이 더 쉽게 이해할 수 있습니다.[2]

코드의 면적과 버그

함수형으로 작성된 코드를 살펴보면 마치 코드가 살을 빼 다이어트 한 것처럼 보입니다. 이 비유가 중요한 이유는 여분의 코드가 버그를 숨길 수있는 추가 표면적을 의미하기 때문입니다 . 즉 더 많은 버그가 숨어 버릴 수 있음을 의미합니다.

적은 코드 = 버그가 적은 표면 = 버그가 적습니다.

부수효과 및 바뀔 수 있는 공유변수

대부분의 경우 if 문은 값으로 평가되는 것 이상을 수행합니다. 이들은 부수효과를 발생시키고 변수를 변경합니다. 부수효과의 영향과 가변적인 공유변수를 다루는 제어 흐름을 완전히 파악하지 못한다면 if 문이 작동하는 모든 그림을 볼 수 없습니다.

언제나 값이 리턴 되도록 하는 것은 일련의 규율을 만듭니다. 즉, 프로그램을 이해하고 디버그하고 리팩토링하고 유지 관리하기가 쉽도록 종속성을 끊어내는 실천입니다.

그리고 이 부분이 바로 삼항연산자의 이점 중 제가 가장 좋아하는 것입니다.

삼항연산자를 사용하면 더 나은 개발자가 될 것입니다.

결론

모든 삼항식은 위에서 아래로 일직선으로 배열하기 쉽기 때문에 "중첩 삼항식"이라고 하는 것은 잘못된 표현입니다. 대신, 이들을 "삼항식 체이닝"라고 부르겠습니다.

체인된 삼항연산자는 if 문과 비교해 몇 가지 장점이 있습니다.

  • 항상 위에서 아래로 일직선으로 읽을 수 있도록 작성하는 것이 좋습니다. 직선을 따라 읽으면 체인된 삼항식을 무리없이 읽을 수 있습니다.
  • 삼항 연산자는 구문의 혼란을 줄입니다. 적은 코드 = 버그가 적은 표면 = 버그가 적습니다.
  • 삼항 연산자는 임시 변수를 필요로하지 않으므로 단기기억력의 부하가 줄어 듭니다.
  • 삼항식은 신호 대 잡음비가 더 좋습니다.
  • if문은 부수작용과 변이를 권장하지만, 삼항연산자는 순수한 코드를 권장합니다.
  • 순수한 코드는 표현과 기능을 서로 분리시킴으로써 우리가 더 나은 개발자가 될 수 있도록 합니다.

다음: 합성과 추상화 >


  1. 전자공학에서 사용하는 용어로 연속적으로 연결되어 있는 하드웨어 장치들의 구성을 뜻합니다

  2. 개발자가 코드를 작성하고 다른 개발자가 이를 이해한다는 것을 라디오의 방송국과 청취자들의 관계에 비유했 습니다. 이 때 신호란 전달되어야하는 의미이고 잡음이란 공간만 차지하는 코드입니다. 즉 더 적은 코드로 같은 의미를 전달하는 것이 좋다는 말이 됩니다.