IT story

생성자가 예외를 발생시키는 것이 좋은 습관입니까?

hot-time 2020. 6. 11. 08:26
반응형

생성자가 예외를 발생시키는 것이 좋은 습관입니까? [복제]


이 질문에는 이미 답변이 있습니다.

생성자가 예외를 발생시키는 것이 좋은 습관입니까? 예를 들어 클래스가 Person있고 age유일한 속성으로 사용합니다. 이제 나는 수업을

class Person{
  int age;
  Person(int age) throws Exception{
   if (age<0)
       throw new Exception("invalid age");
   this.age = age;
  }

  public void setAge(int age) throws Exception{
  if (age<0)
       throw new Exception("invalid age");
   this.age = age;
  }
}

생성자에서 예외를 던지는 것은 나쁜 습관이 아닙니다. 실제로 이것은 생성자가 문제가 있음을 나타내는 유일한 합리적인 방법입니다. 예를 들어 매개 변수가 유효하지 않습니다.

또한 확인 된 예외가 1) 선언되고 2)보고하는 문제와 관련이 있다고 가정하고 확인 된 예외 를 던지는 것이 OK 1 이라고 생각합니다 .

그러나 명시 적으로 선언하거나 던지는 java.lang.Exception것은 거의 항상 나쁜 습관입니다.

발생한 예외 조건과 일치하는 예외 클래스를 선택해야합니다. 던지면 Exception호출자 가이 예외를 다른 가능한 선언 및 선언되지 않은 예외와 분리하는 것이 어렵습니다. 이로 인해 오류 복구가 어려워지고 호출자가 예외를 전파하도록 선택하면 문제가 확산됩니다.


1-일부 사람들은 동의하지 않을 수 있지만 IMO는이 사례와 예외 발생 사례 사이에 차이가 없습니다. 표준 확인 대 확인되지 않은 조언은 두 경우 모두에 동일하게 적용됩니다.


누군가가 assert인수 확인에 사용 하도록 제안했습니다 . 이 문제 assert는 JVM 명령 줄 설정을 통해 어설 션 검사를 켜고 끌 수 있다는 것입니다. 어설 션을 사용하여 내부 불변을 확인하는 것은 좋지만, javadoc에 지정된 인수 확인을 구현하기 위해 어설 션을 사용하는 것은 좋은 생각이 아닙니다 ... 어설 션 확인이 활성화되어 있으면 메소드가 스펙을 엄격하게 구현한다는 의미이기 때문입니다.

두 번째 문제 assert는 주장이 실패하면 AssertionError던져 질 것이며, 지혜를 받는다는 점은 그 하위 유형 을 잡으려고 시도 하는 것이 나쁜 생각이라는 것Error 입니다.


나는 항상 생성자에서 확인 된 예외를 던지는 것이 나쁜 습관이거나 적어도 피해야 할 것으로 생각했습니다.

그 이유는 당신이 이것을 할 수 없기 때문입니다.

private SomeObject foo = new SomeObject();

대신이 작업을 수행해야합니다.

private SomeObject foo;
public MyObject() {
    try {
        foo = new SomeObject()
    } Catch(PointlessCheckedException e) {
       throw new RuntimeException("ahhg",e);
    }
}

SomeObject를 생성 할 때 매개 변수가 무엇인지 알고 왜 시도 캐치로 래핑해야합니까? Ahh 당신이 말하지만 동적 매개 변수로 객체를 구성하는 경우 그것이 유효한지 알 수 없습니다. 음, 매개 변수를 생성자에 전달하기 전에 매개 변수의 유효성을 검사 할 수 있습니다. 좋은 습관이 될 것입니다. 매개 변수가 유효한지 여부 만 신경 쓰면 IllegalArgumentException을 사용할 수 있습니다.

따라서 확인 된 예외를 throw하는 대신 수행하십시오.

