리플렉션을 과도하게 사용하는 것은 나쁜 습관입니까?


16

상용구 코드의 양을 크게 줄이면 리플렉션을 사용하는 것이 좋습니까?

기본적으로 한쪽면의 성능과 가독성과 다른 쪽면의 보일러 플레이트 코드의 추상화 / 자동화 / 감소 사이에는 균형이 있습니다.

편집 : 다음은 반사 사용을 권장하는 예입니다. .

예를 들어, Base10 개의 필드와 3 개의 서브 클래스 SubclassASubclassB있고 SubclassC각각 10 개의 다른 필드 가 있는 추상 클래스가 있다고 가정하십시오 . 그들은 모두 간단한 콩입니다. 문제는 두 개의 Base유형 참조 를 얻고 해당 객체가 동일한 (하위) 유형이고 동일한 지 확인하려는 것입니다.

솔루션으로 먼저 유형이 같은지 확인한 다음 모든 필드를 확인하거나 리플렉션을 사용하고 동일한 유형인지 동적으로 확인하고 "get"(컨벤션)으로 시작하는 모든 메소드를 반복적으로 처리 할 수있는 원시 솔루션이 있습니다. 구성에 대해), 두 객체 모두에서 호출하고 결과에서 equals를 호출하십시오.

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}

20
무엇이든 남용하는 것은 나쁜 습관입니다.
Tulains Córdova

1
그렇습니다. 너무 많은 자유는 독재로 이어집니다. 일부 오래된 그리스인은 이미 이것에 대해 알고있었습니다.
ott--

6
“물이 너무 많으면 나쁘다. 분명히, 너무 많은 양이 너무 과도합니다. 그것이 의미하는 바입니다!”— Stephen Fry
Jon Purdy

4
"언제든지"객체가 T1 유형 인 경우 무언가를 수행하지만, T2 유형 인 경우 다른 것을 수행하는 "형태의 코드를 작성하는 것을 발견하면 스스로를 때리십시오. javapractices.com/topic/TopicAction.do?Id = 31
rm5248

답변:


25

반사는 무엇 유사, 컴파일시 알려지지 않은 클래스의 기능을 발견하기 위해, 특정 목적을 위해 만들어진 dlopen하고 dlsym있어야한다 C.에 그 어떤 것도 사용 외부에서이를 수행 기능 무겁게을 자세히 조사합니다.

Java 디자이너 자신이이 문제를 겪은 적이 있습니까? 그렇기 때문에 실제로 모든 클래스에는 equals메소드가 있습니다. 다른 클래스는 평등의 정의가 다릅니다. 경우에 따라 파생 객체는 기본 객체와 동일 할 수 있습니다. 어떤 상황에서는 게터가없는 개인 필드를 기준으로 평등을 결정할 수 있습니다. 당신은 모른다.

커스텀 평등을 원하는 모든 객체가 equals메소드를 구현해야하는 이유 입니다. 결국 객체를 세트에 넣거나 해시 인덱스로 사용하려면 equals어쨌든 구현해야 합니다. 다른 언어에서는 다르게 작동하지만 Java는equals . 당신은 당신의 언어의 규칙을 고수해야합니다.

또한, "보일러 플레이트"코드는 올바른 클래스에 들어가면 망치기가 매우 어렵습니다. 리플렉션은 추가적인 복잡성을 추가하여 버그 발생 가능성을 높입니다. 예를 들어, 메소드에서 하나는 null특정 필드에 대해 리턴 하고 다른 하나는 그렇지 않으면 두 오브젝트가 동일한 것으로 간주됩니다 . getter 중 하나가 적절한 객체없이 객체 중 하나를 반환하면 equals어떻게됩니까? 당신의 if (!object1.equals(object2))실패합니다. 또한 버그가 발생하기 쉽기 때문에 리플렉션이 거의 사용되지 않으므로 프로그래머는 문제에 익숙하지 않습니다.


12

리플렉션을 과도하게 사용하는 것은 아마도 사용 된 언어에 달려 있습니다. 여기서 당신은 자바를 사용하고 있습니다. 이 경우 리플렉션은 나쁜 디자인에 대한 해결 방법 일 뿐이므로주의해서 사용해야합니다.

그래서, 당신은 다른 클래스를 비교하고 있습니다, 이것은 메소드 재정의에 대한 완벽한 문제입니다 . 서로 다른 두 클래스의 인스턴스는 동일하다고 간주해서는 안됩니다. 동일한 클래스의 인스턴스가있는 경우에만 동등성을 비교할 수 있습니다. 동등 비교를 올바르게 구현하는 방법에 대한 예는 /programming/27581/overriding-equals-and-hashcode-in-java 를 참조 하십시오 .


