사용자 정의 객체를 매개 변수로 사용하지 않아야합니까?


49

사용자 정의 객체 Student 가 있다고 가정합니다 .

public class Student{
    public int _id;
    public String name;
    public int age;
    public float score;
}

그리고 학생의 정보를 표시하는 데 사용되는 Window 클래스 는 다음과 같습니다.

public class Window{
    public void showInfo(Student student);
}

그것은 정상적인 것처럼 보이지만 Window 는 함수를 호출 하는 실제 Student 객체가 필요하기 때문에 개별적으로 테스트하기가 쉽지 않다는 것을 알았 습니다 . 따라서 학생 객체를 직접 허용하지 않도록 showInfo를 수정하려고합니다 .

public void showInfo(int _id, String name, int age, float score);

Window를 개별적 으로 테스트하는 것이 더 쉽습니다 .

showInfo(123, "abc", 45, 6.7);

그러나 수정 된 버전에 다른 문제가 있음을 발견했습니다.

  1. 학생 수정 (예 : 새 속성 추가)은 showInfo의 메소드 서명을 수정해야합니다.

  2. 만약 학생이 많은 속성을 가지고 있다면, 학생의 방법 서명은 매우 길 것입니다.

따라서 사용자 정의 객체를 매개 변수로 사용하거나 객체의 각 속성을 매개 변수로 사용하면 어느 것이 더 유지 관리가 용이합니까?


40
그리고 당신의 '개선' showInfo에는 실제 문자열, 실수 부동 소수점 및 두 개의 정수가 필요합니다. 실제 String객체를 제공하는 것보다 실제 객체를 제공하는 것이 어떻게 더 낫 Student습니까?
Bart van Ingen Schenau

28
매개 변수를 직접 전달할 때의 주요 문제점 : 이제 두 개의 int매개 변수가 있습니다. 콜 사이트에는 실제로 올바른 순서로 전달하고 있는지 확인할 수 없습니다. 만약 당신이 idand age, 또는 firstNameand lastName? 얼굴이 터질 때까지 감지하기 어려울 수있는 잠재적 인 실패 지점을 도입하고 있으며 모든 콜 사이트에 이를 추가하고 있습니다 .
Chris Hayes

38
@ChrisHayes 아, 오래된 showForm(bool, bool, bool, bool, int)방법-나는 그것들을 사랑합니다 ...
거미 Boris the

3
적어도이 아니 JS ... @ChrisHayes
옌스 Schauder

2
잘 평가되지 않은 테스트 속성 : 테스트 에서 자체 객체 를 생성 / 사용하기 어려운 경우 API에서 일부 작업을 사용할 수 있습니다. :
Eevee

답변:


131

사용자 정의 객체를 사용하여 관련 매개 변수를 그룹화하는 것이 실제로 권장되는 패턴입니다. 리팩토링으로서이를 Introduce Parameter Object 라고 합니다.

당신의 문제는 다른 곳에 있습니다. 먼저, generic Window은 Student에 대해 아무것도 몰라 야합니다. 대신, StudentWindow표시 만하는 것에 대해 알고 있어야합니다 Students. 둘째, Student테스트 StudentWindowStudent극도로 복잡하게 만드는 복잡한 논리가 포함되어 있지 않으면 테스트 할 인스턴스를 만드는 데 아무런 문제 가 없습니다 StudentWindow. 그 논리가 있다면 Student인터페이스 를 만들고 조롱하는 것이 좋습니다.


14
새 객체가 실제로 논리적 그룹이 아닌 경우 문제를 일으킬 수 있다는 경고가 필요합니다. 모든 매개 변수 세트를 단일 객체로 묶지 마십시오. 사례별로 결정합니다. 이 질문의 예는 분명히 그것을위한 좋은 후보로 보인다. Student그룹은 의미가 앱의 다른 영역에서 자르기 가능성이 높습니다.
jpmc26

