Является ли хорошей практикой сделать конструктор исключение?

Хорошо ли сделать конструктор для исключения? Например, у меня есть класс 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

Выбрасывание исключений в конструкторе - неплохая практика. Фактически, это единственный разумный способ для конструктора указать, что есть проблема; например что параметры недействительны.

Однако явно декларирование или метание java.lang.Exception почти всегда плохое.

Вы должны выбрать класс исключений, который соответствует исключительному состоянию, которое произошло. Если вы выбрали Exception, вызывающему пользователю сложно отделить это исключение от любого количества других возможных объявленных и необъявленных исключений. Это затрудняет восстановление ошибок, и если вызывающий абонент выбирает распространение Исключения, проблема просто распространяется.


Кто-то предложил использовать assert для проверки аргументов. Проблема заключается в том, что проверка утверждений assert может быть включена и выключена с помощью настройки командной строки JVM. Использование утверждений для проверки внутренних инвариантов в порядке, но использование их для реализации проверки аргументов, указанной в вашем javadoc, не является хорошей идеей... потому что это означает, что ваш метод будет строго выполнять спецификацию только тогда, когда включена проверка утверждения.

Вторая проблема с assert заключается в том, что если утверждение терпит неудачу, тогда будет отклонено AssertionError, и полученная мудрость заключается в том, что это плохая идея попытаться поймать Error и любой из ее подтипов.

Ответ 2

Я всегда считал, что исключить проверенные исключения в конструкторе, чтобы быть плохой практикой, или, по крайней мере, что-то, чего следует избегать.

Причиной этого является то, что вы не можете этого сделать:

private SomeObject foo = new SomeObject();

Вместо этого вы должны сделать это:

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

В тот момент, когда я создаю SomeObject, я знаю, что это за параметры так почему я должен ожидать, чтобы он обернул его в try catch? Ах, вы говорите, но если я строю объект из динамических параметров, я не знаю, действительны они или нет. Ну, вы можете... проверить параметры перед передачей их конструктору. Это будет хорошей практикой. И если все, что вас беспокоит, является ли параметры действительными, вы можете использовать IllegalArgumentException.

Поэтому вместо того, чтобы бросать проверенные исключения, просто

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();
}

Но как часто это возможно?

Ответ 3

Как упоминалось в другом ответе здесь, в Руководстве 7-3 Java Защищенное кодирование Руководства, бросая исключение в конструкторе не конечного класса, открывает потенциальный вектор атаки:

Руководящий принцип 7-3/ОБЪЕКТ-3: защита от частично инициализированного экземпляры нефинальных классов Когда конструктор в классе без конечных элементов выдает исключение, злоумышленники могут попытаться получить доступ к частичному инициализированные экземпляры этого класса. Обеспечить, чтобы не конечный класс остается полностью непригодным до тех пор, пока его конструктор не завершится успешно.

От JDK 6 до, построение класса подкласса может быть предотвращено путем исключения исключения до завершения конструктора Object. к сделайте это, выполните проверки в выражении, которое оценивается в вызовите этот() или 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;
        }
    }

Для совместимости со старыми версиями потенциальное решение включает использование инициализированного флага. Установите флаг как последнюю операцию в конструктор, прежде чем вернуться успешно. Все методы, обеспечивающие шлюз для чувствительных операций должен сначала проконсультироваться с флагом производство:

    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"
                );
            }
        }
    }

Кроме того, любые чувствительные к безопасности применения таких классов должны проверять состояние флага инициализации. В случае ClassLoader конструкции, он должен проверить, что его загрузчик родительского класса инициализированы.

Доступ к частично инициализированным экземплярам не конечного класса через атаку финализатора. Атакующий переопределяет защищенную финализацию метод в подклассе и пытается создать новый экземпляр этого подкласс. Эта попытка не выполняется (в приведенном выше примере Проверка SecurityManager в конструкторе ClassLoader создает защиту исключение), но злоумышленник просто игнорирует любое исключение и ждет для того, чтобы виртуальная машина выполняла финализацию частично инициализированный объект. Когда это происходит, метод вредоносного завершения вызывается реализация, предоставляющая злоумышленнику доступ к этому, ссылка на завершаемый объект. Хотя объект только частично инициализированный, злоумышленник может все же ссылаться на методы на нем, тем самым обходя проверку SecurityManager. Хотя инициализированный флаг не препятствует доступу к частично инициализированному объекту, он не позволяет методам на этом объекте делать что-либо полезное для взломщик.

Использование инициализированного флага, хотя и безопасно, может быть громоздким. Просто гарантируя, что все поля в открытом не-конечном классе содержат безопасные значение (например, null) до завершения инициализации объекта успешно может представлять разумную альтернативу в классах, которые не чувствительны к безопасности.

