重做一个返回代表许多不同状态的整数代码的函数


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
什么是“建立”“需要建立”
TulainsCórdova'17

4
那应该是一个破折号,读起来像“找到有效的用户:建立他们的会话”。
BJ Myers

2
@A_B这些返回值中哪些是成功登录,哪些是失败登录。并非所有人都是不言而喻的。
图兰斯·科尔多瓦

@A_B“建立他们的会话”是指“建立会话”还是“需要建立会话”?
图兰斯·科尔多瓦

@TulainsCórdova:“建立”的含义与“创建”的含义相同(至少在此情况下)-因此“建立他们的会话”大致等于“创建他们的会话”
hoffmale

Answers:


22

该代码是不好的,不仅因为魔术数字,而且因为它在返回代码中合并了几种含义,将其含义隐藏在错误,警告,创建会话的权限或这三者的组合中,这使其成为一个错误。决策输入错误。

我建议进行以下重构:返回带有可能结果的枚举(如其他答案中所建议),但向枚举添加一个属性,该属性指示是否拒绝,弃权(我会让您最后一次通过)或如果可以(通过):

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

==> Test.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和ERROR)。这样一来,很明显,DENIAL(以前为ERROR)本身并不是错误,并且不一定转化为引发异常。调用者检查对象并决定是否引发异常,但是DENIAL是调用产生的有效结果状态processLogin(...)

  • 通过:继续,如果尚未建立一个会话
  • 弃权者:这次继续,但是下一次用户您可能不允许通过
  • 拒绝:对不起,用户无法通过,请勿创建会话

您还可以构建一个“复杂”枚举(带有属性的枚举)以将错误级别嵌入到枚举中。但是请小心,因为如果您使用Somme序列化工具,则可能无法很好地通过。
Walfrat

在错误情况下抛出异常并仅保存枚举以确保成功也是一种选择。
T. Sar

@ T.Sar好,据我所知,它们本身并不是错误,而是由于某种原因而拒绝创建会话。我将编辑答案。
TulainsCórdova17年

@ T.Sar我将值更改为PASS,WAIVER和DENIAL,以明确表示我之前所说的ERROR是有效状态。也许现在我应该给它起个更好的名字Severity
TulainsCórdova17年

我的建议在考虑其他问题,但我真的很喜欢您的建议!我肯定会抛出+1!
T. Sar

15

这是“ 原始痴迷”的一个示例-将原始类型用于“简单”任务,这些任务最终变得不那么简单。

这可能是从返回a bool来指示成功或失败的代码开始的,然后int在出现第三种状态时变成了a,并最终成为几乎未记录的错误条件的完整列表。

解决此问题的典型方法是创建一个新的类/结构/枚举/对象/任何可以更好地表示所讨论值的东西。在这种情况下,您可能会考虑切换到enum包含结果条件的,甚至切换到可能包含bool成功或失败,错误消息,其他信息等的类。

要获得更多有用的重构模式,请查看Industrial Logic的Smells to Refactorings Cheatsheet


7

我称这种情况为“幻数”,即特殊的数字,其本身没有明显的含义。

我将在此处应用的重构是将返回类型重组为枚举,因为它将域关注点封装在类型中。由于Java枚举可以排序和编号,因此处理掉由此产生的编译错误应该是零星的。即使不是,直接处理它们也应该不至于退缩为整数。


这通常不是“魔术数字”的意思。
D Drmmr

2
它会在呼叫站点上显示为魔术数字,例如if (processLogin(..) == 3)
Daenyth

@DDrmmr-这正是“魔术数字”代码气味的含义。该函数签名几乎可以肯定意味着processLogin()包含“ return 8;”之类的行。在其实现中,它几乎迫使使用processLogin()的代码看起来像“ if(resultFromProcessLogin == 7){”。
斯蒂芬·斯蒂尔·斯蒂尔

3
@Stephen数字的实际值在这里无关紧要。它们只是ID。术语幻数通常用于在具有意义的码值,但谁的意思是未公开的(例如,在变量名)。将此处的值替换为命名的整数变量将无法解决该问题。
D Drmmr

2

这是一段特别令人不愉快的代码。该反模式称为“魔术返回码”。您可以在此处找到讨论。

许多返回值指示错误状态。关于是否使用错误处理进行流控制有一个有效的争论,但是在您的情况下,我认为有3种情况:成功(代码4),成功但需要更改密码(代码5)和“不允许”。因此,如果您不关心使用异常进行流控制,则可以使用异常来指示那些状态。

另一种方法是重构设计,以便您返回“用户”对象,该对象具有用于成功登录的“配置文件”和“会话”属性,如果需要,则具有“ must_change_password”属性,以及一堆属性以指示为什么登录-in失败,如果那是流程。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.