Evitare il metodo troppo complesso - Complessità ciclomatica


23

Non sono sicuro di come utilizzare questo metodo per ridurre la complessità ciclomatica. Il sonar riporta 13 mentre 10 è previsto. Sono sicuro che nulla di male nel lasciare questo metodo in quanto, tuttavia, mi sta solo sfidando su come obbedire alla regola di Sonar. Ogni pensiero sarebbe molto apprezzato.

 public static long parseTimeValue(String sValue) {

    if (sValue == null) {
        return 0;
    }

    try {
        long millis;
        if (sValue.endsWith("S")) {
            millis = new ExtractSecond(sValue).invoke();
        } else if (sValue.endsWith("ms")) {
            millis = new ExtractMillisecond(sValue).invoke();
        } else if (sValue.endsWith("s")) {
            millis = new ExtractInSecond(sValue).invoke();
        } else if (sValue.endsWith("m")) {
            millis = new ExtractInMinute(sValue).invoke();
        } else if (sValue.endsWith("H") || sValue.endsWith("h")) {
            millis = new ExtractHour(sValue).invoke();
        } else if (sValue.endsWith("d")) {
            millis = new ExtractDay(sValue).invoke();
        } else if (sValue.endsWith("w")) {
            millis = new ExtractWeek(sValue).invoke();
        } else {
            millis = Long.parseLong(sValue);
        }

        return millis;

    } catch (NumberFormatException e) {
        LOGGER.warn("Number format exception", e);
    }

    return 0;
}

Tutti i metodi ExtractXXX sono definiti come staticclassi interne. Ad esempio, come uno di seguito -

    private static class ExtractHour {
      private String sValue;

      public ExtractHour(String sValue) {
         this.sValue = sValue;
      }

      public long invoke() {
         long millis;
         millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
         return millis;
     }
 }

AGGIORNAMENTO 1

Ho intenzione di sistemarmi con un mix di suggerimenti qui per soddisfare il ragazzo Sonar. Sicuramente margini di miglioramento e semplificazione.

La guava Functionè solo una cerimonia indesiderata qui. Volevo aggiornare la domanda sullo stato corrente. Niente è definitivo qui. Versa i tuoi pensieri per favore ..

public class DurationParse {

private static final Logger LOGGER = LoggerFactory.getLogger(DurationParse.class);
private static final Map<String, Function<String, Long>> MULTIPLIERS;
private static final Pattern STRING_REGEX = Pattern.compile("^(\\d+)\\s*(\\w+)");

static {

    MULTIPLIERS = new HashMap<>(7);

    MULTIPLIERS.put("S", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractSecond(input).invoke();
        }
    });

    MULTIPLIERS.put("s", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractInSecond(input).invoke();
        }
    });

    MULTIPLIERS.put("ms", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractMillisecond(input).invoke();
        }
    });

    MULTIPLIERS.put("m", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractInMinute(input).invoke();
        }
    });

    MULTIPLIERS.put("H", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractHour(input).invoke();
        }
    });

    MULTIPLIERS.put("d", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractDay(input).invoke();
        }
    });

    MULTIPLIERS.put("w", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractWeek(input).invoke();
        }
    });

}

public static long parseTimeValue(String sValue) {

    if (isNullOrEmpty(sValue)) {
        return 0;
    }

    Matcher matcher = STRING_REGEX.matcher(sValue.trim());

    if (!matcher.matches()) {
        LOGGER.warn(String.format("%s is invalid duration, assuming 0ms", sValue));
        return 0;
    }

    if (MULTIPLIERS.get(matcher.group(2)) == null) {
        LOGGER.warn(String.format("%s is invalid configuration, assuming 0ms", sValue));
        return 0;
    }

    return MULTIPLIERS.get(matcher.group(2)).apply(matcher.group(1));
}

private static class ExtractSecond {
    private String sValue;

    public ExtractSecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = Long.parseLong(sValue);
        return millis;
    }
}

private static class ExtractMillisecond {
    private String sValue;

    public ExtractMillisecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue));
        return millis;
    }
}

private static class ExtractInSecond {
    private String sValue;

    public ExtractInSecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 1000);
        return millis;
    }
}

private static class ExtractInMinute {
    private String sValue;