16
+1-우리 중 일부는 리플렉션 사용이 나쁜 디자인을 나타내는 적기라고 생각합니다.
Ross Patterson

1
아마도이 경우 등가에 기반한 솔루션이 바람직하지만 일반적으로 반사 솔루션에 어떤 문제가 있습니까? 실제로 그것은 매우 일반적이며 equals 메소드는 모든 클래스와 서브 클래스에서 명시 적으로 작성 될 필요는 없습니다 (좋은 IDE로 쉽게 생성 할 수는 있지만).
m3th0dman

2
@RossPatterson 왜?
m3th0dman

2
@ m3th0dman compare()"get"으로 시작하는 모든 메소드가 getter이고 비교 연산의 일부로 호출하는 것이 안전하고 적합하다고 가정하면 메소드 와 같은 것들로 이어지기 때문 입니다. 그것은 객체의 의도 된 인터페이스 정의를 위반하는 것이며, 편리 할 수는 있지만 거의 항상 잘못된 것입니다.
로스 패터슨

4
@ m3th0dman 캡슐화를 위반합니다. 기본 클래스는 서브 클래스의 속성에 액세스해야합니다. 그들이 개인이거나 게터 개인이라면 어떻게해야합니까? 다형성은 어떻습니까? 다른 하위 클래스를 추가하기로 결정하고 비교를 다르게하고 싶다면? 글쎄, 기본 수업은 이미 나를 위해하고 있으며 변경할 수 없습니다. 게터가 게으른 짐을 싣는다면 어떨까요? 비교 방법을 원하십니까? 시작하는 메소드 get가 getter이며 무언가를 리턴하는 커스텀 메소드가 아닌지 어떻게 알 수 있습니까?
술탄

1

여기에 두 가지 문제가 있다고 생각합니다.

  1. 동적 코드와 정적 코드의 양은 얼마입니까?
  2. 평등의 커스텀 버전을 어떻게 표현합니까?

동적 코드와 정적 코드

이것은 영원한 질문이며 그 대답은 매우 의견이 많습니다.

한편으로 컴파일러는 모든 종류의 나쁜 코드를 잡는 데 매우 능숙합니다. 다양한 형태의 분석을 통해이 작업을 수행합니다. 유형 분석은 일반적인 분석입니다. Banana코드에서 예상되는 객체를 사용할 수 없다는 것을 알고 있습니다 Cog. 컴파일 오류를 통해 알려줍니다.

이제는 허용되는 유형과 주어진 유형을 컨텍스트에서 유추 할 수있는 경우에만이 작업을 수행 할 수 있습니다. 추론 할 수있는 정도와 추론의 일반적인 정도는 주로 사용되는 언어에 따라 다릅니다. Java는 상속, 인터페이스 및 제네릭과 같은 메커니즘을 통해 유형 정보를 유추 할 수 있습니다. 마일리지는 다양하며 일부 다른 언어는 더 적은 메커니즘을 제공하며 일부는 더 많은 기능을 제공합니다. 여전히 컴파일러가 진실로 알 수있는 것으로 요약됩니다.

반면에 컴파일러는 외래 코드의 모양을 예측할 수 없으며 때로는 언어 유형 시스템을 사용하여 쉽게 표현할 수없는 많은 유형에 대해 일반적인 알고리즘을 표현할 수 있습니다. 이 경우 컴파일러는 항상 결과를 미리 알 수 없으며 어떤 질문을 할지도 모릅니다. 리플렉션, 인터페이스 및 Object 클래스는 이러한 문제를 처리하는 Java의 방법입니다. 올바른 검사 및 처리를 제공해야하지만 이러한 종류의 코드를 사용하는 것은 좋지 않습니다.

코드를 매우 구체적으로 만들 것인지 또는 일반적인 것으로 만들 것인지는 처리하려는 문제에 달려 있습니다. 타입 시스템을 사용하여 쉽게 표현할 수 있다면 그렇게하십시오. 컴파일러가 장점을 발휘하도록 도와주세요. 타입 시스템이 미리 알 수 없거나 (외부 코드) 타입 시스템이 알고리즘의 일반적인 구현에 적합하지 않은 경우 리플렉션 (및 기타 동적 수단)이 올바른 도구입니다.

언어의 입력 시스템을 벗어나는 것은 놀라운 일입니다. 친구에게 걸어가 영어로 대화를 시작한다고 상상해보십시오. 여러분의 생각을 정확하게 표현하기 위해 스페인어, 프랑스어 및 광동어에서 갑자기 몇 마디를 떨어 뜨리십시오. 문맥은 친구에게 많은 것을 말해 줄 것이지만, 모든 종류의 오해로 이어지는 그 단어들을 어떻게 다루어야할지 잘 모릅니다. 더 많은 단어를 사용하여 그러한 아이디어를 영어로 설명하는 것보다 이러한 오해를 다루는 것이 더 낫 습니까?

