메서드에 많은 인수를 전달하는 모범 사례?


94

때때로, 우리는 많은 인수를받는 메소드를 작성해야합니다. 예를 들면 다음과 같습니다.

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

이런 종류의 문제가 발생하면 종종 인수를지도에 캡슐화합니다.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

이것은 좋은 습관이 아닙니다. 매개 변수를 맵으로 캡슐화하는 것은 완전히 효율성을 낭비하는 것입니다. 좋은 점은 깨끗한 시그니처이며 최소한의 수정으로 다른 매개 변수를 쉽게 추가 할 수 있다는 것입니다. 이런 종류의 문제에 대한 모범 사례는 무엇입니까?

답변:


139

효과적인 자바 , 7 장 (방법), 항목 40 (디자인 방법 서명 조심스럽게), 블로흐의 글 :

지나치게 긴 매개 변수 목록을 줄이는 세 가지 기술이 있습니다.

  • 메서드를 여러 메서드로 나눕니다. 각 메서드에는 매개 변수의 하위 집합 만 필요합니다.
  • 매개 변수 그룹 (일반적으로 정적 멤버 클래스)을 보유하기위한 도우미 클래스 생성
  • 객체 생성에서 메소드 호출로 빌더 패턴 을 적용합니다 .

자세한 내용은 책을 구입하는 것이 좋습니다. 그만한 가치가 있습니다.


"과도하게 긴 매개 변수"란 무엇입니까? 메서드에 매개 변수가 너무 많다고 말할 수있는 경우는 언제입니까? 특정 숫자 또는 범위가 있습니까?
Red M

2
@RedM 저는 항상 3 개 또는 4 개 이상의 매개 변수를 "과도하게 긴"것으로 간주했습니다
jtate

2
@jtate는 개인적인 선택입니까 아니면 공식 문서를 팔로우하고 있습니까?
Red M

2
@RedM 개인 취향 :
jtate

2
Effective Java의 세 번째 버전에서는 8 장 (방법), 항목 51
GarethOwen 19

71

마법의 문자열 키가있는 맵을 사용하는 것은 나쁜 생각입니다. 컴파일 시간 검사를 잃고 필요한 매개 변수가 무엇인지 정말 불분명합니다. 이를 보완하려면 매우 완전한 문서를 작성해야합니다. 몇 주 안에 코드를 보지 않고도 문자열이 무엇인지 기억할 수 있습니까? 오타를 만들면 어떻게 되나요? 잘못된 유형을 사용하십니까? 코드를 실행할 때까지 알 수 없습니다.

대신 모델을 사용하십시오. 모든 매개 변수에 대한 컨테이너가 될 클래스를 만듭니다. 그렇게하면 Java의 유형 안전성을 유지할 수 있습니다. 해당 객체를 다른 메소드로 전달하거나 컬렉션에 넣을 수도 있습니다.

물론 매개 변수 집합이 다른 곳에서 사용되지 않거나 전달되지 않으면 전용 모델이 과도해질 수 있습니다. 쳐야 할 균형이 있으므로 상식을 사용하십시오.


24

선택적 매개 변수가 많으면 유창한 API를 만들 수 있습니다. 단일 메서드를 메서드 체인으로 대체

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

정적 가져 오기를 사용하여 내부 유창한 API를 만들 수 있습니다.

... .datesBetween(from(date1).to(date2)) ...

3
모든 매개 변수가 필수이고 선택 사항이 아니라면 어떻게됩니까?
emeraldhieu

1
이 방법으로 기본 매개 변수를 가질 수도 있습니다. 또한 빌더 패턴 은 유창한 인터페이스와 관련이 있습니다. 이것이 정말로 답이되어야한다고 생각합니다. 긴 생성자를 선택적인 더 작은 초기화 메서드로 분해하는 것 외에도.
Ehtesh Choudhury

13

"Introduce Parameter Object"라고합니다. 여러 위치에 동일한 매개 변수 목록을 전달하는 경우 모두를 보유하는 클래스를 작성하십시오.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