    public ExtractInMinute(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 60 * 1000);
        return millis;
    }
}

private static class ExtractHour {
    private String sValue;

    public ExtractHour(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 60 * 60 * 1000);
        return millis;
    }
}

private static class ExtractDay {
    private String sValue;

    public ExtractDay(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 24 * 60 * 60 * 1000);
        return millis;
    }
}

private static class ExtractWeek {
    private String sValue;

    public ExtractWeek(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 7 * 24 * 60 * 60 * 1000);
        return millis;
    }
}

}


AGGIORNAMENTO 2

Anche se ho aggiunto il mio aggiornamento, vale solo la pena. Vado avanti dal momento che Sonar ora non si lamenta. Non preoccuparti molto e sto accettando la risposta di Mattnz in quanto è la strada da percorrere e non voglio dare il cattivo esempio a coloro che rispondono a questa domanda. In conclusione: non sovraccaricare l'ingegnere per il bene di Sonar (o Half Baked Project Manager) si lamenta di CC. Fai solo ciò che vale un centesimo per il progetto. Grazie a tutti.


4
La più semplice risposta OO a mano libera: private static Dictionary<string,Func<string,long>> _mappingStringToParser;lascerò il resto come esercizio per te (o qualcuno con più tempo libero in questo momento di me). C'è un'API più pulita da trovare se hai familiarità con i parser monadici ma non ci andrò proprio ora ..
Jimmy Hoffa

Mi piacerebbe se potessi risparmiare qualche volta su "parser monadici" e come può essere applicato a funzioni piuttosto piccole come questa. E questo pezzo di codice proviene da Java.
attendo il

come vengono ExtractBlahdefinite le classi? vengono da qualche biblioteca o homebrew?
moscerino del

4
Il codice allegato mi porta un po 'più avanti verso un'implementazione più semplice: la tua vera varianza è il moltiplicatore. Crea una mappa di questi: tira i caratteri alfa dalla fine di sValue, usa quelli come chiave e poi tira tutto fino a quando gli alfa sono in primo piano per il valore che moltiplichi per il moltiplicatore mappato.
Jimmy Hoffa,

2
Riaggiornamento: sono l'unico che ha "Over Engineered" che squilla nella mia testa?
Mattnz,

Risposte:


46

Risposta di ingegneria del software:

Questo è solo uno dei tanti casi in cui il semplice conteggio dei fagioli semplici da contare ti farà fare la cosa sbagliata. Non è una funzione complessa, non cambiarla. La complessità ciclomatica è semplicemente una guida alla complessità e la usi male se cambi questa funzione in base ad essa. È semplice, leggibile, mantenibile (per ora), se diventerà più grande in futuro il CC salirà alle stelle in modo esponenziale e otterrà l'attenzione di cui ha bisogno quando ne ha bisogno, non prima.

Minion che lavora per una grande multinazionale Risposta:

Le organizzazioni sono piene di squadre di contatori di fagioli non pagate e poco produttive. Mantenere felici i contatori dei fagioli è più facile, e sicuramente più saggio, che fare la cosa giusta. Devi cambiare la routine per portare il suo CC a 10, ma sii onesto sul motivo per cui lo stai facendo - per mantenere i segnalini fagioli dalla tua schiena. Come suggerito nei commenti, i "parser monadici" potrebbero essere d'aiuto


14
+1 per il rispetto del motivo della regola, non della regola stessa
Radu Murzea,

5
Ben detto! Tuttavia, in altri casi in cui hai CC = 13 con diversi livelli di annidamento e logica (ramificazione) complicata, sarebbe preferibile almeno provare a semplificarlo.
Grizwako,

16

Grazie a @JimmyHoffa, @MichaelT e @ GlenH7 per il loro aiuto!

Pitone

Per prima cosa, dovresti davvero accettare solo i prefissi noti, ovvero "H" o "h". Se si dispone ad accettare sia, è necessario eseguire alcune operazioni per renderlo coerente per risparmiare spazio nella vostra mappa.

In Python potresti creare un dizionario.

EXTRACTION_MAP = {
    'S': ExtractSecond,
    'ms': ExtractMillisecond,
    'm': ExtractMinute,
    'H': ExtractHour,
    'd': ExtractDay,
    'w': ExtractWeek
}