Более надежный, но и более подробный подход - использовать "указатель на реализация" (или "pimpl" ). Ядро класса перемещается в непубличный класс с вызовами метода пересылки класса интерфейса. Любые попытки использовать класс до его полной инициализации приведут к в исключении NullPointerException. Этот подход также хорош для клонов и десериализации.

    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
            ...
        }
    }

Ответ 4

Вам не нужно бросать проверенное исключение. Это ошибка в управлении программой, поэтому вы хотите выбросить исключенное исключение. Используйте одно из непроверенных исключений, уже предоставленных языком Java, например IllegalArgumentException, IllegalStateException или NullPointerException.

Вы также можете избавиться от сеттера. Вы уже предоставили способ инициировать age через конструктор. Нужно ли обновлять его после создания экземпляра? Если нет, пропустите установщик. Хорошее правило, не делайте вещи более публичными, чем необходимо. Начните с частного или по умолчанию и закрепите свои данные с помощью final. Теперь всем известно, что Person был построен правильно и неизменен. Его можно использовать с уверенностью.

Скорее всего, это то, что вам действительно нужно:

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

Ответ 5

Плохая практика - это выбросить исключение, поскольку для этого требуется, чтобы любой, кто называет ваш конструктор, перехватывает исключение, что является плохой практикой.

Хорошо, что конструктор (или любой метод) генерирует исключение, вообще говоря, IllegalArgumentException, которое не проверяется, и, следовательно, компилятор не заставляет вас его поймать.

Вы должны бросить проверенные исключения (вещи, которые простираются от Exception, но не RuntimeException), если вы хотите, чтобы вызывающий абонент поймал его.

Ответ 6

Я никогда не думал, что это плохая практика, чтобы исключить исключение в конструкторе. Когда класс разработан, у вас есть определенная идея с учетом того, какой должна быть структура для этого класса. Если кто-то другой имеет другую идею и пытается выполнить эту идею, тогда вы должны ошибаться соответственно, предоставляя пользователю отзыв о том, что такое ошибка. В вашем случае вы можете рассмотреть что-то вроде

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

где NegativeAgeException - это класс исключений, который вы создали самостоятельно, возможно, расширение другого исключения, такого как IndexOutOfBoundsException или что-то подобное.

Утверждения точно не подходят для вас, так как вы не пытаетесь обнаружить ошибки в своем коде. Я бы сказал, что прекращение с исключением - это абсолютно то, что нужно сделать здесь.

Ответ 7

Это абсолютно верно, я делаю это все время. Обычно я использую IllegalArguemntException, если это результат проверки параметров.

В этом случае я бы не предлагал утверждать, потому что они отключены в сборке развертывания, и вы всегда хотите остановить это, но ваши действительны, если ваша группа делает ВСЕ это тестирование с включенными утверждениями, и вы думаете, что вероятность пропустить проблему с параметром во время выполнения более приемлема, чем бросать исключение, которое, вероятно, может привести к сбою во время выполнения.

Кроме того, для вызывающего абонента было бы сложнее утверждать, что это легко.

Перечислите его как "броски" в вашем методе javadocs.

Ответ 8

Я не собираюсь исключать исключения из конструктора, поскольку рассматриваю это как нечистое. По моему мнению, есть несколько причин.

  • Как упоминал Ричард, вы не можете инициализировать экземпляр простым способом. Особенно в тестах на самом деле раздражает создание тестового объекта только путем его окружения при попытке try во время инициализации.

  • Конструкторы должны быть без логики. Нет никакой причины для инкапсуляции логики в конструктор, поскольку вы всегда стремитесь к разделению проблем и принципу единой ответственности. Поскольку проблема конструктора заключается в том, чтобы "построить объект", он не должен инкапсулировать обработку исключений, если следовать этому подходу.

  • Это пахнет плохим дизайном. Imho, если я вынужден выполнять обработку исключений в конструкторе, я сначала спрашиваю себя, есть ли у меня какие-либо мошенничества в моем классе. Иногда это необходимо, но затем я передаю это строителю или factory, чтобы сохранить конструктор как можно проще.

Итак, если в конструкторе необходимо выполнить некоторую обработку исключений, почему бы вам не передать эту логику в конструктор Factory? Это может быть еще несколько строк кода, но дает вам возможность реализовать гораздо более надежную и хорошо подходящую обработку исключений, так как вы можете дополнительно передать логику обработки исключений и не придерживаться конструктора, который будет инкапсулировать слишком много логика. И клиент не должен ничего знать о вашей логике построения, если вы делегируете обработку исключений должным образом.

Ответ 9

Я бы не выбрал исключение из конструктора, главным образом потому, что, когда я создаю объекты, я хочу, чтобы они сохраняли данные только. Когда вы генерируете исключение из конструктора, вы позволяете объекту данных управлять логическим потоком приложения. Используя шаблон построителя, вы можете создать объект Person, сохраняя при этом все правила/условия в одном классе.