원문: https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html (Translated by Google Gemini)


과도하게 중첩된 코드는 가독성을 해치고 오류를 발생시키기 쉽습니다. 아래 두 버전의 코드에서 버그를 찾아보세요.

과도하게 중첩된 코드:

response = server.Call(request)
 
if response.GetStatus() == RPC.OK:
  if response.GetAuthorizedUser():
    if response.GetEnc() == 'utf-8':
      if response.GetRows():
        vals = [ParseRow(r) for r in 
                response.GetRows()]
        avg = sum(vals) / len(vals)
        return avg, vals
      else:
        raise EmptyError()
    else:
      raise AuthError('unauthorized')
  else:
    raise ValueError('wrong encoding')
else:
  raise RpcError(response.GetStatus())

중첩이 적은 코드:

response = server.Call(request)
 
if response.GetStatus() != RPC.OK:
  raise RpcError(response.GetStatus())

if not response.GetAuthorizedUser():
  raise ValueError('wrong encoding')

if response.GetEnc() != 'utf-8':
  raise AuthError('unauthorized')
 
if not response.GetRows():
  raise EmptyError()

vals = [ParseRow(r) for r in 
        response.GetRows()]
avg = sum(vals) / len(vals)
return avg, vals

정답: “wrong encoding"과 “unauthorized” 에러가 서로 바뀌었습니다. 이 버그는 리팩터링된 버전에서 더 쉽게 발견할 수 있는데, 그 이유는 검사가 에러를 처리하는 부분 바로 옆에서 일어나기 때문입니다.

위에 보인 리팩터링 기법은 보호 구문(guard clauses) 으로 알려져 있습니다. 보호 구문은 특정 기준을 확인하고, 만약 충족되지 않으면 빠르게 실패(fail fast)시킵니다. 이 기법은 계산 로직을 에러 로직으로부터 분리합니다.

에러 확인과 처리 사이의 인지적 간극을 제거함으로써, 정신적인 처리 능력을 확보할 수 있습니다. 결과적으로, 리팩터링된 버전은 읽고 유지보수하기가 훨씬 쉬워집니다.

코드의 중첩을 줄이기 위한 몇 가지 경험 법칙은 다음과 같습니다.

  • 조건부 블록은 짧게 유지하세요. 코드를 지역적으로 유지하여 가독성을 높입니다. 반복문이나 분기문이 2단계보다 깊어지면 리팩터링을 고려해 보세요.
  • 중첩된 로직을 별도의 함수로 옮기는 것을 생각해보세요. 예를 들어, 각각이 리스트를 포함하는 객체들의 리스트를 순회해야 한다면(프로토콜 버퍼의 반복 필드처럼), 이중 중첩 반복문을 사용하는 대신 각 객체를 처리하는 함수를 정의할 수 있습니다.

중첩을 줄이면 더 읽기 쉬운 코드가 되고, 이는 버그 발견, 더 빠른 개발자 반복 작업, 그리고 안정성 증가로 이어집니다. 가능할 때마다, 단순화하세요!