Quindi vogliamo che il metodo utilizzi questo:

def parseTimeValue(sValue)
    ending = ''.join([i for i in sValue if not i.isdigit()])
    return EXTRACTION_MAP[ending](sValue).invoke()

Dovrebbe avere una migliore complessità ciclomatica.


Giava

Abbiamo solo bisogno di 1 (uno) per ogni moltiplicatore. Mettiamoli in una mappa come suggerito da altre risposte.

Map<String, Float> multipliers = new HashMap<String, Float>();
    map.put("S", 60 * 60);
    map.put("ms", 60 * 60 * 1000);
    map.put("m", 60);
    map.put("H", 1);
    map.put("d", 1.0 / 24);
    map.put("w", 1.0 / (24 * 7));

Quindi possiamo semplicemente usare la mappa per prendere il convertitore giusto

Pattern foo = Pattern.compile(".*(\\d+)\\s*(\\w+)");
Matcher bar = foo.matcher(sValue);
if(bar.matches()) {
    return (long) (Double.parseDouble(bar.group(1)) * multipliers.get(bar.group(2);
}

Sì, mappare le stringhe su un fattore di conversione sarebbe una soluzione molto più semplice. Se non hanno bisogno degli oggetti, dovrebbero sbarazzarsene, ma non riesco a vedere il resto del programma, quindi forse gli oggetti vengono usati più come oggetti da qualche altra parte ...?
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner possono, ma nell'ambito di quel metodo, sta solo restituendo un oggetto lungo e l'oggetto istanziato non rientra nell'ambito. A parte questo, ciò ha l'effetto collaterale di se questo codice viene chiamato più frequentemente, il numero di oggetti di breve durata usati frequentemente senza stato viene ridotto.

Nessuna di queste risposte può essere considerata la stessa soluzione del programma originale in quanto si basano su ipotesi che potrebbero non essere valide. Codice Java: Come sei certo che l'unica cosa che i metodi fanno è applicare un moltiplicatore? Codice Python: come sei sicuro che la stringa non contenga caratteri iniziali (o punti medi) diversi dalle cifre (es. "123.456s").
mattnz,

@mattnz: dai un'occhiata ai nomi delle variabili negli esempi forniti. È chiaro che l'OP sta ricevendo un'unità di tempo come una stringa e quindi deve convertirla in un altro tipo di unità di tempo. Pertanto, gli esempi forniti in questa risposta riguardano direttamente il dominio del PO. Ignorando questo aspetto, la risposta fornisce ancora un approccio generico che potrebbe essere utilizzato per un dominio diverso. Questa risposta risolve il problema che è stato presentato non il problema che potrebbe essere stato presentato.

5
@mattnz - 1) OP non lo specifica mai nel loro esempio e potrebbe non interessarsene. Come fai a sapere che i presupposti non sono validi? 2) Il metodo generale continuerebbe a funzionare, richiedendo potenzialmente una regex più complicata. 3) Il punto della risposta è fornire un percorso concettuale per risolvere la complessità ciclomatica e non necessariamente una risposta specifica e compilabile. 4) mentre questa risposta ignora l'aspetto più ampio di "importa la complessità", risponde indirettamente al problema mostrando un modulo alternativo per il codice.

3

Dato che return millisalla fine di quel terribile ifelseifelse comunque, la prima cosa che mi viene in mente è di restituire immediatamente il valore dall'interno degli if-block. Questo approccio segue quello elencato nel catalogo dei modelli di refactoring come Sostituisci condizioni annidate con clausole di guardia .

Un metodo ha un comportamento condizionale che non chiarisce quale sia il normale percorso di esecuzione

Usa le clausole di guardia per tutti i casi speciali

Ti aiuterà a sbarazzarti degli altri, ad appiattire il codice e rendere felice Sonar:

    if (sValue.endsWith("S")) {
        return new ExtractSecond(sValue).invoke();
    } // no need in else after return, code flattened

    if (sValue.endsWith("ms")) {
        return new ExtractMillisecond(sValue).invoke();
    }

    // and so on...
    return Long.parseLong(sValue); // forget millis, these aren't needed anymore

