Должен ли метод быть прощающим с аргументами, которые передаются? [закрыто]

21

Предположим, у нас есть метод, foo(String bar)который работает только со строками, которые соответствуют определенным критериям; например, он должен быть в нижнем регистре, не должен быть пустым или иметь только пробел и должен соответствовать шаблону [a-z0-9-_./@]+. Документация для метода устанавливает эти критерии.

Должен ли метод отклонять какие-либо отклонения от этого критерия или метод должен быть более щадящим по некоторым критериям? Например, если начальный метод

public void foo(String bar) {
    if (bar == null) {
        throw new IllegalArgumentException("bar must not be null");
    }
    if (!bar.matches(BAR_PATTERN_STRING)) {
        throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
    }
    this.bar = bar;
}

И второй способ прощения

public void foo(String bar) {
    if (bar == null) {
        throw new IllegalArgumentException("bar must not be null");
    }
    if (!bar.matches(BAR_PATTERN_STRING)) {
        bar = bar.toLowerCase().trim().replaceAll(" ", "_");
        if (!bar.matches(BAR_PATTERN_STRING) {
            throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
        }
    }
    this.bar = bar;
}

Следует ли изменить документацию, указав, что она будет преобразована и по возможности установлена ​​на преобразованное значение, или метод должен быть максимально простым и отклонять любые отклонения? В этом случае barможет быть установлено пользователем приложения.

Основным вариантом использования для этого будут пользователи, которые обращаются к объектам из хранилища по определенному строковому идентификатору. Каждый объект в хранилище должен иметь уникальную строку для его идентификации. Эти репозитории могут хранить объекты различными способами (sql server, json, xml, binary и т. Д.), Поэтому я попытался определить наименьший общий знаменатель, который соответствовал бы большинству соглашений об именах.

Zymus
источник
1
Это, вероятно, сильно зависит от вашего варианта использования. Любой из них может быть разумным, и я даже видел классы, которые предоставляют оба метода и заставляют пользователя решать. Не могли бы вы уточнить, что должен делать этот метод / класс / поле, чтобы мы могли дать вам реальный совет?
Ixrec
1
Вы знаете всех, кто вызывает метод? Например, если вы измените его, сможете ли вы надежно идентифицировать всех клиентов? Если это так, я бы сделал все, что позволят проблемы производительности. Я мог бы также удалить документацию. Если нет, и это часть библиотечного API, я бы удостоверился, что в коде реализован именно объявленный API, так как в противном случае изменение кода в соответствии с документацией в будущем может привести к созданию отчетов об ошибках.
Джон Честерфилд
7
Вы можете утверждать, что разделение проблем говорит, что при необходимости вы должны иметь строгую fooфункцию, которая строго соответствует аргументам, которые она принимает, и иметь вторую вспомогательную функцию, которая может попытаться «очистить» аргумент, который будет использоваться foo. Таким образом, у каждого метода меньше работы, и они могут более четко управляться и интегрироваться. Если идти по этому пути, вероятно, было бы также полезно отойти от тяжелого дизайна; Optionalвместо этого вы можете использовать что-то подобное , а затем иметь функции, которые потребляют fooисключения исключений, если это необходимо.
gntskn
1
Это все равно что спросить: «Кто-то обидел меня, должен ли я их простить?» Очевидно, что существуют обстоятельства, когда один или другой уместен. Программирование может быть не таким сложным, как человеческие отношения, но оно определенно достаточно сложно, чтобы такой общий рецепт не сработал.
Килиан Фот
2
@ Боггин Я также хотел бы указать вам на пересмотренный принцип надежности . Трудность возникает тогда, когда вам нужно расширить реализацию, а прощающая реализация приводит к неоднозначному случаю с расширенной реализацией.

Ответы:

47

Ваш метод должен делать то, что говорит.

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

Тем не менее, если определенная логика не удобна для пользователя, возможно, ее следует улучшить.

Telastyn
источник
8
Это ключ. Если ваш метод делает именно то, что говорит, кодер, использующий ваш метод, компенсирует их конкретный вариант использования. Никогда не делайте что-то недокументированное с этим методом только потому, что считаете его полезным. Если вам нужно изменить его, напишите контейнер или измените документацию.
Нельсон
Я добавил бы к комментарию @ Нельсона, что метод не должен быть разработан в вакууме. Если кодеры говорят, что они будут использовать его, но будут компенсировать, а их компенсации имеют ценность общего назначения, рассмотрите возможность сделать их частью класса. (Например, есть fooи fooForUncleanStringметоды, где последний вносит исправления, прежде чем передать его первому.)
Blrfl
20

Есть несколько моментов:

  1. Ваша реализация должна делать то, что указано в документированном контракте, и больше ничего не должна делать.
  2. Простота важна как для контракта, так и для реализации, хотя больше для первого.
  3. Попытка исправить ошибочный ввод добавляет сложность, не интуитивно понятную не только для контракта и реализации, но и для использования.
  4. Ошибки следует обнаруживать только на ранней стадии, если это улучшает отладку и не слишком снижает эффективность.
    Помните, что существуют отладочные утверждения для диагностики логических ошибок в режиме отладки, которые в основном устраняют любые проблемы с производительностью.
  5. Эффективность, насколько позволяют время и деньги, не слишком компрометируя простоту, всегда является целью.

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


Настоящим экспериментом по нечеткости и разрешению ввода является HTML.
Это привело к тому, что все делали это немного по-разному, и спецификация, теперь она задокументирована, была гигантским томом, полным особых случаев.
См . Закон Постеля (« Будь консервативен в том, что ты делаешь, будь либеральным в том, что ты принимаешь от других »), и критик, касающийся этого ( или гораздо лучший, о котором Майкл Т дал мне знать ).

Deduplicator
источник
Еще одна критическая статья, написанная автором sendmail: Пересмотрен принцип
15

Поведение метода должно быть четким, интуитивным, предсказуемым и простым. В общем, мы должны очень неохотно делать дополнительную обработку входных данных вызывающей стороны. Такие предположения о том, что подразумевал вызывающий, неизменно имеют множество крайних случаев, которые приводят к нежелательному поведению. Рассмотрим операцию так же просто, как присоединение пути к файлу. Многие (или, возможно, даже большинство) функций присоединения путей к файлам будут молча отбрасывать любые предшествующие пути, если один из соединяемых путей окажется корневым! Например, /abc/xyzсоединение с /evilприведет к просто /evil. Это почти никогда не то, что я имею в виду, когда присоединяюсь к путям к файлам, но поскольку нет интерфейса, который не ведет себя таким образом, я вынужден либо иметь ошибки, либо писать дополнительный код, покрывающий эти случаи.

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

  • Необработанный функционал без какой-либо предварительной обработки.
  • Этап предварительной обработки сам по себе .
  • Сочетание сырой функциональности и предварительной обработки.

Последнее не является обязательным; Вы должны предоставить его, только если большое количество звонков будет использовать его.

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

jpmc26
источник
2
+1 за предсказуемый. И еще +1 (желаю) для простых. Я бы предпочел, чтобы вы помогли мне определить и исправить мои ошибки, чем пытаться их скрыть.
Джон М Гант
4

Как уже говорили другие, сделать соответствие строк «прощающим» означает внести дополнительную сложность. Это означает больше работы по реализации сопоставления. Например, теперь у вас есть еще много тестов. Вы должны выполнить дополнительную работу, чтобы гарантировать отсутствие семантически равных имен в пространстве имен. Большая сложность также означает, что в будущем будет больше ошибок. Более простой механизм, такой как велосипед, требует меньше обслуживания, чем более сложный, такой как автомобиль.

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

С другой стороны, если бы входные данные поступали, скажем, из файлов свойств, собранных техническими специалистами, которые должны это понимать "Fred Mertz" != "FredMertz", я был бы более склонен сделать соответствие более строгим и сэкономить на стоимости разработки.

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

mat_noshi
источник
3

Вы упоминаете некоторые из контекста, из которого возникает этот вопрос.

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

Если вы хотите преобразовать данные, поступающие из пользовательской базы данных, более щадящим образом, поместите эти функции в отдельный метод преобразования и документируйте связанные функции .

В какой-то момент требования функции должны быть отмерены, четко задокументированы и выполнение должно продолжаться. «Прощение», с его точки зрения, немного немое, это дизайнерское решение, и я бы поспорил за функцию, не изменяющую аргумент. Наличие функции mutate на входе скрывает некоторую валидацию, которая потребуется клиенту. Наличие функции, которая выполняет мутацию, помогает клиенту понять это правильно.

Большой акцент здесь ясность и документ , что код делает .

Найл
источник
-1
  1. Вы можете назвать метод в соответствии с действием, таким как doSomething (), takeBackUp ().
  2. Чтобы упростить обслуживание, вы можете сохранить общие контракты и проверки для различных процедур. Позвоните им в соответствии с вариантами использования.
  3. Защитное программирование: ваша процедура обрабатывает широкий диапазон входных данных, включая (минимальные вещи, которые являются случаями использования, должны быть покрыты в любом случае)
user435491
источник