public SomeObject(final String param) {
    if (param==null) throw new NullPointerException("please stop");
    if (param.length()==0) throw new IllegalArgumentException("no really, please stop");
}

물론 확인 된 예외를 던지는 것이 합리적 일 수있는 경우가 있습니다

public SomeObject() {
    if (todayIsWednesday) throw new YouKnowYouCannotDoThisOnAWednesday();
}

그러나 그 빈도는 얼마나됩니까?


여기또 다른 답변에서 언급 된 바와 같이 Java Secure Coding Guidelines 의 Guideline 7-3 에서 비 최종 클래스의 생성자에서 예외를 throw하면 잠재적 공격 경로가 열립니다.

지침 7-3 / OBJECT-3 : 최종 클래스가 아닌 클래스의 부분적으로 초기화 된 인스턴스로부터 방어 최종 클래스가 아닌 클래스의 생성자가 예외를 throw하면 공격자는 해당 클래스의 부분적으로 초기화 된 인스턴스에 액세스하려고 시도 할 수 있습니다. 생성자가 성공적으로 완료 될 때까지 비 최종 클래스를 완전히 사용할 수없는 상태로 유지하십시오.

JDK 6부터는 Object 생성자가 완료되기 전에 예외를 발생시켜 서브 클래스 가능 클래스의 생성을 막을 수 있습니다. 이렇게하려면 this () 또는 super ()에 대한 호출에서 평가되는 표현식에서 검사를 수행하십시오.

    // non-final java.lang.ClassLoader
    public abstract class ClassLoader {
        protected ClassLoader() {
            this(securityManagerCheck());
        }
        private ClassLoader(Void ignored) {
            // ... continue initialization ...
        }
        private static Void securityManagerCheck() {
            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkCreateClassLoader();
            }
            return null;
        }
    }

For compatibility with older releases, a potential solution involves the use of an initialized flag. Set the flag as the last operation in a constructor before returning successfully. All methods providing a gateway to sensitive operations must first consult the flag before proceeding:

    public abstract class ClassLoader {

        private volatile boolean initialized;

        protected ClassLoader() {
            // permission needed to create ClassLoader
            securityManagerCheck();
            init();

            // Last action of constructor.
            this.initialized = true;
        }
        protected final Class defineClass(...) {
            checkInitialized();

            // regular logic follows
            ...
        }

        private void checkInitialized() {
            if (!initialized) {
                throw new SecurityException(
                    "NonFinal not initialized"
                );
            }
        }
    }

Furthermore, any security-sensitive uses of such classes should check the state of the initialization flag. In the case of ClassLoader construction, it should check that its parent class loader is initialized.