Un'altra cosa che vale la pena considerare è far cadere il blocco try-catch. Ciò ridurrà anche la complessità ciclomatica, ma il motivo principale per cui lo consiglio è che con questo blocco, non c'è modo per il codice chiamante di distinguere lo 0 legalmente analizzato dall'eccezione del formato numerico.

A meno che tu non sia sicuro al 200% che la restituzione di 0 per errori di analisi è ciò di cui ha bisogno il codice del chiamante, è meglio propagare quell'eccezione e lasciare che il codice del chiamante decida come gestirlo. In genere è più conveniente decidere al chiamante se interrompere l'esecuzione o riprovare a ottenere l'input o tornare a un valore predefinito come 0 o -1 o qualsiasi altra cosa.


Il tuo frammento di codice per un esempio ExtractHour mi fa sentire che la funzionalità ExtractXXX è progettata in un modo tutt'altro che ottimale. Scommetto che tutti i rimanenti classi sconsideratamente ripete lo stesso parseDoublee substring, e moltiplicando roba come 60 e il 1000 più e più e più volte.

Questo perché hai perso l' essenza di ciò che deve essere fatto a seconda di sValue- vale a dire, definisce quanti caratteri tagliare dalla fine della stringa e quale sarebbe il valore del moltiplicatore. Se si progetta il proprio oggetto "core" attorno a queste funzionalità essenziali, sarebbe simile al seguente:

private static class ParseHelper {
    // three things you need to know to parse:
    final String source;
    final int charsToCutAtEnd;
    final long multiplier;

    ParseHelper(String source, int charsToCutAtEnd, long multiplier) {
        this.source = source == null ? "0" : source; // let's handle null here
        this.charsToCutAtEnd = charsToCutAtEnd;
        this.multiplier = multiplier;
    }

    long invoke() {
        // NOTE consider Long.parseLong instead of Double.parseDouble here
        return (long) (Double.parseDouble(cutAtEnd()) * multiplier);
    }

    private String cutAtEnd() {
        if (charsToCutAtEnd == 0) {
            return source;
        }
        // write code that cuts 'charsToCutAtEnd' from the end of the 'source'
        throw new UnsupportedOperationException();
    }
}

Dopodiché, avresti bisogno di un codice che configura sopra gli oggetti per una particolare condizione se viene soddisfatto, o in qualche modo "ignora" altrimenti. Questo potrebbe essere fatto come segue:

private ParseHelper setupIfInSecond(ParseHelper original) {
    final String sValue = original.source;
    return sValue.endsWith("s") && !sValue.endsWith("ms")
            ? new ParseHelper(sValue, 1, 1000)
            :  original; // bypass
}

private ParseHelper setupIfMillisecond(ParseHelper original) {
    final String sValue = original.source;
    return sValue.endsWith("ms")
            ? new ParseHelper(sValue, 2, 1)
            : original; // bypass
}

// and so on...

Sulla base dei blocchi di cui sopra , il codice del metodo potrebbe apparire come segue:

public long parseTimeValue(String sValue) {

   return setupIfSecond(
           setupIfMillisecond(
           setupIfInSecond(
           setupIfInMinute(
           setupIfHour(
           setupIfDay(
           setupIfWeek(
           new ParseHelper(sValue, 0, 1))))))))
           .invoke();
}

Vedi, non è rimasta alcuna complessità, nessuna parentesi graffa all'interno del metodo (né più ritorni come nel mio suggerimento originale sulla forza bruta sul codice di appiattimento). Basta controllare in sequenza l'input e regolare l'elaborazione secondo necessità.


1

Se vuoi davvero riformattarlo, potresti fare qualcosa del genere:

// All of your Extract... classes will have to implement this interface!
public Interface TimeExtractor
{
    public long invoke();
}

private static class ExtractHour implements TimeExtractor
{
  private String sValue;


  /*Not sure what this was for - might not be necessary now
  public ExtractHour(String sValue)
  {
     this.sValue = sValue;
  }*/

  public long invoke(String s)
  {
     this.sValue = s;
     long millis;
     millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
     return millis;
 }
}

private static HashMap<String, TimeExtractor> extractorMap= new HashMap<String, TimeExtractor>();

private void someInitMethod()
{
   ExtractHour eh = new ExtractorHour;
   extractorMap.add("H",eh);
   /*repeat for all extractors */
}

