Testing & Code Quality

[코드 냄새 #3] 라비올리 코드, 흐름을 아무도 모르는 코드

함수는 전부 다섯 줄이고 책임도 하나씩인데 버튼을 누르면 무슨 일이 벌어지는지 아무도 답하지 못하는 코드가 라비올리 코드입니다. 쪼개기가 과해지는 이유와 점프 횟수로 드러나는 비용, 흐름을 되찾는 법을 정리했습니다.

5분 읽기
[코드 냄새 #3] 라비올리 코드, 흐름을 아무도 모르는 코드 대표 이미지

코드 리뷰에서 이런 상황이 있습니다. 파일을 열어 보면 함수가 전부 다섯 줄 이내입니다.

이전 글 코드 냄새 #2에서 이어지는 내용입니다.

이름도 잘 붙어 있고 클래스마다 책임이 하나씩만 있습니다. 지적할 게 없습니다.

그런데 “이 버튼을 누르면 무슨 일이 일어나나요”라는 질문에 아무도 바로 답을 못 합니다.

이런 코드를 라비올리 코드(Ravioli Code)라고 부릅니다(C2 원문).

흉만은 아니었습니다

재미있게도 이 말이 늘 흉으로만 쓰이지는 않았습니다. 라비올리는 작은 덩어리 하나하나가 속을 잘 감싼 파스타입니다.

잘 캡슐화된 작은 객체들, 그러니까 객체지향이 지향하던 모습 그 자체죠. 실제로 스파게티 코드의 대척점으로 라비올리를 드는 용례도 있습니다.

문제는 개수입니다.

덩어리 하나는 완벽한데 접시에 200개가 담겨 있고 어떤 순서로 어떻게 이어지는지는 어디에도 적혀 있지 않습니다.

구체적으로 이런 모습입니다.

final class CheckoutCoordinator {
    func start() { validator.validate(cart) }
}

final class CartValidator {
    func validate(_ cart: Cart) { stockChecker.check(cart.items) }
}

final class StockChecker {
    func check(_ items: [Item]) { priceCalculator.calculate(items) }
}

final class PriceCalculator {
    func calculate(_ items: [Item]) { paymentPreparer.prepare(items) }
}
// ... 이런 클래스가 열여섯 개 더 있습니다

각 클래스는 흠잡을 데가 없습니다. 이름도 정확하고 하는 일도 하나입니다.

그런데 결제 흐름 전체를 아는 파일이 하나도 없습니다.

흐름은 클래스들 사이의 호출 관계로만 존재하고 그건 코드를 읽어서가 아니라 디버거를 붙여야 알 수 있습니다.

쪼개기는 왜 과해지나

“함수는 짧을수록 좋다”를 규칙으로 받아들인 경우. 『클린 코드』는 함수가 두세 줄, 길어야 네 줄이어야 한다고까지 말합니다.

이 조언이 실제로 겨냥한 건 한 함수가 한 가지 추상화 수준만 다루게 하는 쪽에 가깝습니다. 그런데 줄 수라는 숫자로 옮겨지면서 목적이 사라집니다.

결과는 다섯 줄짜리 함수 서른 개고 그중 스물여덟 개는 딱 한 곳에서만 호출됩니다.

이름이 내용을 요약하지 못하는 경우. handleUserAction, processData, updateState 같은 이름은 무엇을 하는지 알려주지 않습니다.

이런 이름이 붙은 함수로 쪼개면 읽는 사람은 결국 본문을 열어 봐야 하고 쪼갠 만큼 열어 볼 곳이 늘어납니다.

좋은 추출은 본문을 안 봐도 되게 만드는 것인데 그 반대가 됩니다.

추상화 고도가 섞인 경우. 한 함수 안에 “주문을 검증한다” 같은 정책 수준 문장과 “인덱스를 1 증가시킨다” 같은 세부 수준 문장이 섞여 있습니다. 그러면 읽는 사람의 시선이 계속 오르내립니다.

이 상태에서 무작정 쪼개면 고도가 뒤죽박죽인 조각이 생깁니다.

한 번 쓰이는 코드를 재사용 대비로 뺀 경우. 라자냐 코드를 만든 것과 같은 심리입니다. 방향만 세로에서 가로로 바뀌었습니다.

체인처럼 이어진 라비올리 클래스 구조와 오케스트레이터 함수 구조 비교 다이어그램
흐름을 적어 두는 자리 하나면 상당 부분 해결됩니다

비용은 점프 횟수로 나타납니다

라비올리 코드의 비용은 한 문장으로 요약됩니다. 질문 하나에 답하기 위해 파일을 몇 번 여는가.

코드를 읽을 때 사람은 짧은 기간 기억에 흐름을 담아 둡니다. 이 기억은 서너 개를 넘어가면 흐려집니다.

파일을 여섯 번 점프하고 나면 처음에 뭘 찾으려 했는지가 흐릿해지고 다시 처음으로 돌아갑니다.

이게 반복되면 코드를 읽는 대신 실행해서 확인하는 쪽이 빨라집니다.

부작용도 따라옵니다.

  • 새 기능을 넣을 때 기존 조각을 못 찾아 비슷한 걸 하나 더 만듭니다. 중복이 조용히 늘어납니다.
  • 버그 수정 지점을 못 찾아서 흐름의 끝단, 그러니까 화면 쪽에 임시 처리를 붙입니다.
  • 흐름 전체가 코드에 안 적혀 있으니 문서나 사람 머릿속에만 남습니다. 그 사람이 팀을 떠나면 같이 사라집니다.

흐름을 되찾는 법

흐름을 적는 자리를 하나 만듭니다. 가장 효과가 큰 조치입니다.

전체 순서를 한눈에 보여주는 함수 하나를 두고 그 함수는 순서만 말하게 합니다.

func checkout(_ cart: Cart) async throws -> Receipt {
    try validate(cart)
    try await reserveStock(cart.items)
    let amount = calculateTotal(cart)
    let payment = try await charge(amount)
    return try await confirm(cart, payment)
}

세부는 여전히 각자의 자리에 있습니다. 달라진 건 흐름이 한 화면에 적혀 있다는 점입니다.

이런 함수를 오케스트레이터라고 부르기도 합니다. 라비올리 코드에 빠진 게 정확히 이 자리입니다.

한 층에는 한 고도만 둡니다. 위 함수의 다섯 줄은 전부 같은 높이의 문장입니다. 여기에 items.count > 0 같은 세부 조건이 끼어들면 고도가 깨집니다.

함수 하나를 읽었을 때 문장들이 같은 눈높이인지 확인하는 습관이 쪼개기 기준보다 유용합니다.

추출 여부는 이름으로 결정합니다. 기준은 줄 수가 아니라 이 질문입니다. “이 조각에 붙일 이름이 본문보다 더 많은 걸 말해 주는가.”

if user.age >= 19를 isAdult(user)로 빼면 의도가 드러나니 이득입니다. array.append(item)을 addItem으로 빼면 아무것도 얻지 못합니다.

가까운 것은 가까이 둡니다. 함께 바뀌는 코드는 같은 파일, 같은 폴더에 둡니다.

파일 하나당 타입 하나라는 관례를 지키느라 늘 같이 열리는 타입들이 흩어져 있다면, 관례보다 근접성이 낫습니다.

서비스 단위에도 같은 기준이 적용됩니다. 마이크로서비스를 지나치게 잘게 나눈 상태를 나노서비스라고 부릅니다.

요청 하나를 처리하려고 서비스 열두 개를 거치는 구조는 라비올리 코드가 네트워크 너머로 확장된 형태입니다. 이 경우 점프 비용에 지연 시간과 장애 지점까지 얹힙니다.

스파게티·라자냐·라비올리 세 가지 코드 냄새의 구조를 나란히 비교한 이미지
셋 다 조각 단위로는 만점입니다

라자냐와 라비올리, 그리고 스파게티

세 가지를 한자리에 놓으면 이렇게 정리됩니다.

형태 조각의 상태 문제
스파게티 크고 얽혀 있음 실행 흐름을 예측할 수 없음
라자냐 세로로 층층이 변경 하나에 여러 층을 관통해야 함
라비올리 잘고 깔끔함 조각 사이의 흐름이 어디에도 없음

셋 모두 전체를 파악하는 비용이 큽니다. 방식만 다를 뿐입니다.

코드 품질을 조각 하나하나의 아름다움으로만 재면 라자냐와 라비올리는 잡히지 않습니다. 둘 다 조각 단위로는 만점이니까요.

정리하면

  • 라비올리 코드는 작고 깔끔한 조각이 너무 많아 전체 흐름을 아무도 설명하지 못하는 코드입니다.
  • 캡슐화가 잘된 코드를 가리키는 긍정적 용례로도 쓰였습니다. 개수 때문에 문제가 생깁니다.
  • 줄 수 규칙, 모호한 이름, 뒤섞인 추상화 고도가 주된 원인입니다.
  • 처방의 핵심은 흐름을 적어 두는 자리입니다.
  • 추출 기준은 길이가 아니라 이름입니다. 이름이 본문보다 많은 걸 말해 줄 때만 빼냅니다.

다음 편은 정반대 극단입니다. 조각이 너무 많은 게 아니라 하나뿐인 경우.

클래스 하나가 앱 전체를 아는 갓 오브젝트를 봅니다.

출처 및 확인 기준

  • Ravioli Code — C2 Wiki · 저자 원문 · 확인 2026-08-17 · 근거: 작고 캡슐화된 객체가 지나치게 분절되는 라비올리 코드 은유

이어서 읽기