[토론] 12장 도우미 추출

책 12장을 읽고 다양한 의견을 댓글로 남겨 주세요.

photo
유영모

솔루션 회사를 시작으로 컨설팅 회사 그리고 서비스 회사를 거쳐 지금은 스타트업에서 개발자로 일하고 있습니다. 주로 오픈소스 제품 개발, 기술 자문 및 코칭을 하고 있으며 기술과 인간의 상호작용에 관심이 많습니다.

댓글 5

img

nimkoes

2024-10-24 14:12 (수정됨)

과도한 메서드 추출

메서드 추출을 과도하게 사용하면, 코드가 잘게 나뉘고 참조해 들어가는 메서드가 많아지기 때문에 오히려 코드를 이해하기 어려워질 수 있습니다. 지난번 인간이 단기적으로 기억할 수 있는 청크(chunk)가 4개 정도인 것으로 미루어 보았을 때, 과도한 메서드 추출은 지양해야 겠습니다. 그래서 적절한 수준에서 메서드를 추출해야 하고, 추상화 수준이 일관되게 유지되도록 하는 것이 중요하다고 생각했습니다.


추출한 메서드 이름 짓기

메서드를 추출할 때, 그 메서드의 이름을 짓는 것도 매우 중요하다고 생각 했습니다. 그 메서드가 '어떻게' 동작하는지가 아닌, '왜 필요한지' 표현할 수 있어야 합니다.
 
예를 들어, 할인 금액을 계산하는 메서드를 추출했다고 가정 해보겠습니다.

// '어떻게' 관점에서 지은 메서드 이름
private double subtractDiscountFromTotal(double total, double discount) {
    return total - discount;
}

이름을 보면 알겠지만, '어떻게 계산하는지' 를 표현하는 이름 입니다.
 
다음은 '왜 필요한지' 관점에서 메서드 이름을 지어본 예시입니다.

// '왜 필요한지' 관점에서 지은 메서드 이름
private double applyDiscount(double total, double discount) {
    return total - discount;
}

이름에서 알 수 있듯, 할인 금액이 '어떻게' 계산 되는지가 아닌 '할인을 적용한다' 는 존재 이유를 표현하고 있습니다.
 
더 나아가 이것을 '변경' 관점에서도 생각해볼 수 있습니다.
'왜 필요한지' 의 관점에서 이름을 지으면, 메서드의 '목적'을 나타내기 때문에 내부 구현이 바뀌어도 이름을 유지할 수 있지만, '어떻게' 관점에서 지은 이름은 계산 방법이 바뀌면 이름도 같이 바뀌어야 합니다. 그렇기 때문에 유지 보수 관점에서 보다 유연한 메서드 이름을 지을 수 있습니다.
 
따라서 이름만 보고도 그 메서드가 어떤 목적을 위해 존재하는지 알 수 있도록 이름을 짓는 것이 중요하다고 생각합니다.


재사용 여부와 관계없이 도우미 추출의 가치는 있다

메서드를 추출하는 이유 중 하나는 재사용성을 높이기 위함도 있지만, 재사용 하지 않더라도 도우미 메서드를 추출하는 것 자체만으로 가독성이 좋아질 수 있습니다. 그래서 비록 한 번만 사용되더라도, 도우미를 추출하는 것은 가치가 있다고 생각 했습니다. 또한 메서드 추출로 책임을 명확히 분리했기 때문에, 뒷통수(?) 맞는 일을 방지할 수 있습니다.
 

도우미 메서드를 없애고 바라보면, 보이지 않던 코드 구조를 발견할 수도 있다

가끔은 도우미 메서드를 없애고 전체적인 코드를 다시 바라볼 필요가 있다고 생각했습니다. 특히 습관적으로 추출한 경우, 또는 시간이 지나 추출한 메서드 구현이 복잡해진 경우 유효한 방법이 될 수 있겠습니다. 추출한 메서드를 인라인에 포함 시키는 과정에서 도우미 메서드가 정말 필요했는지 다시 한 번 고민할 수 있습니다. 경우에 따라서는 도우미를 없애는 것이 더 읽기 편할 수도 있고, 보다 응집도 있게 추출 할 메서드를 발견하는 데 도움이 될 수 있겠다고 생각 했습니다.


도우미 함수는 결과를 반환하는 것이 좋다

도우미 함수는 가능하면 결과를 반환하도록 작성하는 것이 좋겠다고 생각 했습니다. 참조하고 있는 값을 직접 변경하면 그 값이 어떻게 변경되었는지 추적하기 어렵기 때문입니다. 반환값을 명확하게 처리하는 방식은 코드의 예측 가능성을 높이고, 부작용을 줄이는 데 도움이 된다고 생각합니다.