예를 들어 Student, 당신이 이미 그 물체를 가지고 있다면, 예를 들어 , 그것은 전체 물체 보존
abuzittin gillifirca

4
또한 데메테르법칙을 기억하십시오 . 이 강타 할 균형입니다하지만 tldr은하지 않는 것입니다 a.b.c귀하의 방법이 걸리는 경우 a. 분석법이 대략 4 개 이상의 매개 변수 또는 2 단계 깊이의 속성 접근이 필요한 지점에 도달하면 아마도 인수 분해되어야합니다. 또한 다른 모든 가이드 라인과 마찬가지로 가이드 라인이므로 사용자의 판단이 필요합니다. 맹목적으로 따르지 마십시오.
Dan Pantry

7
이 답변의 첫 번째 문장은 파싱하기가 매우 어렵다는 것을 알았습니다.
helrich

5
@ Qwerky 나는 매우 동의하지 않을 것입니다. Student REALLY는 객체 그래프에서 리프처럼 들립니다 (Name, DateOfBirth 등과 같은 다른 사소한 객체는 제외). 단지 학생 상태의 컨테이너입니다. 학생이 레코드 유형이어야하기 때문에 학생이 구성하기 어려운 이유는 없습니다. 학생을 위해 테스트 배가를 만드는 것은 테스트를 유지하기 어렵고 멋진 격리 프레임 워크에 크게 의존하는 레시피처럼 들립니다.
sara may

26

당신은 그것이

함수를 호출하려면 실제 Student 객체가 필요하기 때문에 개별적으로 테스트하기가 쉽지 않습니다.

그러나 창에 전달할 학생 개체를 만들 수 있습니다.

showInfo(new Student(123,"abc",45,6.7));

전화하기가 훨씬 더 복잡해 보이지는 않습니다.


7
때 문제가 온다 StudentA를 말합니다 University많은 의미하는 Facultys와 Campus함께, S Professors와 BuildingS, 어느 것도 showInfo실제로 사용하지만, 당신이 테스트를 가능하게하는 모든 인터페이스를 정의 적이 없다 "알고"그 공급에만 관련 학생 전체 조직을 구축하지 않고 데이터. 예 Student는 평범한 데이터 객체이며 테스트와 마찬가지로 테스트를 수행해도 좋습니다.
Steve Jessop

4
문제는 학생이 대학을 언급 할 때 발생합니다.이 대학은 많은 교수진과 캠퍼스를 말하며 교수와 건물 이 있으며 악인에게는 안식하지 않습니다.
abuzittin gillifirca

1
@abuzittingillifirca, "개체 어머니"는 하나의 솔루션이며, 학생 개체도 너무 복잡 할 수 있습니다. UniversityId 및 UniversityId에서 University 개체를 제공하는 서비스 (종속성 주입 사용) 만있는 것이 좋습니다.
Ian

12
Student가 매우 복잡하거나 초기화하기 어려운 경우 모의하십시오. Mockito 또는 다른 언어와 동등한 프레임 워크를 사용하면 테스트가 훨씬 강력 해집니다.
Borjab

4
showInfo가 University를 신경 쓰지 않으면 간단히 null로 설정하십시오. 널은 생산에서 끔찍하고 시험에서 신이 보냅니다. 테스트에서 매개 변수를 null로 지정할 수 있다는 것은 의도를 전달하고 "이것은 여기에 필요하지 않습니다"라고 말합니다. 관련 데이터 만 포함하는 학생들을 위해 일종의 뷰 모델을 만드는 것을 고려하고 있지만 showInfo는 UI 클래스의 메서드처럼 들립니다.
sara

22

