Переробка функції, що повертає цілий код, який представляє багато різних статусів


10

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

  • Чи існує назва цього анти-шаблону?
  • Які рекомендації щодо цього рефакторингу є?

    // 0=Need to log in / present username and password
    // 2=Already logged in
    // 3=Inactive User found
    // 4=Valid User found-establish their session
    // 5=Valid User found with password change needed-establish their session
    // 6=Invalid User based on app login
    // 7=Invalid User based on network login
    // 8=User is from an non-approved remote address
    // 9=User account is locked
    // 10=Next failed login, the user account will be locked
    
    public int processLogin(HttpServletRequest request, HttpServletResponse response, 
                            int pwChangeDays, ServletContext ServContext) { 
    }
    

2
Що таке "знайдено-встановити" та "потрібно-встановити" ?
Tulains Córdova

4
Це повинно бути тире , яке читається як "знайдений дійсний користувач: встановити їх сеанс".
BJ Майєрс

2
@A_B Які з цих повернених значень є вдалими, це невдалі входи. Далеко не всі зрозумілі.
Тулен Кордова

@A_B Чи означає "встановити свій сеанс" означає "сеанс створений" або "потребує стабілізації сеансу"?
Tulains Córdova

@ TulainsCórdova: "Встановити" означає стільки, скільки "створити" (принаймні в цьому контексті) - так "встановити свою сесію" приблизно дорівнює "створити свою сесію"
hoffmale

Відповіді:


22

Код поганий не тільки тому, що магічні числа , а й тому, що він поєднує кілька значень у зворотному коді, ховаючи всередині свого значення помилку, попередження, дозвіл на створення сеансу або комбінацію трьох, що робить його поганий внесок для прийняття рішень.

Я б запропонував такий рефакторинг: повернення перерахунку з можливими результатами (як це пропонується в інших відповідях), але додавання до enum атрибута, який вказує, чи це відмова, відмова (я дам вам пройти цей останній раз) або якщо це нормально (PASS):

public LoginResult processLogin(HttpServletRequest request, HttpServletResponse response, 
                            int pwChangeDays, ServletContext ServContext) { 
    }

==> LoginResult.java <==

public enum LoginResult {
    NOT_LOGGED_IN(Severity.DENIAL),
    ALREADY_LOGGED_IN(Severity.PASS),
    INACTIVE_USER(Severity.DENIAL),
    VALID_USER(Severity.PASS),
    NEEDS_PASSWORD_CHANGE(Severity.WAIVER),
    INVALID_APP_USER(Severity.DENIAL),
    INVALID_NETWORK_USER(Severity.DENIAL),
    NON_APPROVED_ADDRESS(Severity.DENIAL),
    ACCOUNT_LOCKED(Severity.DENIAL),
    ACCOUNT_WILL_BE_LOCKED(Severity.WAIVER);

    private Severity severity;

    private LoginResult(Severity severity) {
        this.severity = severity;
    }

    public Severity getSeverity() {
        return this.severity;
    }
}

==> Серйозність.java <==

public enum Severity {
    PASS,
    WAIVER,
    DENIAL;
}

==> Тест.java <==

public class Test {

    public static void main(String[] args) {
        for (LoginResult r: LoginResult.values()){
            System.out.println(r + " " +r.getSeverity());           
        }
    }
}

Вихід для Test.java, що показує суворість для кожного LoginResult:

NOT_LOGGED_IN : DENIAL
ALREADY_LOGGED_IN : PASS
INACTIVE_USER : DENIAL
VALID_USER : PASS
NEEDS_PASSWORD_CHANGE : WAIVER
INVALID_APP_USER : DENIAL
INVALID_NETWORK_USER : DENIAL
NON_APPROVED_ADDRESS : DENIAL
ACCOUNT_LOCKED : DENIAL
ACCOUNT_WILL_BE_LOCKED : WAIVER

Виходячи як із значення перерахунку, так і з його суворості, ви можете вирішити, триває чи створення сеансу чи ні.

Редагувати:

