Когда я пытаюсь создать интерфейс для конкретной программы, я обычно стараюсь избегать исключений, которые зависят от неподтвержденного ввода.
Поэтому часто случается так, что я думаю о таком фрагменте кода (это просто пример ради примера, не обращайте внимания на функцию, которую он выполняет, пример в Java):
public static String padToEvenOriginal(int evenSize, String string) {
if (evenSize % 2 == 1) {
throw new IllegalArgumentException("evenSize argument is not even");
}
if (string.length() >= evenSize) {
return string;
}
StringBuilder sb = new StringBuilder(evenSize);
sb.append(string);
for (int i = string.length(); i < evenSize; i++) {
sb.append(' ');
}
return sb.toString();
}
Хорошо, так сказать, что evenSize
на самом деле происходит от ввода пользователя. Так что я не уверен, что это даже. Но я не хочу вызывать этот метод с вероятностью возникновения исключения. Поэтому я выполняю следующую функцию:
public static boolean isEven(int evenSize) {
return evenSize % 2 == 0;
}
но теперь у меня есть две проверки, которые выполняют одинаковую проверку входных данных: выражение в if
операторе и явная регистрация isEven
. Дублирующий код, не очень хороший, так что давайте сделаем рефакторинг:
public static String padToEvenWithIsEven(int evenSize, String string) {
if (!isEven(evenSize)) { // to avoid duplicate code
throw new IllegalArgumentException("evenSize argument is not even");
}
if (string.length() >= evenSize) {
return string;
}
StringBuilder sb = new StringBuilder(evenSize);
sb.append(string);
for (int i = string.length(); i < evenSize; i++) {
sb.append(' ');
}
return sb.toString();
}
Хорошо, это решило проблему, но теперь мы попадаем в следующую ситуацию:
String test = "123";
int size;
do {
size = getSizeFromInput();
} while (!isEven(size)); // checks if it is even
String evenTest = padToEvenWithIsEven(size, test);
System.out.println(evenTest); // checks if it is even (redundant)
Теперь у нас есть избыточная проверка: мы уже знаем, что значение четное, но padToEvenWithIsEven
все еще выполняет проверку параметров, которая всегда будет возвращать true, как мы уже вызывали эту функцию.
Конечно, сейчас isEven
проблем не возникает, но если проверка параметров более громоздкая, то это может повлечь за собой слишком большие затраты. Кроме того, выполнение избыточного вызова просто не кажется правильным.
Иногда мы можем обойти это, введя «проверенный тип» или создав функцию, где эта проблема не может возникнуть:
public static String padToEvenSmarter(int numberOfBigrams, String string) {
int size = numberOfBigrams * 2;
if (string.length() >= size) {
return string;
}
StringBuilder sb = new StringBuilder(size);
sb.append(string);
for (int i = string.length(); i < size; i++) {
sb.append('x');
}
return sb.toString();
}
но это требует умного мышления и довольно большого рефакторинга.
Есть ли (более) общий способ, которым мы можем избежать избыточных вызовов isEven
и выполнять двойную проверку параметров? Я бы хотел, чтобы решение не вызывало padToEven
неверный параметр, вызывая исключение.
Без исключений я не имею в виду программирование без исключений, я имею в виду, что пользовательский ввод не вызывает исключение по конструкции, в то время как сама универсальная функция все еще содержит проверку параметров (если только для защиты от ошибок программирования).
источник
padToEvenWithIsEven
не выполняет проверку ввода пользователя. Он выполняет проверку достоверности своего ввода, чтобы защитить себя от ошибок программирования в вызывающем коде. Насколько обширной должна быть эта проверка, зависит от анализа затрат / рисков, где вы сравниваете стоимость проверки с риском того, что лицо, пишущее код вызова, передает неверный параметр.Ответы:
В случае вашего примера лучшее решение - использовать более общую функцию заполнения; если вызывающий абонент хочет увеличить размер до четного, он может проверить это самостоятельно.
Если вы неоднократно выполняете одну и ту же проверку значения или хотите разрешить только подмножество значений типа, то микротипы / крошечные типы могут быть полезны. Для утилит общего назначения, таких как заполнение, это не очень хорошая идея, но если ваше значение играет определенную роль в вашей доменной модели, использование выделенного типа вместо примитивных значений может стать большим шагом вперед. Здесь вы можете определить:
Теперь вы можете объявить
и не нужно делать никаких внутренних проверок. Для такого простого теста упаковка int внутри объекта, вероятно, будет более дорогой с точки зрения производительности во время выполнения, но использование системы типов в ваших интересах может уменьшить количество ошибок и прояснить ваш дизайн.
Использование таких крошечных типов может быть даже полезным, даже если они не выполняют проверку, например, для устранения неоднозначности строки, представляющей a
FirstName
из aLastName
. Я часто использую этот шаблон в статически типизированных языках.источник
EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
unsafePadStringToEven
операция, которая не выполняет никаких проверок, но кажется плохой идеей просто избегать проверки.В качестве дополнения к ответу @ amon мы можем объединить его
EvenInteger
с тем, что сообщество функционального программирования может назвать «умным конструктором» - функцией, которая обертывает тупой конструктор и гарантирует, что объект находится в допустимом состоянии (мы делаем тупой класс конструктора или в языковых модулях, не относящихся к классу, закрытый модуль / пакет, чтобы убедиться, что используются только умные конструкторы). Хитрость заключается в том, чтобы вернутьOptional
(или эквивалент), чтобы сделать функцию более компонуемой.Затем мы можем легко использовать стандартные
Optional
методы для написания логики ввода:Вы также можете написать
keepAsking()
в более идиоматическом стиле Java с циклом do-while.Тогда в остальной части вашего кода вы можете
EvenInteger
с уверенностью полагаться, что он действительно будет четным, а наша проверка была записана только один раз, вEvenInteger::of
.источник
Выполнение проверки дважды является проблемой, если результат проверки совпадает И проверка выполняется в том же классе. Это не твой пример. В вашем рефакторинговом коде первая проверка isEven - это проверка правильности ввода, в результате сбоя запрашивается новый ввод. Вторая проверка полностью независима от первой, так как она выполняется для открытого метода padToEvenWithEven, который может быть вызван извне класса и имеет другой результат (исключение).
Ваша проблема похожа на проблему того, что случайно идентичный код путают с несухим. Вы путаете реализацию с дизайном. Они не одинаковы, и то, что у вас одна или дюжина одинаковых строк, не означает, что они служат одной и той же цели и могут навсегда считаться взаимозаменяемыми. Кроме того, возможно, ваш класс делает слишком много, но пропустите это, поскольку это, вероятно, просто игрушечный пример ...
Если это проблема с производительностью, вы можете решить эту проблему, создав закрытый метод, который не выполняет никакой проверки, который ваш public padToEvenWithEven вызвал после выполнения проверки, и который вместо этого будет вызывать ваш другой метод. Если это не проблема производительности, то пусть ваши разные методы выполняют проверки, которые им требуются для выполнения назначенных им задач.
источник