평신도의 관점에서 :

  • 당신이 부르는 "사용자 정의 개체은" 일반적으로 단순히 객체라고합니다.
  • 사소하지 않은 프로그램이나 API를 디자인하거나 사소하지 않은 API 또는 라이브러리를 사용할 때 객체를 매개 변수로 전달하는 것을 피할 수 없습니다.
  • 객체를 매개 변수로 전달해도됩니다. Java API를 살펴보면 객체를 매개 변수로받는 많은 인터페이스가 표시됩니다.
  • 당신과 나 같은 단순한 필사자들에 의해 쓰여진 곳에서 사용하는 라이브러리의 클래스는 우리가 쓰는 것은 "맞춤형" 이 아닙니다 .

편집하다:

따라 @ Tom.Bowen89이 상태는 훨씬 더 복잡은 showInfo 방법을 테스트하지 않는 것입니다 :

showInfo(new Student(8812372,"Peter Parker",16,8.9));

3
  1. 학생 예제에서 showInfo에 전달할 학생을 만들기 위해 Student 생성자를 호출하는 것이 사소한 것으로 가정합니다. 따라서 문제가 없습니다.
  2. 예제 학생이이 질문에 대해 의도적으로 사소한 것으로 가정하고 구성하기가 더 어렵다고 가정하면 테스트 double을 사용할 수 있습니다 . Martin Fowler의 기사에서 선택할 수있는 테스트 복식, 모의 품, 스터브 등에 대한 여러 옵션이 있습니다.
  3. showInfo 함수를보다 일반화하려면 공용 변수 또는 전달 된 객체의 공용 접근자를 반복하여 모든 것에 대해 표시 논리를 수행 할 수 있습니다. 그런 다음 해당 계약을 준수하는 오브젝트를 전달하면 예상대로 작동합니다. 인터페이스를 사용하기에 좋은 장소입니다. 예를 들어 Showable 또는 ShowInfoable 객체를 showInfo 함수에 전달하면 학생 정보뿐만 아니라 인터페이스를 구현하는 객체 정보도 표시 할 수 있습니다 (물론 전달할 수있는 객체의 특정 또는 일반에 따라 더 나은 이름이 필요함) 학생의 하위 클래스가 무엇인지).
  4. 프리미티브를 전달하는 것이 더 쉽고 때로는 성능에 필요할 수도 있지만 유사한 개념을 더 많이 그룹화할수록 일반적으로 코드를 더 잘 이해할 수 있습니다. 주의해야 할 유일한 것은 그것을 과도하게하지 않고 기업의 fizzbuzz로 끝나는 것 입니다.

3

Code Complete의 Steve McConnell은이 문제를 해결하면서 속성을 사용하는 대신 객체를 메서드에 전달할 때의 이점과 단점에 대해 설명했습니다.

세부 사항 중 일부가 잘못되면 저를 용서하십시오. 책에 액세스 한 지 1 년이 지났으므로 메모리에서 작업하고 있습니다.

그는 객체를 사용하지 않고 메소드에 절대적으로 필요한 속성 만 보내는 것이 좋습니다. 메소드는 조작의 일부로 사용할 특성 외부의 오브젝트에 대해 아무것도 알 필요가 없습니다. 또한 시간이 지남에 따라 객체가 변경되면 객체를 사용하는 방법에 의도하지 않은 결과가 발생할 수 있습니다.

또한 그는 당신이 많은 다른 주장을 받아들이는 방법으로 끝내면 아마도 그 방법이 너무 많은 일을하고 더 작은 방법으로 나뉘어 야한다는 신호 일 것이라고 말했습니다.

그러나 때로는 때로는 많은 매개 변수가 필요합니다. 그가 제공하는 예제는 여러 가지 주소 속성을 사용하여 전체 주소를 작성하는 방법입니다 (생각할 때 문자열 배열을 사용하여 해결할 수는 있음).


7
Code Complete 2가 있습니다.이 문제와 관련된 전체 페이지가 있습니다. 결론은 매개 변수가 올바른 추상화 레벨에 있어야한다는 것입니다. 때로는 전체 객체, 때로는 개별 속성 만 전달해야합니다.
에서 오는

