나는 “true, false, true”에 계속 걸린다
Source: Hacker News
가끔씩 PR을 열면 이런 코드를 보게 됩니다:
deployFeature(flag, true, false, true);
나는 이것을 생각보다 더 자주 마주합니다. 복잡해서가 아니라—그저 내가 무엇을 보고 있는지 전혀 감을 잡지 못해서죠. 그래서 함수 정의로 들어가서 조금 스크롤하고, 위치를 잃어버리고, 다시 돌아와서 그 줄을 다시 읽어보면… 비로소 이해가 됩니다. 사소한 방해이지만 매번 짜증이 납니다.
나는 더 이상 코드를 읽지 않는다, 해독하고 있다
여기 더 간단한 예가 있습니다:
createUser(user, true, false);
이게 무슨 뜻일까요? 사용자가 관리자인가요? 환영 이메일을 보내는 건가요, 아니면 검증을 건너뛰는 건가요? 잘 모르겠습니다. 이때는 더 이상 코드를 읽는 것이 아니라 해독하고 있는 겁니다.
이에 대한 이름이 있습니다(“플래그 인수”, 때때로 “불리언 블라인드니스”라고도 합니다), 하지만 문제를 느끼는 데는 그 용어가 필요하지 않죠.
분명히 이런 코드를 작성한 적이 있습니다:
createUser(user, true, false); // isAdmin, sendWelcomeEmail
함수 호출에 인수를 설명하기 위해 주석이 필요하다면, 그 API는 아마도 나에게 불리하게 작동하고 있는 것입니다.
왜 이때는 괜찮게 느껴지는가
함수를 작성할 때는 전혀 문제가 없다고 느껴집니다:
function createUser(user, isAdmin, sendWelcomeEmail) {
// …
}
추가 객체도, 추가 구조도 없습니다—값만 전달하고 넘어가면 됩니다. 나는 이것을 여러 번 별다른 고민 없이 해왔습니다. 하지만 나중에 호출 위치를 읽을 때 비정상적으로 느껴지기 시작합니다. 편리함은 나중에, 이를 읽어야 하는 사람(지금의 나 포함)에게 대가를 요구합니다.
지금 제가 사용하는 방법
대부분의 경우, 대신 객체를 사용합니다:
createUser(user, {
isAdmin: true,
sendWelcomeEmail: false,
});
이제 함수 정의로 돌아가지 않아도 실제로 무슨 일이 일어나고 있는지 알 수 있습니다. 또한 자연스럽게 확장됩니다:
createUser(user, {
isAdmin: true,
sendWelcomeEmail: false,
skipValidation: true,
});
그렇게 멀리까지 위치 기반 불리언을 늘리려고 하면 금방 어색해집니다.
때때로 불리언이 다른 동작을 숨기고 있다
createUser(user, true);
true가 실제로 “관리자 사용자 생성”을 의미한다면, 이는 더 이상 플래그가 아니라 다른 동작입니다—다른 액션이라고 볼 수 있죠. 보통은 명시적으로 작성합니다:
createAdminUser(user);
createRegularUser(user);
이제 해석할 여지가 거의 없습니다.
공정하게 말하자면, 이것이 항상 나쁜 것은 아니다
때때로 이것은 완전히 괜찮다:
toggleMenu(true);
그것은 충분히 명확합니다. 이는 다음과 같은 경우에 잘 작동합니다:
- 의미가 명확할 때
- 함수가 작고 로컬일 때
- 플래그가 하나만 있을 때
하지만 두 번째 불리언을 추가하면 가독성이 보통 빠르게 떨어집니다.
TypeScript가 실제로 이것을 해결해 주지는 않는다
TypeScript는 값이 boolean이라고 알려주지만, 그게 문제는 아니다.
createUser(user, true, false);
타입 자체는 기술적으로 맞지만, 나는 여전히 인수가 무엇을 의미하는지 기억해야 한다.
내게 더 도움이 된 것은 옵션 객체로 전환하는 것이었다:
createUser(user, {
isAdmin: true,
sendWelcomeEmail: false,
});
또는 때때로 boolean 자체를 완전히 대체하는 경우도 있다:
createAdminUser(user);
보통 이는 해당 플래그가 두 가지 다른 동작을 숨기고 있다는 신호이다.
동일한 동작, 훨씬 읽기 쉬움
전:
fetchData(url, false, true, 3);
후:
fetchData(url, {
useCache: false,
retryOnFail: true,
retries: 3,
});
실제 프로덕션 코드에서 이런 호출을 본 적도 있습니다:
updateSettings(user, true, false, true, false);
그때는 손가락으로 인자를 세어야 해서—동일한 동작이지만, 훨씬 적은 정신적 부담이 있습니다.
왜 이것이 계속 시간을 잡아먹는가
대부분의 시간은 코드를 작성하는 것이 아니라, 코드를 이해하려고 애쓰는 데 보낸다. 몇 주 전 내가 쓴 코드라도 마찬가지다. 다음과 같은 코드를 마주칠 때마다:
updateSettings(user, true, false, true, false);
잠시 멈춰서 각 인수가 무엇을 의미했는지 기억하려고 한다. 사소한 속도 저하이지만, 계속 반복해서 겪는 문제다.