맞춤 평등

Java equals는 주어진 컨텍스트에서 항상 두 가지 객체를 비교 하는 방법에 크게 의존한다는 것을 알고 있습니다.

다른 방법이 있으며 Java 표준이기도합니다. 이를 Comparator 라고합니다 .

비교기를 구현하는 방법은 비교 대상과 방법에 따라 다릅니다.

  • 특정 equals구현에 관계없이 두 개체에 적용 할 수 있습니다 .
  • 두 객체를 처리하기위한 일반적인 (반사 기반) 비교 방법을 구현할 수 있습니다.
  • 상용구 유형 비교를 위해 상용구 비교 기능을 추가하여 유형 안전성과 최적화를 제공 할 수 있습니다.

1

나는 반사 프로그래밍을 가능한 많이 피하는 것을 선호합니다.

  • 컴파일러가 코드를 정적으로 확인하기 어렵게 만듭니다.
  • 코드를 추론하기 어렵게 만듭니다.
  • 코드를 리팩토링하기 어렵게 만듭니다.

간단한 메소드 호출보다 성능이 훨씬 떨어진다. 예전에는 속도가 느려졌습니다.

정적으로 코드 확인

모든 반사 코드는 문자열을 사용하여 클래스와 메소드를 찾습니다. 원래 예제에서는 "get"으로 시작하는 모든 메소드를 찾고 있습니다. getters를 반환하지만 "gettysburgAddress ()"와 같은 다른 메소드도 반환합니다. 규칙은 코드에서 강화 될 수 있지만 요점은 런타임 점검 이라는 점입니다 . IDE와 컴파일러는 도움을 줄 수 없습니다. 일반적으로 "문자열 형식"또는 "기본적인 집착"코드를 싫어합니다.

추론하기 어려운

반사 코드는 간단한 메소드 호출보다 더 장황합니다. 더 많은 코드 = 더 많은 버그, 또는 더 많은 버그 가능성, 더 많은 코드 읽기, 테스트 등. 더 적은 것입니다.

리팩토링하기 어려움

코드는 문자열 / 동적 기반이기 때문에; IDE가 반사 용도를 선택할 수 없으므로 IDE의 리팩토링 도구를 사용하여 100 % 신뢰도로 코드를 리팩터링 할 수 없습니다.

기본적으로 가능한 경우 일반 코드에서 반성을 피하십시오. 개선 된 디자인을 찾으십시오.


0

정의에 따라 어떤 것을 남용하는 것은 좋지 않습니다. 이제 (오버)를 제거합시다.

봄의 놀랍도록 무거운 내부 반사 사용을 나쁜 습관이라고 부릅니까?

스프링은 주석을 사용하여 리플렉션을 길들입니다 .Hibernate (그리고 아마도 수십 / 수백 개의 다른 툴들)도 마찬가지입니다.

자신의 코드에서 사용하는 경우 해당 패턴을 따르십시오. 주석을 사용하여 사용자의 IDE가 여전히 도움을 줄 수 있도록하십시오 (코드의 유일한 "사용자"인 경우에도 부주의하게 반사를 사용하면 결국 엉덩이에 물릴 수 있습니다).

그러나 개발자가 코드를 사용하는 방법을 고려하지 않으면 가장 간단한 리플렉션 사용조차도 과도하게 사용됩니다.


0

나는이 답변의 대부분이 요점을 놓친다고 생각합니다.

  1. 예, @KarlBielefeldt가 지적한 것처럼 equals()및을 작성해야합니다 hashcode().

  2. 그러나 많은 분야가있는 수업의 경우 지루한 보일러 플레이트가 될 수 있습니다.

  3. 따라서에 따라 다릅니다 .

    • equals와 hashcode가 거의 필요 하지 않은 경우 범용 반사 계산을 사용하는 것이 실용적이며 아마도 좋습니다. 적어도 빠르고 더러운 첫 패스.
    • 그러나 많은 것들이 필요하다면, 예를 들어 이러한 객체가 HashTables에 저장되어 성능이 문제가 될 수 있으므로 코드를 작성해야합니다. 더 빠를 것입니다.
  4. 또 다른 가능성 : 당신이 수업에 실제로 많은 분야를 가지고 있다면 등호를 쓰는 것이 지루합니다.

    • 필드를지도에 넣습니다.
    • 여전히 커스텀 게터와 세터를 작성할 수 있습니다
    • Map.equals()당신의 비교를 위해 사용 하십시오. (또는 Map.hashcode())

예를 들어 ( 참고 : null 검사를 무시하면 String 키 대신 Enum을 사용해야 할 것입니다.

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.