Як відповідь на коментар @ T.Sar, я змінив можливі значення суворості на PASS, WAIVER і DENIAL замість (OK, WARNING and ERROR). Таким чином зрозуміло, що DENIAL (раніше ПОМИЛКА) сам по собі не є помилкою і не повинен обов'язково перетворюватись на метання винятку. Абонент вивчає об'єкт і вирішує, кидати виняток чи ні, але DENIAL - це дійсний результат результату, викликаний викликом processLogin(...).

  • ПАСА: вперед, створіть сеанс, якщо такого ще не існує
  • ВАЙВЕР: продовжуйте цього разу, але наступного разу користувач може вам не дозволити проходити
  • ДЕНІАЛ: вибачте, користувач не може пройти, не створювати сеанс

ви також можете побудувати "складний" enum (enum з атрибутами), щоб вбудувати рівень помилки в Enum. Однак будьте обережні, тому що якщо ви використовуєте інструменти серіалізації сомма, це може не пройти дуже добре.
Вальфрат

Викидання винятків у випадках помилок та збереження перерахунків лише на успіх - це також варіант.
Т. Сар

@ T.Sar Ну, як я зрозумів, вони самі по собі не є помилками, а відмовою створювати сеанс чомусь. Я відредагую відповідь.
Tulains Córdova

@ T.Sar Я змінив значення на PASS, WAIVER та DENIAL, щоб зрозуміти, що я раніше називав ПОМИЛКУ - це дійсний статус. Можливо, зараз мені слід придумати краще ім'я дляSeverity
Tulains Córdova

Я думав про щось інше зі своєю пропозицією, але мені дуже сподобалась ваша пропозиція! Я кидаю +1, точно!
Т. Сар

15

Це приклад примітивної одержимості - використання примітивних типів для "простих" завдань, які з часом стають не такими простими.

Це, можливо, почалося як код, який повернув a, boolщоб вказувати на успіх чи невдачу, потім перетворився на, intколи був третій стан, і врешті-решт став цілим списком майже недокументованих умов помилок.

Типовим рефакторингом для цієї проблеми є створення нового класу / struct / enum / object / все, що може краще представити значення, про яке йдеться. У цьому випадку ви можете розглянути можливість переходу на режим, enumякий містить умови результату, або навіть клас, який міг би містити boolуспіх чи збій, повідомлення про помилку, додаткову інформацію тощо.

Щоб отримати більше шаблонів рефакторингу, які можуть бути корисними, ознайомтеся з шаблоном « Запахи до рефакторингу» Industrial Logic .


7

Я б назвав це випадком "магічних чисел" - числа, які є особливими і не мають явного значення самі по собі.

Я б застосував тут рефакторинг - це реструктуризувати тип повернення до enum, оскільки він інкапсулює проблему домену у тип. Усунення помилок компіляції, що випадають із цього, повинно бути можливим, тому що переліки Java можуть бути впорядковані та пронумеровані. Навіть якщо ні, не повинно бути важко поводитися з ними безпосередньо, а не повертатися до ints.


Це не те, що зазвичай розуміється під "магічними числами".
D Drmmr

2
Він відображатиметься як чарівні цифри на сайтах дзвінків, як уif (processLogin(..) == 3)
Daenyth

@DDrmmr - Це саме те, що мається на увазі під запахом коду "магічні числа". Цей підпис функції майже напевно означає, що processLogin () містить рядки типу "return 8;" у своїй реалізації, і це в значній мірі змушує код, використовуючи processLogin (), щоб виглядати приблизно так "if (resultFromProcessLogin == 7) {".
Стівен К. Сталь

3
@Stephen Фактичне значення чисел тут не має значення. Вони - лише посвідчення особи. Термін магічні числа зазвичай використовується для значень у коді, які мають значення, але значення яких недокументовано (наприклад, у назві змінної). Заміна значень тут назначеними цілими змінними не вирішить проблему.
D Drmmr

2

Це особливо неприємний біт коду. Антипатерн відомий як "магічні коди повернення". Ви можете знайти дискусію тут .

Багато повернених значень вказують на стани помилок. Існує вагома дискусія щодо того, чи використовувати обробку помилок для контролю потоку, але у вашому випадку, я думаю, є 3 випадки: успіх (код 4), успіх, але потрібно змінити пароль (код 5) і "не дозволено". Отже, якщо вам не байдуже використання виключень для контролю потоку, ви можете використовувати винятки для позначення цих станів.

Іншим підходом було б переробляти дизайн, щоб ви повернули об’єкт "користувач" з атрибутом "профіль" та "сеанс" для успішного входу, атрибут "must_change_password", якщо це необхідно, та купу атрибутів, які вказують, чому журнал -в невдачі, якщо це був потік.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.