Partially initialized instances of a non-final class can be accessed via a finalizer attack. The attacker overrides the protected finalize method in a subclass and attempts to create a new instance of that subclass. This attempt fails (in the above example, the SecurityManager check in ClassLoader's constructor throws a security exception), but the attacker simply ignores any exception and waits for the virtual machine to perform finalization on the partially initialized object. When that occurs the malicious finalize method implementation is invoked, giving the attacker access to this, a reference to the object being finalized. Although the object is only partially initialized, the attacker can still invoke methods on it, thereby circumventing the SecurityManager check. While the initialized flag does not prevent access to the partially initialized object, it does prevent methods on that object from doing anything useful for the attacker.

Use of an initialized flag, while secure, can be cumbersome. Simply ensuring that all fields in a public non-final class contain a safe value (such as null) until object initialization completes successfully can represent a reasonable alternative in classes that are not security-sensitive.

A more robust, but also more verbose, approach is to use a "pointer to implementation" (or "pimpl"). The core of the class is moved into a non-public class with the interface class forwarding method calls. Any attempts to use the class before it is fully initialized will result in a NullPointerException. This approach is also good for dealing with clone and deserialization attacks.

    public abstract class ClassLoader {

        private final ClassLoaderImpl impl;

        protected ClassLoader() {
            this.impl = new ClassLoaderImpl();
        }
        protected final Class defineClass(...) {
            return impl.defineClass(...);
        }
    }

    /* pp */ class ClassLoaderImpl {
        /* pp */ ClassLoaderImpl() {
            // permission needed to create ClassLoader
            securityManagerCheck();
            init();
        }

        /* pp */ Class defineClass(...) {
            // regular logic follows
            ...
        }
    }

You do not need to throw a checked exception. This is a bug within the control of the program, so you want to throw an unchecked exception. Use one of the unchecked exceptions already provided by the Java language, such as IllegalArgumentException, IllegalStateException or NullPointerException.

You may also want to get rid of the setter. You've already provided a way to initiate age through the constructor. Does it need to be updated once instantiated? If not, skip the setter. A good rule, do not make things more public than necessary. Start with private or default, and secure your data with final. Now everyone knows that Person has been constructed properly, and is immutable. It can be used with confidence.

Most likely this is what you really need:

class Person { 

  private final int age;   

  Person(int age) {    

    if (age < 0) 
       throw new IllegalArgumentException("age less than zero: " + age); 

    this.age = age;   
  }

  // setter removed

This is totally valid, I do it all the time. I usually use IllegalArguemntException if it is a result of parameter checking.

In this case I wouldn't suggest asserts because they are turned off in a deployment build and you always want to stop this from happening, but they are valid if your group does ALL it's testing with asserts turned on and you think the chance of missing a parameter problem at runtime is more acceptable than throwing an exception that is maybe more likely to cause a runtime crash.

Also, an assert would be more difficult for the caller to trap, this is easy.

You probably want to list it as a "throws" in your method's javadocs along with the reason so that callers aren't surprised.


It is bad practice to throw Exception, as that requires anyone who calls your constructor to catch Exception which is a bad practice.

It is a good idea to have a constructor (or any method) throw an exception, generally speaking IllegalArgumentException, which is unchecked, and thus the compiler doesn't force you to catch it.

You should throw checked exceptions (things that extend from Exception, but not RuntimeException) if you want the caller to catch it.


I have never considered it to be a bad practice to throw an exception in the constructor. When the class is designed, you have a certain idea in mind of what the structure for that class should be. If someone else has a different idea and tries to execute that idea, then you should error accordingly, giving the user feedback on what the error is. In your case, you might consider something like

if (age < 0) throw new NegativeAgeException("The person you attempted " +
                       "to construct must be given a positive age.");

where NegativeAgeException is an exception class that you constructed yourself, possibly extending another exception like IndexOutOfBoundsException or something similar.

Assertions don't exactly seem to be the way to go, either, since you're not trying to discover bugs in your code. I would say terminating with an exception is absolutely the right thing to do here.


I am not for throwing Exceptions in the constructor since I am considering this as non-clean. There are several reasons for my opinion.

  1. As Richard mentioned you cannot initialize an instance in an easy manner. Especially in tests it is really annoying to build a test-wide object only by surrounding it in a try-catch during initialization.

  2. Constructors should be logic-free. There is no reason at all to encapsulate logic in a constructor, since you are always aiming for the Separation of Concerns and Single Responsibility Principle. Since the concern of the constructor is to "construct an object" it should not encapsulate any exception handling if following this approach.

  3. It smells like bad design. Imho if I am forced to do exception handling in the constructor I am at first asking myself if I have any design frauds in my class. It is necessary sometimes, but then I outsource this to a builder or factory to keep the constructor as simple as possible.

So if it is necessary to do some exception handling in the constructor, why would you not outsource this logic to a Builder of Factory? It might be a few more lines of code but gives you the freedom to implement a far more robust and well suited exception handling since you can outsource the logic for the exception handling even more and are not sticked to the constructor, which will encapsulate too much logic. And the client does not need to know anything about your constructing logic if you delegate the exception handling properly.

참고URL : https://stackoverflow.com/questions/6086334/is-it-good-practice-to-make-the-constructor-throw-an-exception

반응형