UV. 코드 완성 참조 는 두 배 더 좋습니다. 테스트 편의성에 비해 디자인을 신중하게 고려해야합니다. 디자인을 선호합니다. McConnell이 우리의 맥락에서 말할 것이라고 생각합니다. 따라서 " Student이 경우 매개 변수 개체를 디자인에 통합"하는 것이 좋습니다. 그리고 이것이 테스트가 설계정보를 전달 하는 방식으로 설계 무결성을 유지하면서 대부분의 투표 응답을 완전히 수용합니다.
radarbob

2

전체 객체를 전달하면 테스트를 작성하고 읽는 것이 훨씬 쉽습니다.

public class AStudentView {
    @Test 
    public void displays_failing_grade_warning_when_a_student_with_a_failing_grade_is_shown() {
        StudentView view = aStudentView();
        view.show(aStudent().withAFailingGrade().build());
        Assert.that(view, displaysFailingGradeWarning());
    }

    private Matcher<StudentView> displaysFailingGradeWarning() {
        ...
    }
}

비교하려고,

view.show(aStudent().withAFailingGrade().build());

다음과 같이 값을 별도로 전달하면 줄을 쓸 수 있습니다.

showAStudentWithAFailingGrade(view);

실제 메소드 호출은 다음과 같은 곳에 묻혀 있습니다.

private showAStudentWithAFailingGrade(StudentView view) {
    int someId = .....
    String someName = .....
    int someAge = .....
    // why have been I peeking and poking values I don't care about
    decimal aFailingGrade = .....
    view.show(someId, someName, someAge, aFailingGrade);
}

요컨대, 실제 메소드 호출을 테스트에 넣을 수 없다는 것은 API가 잘못되었다는 신호입니다.


1

이해가되는 내용, 몇 가지 아이디어를 전달해야합니다.

테스트하기가 더 쉽습니다. 객체를 편집해야하는 경우 리팩토링이 가장 필요한 것은 무엇입니까? 다른 목적으로이 기능을 재사용하는 것이 유용합니까? 이 기능을 수행하기 위해 필요한 최소한의 정보는 무엇입니까? (이 코드를 재사용하면이 코드를 재사용 할 수 있습니다.)이 기능을 만드는 디자인 홀을 떨어 뜨린 다음이 객체를 독점적으로 사용하기 위해 모든 것을 병목 현상에주의하십시오.

이러한 모든 프로그래밍 규칙은 올바른 방향으로 생각할 수 있도록 안내하는 것입니다. 코드 짐승을 만들지 마십시오. 확실하지 않고 진행해야하는 경우 방향 / 자신의 제안 또는 제안을 여기에서 선택하십시오. 방법 '-아마도 돌아가서 쉽게 리팩토링 할 수 있습니다. (예를 들어 Teacher 클래스가있는 경우-Student와 동일한 속성 세트 만 필요하고 Person 양식의 객체를 허용하도록 함수를 변경합니다)

코딩하는 방법 때문에이 기능이 수행하는 작업을보다 쉽게 ​​설명 할 수 있기 때문에 주 객체가 계속 전달되는 경향이 있습니다.


1

이에 대한 한 가지 일반적인 경로는 두 프로세스 사이에 인터페이스를 삽입하는 것입니다.

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

interface HasInfo {
    public String getInfo();
}

public class StudentInfo implements HasInfo {
    final Student student;

    public StudentInfo(Student student) {
        this.student = student;
    }

    @Override
    public String getInfo() {
        return student.name;
    }

}

public class Window {

    public void showInfo(HasInfo info) {

    }
}

때로는 약간 지저분하지만 내부 클래스를 사용하면 Java에서 조금 더 깔끔해집니다.

interface HasInfo {
    public String getInfo();
}

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;

    public HasInfo getInfo() {
        return new HasInfo () {
            @Override
            public String getInfo() {
                return name;
            }

        };
    }
}

