Abseil Tip 161 좋은 지역 변수와 나쁜 지역 변수

James Dennett (jdennett@google.com) 작성
최초 게시일: 2019년 4월 16일
최종 업데이트: 2020년 4월 6일

빠른 링크: abseil.io/tips/161


“우리는 전역적으로 당황할 수 있지만, 지역적으로 고통받는다.”
– 조너선 프랜즌


개요

지역 변수는 매우 유용하지만, 과도하게 사용될 수 있습니다. 지역 변수는 특정 이점을 제공하는 상황으로 사용을 제한함으로써 코드의 복잡성을 줄일 수 있습니다.


권장 사항

다음 중 하나 이상의 조건에 해당하는 경우에만 지역 변수를 사용하세요:

  • 이름이 유용한 문서 역할을 하는 경우
  • 지나치게 복잡한 표현식을 단순화하는 경우
  • 반복되는 표현식을 분리하여 사람이 (그리고 어느 정도 컴파일러가) 매번 동일한 값임을 명확히 알 수 있도록 하는 경우
  • 객체의 수명이 여러 문장을 넘어서야 하는 경우
    (예: 객체에 대한 참조가 단일 문장의 끝 이후에도 유지되거나, 변수에 수명 동안 업데이트되는 값이 저장되는 경우)

그 외의 경우, 지역 변수를 제거하고 표현식을 직접 사용하여 간접성을 줄이는 것이 좋습니다.


근거

값에 이름을 지정하면 코드 이해에 있어 간접적인 계층이 추가되며, 변수의 이름이 관련된 의미를 완전히 포착하지 못하는 경우 더욱 그렇습니다. 또한 C++에서 값에 이름을 지정하면 해당 이름이 범위 전체에 노출됩니다. 모든 이름이 지정된 변수는 rvalue 참조로 선언되고 rvalue로 초기화되더라도 lvalue가 되기 때문에 “값 범주”에도 영향을 미칩니다. 이는 std::move의 추가 사용을 요구할 수 있으며, 코드 리뷰 시 이동 후 사용(use-after-move) 오류를 피하기 위해 신중을 기해야 합니다. 이러한 단점으로 인해 지역 변수는 특정 이점을 제공하는 경우에만 사용하는 것이 가장 좋습니다.


나쁜 지역 변수 사용 예시

즉시 반환되는 지역 변수 제거

다음과 같은 코드 대신:

MyType value = SomeExpression(args);
return value;

다음과 같이 작성하는 것이 좋습니다:

return SomeExpression(args);

GoogleTest의 EXPECT_THAT에서 표현식을 인라인 처리하기

다음은 나쁜 코드의 예입니다:

std::vector<string> actual = SortedAges(args);
EXPECT_THAT(actual, ElementsAre(21, 42, 63));

여기서 actual이라는 변수명은 유용한 정보를 추가하지 않으며, 복잡한 표현식을 단순화하지 않고, 값이 한 번만 사용됩니다. 아래와 같이 표현식을 인라인 처리하는 것이 더 좋습니다:

EXPECT_THAT(SortedAges(args), ElementsAre(21, 42, 63));

이는 테스트하려는 내용을 한눈에 명확히 보여줄 뿐만 아니라, actual이라는 이름을 사용하지 않음으로써 의도치 않게 다시 사용되는 것을 방지합니다.


테스트에서 Matcher 사용으로 지역 변수 제거

다음과 같은 코드:

std::optional<std::vector<int>> maybe_ages = GetAges(args);
ASSERT_NE(maybe_ages, std::nullopt);
std::vector<int> ages = maybe_ages.value();
ASSERT_EQ(ages.size(), 3);
EXPECT_EQ(ages[0], 21);
EXPECT_EQ(ages[1], 42);
EXPECT_EQ(ages[2], 63);

대신 아래와 같이 작성할 수 있습니다:

EXPECT_THAT(GetAges(args), Optional(ElementsAre(21, 42, 63)));

이 방식은 테스트 의도를 더 직접적으로 표현할 수 있습니다.


좋은 지역 변수 사용 예시

반복되는 표현식 추출

다음과 같은 코드:

myproto.mutable_submessage()->mutable_subsubmessage()->set_foo(21);
myproto.mutable_submessage()->mutable_subsubmessage()->set_bar(42);
myproto.mutable_submessage()->mutable_subsubmessage()->set_baz(63);

에서 반복되는 표현식을 추출하면 코드가 더 간결해지고 가독성이 높아집니다:

SubSubMessage& subsubmessage = *myproto.mutable_submessage()->mutable_subsubmessage();
subsubmessage.set_foo(21);
subsubmessage.set_bar(42);
subsubmessage.set_baz(63);

Pair와 Tuple 요소에 의미 있는 이름 부여

pairtuple 대신 의미 있는 필드 이름을 가진 struct를 사용하는 것이 좋지만, 의미 있는 이름을 가진 별칭을 요소에 바인딩하여 문제를 완화할 수 있습니다. 예를 들어:

for (const auto& name_and_age : ages_by_name) {
  if (IsDisallowedName(name_and_age.first)) continue;
  if (name_and_age.second < 18) children.insert(name_and_age.first);
}

를 다음과 같이 작성할 수 있습니다:

for (const auto& [name, age] : ages_by_name) {
  if (IsDisallowedName(name)) continue;
  if (age < 18) children.insert(name);
}

이로써 가독성이 향상됩니다.


위 내용이 여러분의 C++ 코드 품질 향상에 도움이 되길 바랍니다! 😊