동일한 매개 변수 목록을 너무 자주 전달하지 않더라도 쉽게 리팩토링하면 코드 가독성이 향상되며 이는 항상 좋습니다. 3 개월 후 코드를 살펴보면 버그를 수정하거나 기능을 추가해야 할 때 이해하기가 더 쉽습니다.

물론 일반적인 철학이며, 세부 사항을 제공하지 않았기 때문에 더 자세한 조언을 드릴 수 없습니다. :-)


가비지 수집이 문제가 될까요?
rupinderjeet

1
호출자 함수에서 매개 변수 개체를 로컬 범위로 만들거나 변경하지 않으면 아닙니다. 이러한 상황에서는 수집 될 가능성이 높고 메모리가 매우 빠르게 재사용됩니다.
dimitarvp

이모, 당신은 또한 XXXParameter param = new XXXParameter();사용할 수 있어야하고 사용하십시오 XXXParameter.setObjA(objA); etc ...
satibel

10

먼저 방법을 리팩토링하려고합니다. 그렇게 많은 매개 변수를 사용하는 경우 너무 길 수 있습니다. 그것을 세분화하면 코드가 향상되고 잠재적으로 각 메서드에 대한 매개 변수 수가 감소합니다. 전체 작업을 자체 클래스로 리팩토링 할 수도 있습니다. 둘째, 동일한 매개 변수 목록의 동일한 (또는 수퍼 세트)를 사용하는 다른 인스턴스를 찾습니다. 인스턴스가 여러 개인 경우 이러한 속성이 함께 속한다는 신호를 보낼 수 있습니다. 이 경우 매개 변수를 보유 할 클래스를 작성하고 사용하십시오. 마지막으로, 매개 변수의 수가 코드 가독성을 높이기 위해 맵 객체를 생성 할 가치가 있는지 평가합니다. 저는 이것이 개인적인 요청이라고 생각합니다.이 솔루션에는 각 방식마다 고통이 있으며 절충점이 다를 수 있습니다. 6 개의 매개 변수에 대해서는 아마하지 않을 것입니다. 10의 경우 아마도 (다른 방법 중 어느 것도 먼저 작동하지 않으면) 그럴 것입니다.


8

이것은 객체를 구성 할 때 종종 문제가됩니다.

이 경우 빌더 객체 패턴을 사용 하면 매개 변수 목록이 많고 모든 매개 변수가 항상 필요하지는 않은 경우 잘 작동합니다.

메서드 호출에 적용 할 수도 있습니다.

또한 가독성을 크게 향상시킵니다.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

검증 로직을 Builder set .. () 및 build () 메소드에 넣을 수도 있습니다.


당신의 분야가 많다면 무엇을 추천 final하겠습니까? 이것이 내가 도우미 함수를 작성하지 못하게하는 중요한 것입니다. 필드를 비공개로 만들고 해당 클래스의 코드에서 필드를 잘못 수정하지 않도록 할 수 있다고 생각하지만 더 우아한 것을 원합니다.
ragerdl

7

Parameter object 라는 패턴이 있습니다 .

아이디어는 모든 매개 변수 대신 하나의 객체를 사용하는 것입니다. 이제 나중에 매개 변수를 추가해야하는 경우에도 매개 변수를 객체에 추가하기 만하면됩니다. 메소드 인터페이스는 동일하게 유지됩니다.


5

해당 데이터를 보관할 클래스를 만들 수 있습니다. 그래도 충분히 의미가 있어야하지만지도 (OMG)를 사용하는 것보다 훨씬 낫습니다.


메서드 매개 변수를 보유하기 위해 클래스를 만드는 것이 필요하다고 생각하지 않습니다.
Sawyer

동일한 매개 변수를 전달하는 인스턴스가 여러 개있는 경우에만 클래스를 만듭니다. 이것은 매개 변수가 관련되어 있고 어쨌든 함께 속할 수 있음을 나타냅니다. 단일 방법에 대한 클래스를 만드는 경우 치료가 질병보다 더 나쁠 수 있습니다.
tvanfosson