그런 다음 Window가짜 HasInfo객체를 제공 하여 클래스 를 테스트 할 수 있습니다 .

나는 이것이 Decorator Pattern 의 예라고 생각한다 .

추가

코드의 단순성으로 인해 약간의 혼동이있는 것 같습니다. 이 기술을 더 잘 보여줄 수있는 다른 예가 있습니다.

interface Drawable {

    public void Draw(Pane pane);
}

/**
 * Student knows nothing about Window or Drawable.
 */
public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

/**
 * DrawsStudents knows about both Students and Drawable (but not Window)
 */
public class DrawsStudents implements Drawable {

    private final Student subject;

    public DrawsStudents(Student subject) {
        this.subject = subject;
    }

    @Override
    public void Draw(Pane pane) {
        // Draw a Student on a Pane
    }

}

/**
 * Window only knows about Drawables.
 */
public class Window {

    public void showInfo(Drawable info) {

    }
}

showInfo가 학생의 이름 만 표시하기를 원한다면 왜 이름을 전달하지 않습니까? 문자열이 무엇을 나타내는 지에 대한 단서가없는 문자열을 포함하는 추상 인터페이스에 의미 적으로 의미가있는 명명 된 필드를 배치하면 유지 관리 성 및 이해성 측면에서 거대한 다운 그레이드처럼 느껴집니다.
sara

@kai -의 사용 StudentString여기 반환 형식에 대한 데모 순전히이다. 그림 그리기 getInfo와 같은 추가 매개 변수가있을 수 있습니다 Pane. 여기서 개념은 기능적 구성 요소를 원래 객체의 데코레이터전달하는 것 입니다.
OldCurmudgeon

이 경우 학생 개체를 UI 프레임 워크에 단단히 연결하면 훨씬 더 나빠질 것입니다.
sara may

1
@kai-정반대입니다. 내 UI는 HasInfo객체에 대해서만 알고 있습니다. Student하나가되는 방법을 알고 있습니다.
OldCurmudgeon

당신이주는 경우에 getInfo그것을 통과 반환 무효를 Pane다음에 그릴합니다 (의 구현 Student클래스) 갑자기 스윙 결합 또는 무엇이든 당신이 사용하고 있습니다. 문자열을 반환하고 매개 변수를 0으로 설정하면 UI는 마법의 가정과 암시 적 커플 링없이 문자열을 어떻게 처리할지 알 수 없습니다. getInfo실제로 관련 속성을 가진 일부 뷰 모델을 반환 하면 Student클래스가 프레젠테이션 논리에 다시 결합되는 것보다 큽니다 . 이 대안들 중 어느 것도 바람직하지 않다고 생각합니다.
sara

1

이미 좋은 답변이 많이 있지만 다음은 대체 솔루션을 볼 수있는 몇 가지 제안입니다.

  • 귀하의 예제는 학생 (명확히 모델 객체)이 Window (겉보기 뷰 수준 객체)로 전달되는 것을 보여줍니다. 중개 컨트롤러 또는 발표자 개체가없는 경우 유리할 수 있으므로 사용자 인터페이스를 모델에서 분리 할 수 ​​있습니다. 컨트롤러 / 프레젠터는 UI 테스트를 위해이를 대체하는 데 사용할 수있는 인터페이스를 제공해야하며 인터페이스를 사용하여 모델 오브젝트를 참조하고 테스트 할 두 오브젝트와 분리 할 수 ​​있도록 오브젝트를 볼 수 있어야합니다. 팩토리 객체, 리포지토리 객체 또는 이와 유사한 객체를 생성하거나로드하는 추상적 인 방법을 제공해야 할 수도 있습니다.

  • 모델 객체의 관련 부분을 데이터 전송 객체로 전송하는 것은 모델이 너무 복잡해지면 인터페이스에 유용한 접근 방식입니다.

  • 학생이 인터페이스 분리 원칙을 위반 한 것일 수 있습니다. 그렇다면 사용하기 쉬운 여러 인터페이스로 분할하는 것이 좋습니다.

  • Lazy Loading을 사용하면 큰 개체 그래프를보다 쉽게 ​​구성 할 수 있습니다.