img

Sunghoon Park

2024-10-28 12:04

공감이 많이 되네요~!
저도 비슷한 의견들을 가지고 있는듯 해요!


언급 주신 과도한 메서드 추출 관련해서 궁금한 것이 있습니다.
과도한 메서드는 깊이 관점에서 고민하시나요, 아니면 하나의 메서드에서 부르고 있는 있는 추상화된 메서드의 개수 관점에서 고민하시나요?

img

정승주

2024-10-28 13:31

메서드 추출을 과도하게 사용하면, 코드가 잘게 나뉘고 참조해 들어가는 메서드가 많아지기 때문에 오히려 코드를 이해하기 어려워질 수 있습니다.


혹시 어떤 코드인지 예시를 한 번 들어봐주실 수 있으실까요?

img

nimkoes

2024-10-29 09:31

@Sunghoon Park 둘 다 고려 해야겠지만, 깊이 관점에서 더 많은 고민을 하는 편입니다
개수 관점에서는 '이거를 추출하는게 정말 도움이 될까?' 라거나, 많아지면 '이거 얘 혼자 다 해도 되나?' 고민을 합니다 : )

img

nimkoes

2024-10-29 09:51 (수정됨)

@정승주 이게 잘 표현 될지 모르겠지만,

  1. 메소드 추출을 하면서 깊이가 깊어 지다가 갑자기 추상화 수준이 올라가는 경우
  2. 너무 추출을 많이 해서 코드를 읽다가 길을 잃는 경우
    를 생각 했습니다.

GPT 도움을 받은 예시 입니다 : )


사용자_데이터_처리_서비스/

└── 사용자_데이터_처리()
 ├── 사용자_인증_확인()
 │ └── 세션_검증()
 │  └── 만료_시간_계산()
 │
 ├── 사용자_데이터_정제()
 │ └── 필드_검증()
 │  └── 필수_필드_확인()
 │   └── 기본값_적용()
 │
 └── 사용자_데이터_저장()
  ├── 데이터_중복_검사()
  │ └── 사용자_ID_매칭()
  └── 데이터_삽입()
   ├── SQL_쿼리_작성()
   └── 비즈니스_정책_적용() <-- 추상화 레벨 급상승
    ├── 프로모션_포인트_적립()
    └── 사용자_알림_발송()

img

[yeTi] 예티

2024-10-25 04:25

목적이 분명하고 나머지 코드와 상호작용이 적은 코드는 도우미로 추출하자


Hepler 개념을 언제 적용해야하는지 궁금해오던 차에 드디어 알게 될 것 같은 느낌이 들어 흥미롭습니다.


코드를 보다가 루틴 속 코드 중에서, 목적이 분명하고 나머지 코드와는 상호작용이 적은 코드 블록을 만날 때가 있습니다. 그 코드 블록을 추려내고, 도우미로 추출한 후에 이름을 붙입니다. - p.60


켄트 벡은 목적이 분명하고 나머지 코드와 상호작용이 적은 코드를 도우미로 추출하라고 말합니다.


11장 비슷한 코드끼리에서 말한 빈줄로 개념을 나누는 것과 연관된 내용이라는 생각이 들었습니다.


문득 상호작용이 적다는 판단을 어떻게 할 수 있을지 궁금해집니다.


비슷한 코드끼리 묶여있을테니 반줄로 구분이 될 것인데, 빈줄간(문단간) 상호작용을 판단해 본 적은 없다는 생각이 들었습니다.


이전에 유틸 클래스를 만들 때도 직관적으로 기능 목적의 것들을 묶는 개념으로 사용했기 때문입니다.


또 다른 특수 사례로 함수간 선후 관계가 있을 경우 도우미로 추출하라고 말합니다.


도우미 추출의 또 다른 특수 사례는 시간적 결합을 표현하는 경우입니다(a()가 b()보다 앞서 호출되어야 하는 경우). 다음과 같은 코드입니다. - p.61


‘‘‘
foo.a()
foo.b()

ab()
a()
b()
’’’


시간적 의존성을 함께 묶는다는 의미로는 좋다고 생각하니다. 그러나 경험적으로 이런 경우는 대부분 도메인 로직인 경우가 많았어서 실무적으로 적용할 수 있는 케이스가 궁금해집니다.


가령, 푸시도 보내고 이메일도 보내는 경우일까요?

img

Sunghoon Park

2024-10-28 12:16

