Попробуйте / поймайте в конструкторе - рекомендуемая практика?

Что-то, что мне всегда было любопытно

public class FileDataValidator {

private String[] lineData;

public FileDataValidator(String[] lineData){

    this.lineData = lineData;
    removeLeadingAndTrailingQuotes();

    try
    {
        validateName();
        validateAge();
        validateTown();
    }
    catch(InvalidFormatException e)
    {
        e.printStackTrace();
    }

}

//validation methods below all throwing InvalidFormatException

Не рекомендуется ли включать блок try/catch в мой конструктор? Я знаю, что могу заставить конструктор вернуть исключение вызывающей стороне. Ребята, что вы предпочитаете в вызове методов, как я сделал в конструкторе? В вызывающем классе вы бы предпочли создать экземпляр FileDataValidator и вызывать методы в этом экземпляре? Просто интересно услышать отзывы!


person deanmau5    schedule 28.05.2013    source источник
comment
Больше беспокоит то, что вы будете делать с e в случае API, printStackTrace пахнет забавно. Конечно, вы должны позволить пользователям кода столкнуться с исключением, чтобы они могли что-то с этим сделать? Вот для чего нужны исключения.   -  person Grant Thomas    schedule 28.05.2013
comment
Почему бы не изменить операции validateXXX, чтобы они возвращали логические значения, а затем установить переменную с именем valid, если все три вызова validateXXX допустимы. Затем выставьте эту переменную с помощью метода isValid   -  person cmbaxter    schedule 28.05.2013
comment
Здесь может помочь действие с шаблоном команды; то есть создайте экземпляр метода, который вы будете вызывать несколько раз, передавая данные для проверки, и позвольте этому методу генерировать исключения.   -  person Clockwork-Muse    schedule 28.05.2013


Ответы (3)


В коде, который вы показываете, проблемы проверки не возвращаются к коду, который создает этот экземпляр объекта. Наверное, это НЕ ХОРОШО.

Вариант 1:

Если вы поймаете исключение внутри метода/конструктора, обязательно передайте что-то обратно вызывающей стороне. Вы можете поместить поле isValid, которое будет установлено в true, если все работает. Это будет выглядеть так:

private boolean isValid = false;

public FileDataValidator(String[] lineData){

    this.lineData = lineData;
    removeLeadingAndTrailingQuotes();

    try
    {
        validateName();
        validateAge();
        validateTown();
        isValid = true;
    }
    catch(InvalidFormatException e)
    {
        isValid = false;
    }
}

public boolean isValid() {
    return isValid;
}

Вариант 2:

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

public FileDataValidator(String[] lineData){

    this.lineData = lineData;
    removeLeadingAndTrailingQuotes();

    try
    {
        validateName();
        validateAge();
        validateTown();
    }
    catch(InvalidFormatException e)
    {
        throw new com.myco.myapp.errors.InvalidDataException(e.getMessage());
    }

}

Вариант 3:

Третий метод, который я хочу упомянуть, имеет такой код. В вызывающем коде вы должны вызвать конструктор, а затем вызвать функцию build(), которая либо будет работать, либо нет.

String[] lineData = readLineData();
FileDataValidator onePerson = new FileDataValidator();
try {
    onePerson.build(lineData);
} catch (InvalidDataException e) {
    // What to do it its bad?
}

Вот код класса:

public FileDataValidator() {
    // maybe you need some code in here, maybe not
}

public void build(String[] lineData){

    this.lineData = lineData;
    removeLeadingAndTrailingQuotes();

    try
    {
        validateName();
        validateAge();
        validateTown();
    }
    catch(InvalidFormatException e)
    {
        throw new com.myco.myapp.errors.InvalidDataException(e.getMessage());
    }

}

Конечно, функция build() может использовать метод isValid(), который вы вызываете, чтобы убедиться, что это правильно, но исключение кажется мне правильным способом для функции сборки.

Вариант 4:

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

Это похоже на то, как работают JaxB и JaxRS, что похоже на вашу ситуацию.