0

이것은 실제로 괜찮은 질문입니다. 여기서 실제 문제는 "개체"라는 일반적인 용어를 사용하는 것입니다.

일반적으로 고전적인 OOP 언어에서 "개체"라는 용어는 "클래스 인스턴스"를 의미합니다. 클래스 인스턴스는 공용 및 개인 속성 (및 그 사이의 속성), 메서드, 상속, 종속성 등이 상당히 무거울 수 있습니다. 실제로 일부 속성을 전달하기 위해 이와 같은 것을 사용하고 싶지는 않습니다.

이 경우 객체를 컨테이너로 사용하여 단순히 일부 기본 요소를 보유합니다. C ++에서 이와 같은 객체는 structs(그리고 여전히 C #과 같은 언어로 존재합니다)로 알려져 있습니다. 실제로 Structs는 사용자가 사용하는 용도에 맞게 정확하게 설계되었습니다. 논리적 관계가있을 때 관련 개체와 기본 요소를 함께 그룹화했습니다.

그러나 현대 언어에서는 코드를 작성할 때 구조체와 클래스간에 차이가 없으므로 객체를 사용해도됩니다. (그러나 장면 뒤에는 알아야 할 몇 가지 차이점이 있습니다. 예를 들어 구조체는 참조 유형이 아닌 값 유형입니다.) 기본적으로 객체를 단순하게 유지하는 것이 쉬울 것입니다. 수동으로 테스트합니다. 현대 언어와 도구를 사용하면 인터페이스, 모의 프레임 워크, 의존성 주입 등을 통해 상당히 완화 할 수 있습니다.


1
참조가 여전히 대부분의 언어에서 int의 크기에 불과하기 때문에 객체가 10 억 테라 바이트 인 경우에도 참조를 전달하는 것은 비용이 많이 들지 않습니다. 수신 방법이 너무 큰 API에 노출되는지 여부와 원하지 않는 방식으로 항목을 결합하는지에 대해 더 걱정해야합니다. 비즈니스 객체 ( Student)를 뷰 모델 ( StudentInfo또는 StudentInfoViewModel기타) 로 변환하는 매핑 레이어를 만드는 것을 고려하고 있지만 필요하지 않을 수도 있습니다.
sara may

클래스와 구조는 매우 다릅니다. 하나는 값으로 전달되고 (받는 메소드가 사본을 얻는다는 것을 의미 ) 다른 하나는 참조로 전달됩니다 (수신자는 원본에 대한 포인터를 얻습니다). 이 차이를 이해하지 못하는 것은 위험합니다.
RubberDuck

@kai 나는 참조를 전달하는 것이 비싸지 않다는 것을 이해합니다. 내가 말하는 것은 전체 클래스 인스턴스를 필요로하는 함수를 만드는 것은 클래스의 종속성, 메서드 등에 따라 테스트하기가 더 어려울 수 있다는 것입니다. 어떻게 클래스를 조롱해야합니다.
lunchmeat317

나는 개인적으로 외부 시스템 (파일 / 네트워크 IO)에 액세스하는 경계 클래스 또는 결정적이지 않은 (예 : 무작위, 시스템 시간 기반 등) 클래스를 제외한 모든 것을 조롱하는 것에 반대합니다. 테스트중인 클래스에 테스트중인 현재 기능과 관련이없는 종속성이있는 경우 가능한 경우 null을 전달하는 것을 선호합니다. 그러나 매개 변수 객체를 사용하는 메서드를 테스트하는 경우 해당 객체에 많은 종속성이 있으면 전반적인 디자인에 대해 걱정할 것입니다. 이러한 물체는 가벼워 야합니다.
sara
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.