저는 상호작용이 작다는 것을 특정 블럭에서 쓰이는 값들을 보고 판단하고 있습니다.


특정 블럭이 어떤 블럭의 결과물을 많이 참조하여 사용하거나,
그 블럭의 결과물에 영향을 준다면 둘 블럭 간에는 상호작용이 있다고 생각합니다.
그 반대편에서는 특정 블럭이 만든 결과물을 사용만 하는 경우가 있을 것 같아요.


이 상황일 때, 함수의 문맥과 추상화 레벨에서 그 둘이 다른 단위라면 도우미 함수로 추출하고 있습니다.
그리고 시간적 결합 처럼 순서가 중요한 경우에도 추출하는 경우들이 있는 것 같습니다.


언급주신 시간적 의존성 관련은 도메인 로직을 저 형태로 만들면 되지 않을까요?
도메인 로직 또한 도우미 메서드 추출의 대상이라 생각이 들어서요!

img

Sunghoon Park

2024-10-28 12:18

'도우미'라고 해서 'xxxHelpService' 같은 것이 떠올라서 부정적인 감정이 솟구쳤었습니다. 🙀

img

Sunghoon Park

2024-10-28 12:40 (수정됨)

공백으로 나눴으니 이번엔..


전 챕터에서 공백으로 맥락을 나눈 것을 의미 있게 분리해 보는 챕터였습니다.


사실 저는 method 내에 유사 수준으로 추상화된 private method의 시그니처들를 통해
그 메서드의 문맥을 목적 관점에서 서술적으로 표현하는 것을 좋아해서 이번 챕터가 굉장히 자연스럽게 이해된 것 같아요.
(덕분에 켄트백은 이런걸 도우미 메서드라고 부른다는 것을 알았습니다.)


통상적으로 최상위 함수에서 사용된 차상위 추상화 private method들은
새로운 책임을 가지는 클래스로 분리되는 경험을 많이 해본 것 같습니다.


이런 것들이 많이 일어나는 경우에는 최상위 메서드를 가지는 클래스는 다른 클래스에게 메시지를 전달하여 위임하고, 그 결과에 따라 또 다른 클래스에게 처리를 위임하는, '조율자' 책임을 가지는 클래스가 되더라구요.
(보통 UseCase를 구현하는 인터페이스에서 제일 많이 겪어본 것 같아요)


시간적 결합


이부분도 굉장히 공감 되는 부분이었습니다.


시간적 결합이 중요한 코드들은 그 둘간에 아무런 데이터 참조나 심볼 참조가 없더라도
그 순서만 바꿔도 문제를 일으킬 가능성들을 가지게 되어 문제을 일으킬 확률이 존재합니다.
오히려 둘 간의 관계를 잘 모르면 별 생각 없이 순서를 바꿔서 문제가 되기도 하지요.


저는 이런 코드는 절대 외부에서 순서를 통제할 수 없게 만드는 편입니다. 책에서 언급한 foo.a(), foo.b()같은 함수를 외부에 오픈하지 않고, foo.ab()라는 함수만 오픈함으로서 순서를 바꾸고 싶어도 바꿀 수 없게 만드는 것이죠.


private method 내에서도 순서가 중요하다면 이런 기조를 유지합니다. 비록 모두가 private method 이기 때문에 접근을 할 수 있지만, 이런 함수가 있다는 것만으로도 순서에 대한 경각심을 줄 수 있고, 표현이 잘 되지 않으면 주석으로라도 남겨볼 여지를 둘 수 있으니까요~!

img

정승주

2024-10-28 13:32

비록 모두가 private method 이기 때문에 접근을 할 수 있지만


이게 무슨 뜻인지 알 수 있을까요?

img

유영모

2024-10-29 10:40

시작은 클린코드의 함수 규칙에 따르고 여집합에 도우미 추출?

저는 함수를 만들 때 로버트 마틴이 클린 코드에서 말하는 두 가지 규칙을 좋아합니다.

  • 작게 만들어라
  • 한 가지만 해라


그렇게 큰 함수를 작게 쪼개다 보면 자연스럽게 리펙토링 '메소드 추출'이 일어나는 것을 경험합니다.
위의 규칙에 따라 함수를 만드는 과정에 '도우미 추출'이라는 것이 포함되어 있다는 느낌이 들었습니다.


다만. 켄트 벡이 말하는 도우미 즉 "목적이 분명하고 나머지 코드와는 상호작용이 적은 코드 블록"을 구분하는 것이 어떤 도움이 될지는 해보지 않아 불명확해 보입니다.

하고 자유롭게 의견을 남겨 주세요.