public static long parseTimeValue(String sValue)
{
    if (sValue == null)
    {
        return 0;
    }
    String key = extractKeyFromSValue(sValue);
    long millis;
    TimeExtractor extractor = extractorMap.get(key);
    if (extractor!=null)
    {
      try
      {
         millis= extractor.invoke(sValue);
      }
        catch (NumberFormatException e)
      {
          LOGGER.warn("Number format exception", e);
      }
    }
    else
       LOGGER.error("NO EXTRACTOR FOUND FOR "+key+", with sValue: "+sValue);

    return millis;
}

L'idea è che tu abbia una mappa di chiavi (che usi sempre in "limitsWith") che si mappano su oggetti specifici che eseguono l'elaborazione che desideri.

È un po 'agitato qui, ma spero sia abbastanza chiaro. Non ho inserito i dettagli extractKeyFromSValue()perché non so abbastanza di cosa sono queste stringhe per farlo correttamente. Sembra che siano gli ultimi 1 o 2 caratteri non numerici (un regex potrebbe probabilmente estrarlo abbastanza facilmente, forse .*([a-zA-Z]{1,2})$funzionerebbe), ma non sono sicuro al 100% ...


Risposta originale:

Potresti cambiare

else if (sValue.endsWith("H") || sValue.endsWith("h")) {

a

else if (sValue.toUpper().endsWith("H")) {

Ciò potrebbe salvarti un po ', ma onestamente, non mi preoccuperei troppo. Sono d'accordo con te sul fatto che non penso che ci sia molto danno a lasciare il metodo così com'è. Invece di cercare di "obbedire alle regole del sonar", cerca di "stare vicino alle linee guida del sonar, per quanto ragionevolmente possibile".

Puoi farti impazzire cercando di seguire ogni singola regola che questi strumenti di analisi avranno in loro, ma devi anche decidere se le regole hanno senso per il tuo progetto e per casi specifici in cui il tempo impiegato per il refactoring potrebbe non valerne la pena .


3
Non molto utile, il sonar si lamenta ancora. Lo sto provando per divertimento, almeno mi permette di imparare uno o due. Tuttavia, il codice viene spostato nella gestione temporanea.
attendo il

@asyncwait: Ah, pensavo che prendessi questo rapporto dal sonar più seriamente di così. Sì, il cambiamento che ho suggerito non farebbe una grande differenza - non penso che ti porterebbe da 13 a 10, ma in alcuni strumenti ho visto che questo genere di cose fa una differenza piuttosto evidente.
FrustratedWithFormsDesigner

L'aggiunta di un altro ramo IF ha aumentato CC a +1.
attendo il

0

Potresti prendere in considerazione l'utilizzo di un enum per archiviare tutti i casi e predicati disponibili per la corrispondenza dei valori. Come accennato prima, la tua funzione è abbastanza leggibile solo per lasciarla invariata. Quelle metriche sono lì per aiutarti non viceversa.

//utility class for matching values
private static class ValueMatchingPredicate implements Predicate<String>{
    private final String[] suffixes;

    public ValueMatchingPredicate(String[] suffixes) {      
        this.suffixes = suffixes;
    }

    public boolean apply(String sValue) {
        if(sValue == null) return false;

        for (String suffix : suffixes) {
            if(sValue.endsWith(suffix)) return true;
        }

        return false;
    }

    public static Predicate<String> withSuffix(String... suffixes){         
        return new ValueMatchingPredicate(suffixes);
    }       
}

//enum containing all possible options
private static enum TimeValueExtractor {                
    SECOND(
        ValueMatchingPredicate.withSuffix("S"), 
        new Function<String, Long>(){ 
            public Long apply(String sValue) {  return new ExtractSecond(sValue).invoke(); }
        }),

    MILISECOND(
        ValueMatchingPredicate.withSuffix("ms"), 
        new Function<String, Long>(){
            public Long apply(String sValue) { return new ExtractMillisecond(sValue).invoke(); }
        }),

    IN_SECOND(
        ValueMatchingPredicate.withSuffix("s"),
        new Function<String, Long>(){
            public Long apply(String sValue) { return new ExtractInSecond(sValue).invoke(); }
        }),

    IN_MINUTE(
        ValueMatchingPredicate.withSuffix("m"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractInMinute(sValue).invoke(); }
        }),

    HOUR(
        ValueMatchingPredicate.withSuffix("H", "h"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractHour(sValue).invoke(); }
        }),

    DAY(
        ValueMatchingPredicate.withSuffix("d"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractDay(sValue).invoke(); }
        }),

    WEEK(
        ValueMatchingPredicate.withSuffix("w"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractWeek(sValue).invoke(); }
        });

    private final Predicate<String>      valueMatchingRule;
    private final Function<String, Long> extractorFunction;

    public static Long DEFAULT_VALUE = 0L;

    private TimeValueExtractor(Predicate<String> valueMatchingRule, Function<String, Long> extractorFunction) {
        this.valueMatchingRule = valueMatchingRule;
        this.extractorFunction = extractorFunction;
    }

    public boolean matchesValueSuffix(String sValue){
        return this.valueMatchingRule.apply(sValue);
    }

    public Long extractTimeValue(String sValue){
        return this.extractorFunction.apply(sValue);
    }

    public static Long extract(String sValue) throws NumberFormatException{
        TimeValueExtractor[] extractors = TimeValueExtractor.values();

        for (TimeValueExtractor timeValueExtractor : extractors) {
            if(timeValueExtractor.matchesValueSuffix(sValue)){
                return timeValueExtractor.extractTimeValue(sValue);
            }
        }

        return DEFAULT_VALUE;
    }
}

