단일 참조를 가진 개인 메소드는 나쁜 스타일입니까?


139

일반적으로 개인 메서드를 사용하여 클래스의 여러 곳에서 재사용되는 기능을 캡슐화합니다. 그러나 때로는 큰 공개 방법을 사용하여 각기 개인 방법으로 작은 단계로 나눌 수 있습니다. 이렇게하면 공용 메서드가 더 짧아 지지만 메서드를 읽는 사람이 다른 개인용 메서드로 이동하면 가독성이 손상 될 것으로 걱정됩니다.

이것에 대한 합의가 있습니까? 긴 공용 메소드를 사용하거나 각 조각을 재사용 할 수없는 경우에도 더 작은 조각으로 나누는 것이 더 낫습니까?



7
모든 답변은 의견을 바탕으로합니다. 원작자는 항상 자신의 라비올리 코드가 더 읽기 쉽다고 생각합니다. 후속 편집자들은 그것을 라비올리라고 부릅니다.
Frank Hileman

5
Clean Code를 읽는 것이 좋습니다. 이 스타일을 권장하고 많은 예제가 있습니다. 또한 "jumping around"문제에 대한 솔루션도 있습니다.
Kat

1
나는 이것이 당신의 팀과 어떤 코드 라인에 달려 있다고 생각합니다.
희망적으로

1
STRUTS의 전체 프레임 워크가 일회용 Getter / Setter 메소드를 기반으로하지 않습니까? 모두 일회용 메소드입니까?
Zibbobz 2016 년

답변:


203

아니요, 이것은 나쁜 스타일이 아닙니다. 사실 그것은 아주 좋은 스타일입니다.

개인 기능은 단순히 재사용 성으로 인해 존재할 필요는 없습니다. 그것이 그것들을 만들어야 할 좋은 이유 중 하나이지만, 또 다른 이유는 분해입니다.

너무 많은 기능을 고려하십시오. 길이는 100 줄이며 추론하기가 불가능합니다.

이 기능을 더 작은 조각으로 나누면 여전히 이전과 같이 많은 작업을 "하지만"더 작은 조각으로 수행합니다. 설명이 필요한 다른 함수를 호출합니다. 주요 함수는 거의 책처럼 읽습니다 : A, B, C 등. 특정 기능은 반드시 다른 기능에서 샌드 박스 처리됩니다. 범위가 다릅니다.

큰 문제를 작은 문제로 분해 할 때 작은 문제 (기능)가 한 번만 사용 / 해결 된 경우에도 몇 가지 이점이 있습니다.

  • 가독성. 아무도 모 놀리 식 함수를 읽고 그 기능을 완전히 이해할 수 없습니다. 계속 자기 자신에게 거짓말을하거나 이해하기 쉬운 한 입 크기의 덩어리로 나눌 수 있습니다.

  • 참조의 지역. 이제 변수를 선언하고 사용하는 것이 불가능 해졌으며, 계속 사용하고 100 줄 후에 다시 사용했습니다. 이러한 기능은 범위가 다릅니다.

  • 테스트. 클래스 의 공개 멤버 를 단위 테스트하는 것만 필요하지만 특정 개인 멤버도 테스트하는 것이 바람직 할 수 있습니다. 테스트에서 이점을 얻을 수있는 긴 함수의 중요한 섹션이 있으면 별도의 함수로 추출하지 않고 독립적으로 테스트 할 수 없습니다.

  • 모듈성. 개인 함수가 있으므로 여기에서만 사용하거나 재사용 가능한지 여부에 관계없이 별도의 클래스로 추출 할 수있는 하나 이상의 함수를 찾을 수 있습니다. 이전에는이 ​​별도의 클래스는 공용 인터페이스가 필요하기 때문에 테스트하기가 더 쉬울 것입니다.

큰 코드를 이해하기 쉽고 테스트하기 쉬운 작은 조각으로 나누는 아이디어는 밥 삼촌의 책 Clean Code의 핵심 입니다. 이 답변을 쓸 당시이 책은 9 살이되었지만 그 당시와 마찬가지로 오늘날에도 관련이 있습니다.


7
메소드 내에서 엿보기가 당신을 놀라게하지 않도록 일관된 추상화 수준과 좋은 이름을 유지하는 것을 잊지 마십시오. 전체 물체가 숨겨져 있어도 놀라지 마십시오.
candied_orange 2016 년