  1. Внешний источник данных — у вас файл, у них входящее сообщение в формате XML или JSON.
  2. Код для создания объектов — у вас есть свой код, у них есть свои библиотеки кода, работающие в соответствии со спецификациями в различных JSR.
  3. Валидация не привязана к строительству объектов.

Код вызова:

String[] lineData = readLineData();
Person onePerson = new Person();
FileDataUtilities util = new FileDataUtilities();
try {
    util.build(onePerson, lineData);
    util.validate(onePerson);
} catch (InvalidDataException e) {
    // What to do it its bad?
}

Вот код класса, в котором живут данные:

public class Person {
    private Name name;
    private Age age;
    private Town town;
... lots more stuff here ...
}

И код утилиты для сборки и проверки:

public FileDataValidator() {
    // maybe you need some code in here, maybe not
}

public void build(Person person, String[] lineData){

    this.lineData = lineData;
    removeLeadingAndTrailingQuotes();
    setNameFromData(person);
    setAgeFromData(person);
    setTownFromData(person);
}

public boolean validate(Person person) {

    try
    {
        validateName(person);
        validateAge(person);
        validateTown(person);
        return true;
    }
    catch(InvalidFormatException e)
    {
        throw new com.myco.myapp.errors.InvalidDataException(e.getMessage());
    }

}
person Lee Meador    schedule 28.05.2013
comment
Ваш первый тип не работает (методы внутри конструктора?), а второй - просто упражнение в избыточности. - person Grant Thomas; 28.05.2013
comment
@GrantThomas Я хотел бы услышать больше. Я не вижу проблем с вызовом методов в конструкторе, если они служат полезной цели. Может быть, вы могли бы объяснить. ОП должен был бы решить, что полезно. Во-вторых, вы говорите, что catch-rethrow избыточен? - person Lee Meador; 28.05.2013
comment
@LeeMeador Спасибо, что потратили столько времени на написание нескольких идей! Они дали много пищи для размышлений. Я думаю, что я выберу вариант 3. Перемещение вызовов проверки из конструктора и передача исключения вызывающей стороне гораздо удобнее с точки зрения возможности повторного использования одного объекта, а вызывающий класс гораздо больше подходит для того, чтобы знать, что должно произойти, если были переданы неверные данные. - person deanmau5; 28.05.2013
comment
@LeeMeador Я также думаю, что ваш первый пример неисправен. Вы действительно вызываете методы из конструктора, что синтаксически нормально, но вы также объявляете метод isValid() в своем конструкторе. Это проблема с фигурными скобками. - person Timmos; 20.11.2013
comment
@Timmos Исправлено! Спасибо. - person Lee Meador; 26.11.2013
comment
@GrantThomas: перехватывать проверенные исключения и повторно выдавать их как непроверенные исключения часто является хорошей идеей, и ее следует делать гораздо чаще, чем на самом деле (действительно, я бы сказал, что едва ли не единственная проблема с проверенными исключениями - это отсутствие удобного синтаксиса это выразить). Отмеченные исключения следует разрешать распространять только в тех случаях, когда вызывающая сторона воспринимает исключение как имеющее то же значение, что и код, который его вызвал. Если метод ReadSoundFile пытается загрузить КОДЕК, и эта попытка вызывает исключение FileNotFoundException, разрешение его распространения будет... - person supercat; 26.11.2013
comment
... ложно заставить вызывающего абонента поверить, что у метода ReadSoundFile возникли проблемы с загрузкой файла, содержащего звук (в отличие от файла, содержащего программное обеспечение, необходимое для его декодирования). Если бы в Java было объявление doesntexpect X, с помощью которого можно было бы указать, что он вызывает методы, объявленные как выбрасывающие X, но не ожидает, что они это сделают, и что если они выбрасывают X, это должно рассматриваться как неожиданное и повторно выбрасываться как непроверенное исключение, то проверенные исключения могут быть весьма полезными. - person supercat; 26.11.2013

Вы должны рассмотреть шаблон статической фабрики. Сделайте свой конструктор всех аргументов закрытым. Предоставьте статический метод FileDataValidator(args...). Это принимает и проверяет все аргументы. Если все в порядке, он может вызвать приватный конструктор и вернуть только что созданный объект. Если что-то не удается, сгенерируйте исключение, чтобы сообщить вызывающей стороне, что она предоставила неверные значения.

Я также должен упомянуть, что это: catch (Exception e) { printSomeThing(e); }

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

person gyorgyabraham    schedule 28.05.2013
comment
Спасибо, что предложили этот шаблон. Я очень люблю реализовывать шаблоны в своем коде, и я обязательно поэкспериментирую с этим. Спасибо за совет против шаблона! Честно говоря, он у меня есть только для целей разработки, пока я не найду решение первоначального вопроса! - person deanmau5; 28.05.2013

Я предпочитаю, чтобы исключения обрабатывались тем фрагментом кода, который знает, как с ними работать. В этом случае я бы предположил, что фрагмент кода, создающий FileDataValidator, знает, что должно произойти, если данные файла недействительны, и с исключениями следует обращаться там (я выступаю за распространение к вызывающей стороне).

Обсуждая передовой опыт, мне кажется, что имя класса FileDataValidator пахнет. Если объект, который вы создаете, хранит данные файла, я бы назвал его FileData - возможно, с методом проверки? Если вы хотите только проверить данные своего файла, то статического метода будет достаточно.

person selig    schedule 28.05.2013