예-관련 매개 변수를 DTO 또는 값 개체로 이동할 수 있습니다. 여러 매개 변수 중 일부는 선택 사항입니까? 즉, 기본 메소드가 이러한 추가 매개 변수로 오버로드됩니까? 그런 경우-나는 그것이 받아 들일 것이라고 생각합니다.
JoseK 2010 년

그것이 제가 의미하는 바는 충분히 의미가 있어야한다는 것입니다.
Johannes Rudolph

4

Code Complete *는 다음 두 가지를 제안합니다.

  • "루틴의 매개 변수 수를 약 7 개로 제한하십시오. 7 개는 사람들의 이해를위한 마법의 숫자입니다"(p 108).
  • "매개 변수를 입력-수정-출력 순서로 배치 ... 여러 루틴이 유사한 매개 변수를 사용하는 경우 유사한 매개 변수를 일관된 순서로 배치"(105 페이지).
  • 상태 또는 오류 변수를 마지막에 넣으십시오.
  • 마찬가지로 tvanfosson이 언급 구조화 변수 (개체)에 해당 루틴의 필요 부분만을 통과한다. 즉, 함수에서 구조화 된 변수의 대부분을 사용하는 경우 전체 구조를 전달하지만 이것이 어느 정도 결합을 촉진한다는 점에 유의하십시오.

* First Edition, 업데이트해야한다는 것을 알고 있습니다. 또한 OOP가 인기를 끌기 시작했을 때 두 번째 버전이 작성된 이후로이 조언 중 일부가 변경되었을 가능성이 있습니다.


2

리팩토링하는 것이 좋습니다. 이 객체가이 메소드에 전달되어야한다는 의미는 무엇입니까? 단일 객체로 캡슐화해야합니까?


네, 그래야합니다. 예를 들어, 큰 검색 양식에는 관련없는 제약 조건이 많고 페이지 매김이 필요합니다. currentPageNumber, searchCriteria, pageSize ...를 전달해야합니다.
Sawyer

2

지도를 사용하는 것은 호출 서명을 정리하는 간단한 방법이지만 다른 문제가 있습니다. 메소드가 해당 맵에서 기대하는 바, 키 이름이 무엇인지 또는 값의 유형이 무엇인지 확인하려면 메소드 본문 내부를 살펴 봐야합니다.

더 깨끗한 방법은 오브젝트 빈의 모든 매개 변수를 그룹화하는 것이지만 여전히 문제를 완전히 해결하지는 못합니다.

여기에있는 것은 디자인 문제입니다. 메소드에 대한 7 개 이상의 매개 변수를 사용하면 그들이 나타내는 것과 순서를 기억하는 데 문제가 생기기 시작할 것입니다. 여기에서 잘못된 매개 변수 순서로 메소드를 호출하는 것만으로도 많은 버그가 발생합니다.

많은 매개 변수를 보내는 모범 사례가 아닌 더 나은 앱 디자인이 필요합니다.


1

Bean 클래스를 만들고 모든 매개 변수 (setter 메소드)를 설정하고이 Bean 객체를 메소드에 전달합니다.


1
  • 코드를보고 이러한 모든 매개 변수가 전달 된 이유를 확인하십시오. 때때로 메서드 자체를 리팩터링 할 수 있습니다.

  • 맵을 사용하면 메서드가 취약 해집니다. 메서드를 사용하는 누군가가 매개 변수 이름을 잘못 입력하거나 메서드가 UDT를 예상하는 곳에 문자열을 게시하면 어떻게됩니까?

  • 전송 개체를 정의합니다 . 최소한 유형 검사를 제공 할 것입니다. 메서드 내에서가 아니라 사용 시점에서 일부 유효성 검사를 수행 할 수도 있습니다.


0

이것은 종종 당신의 클래스가 하나 이상의 책임을 가지고 있음을 나타냅니다 (즉, 당신의 클래스가 너무 많이합니다).

단일 책임 원칙 참조

자세한 내용은.


0