14
때로는 분할 방법이 좋을 수도 있지만 일을 인라인으로 유지하면 이점도 있습니다. 예를 들어, 코드 블록 내부 또는 외부에서 경계 검사를 감지 할 수있는 경우 해당 코드 블록을 인라인으로 유지하면 경계 검사가 한 번, 두 번 이상 수행되는지 여부를 쉽게 확인할 수 있습니다. . 코드 블록을 자체 서브 루틴으로 가져 오면 확인하기가 더 어려워집니다.
supercat

7
"가독성"글 머리 기호는 "추상"으로 표시 될 수 있습니다. 추상화에 관한 것입니다. "물린 크기의 덩어리" 공용 메소드를 읽거나 밟을 때 중요한 세부 사항에 신경 쓰지 않는 하나의 일 을 수행합니다. 그것을 함수로 추상화함으로써 당신은 그것을 넘어서고 큰 것들의 계획 / 공공 방법이 더 높은 수준에서하는 일에 계속 집중합니다.
Mathieu Guindon 2016 년

7
"테스트"글 머리 기호는 의심스러운 IMO입니다. 당신의 개인 회원이 테스트를 원한다면, 그들은 아마도 자신의 반에 공개 회원으로 속해있을 것입니다. 물론 클래스 / 인터페이스를 추출하는 첫 번째 단계는 메소드를 추출하는 것입니다.
Mathieu Guindon 2016 년

6
실제 기능에 신경 쓰지 않고 순수하게 라인 번호, 코드 블록 및 변수 범위로 함수를 분할하는 것은 끔찍한 아이디어이며, 하나의 함수에 그대로 두는 것이 더 나은 대안이라고 주장합니다. 기능에 따라 기능을 분할해야합니다. DaveGauer의 답변을 참조하십시오 . 당신은 당신의 대답 (이름과 문제에 대한 언급과 함께)에서 이것을 암시하고 있지만, 나는이 측면이 질문과 관련하여 중요성과 관련성을 감안할 때 훨씬 더 집중해야한다고 생각합니다.
Dukeling 2016

36

아마 좋은 생각입니다!

긴 선형 동작 시퀀스를 별도의 함수로 분리하여 코드베이스의 평균 함수 길이를 줄이는 데 문제가 있습니다 .

function step1(){
  // ...
  step2(zarb, foo, biz);
}

function step2(zarb, foo, biz){
  // ...
  step3(zarb, foo, biz, gleep);
}

function step3(zarb, foo, biz, gleep){
  // ...
}

이제 실제로 소스 라인을 추가 하고 전체 가독성을 크게 줄였습니다 . 특히 상태를 추적하기 위해 각 함수 사이에 많은 매개 변수를 전달하는 경우. 이케!

그러나 하나 이상의 명확한 목적을 제공하는 순수한 함수로 하나 이상의 행을 추출한 경우 ( 한 번만 호출하더라도 ) 가독성이 향상되었습니다.

function foo(){
  f = getFrambulation();
  g = deglorbFramb(f);
  r = reglorbulate(g);
}

실제 상황에서는 쉽지 않을 수 있지만, 충분히 오래 생각하면 순수한 기능을 사용하는 경우가 종종 있습니다.

동사 이름이 좋은 함수가 있고 부모 함수가 함수를 호출하면 실제로 산문의 단락처럼 읽습니다.

그런 다음 몇 주 후에 더 많은 기능을 추가하고 실제로 그 기능 중 하나를 재사용 할 수 있다는 사실을 알게 된다면 아, 기쁜 기쁨입니다! 놀라운 기쁨!


2
나는이 주장을 몇 년 동안 들었고, 현실에서는 결코 그런 일이 일어나지 않았다. 한곳에서만 호출되는 하나 또는 두 개의 회선 기능에 대해 구체적으로 말하고 있습니다. 원저자는 항상 "더 읽기 쉽다"고 생각하지만, 후속 편집자들은 종종 라비올리 코드라고 부릅니다. 이것은 일반적으로 말하면 통화 깊이에 따라 다릅니다.
Frank Hileman

34
@FrankHileman 공평하게, 나는 어떤 개발자도 그의 전임자의 코드를 칭찬하는 것을 보지 못했습니다.
T. Sar

4
@TSar 이전 개발자는 모든 문제의 근원입니다!
Frank Hileman

18
@TSar 저는 이전 개발자 일 때 이전 개발자를 칭찬 한 적이 없습니다.
IllusiveBrian

3
분해에 대한 좋은 점은 다음과 같이 작성할 수 있다는 것입니다. foo = compose(reglorbulate, deglorbFramb, getFrambulation);;)
Warbo

19