//your function
public static long parseTimeValue(String sValue){
    try{
        return TimeValueExtractor.extract(sValue);
    } catch (NumberFormatException e) {
        //LOGGER.warn("Number format exception", e);
        return TimeValueExtractor.DEFAULT_VALUE;
    }
}

0

In relazione al tuo commento di:

In conclusione: non sovraccaricare l'ingegnere per il bene di Sonar (o Half Baked Project Manager) si lamenta di CC. Fai solo ciò che vale un centesimo per il progetto.

Un'altra opzione da considerare è quella di cambiare gli standard di codifica del tuo team per situazioni come questa. Forse puoi aggiungere una sorta di voto di squadra per fornire una misura di governance ed evitare situazioni scorciatoie.

Ma cambiare gli standard del team in risposta a situazioni che non hanno senso è un segno di un buon team con il giusto atteggiamento nei confronti degli standard. Gli standard sono lì per aiutare il team, non ostacolare la scrittura del codice.


0

Ad essere onesti, tutte le risposte tecniche sopra sembrano terribilmente complicate per il compito da svolgere. Come già scritto, il codice stesso è pulito e buono, quindi opterei per il minimo cambiamento possibile per soddisfare il contatore di complessità. Che ne dici del seguente refactor:

public static long parseTimeValue(String sValue) {

    if (sValue == null) {
        return 0;
    }

    try {
        return getMillis(sValue);
    } catch (NumberFormatException e) {
        LOGGER.warn("Number format exception", e);
    }

    return 0;
}

private static long getMillis(String sValue) {
    if (sValue.endsWith("S")) {
        return new ExtractSecond(sValue).invoke();
    } else if (sValue.endsWith("ms")) {
        return new ExtractMillisecond(sValue).invoke();
    } else if (sValue.endsWith("s")) {
        return new ExtractInSecond(sValue).invoke();
    } else if (sValue.endsWith("m")) {
        return new ExtractInMinute(sValue).invoke();
    } else if (sValue.endsWith("H") || sValue.endsWith("h")) {
        return new ExtractHour(sValue).invoke();
    } else if (sValue.endsWith("d")) {
        return new ExtractDay(sValue).invoke();
    } else if (sValue.endsWith("w")) {
        return new ExtractWeek(sValue).invoke();
    } else {
        return Long.parseLong(sValue);
    }
}

Se sto contando correttamente, la funzione estratta dovrebbe avere una complessità di 9, che comunque soddisfa i requisiti. Ed è sostanzialmente lo stesso codice di prima , il che è una buona cosa, dal momento che il codice era buono per cominciare.

Inoltre, i lettori di Clean Code potrebbero apprezzare il fatto che il metodo di livello superiore è ora semplice e breve, mentre quello estratto tratta i dettagli.

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.