너무 많은 매개 변수를 전달하는 경우 메소드를 리팩토링하십시오. 아마도하지 말아야 할 일을 많이하고있을 수도 있습니다. 그렇지 않은 경우 매개 변수를 단일 클래스로 대체 해보십시오. 이렇게하면 모든 것을 단일 클래스 인스턴스에 캡슐화하고 매개 변수가 아닌 인스턴스를 전달할 수 있습니다.


0

나는 당신이 이전에했던 방식을 고수한다고 말할 것입니다. 예제의 매개 변수 수는 많지 않지만 대안은 훨씬 더 끔찍합니다.

  1. 지도-당신이 언급 한 효율성이 있지만 여기서 더 큰 문제는 다음과 같습니다.

    • 호출자는
      다른 것을 참조하지 않고 무엇을 보내야할지 모릅니다 ... 정확히 어떤 키와
      값이 사용 되는지 명시하는 javadocs가 있습니까? 그렇게한다면 (훌륭한), 매개 변수가 많은 것도 문제가되지 않습니다.
    • 다른 인수 유형을 받아들이는 것은 매우 어려워집니다. 입력 매개 변수를 단일 유형으로 제한하거나 Map <String, Object>를 사용하고 모든 값을 캐스트 할 수 있습니다. 두 옵션 모두 대부분 끔찍합니다.
  2. 래퍼 개체-처음에 래퍼 개체를 채워야하기 때문에 문제를 이동합니다. 메서드에 직접 연결하는 대신 매개 변수 개체의 생성자에 대한 것입니다. 문제를 이동하는 것이 적절한 지 여부를 결정하는 것은 해당 개체의 재사용에 달려 있습니다. 예를 들면 :

사용하지 않을 것 : 첫 번째 호출에서 한 번만 사용되므로 한 줄을 처리하기위한 많은 추가 코드 ...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

그것을 사용할 수 있습니다 : 여기, 조금 더 할 수 있습니다. 첫째, 3 개의 메서드 호출에 대한 매개 변수를 인수 할 수 있습니다. 그것은 또한 2 개의 다른 라인을 그 자체로 수행 할 수 있습니다 ... 그래서 그것은 어떤 의미에서 상태 변수가됩니다 ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. 빌더 패턴-이것은 내 관점에서 안티 패턴입니다. 가장 바람직한 오류 처리 메커니즘은 나중에가 아니라 더 일찍 감지하는 것입니다. 그러나 빌더 패턴을 사용하면 필수 매개 변수가 누락 된 (프로그래머가이를 포함하지 않았 음) 호출이 컴파일 시간에서 런타임으로 이동됩니다. 물론 프로그래머가 의도적으로 슬롯에 널 (null) 등을 넣는 경우 런타임이 될 수 있지만 호출하는 메서드의 매개 변수 이름을 보지 않는 프로그래머를 처리하는 데 훨씬 더 큰 이점이 있습니다. 많은 수의 선택적 매개 변수를 다룰 때만 적절하다고 생각하며 , 그럼에도 불구하고 이점은 기껏해야 미미합니다. 나는 빌더 "패턴"에 매우 반대합니다.

사람들이 고려해야 할 또 다른 사항은이 모든 것에서 IDE의 역할입니다. 메서드에 매개 변수가 있으면 IDE가 대부분의 코드를 생성하고 제공 / 설정해야하는 항목을 알려주는 빨간색 선이 표시됩니다. 옵션 3을 사용하면 ... 완전히 손실됩니다. 이제 제대로하는 것은 프로그래머의 몫이며, 코딩과 컴파일 시간에는 단서가 없습니다. 프로그래머는이를 확인하기 위해 테스트해야합니다.

또한 옵션 2와 3은 불필요하게 광범위하게 확산 될 경우 생성되는 많은 양의 중복 코드로 인해 유지 관리 측면에서 장기적으로 부정적인 영향을 미칩니다. 코드가 많을수록 유지 관리 할 수있는 코드가 많을수록 유지 관리하는 데 더 많은 시간과 비용이 소비됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.