정답은 상황에 따라 크게 다릅니다. @Snowman은 대규모 공공 기능을 파괴 할 때의 긍정적 인 점을 다루고 있지만, 우려하는 바에 따라 부정적인 영향을 미칠 수도 있다는 점을 기억해야합니다.

  • 부작용이있는 많은 개인 함수는 코드를 읽기 어렵고 깨지기 쉽습니다. 이러한 개인 기능이 서로의 부작용에 의존 할 때 특히 그렇습니다. 단단히 결합 된 기능을 피하십시오.

  • 추상화가 새고 있습니다. 작업 수행 방법 또는 데이터 저장 방법을 가장 잘 설명하는 것은 중요하지 않지만 실제로 수행되는 경우가 있으므로이를 식별하는 것이 중요합니다.

  • 의미론과 맥락 문제. 함수가 이름에서하는 일을 명확하게 포착하지 못하면 가독성을 줄이고 공용 함수의 취약성을 다시 증가시킬 수 있습니다. 많은 수의 입력 및 출력 매개 변수로 개인용 함수 작성을 시작할 때 특히 그렇습니다. 그리고 공용 함수에서 호출하는 개인 함수를, 개인 함수에서 호출하는 공용 함수를 볼 수 없습니다. 이로 인해 개인 기능에서 "버그 수정"이 발생하여 공용 기능이 중단 될 수 있습니다.

  • 심하게 분해 된 코드는 다른 사람보다 항상 저자에게 더 명확합니다. 그렇다고해서 여전히 다른 사람들에게 명확하지 않다는 것을 의미하는 것은 아니지만 글을 쓰는 시점 bar()이전 foo()에 호출해야한다고 완벽하게 이해하는 것은 쉬운 일입니다.

  • 위험한 재사용. 귀하의 공공 기능은 각 개인 기능에 대해 가능한 입력을 제한했을 것입니다. 이러한 입력 가정이 제대로 포착되지 않으면 (모든 가정을 문서화하기 어렵다) 누군가가 개인 함수 중 하나를 부적절하게 재사용하여 코드베이스에 버그를 도입 할 수 있습니다.

절대 길이가 아니라 내부 응집력과 커플 링을 기준으로 함수를 분해합니다.


6
가독성을 무시하고 버그보고 측면을 살펴 보겠습니다. 사용자가 버그가 발생했다고보고하는 경우 MainMethod(충분히 쉽게 얻을 수 있으며 일반적으로 기호가 포함되며 기호 역 참조 파일이 있어야 함) 2k 라인 (행 번호는 포함되지 않음) 버그 보고서) 그리고 그것은 그 라인의 1k에서 발생할 수있는 NRE입니다. 잘 살펴보면 저주받을 것입니다. 그러나 그들이 그것이 일어난 일 SomeSubMethodA이고 8 줄이고 예외가 NRE 라고 말하면 , 아마도 그것을 쉽게 찾아서 고칠 것입니다.
410_ 지난

2

복잡성을 해소하기위한 수단으로 코드 블록을 뽑아내는 가치 인 IMHO는 종종 다음과 같은 복잡성의 차이와 관련이 있습니다.

  1. 코드는 무엇의 완전하고 정확한 인간의 언어로 설명 이 코너 케이스를 처리하는 방법을 포함 하고,

  2. 코드 자체

코드가 설명보다 훨씬 복잡한 경우 인라인 코드를 함수 호출로 바꾸면 주변 코드를 더 쉽게 이해할 수 있습니다. 반면에 일부 개념은 인간 언어보다 컴퓨터 언어로 더 읽기 쉽게 표현 될 수 있습니다. w=x+y+z;예를 들어보다 읽기 쉬운 것으로 간주 w=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);합니다.

큰 기능이 분리됨에 따라 하위 기능의 복잡성과 설명에 대한 차이가 점점 줄어들고 추가 세분화의 이점이 줄어 듭니다. 설명이 코드보다 복잡 할 정도로 분할 된 경우, 추가 분할은 코드를 악화시킵니다.


2
함수 이름이 잘못되었습니다. w = x + y + z에는 어떤 종류의 오버플로 제어를 나타내는 것이 없지만 메소드 이름 자체는 그렇지 않은 것처럼 보입니다. 더 좋은 함수 이름은 "addAll (x, y, z)"또는 "Sum (x, y, z)"입니다.
T. Sar

6
개인 메소드 에 대해 이야기하고 있었 으므로이 질문은 C를 언급하지 않을 가능성이 있습니다. 예를 들어, 논리에 따라 재귀 메서드를 "DoThisAssumingStackDoesntOverflow"라고합니다. "FindSubstringAssumingStartingPointIsInsideBounds"도 마찬가지입니다.
T. Sar

