Rielaborazione di una funzione che restituisce un codice intero che rappresenta molti stati diversi


10

Ho ereditato un codice terribile che ho incluso un breve esempio di seguito.

  • C'è un nome per questo particolare anti-pattern?
  • Quali sono alcuni consigli per refactoring questo?

    // 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
Cosa sono "found-stabilisce" e "necessario-stabilisce" ?
Tulains Córdova,

4
Dovrebbe essere un trattino , letto come "trovato un utente valido: stabilisci la sua sessione".
BJ Myers,

2
@A_B Quali di questi valori restituiti sono accessi riusciti, sono accessi non riusciti. Non tutti sono evidenti.
Tulains Córdova,

@A_B "Stabilire la loro sessione" significa "sessione stabilita" o "deve stabilire una sessione"?
Tulains Córdova,

@ TulainsCórdova: "Stabilire" significa tanto quanto "creare" (almeno in questo contesto) - quindi "stabilire la loro sessione" equivale all'incirca a "creare la loro sessione"
hoffmale

Risposte:


22

Il codice non è male solo perché i numeri magici , ma perché fonde diversi significati nel codice di ritorno, nascondendo all'interno del suo significato un errore, un avviso, un'autorizzazione a creare una sessione o una combinazione dei tre, che lo rende un input negativo per il processo decisionale.

Suggerirei il seguente refactoring: restituire un enum con i possibili risultati (come suggerito in altre risposte), ma aggiungendo all'enum un attributo che indica se si tratta di un rifiuto, una rinuncia (ti lascerò passare quest'ultima volta) o se è OK (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;
    }
}

==> 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());           
        }
    }
}

Output per Test.java che mostra la gravità per ogni 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

In base sia al valore enum che alla sua gravità, puoi decidere se la creazione della sessione procede o meno.

MODIFICARE:

In risposta al commento di @ T.Sar, ho modificato i possibili valori della gravità su PASS, WAIVER e DENIAL anziché su (OK, WARNING ed ERROR). In questo modo è chiaro che un DENIAL (precedentemente ERRORE) non è un errore di per sé e non deve necessariamente tradursi in un'eccezione. Il chiamante esamina l'oggetto e decide se generare o meno un'eccezione, ma DENIAL è uno stato risultato valido derivante dalla chiamata processLogin(...).

  • PASS: vai avanti, crea una sessione se non ne esiste già una
  • RINUNCIA: vai avanti questa volta, ma alla prossima volta potresti non essere autorizzato a passare
  • DENIAL: scusa, l'utente non può passare, non creare una sessione

puoi anche creare un enum "complesso" (enum con attributi) per incorporare il livello di errore nell'Enum. Tuttavia, fai attenzione perché se usi gli strumenti di serializzazione somme, ciò potrebbe non andare molto bene.
Walfrat,

Anche sollevare eccezioni nei casi di errore e salvare solo le enumerazioni per il successo è un'opzione.
T. Sar,

@ T.Sar Bene, ho capito che non sono errori di per sé, ma si rifiuta di creare una sessione per qualche motivo. Modificherò la risposta.
Tulains Córdova,

@ T.Sar Ho modificato i valori in PASS, WAIVER e DENIAL per chiarire che quello che in precedenza ho chiamato ERROR è uno stato valido. Forse ora dovrei trovare un nome migliore perSeverity
Tulains Córdova,

Stavo pensando a qualcos'altro con il mio suggerimento, ma mi è piaciuto molto il tuo suggerimento! Sto vomitando un +1, di sicuro!
T. Sar,

15

Questo è un esempio di ossessione primitiva - usare tipi primitivi per compiti "semplici" che alla fine non diventano così semplici.

Questo potrebbe essere iniziato come codice che ha restituito a boolper indicare l'esito positivo o negativo, per poi trasformarsi in un intquando c'era un terzo stato e alla fine è diventato un intero elenco di condizioni di errore quasi non documentate.

Il refactoring tipico di questo problema è creare una nuova classe / struct / enum / object / qualunque cosa possa rappresentare meglio il valore in questione. In questo caso, potresti prendere in considerazione il passaggio a una enumche contiene le condizioni del risultato, o persino a una classe che potrebbe contenere un boolerrore o un errore, un messaggio di errore, informazioni aggiuntive, ecc.

Per ulteriori schemi di refactoring che potrebbero essere utili, dai un'occhiata al Cheatsheet di Odori di refactoring di Logica industriale .


7

Definirei un caso di "numeri magici" - numeri speciali che non hanno alcun significato ovvio da soli.

Il refactoring che applicherei qui è quello di ristrutturare il tipo restituito in un enum, poiché racchiude in sé un problema relativo al dominio. Affrontare gli errori di compilazione che ne derivano dovrebbe essere possibile frammentariamente, dal momento che java enum può essere ordinato e numerato. Anche in caso contrario, non dovrebbe essere difficile gestirli direttamente invece di ricorrere agli Ints.


Non è quello che di solito si intende con "numeri magici".
D Drmmr,

2
Apparirà come numeri magici nei siti di chiamata, come inif (processLogin(..) == 3)
Daenyth il

@DDrmmr - Questo è esattamente ciò che si intende per odore di codice "numeri magici". Questa firma della funzione implica quasi certamente che processLogin () contiene righe come "return 8;" nella sua implementazione e praticamente forza il codice usando processLogin () a somigliare a questo "if (resultFromProcessLogin == 7) {".
Stephen C. Steel,

3
@Stephen Il valore reale dei numeri è irrilevante qui. Sono solo ID. Il termine numeri magici viene solitamente utilizzato per valori nel codice che hanno un significato, ma il cui significato non è documentato (ad esempio in un nome di variabile). La sostituzione dei valori qui con variabili intere nominate non risolverà il problema.
D Drmmr,

2

Questo è un codice particolarmente spiacevole. L'antipasto è noto come "codici di ritorno magici". Puoi trovare una discussione qui .

Molti dei valori restituiti indicano stati di errore. Esiste un dibattito valido sull'opportunità di utilizzare la gestione degli errori per il controllo del flusso, ma nel tuo caso, penso che ci siano 3 casi: successo (codice 4), successo ma è necessario modificare la password (codice 5) e "non consentito". Pertanto, se non ti interessa utilizzare le eccezioni per il controllo del flusso, puoi utilizzare le eccezioni per indicare tali stati.

Un altro approccio potrebbe essere il refactoring del progetto in modo da restituire un oggetto "utente", con un attributo "profilo" e "sessione" per un accesso riuscito, un attributo "must_change_password" se necessario e un gruppo di attributi per indicare il motivo per cui il registro -in fallito se quello era il flusso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.