1
다시 말해, 기본 가정은 무엇이든 관계없이 (두 숫자가 오버플로되지 않고, 스택이 오버플로되지 않으며, 인덱스가 올바르게 전달됨) 메소드 이름에 존재하지 않아야합니다. 물론, 필요한 경우 메소드 서명에는 문서화되지 않아야합니다.
T. Sar

1
@ TSar : 나는 이름에 약간의 애국심을 가지고 있었지만 핵심 요점은 문맥 상 "(x + y + z + 1) / 3"를 보는 프로그래머는 (더 나은 예이기 때문에 평균으로 전환) 의 정의 또는 호출 사이트를보고있는 사람보다 호출 코드가 주어진 평균을 계산하는 적절한 방법인지 알 수있는 것이 훨씬 좋습니다 int average3(int n1, int n2, int n3). "평균"기능을 분리한다는 것은 요구 사항에 적합한 지 확인하려는 사람이 두 곳을 봐야한다는 것을 의미합니다.
supercat

1
예를 들어 C #을 사용하면 하나의 "평균"방법 만 사용할 수 있습니다.이 방법은 여러 매개 변수를 허용하도록 만들 수 있습니다. 실제로 C #에서는 모든 숫자 모음에서 작동하며 원유 수학을 코드에 입력하는 것보다 이해하기 쉽다고 확신합니다.
T. Sar

2

균형 잡힌 도전입니다.

미세할수록 좋습니다

private 메소드 는 포함 된 코드 의 이름 과 때로는 의미있는 서명을 효과적으로 제공합니다 (매개 변수의 절반이 명확하지 않은 문서화되지 않은 상호 의존성이있는 임시 임시 데이터 가 아닌 경우 ).

이름 이 호출자 에게 의미있는 계약 을 제안 하고 개인 메소드의 계약이 이름이 제안하는 것과 정확하게 일치하는 한 , 코드 구조에 이름을 부여하는 것이 일반적으로 좋습니다 .

코드의 작은 부분에 대한 의미있는 계약을 스스로 생각하게함으로써 초기 개발자는 일부 테스트를 수행하기 전에도 몇 가지 버그를 발견 하고이를 피할 수 있습니다. 이것은 개발자가 간결한 이름 지정을 위해 노력하고 있고 (간단한 소리지만 정확함) 간결한 이름 지정이 가능하도록 개인 방법의 경계를 기꺼이 조정하려는 경우에만 작동합니다.

추가적인 이름 지정은 코드 자체 문서 작성에 도움이되므로 후속 유지 보수에도 도움이됩니다 .

  • 작은 코드 조각을 통한 계약
  • 상위 레벨의 메소드는 때때로 짧은 호출 순서로 바뀌며, 각 호출은 일반적이고 가장 바깥 쪽 오류 처리의 얇은 계층과 함께 중요하고 뚜렷한 이름의 무언가를 수행합니다. 이러한 방법은 모듈의 전체 구조에 대해 빠르게 전문가가되어야하는 사람에게는 소중한 교육 리소스가 될 수 있습니다.

너무 좋아질 때까지

작은 코드 덩어리에 이름을 부여하고 너무 많은 너무 작은 개인 메소드로 끝날 수 있습니까? 확실한. 다음 증상 중 하나 이상이 방법이 너무 작아서 유용하지 않음을 나타냅니다.

  • 동일한 필수 논리에 대한 대체 서명을 나타내지 않고 단일 고정 호출 스택을 나타내는 과부하가 너무 많습니다.
  • 동일한 개념을 반복적으로 참조하기 위해 사용되는 동의어 ( "이름 지정")
  • XxxInternal또는 과 같은 많은 기능성 명명 장식 DoXxx, 특히 그것들을 도입하는 데 통일 된 계획이없는 경우.
  • 서투른 이름은 구현 자체보다 거의 길다. LogDiskSpaceConsumptionUnlessNoUpdateNeeded

1

다른 사람들이 말한 것과는 달리, 긴 공개 방법은 개인 방법으로 분해되어 수정 되지 않는 디자인 냄새라고 주장합니다 .

그러나 때로는 작은 단계로 나눌 수있는 큰 공개 방법이 있습니다.

이 경우 각 단계는 각자 책임이있는 일류 시민이어야한다고 주장합니다. 객체 지향 패러다임에서는 각 단계마다 쉽게 식별 할 수있는 단일 책임을 가지며 책임이 분명한 방식으로 이름을 지정할 수 있도록 인터페이스와 구현을 만드는 것이 좋습니다. 이를 통해 (이전의) 대규모 공용 방법과 각 개별 단계를 서로 독립적으로 단위 테스트 할 수 있습니다. 그것들도 모두 문서화되어야합니다.

왜 개인 메소드로 분해되지 않습니까? 몇 가지 이유는 다음과 같습니다.

  • 긴밀한 결합 및 테스트 가능성. 공용 메소드의 크기를 줄이면 가독성이 향상되었지만 모든 코드는 여전히 밀접하게 결합됩니다. 테스트 프레임 워크의 고급 기능을 사용하여 개별 개인용 메소드를 개별적으로 테스트 할 수 있지만 개인용 메소드와 독립적으로 공용 메소드를 쉽게 테스트 할 수는 없습니다. 이것은 단위 테스트의 원칙에 위배됩니다.
  • 수업 규모와 복잡성. 메서드의 복잡성을 줄 였지만 클래스의 복잡성을 높였습니다. public 메소드는 읽기가 쉽지만 클래스의 동작을 정의하는 함수가 많기 때문에 클래스를 읽기가 더 어렵습니다. 제가 선호하는 것은 작은 단일 책임 클래스에 대한 것이므로 긴 방법은 클래스가 너무 많은 일을하고 있다는 신호입니다.
  • 쉽게 재사용 할 수 없습니다. 코드 본문이 성숙할수록 재사용 성이 유용한 경우가 종종 있습니다. 단계가 개인용 메소드 인 경우 먼저 추출하지 않고 다른 곳에서는 재사용 할 수 없습니다. 또한 다른 단계가 필요한 경우 복사 붙여 넣기를 권장 할 수 있습니다.
  • 이러한 방식으로 분할하는 것은 임의적 일 수 있습니다. 긴 공개 방법을 분할하는 것이 책임을 클래스로 나누는 것처럼 많은 생각이나 디자인 고려 사항을 취하지 않는다고 주장합니다. 각 클래스는 적절한 이름, 문서 및 테스트로 정당화되어야하지만 개인 메서드는 그다지 고려되지 않습니다.
  • 문제를 숨 깁니다. 그래서 당신은 공개 방법을 작은 개인 방법으로 나누기로 결정했습니다. 이제 아무런 문제가 없습니다! 점점 더 많은 전용 메소드를 추가하여 더 많은 단계를 계속 추가 할 수 있습니다! 반대로, 나는 이것이 중요한 문제라고 생각합니다. 후속 버그 수정 및 기능 구현이 뒤따를 클래스에 복잡성을 추가하기위한 패턴을 설정합니다. 머지 않아 당신의 개인적인 방법이 커져서 나누어 져야 것입니다.

하지만 방법을 읽는 사람이 다른 개인 방법으로 이동하면 가독성이 손상 될까 걱정됩니다.

이것은 최근 동료 중 한 사람과 함께 한 논쟁입니다. 그는 동일한 파일 / 방법으로 모듈의 전체 동작을 수행하면 가독성이 향상된다고 주장합니다. 모든 코드를 함께 사용 하면 코드를 따라 가기 가 더 쉽지만 , 복잡성이 커짐에 따라 코드를 추론 하기가 쉽지 않습니다 . 시스템이 성장함에 따라 전체 모듈을 전체적으로 추론하기가 어렵습니다. 복잡한 논리를 단일 책임을 갖는 여러 클래스로 각각 분해하면 각 파트에 대해 추론하기가 훨씬 쉬워집니다.


1
동의하지 않을 것입니다. 너무 많거나 조기에 추상화하면 불필요하게 복잡한 코드가 생성됩니다. 비공개 방법은 인터페이스와 추상화가 필요할 수있는 경로의 첫 번째 단계 일 수 있지만 여기서는 접근 할 필요가없는 곳을 돌아 다니는 것이 좋습니다.
WillC

1
나는 당신이 어디에서 왔는지 이해하지만 OP가 요구하는 것과 같은 코드 냄새 는 더 나은 디자인을위한 준비 되어 있다는 신호입니다 . 조기 추상화는 이러한 문제가 발생하기 전에 이러한 인터페이스를 설계하는 것입니다.
Samuel

이 접근법에 동의합니다. 실제로 TDD를 따르면 자연스럽게 진행됩니다. 다른 사람은 너무 이른 추상화라고 말하지만 테스트를 통과하기 위해 실제로 개인 메서드로 구현 해야하는 것보다 별도의 클래스 (인터페이스 뒤에있는)에서 필요한 기능을 신속하게 조롱하는 것이 훨씬 쉽다는 것을 알게되었습니다 .
Eternal21
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.