Variabili temporanee rispetto ai requisiti di lunghezza della linea

10

Ho letto Refactoring di Martin Fowler . È generalmente eccellente, ma una delle raccomandazioni di Fowler sembra causare un piccolo problema.

Fowler raccomanda di sostituire le variabili temporanee con una query, quindi invece di questo:

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

estrai un metodo di supporto:

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

In generale sono d'accordo tranne che una delle ragioni per cui uso variabili temporanee è quando una linea è troppo lunga. Ad esempio:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Se provassi a inserirli, la linea andrebbe più lunga di 80 caratteri.

In alternativa, finisco con le catene di codice, che a loro volta non sono molto più facili da leggere:

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Quali sono alcune strategie per riconciliare le due?

    
posta Kevin Burke 16.01.2013 - 07:25
fonte

7 risposte

16

How To
1. Esistono restrizioni sulla lunghezza della linea in modo che tu possa vedere + capire più codice. Sono ancora validi.
2. Enfatizza il giudizio sulla convenzione cieca .
3. Evita le variabili temporanee se non ottimizzi le prestazioni .
4. Evitare l'utilizzo di indentazione profonda per l'allineamento in istruzioni multilinea.
5. Suddividi le istruzioni lunghe in più righe lungo i confini delle idee :

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

Ragionamento
La fonte principale dei miei problemi (di debug) con le variabili temporanee è che tendono a essere mutabili. Vale a dire, presumo che siano un valore quando scrivo il codice, ma se la funzione è complessa, qualche altra parte di codice cambia il loro stato a metà. (Oppure il contrario, dove lo stato della variabile rimane lo stesso ma il risultato della query è cambiato).

Considera di attenersi alle query a meno che tu non stia ottimizzando il rendimento . Ciò mantiene qualsiasi logica che hai usato per calcolare quel valore in un unico posto.

Gli esempi che hai fornito (Java e ... PHP?) consentono entrambe le istruzioni su più righe. Se le linee diventano lunghe, rompile. L'origine jquery porta questo all'estremo. (La prima affermazione va alla riga 69!) Non che io sia necessariamente d'accordo, ma ci sono altri modi per rendere leggibile il tuo codice rispetto all'utilizzo di vars temporanei.

Alcuni esempi
1. Guida allo stile di PEP 8 per python (non l'esempio più grazioso) < br> 2. Paul M Jones sulla guida allo stile delle pere (argomento centrale della strada)
3. Lunghezza della linea Oracle + convenzioni di wrapping (utili stratagemmi per mantenere 80 caratteri)
4. MDN Java Practices (enfatizza il giudizio del programmatore sulla convenzione)

    
risposta data 16.01.2013 - 07:58
fonte
3

Penso che l'argomento migliore per l'utilizzo di metodi di supporto anziché di variabili temporanee sia la leggibilità umana. Se tu, come umano, hai più problemi a leggere la catena del metodo helper rispetto alla varialba temporanea, non vedo ragioni per cui dovresti estrarli.

(correggimi se ho torto)

    
risposta data 17.01.2013 - 16:54
fonte
3

Non penso che sia necessario seguire rigorosamente le 80 linee guida sui caratteri o che sia sempre possibile estrarre la variabile temporanea locale. Ma le linee lunghe e le temp locali dovrebbero essere studiate per modi migliori di esprimere la stessa idea. Fondamentalmente, indicano che una data funzione o linea è troppo complicata e abbiamo bisogno di scomporla. Ma dobbiamo stare attenti, perché rompere un compito in pezzi in un modo sbagliato rende la situazione più complicata. Quindi devo rompere le cose in componenti semplici e resilienti.

Fammi vedere gli esempi che hai postato.

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

La mia osservazione è che tutte le chiamate di twilio api inizieranno con "https://api.twilio.com/2010-04-1/", e quindi c'è una funzione riutilizzabile molto ovvia da avere:

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

In effetti, immagino che l'unica ragione per generare un URL sia di fare la richiesta, quindi lo farei:

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

In effetti, molti degli URL in realtà iniziano con "Account / $ accountSid", quindi probabilmente estraperei anche quello:

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

E se facciamo la twilio api un oggetto che contiene il numero dell'account, potremmo fare qualcosa del tipo:

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

L'utilizzo di un oggetto $ twilio ha il vantaggio di semplificare il test delle unità. Posso dare all'oggetto un oggetto $ twilio diverso che in realtà non richiama twilio, che sarà più veloce e non farà cose strane a twilio.

Diamo un'occhiata all'altro

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Qui ci penserei su entrambi:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

o

$params = MustacheOptions::build($bagcheck->getFlatParams());

o

$params = MustacheOptions::build(flatParams($backCheck));

A seconda di quale sia l'idioma più riusabile.

    
risposta data 22.01.2013 - 17:00
fonte
1

In realtà, non sono d'accordo con l'eminente Mr. Fowler su questo nel caso generale.

Il vantaggio di estrarre un metodo dal codice precedentemente inserito è il riutilizzo del codice; il codice nel metodo è ora separato dal suo utilizzo iniziale e può ora essere utilizzato in altre posizioni nel codice senza essere copiato e incollato (il che richiederebbe apportare modifiche in più punti se la logica generale del codice copiato dovesse mai cambiare) .

Tuttavia, di uguale, spesso maggiore valore concettuale è il "riutilizzo del valore". Mr. Fowler chiama questi metodi estratti per sostituire le "variabili" delle variabili temporali. Bene, cos'è più efficiente; interrogando un database ognuna delle volte in cui hai bisogno di un particolare valore, o eseguendo una query una volta e memorizzando il risultato (assumendo che il valore sia statico abbastanza da non aspettarti che cambi)?

Per quasi tutti i calcoli oltre a quelli relativamente banali del tuo esempio, nella maggior parte delle lingue è più economico memorizzare il risultato di un calcolo piuttosto che continuare a calcolarlo. Pertanto, la raccomandazione generale di ricalcolare a richiesta è ingannevole; costa più tempo per gli sviluppatori e più tempo per la CPU e consente di risparmiare una quantità insignificante di memoria, che nella maggior parte dei sistemi moderni è la risorsa più economica di questi tre.

Ora, il metodo helper, in combinazione con un altro codice, potrebbe essere reso "pigro". All'inizio, inizializza una variabile. Tutte le ulteriori chiamate restituirebbero tale variabile finché il metodo non fosse stato esplicitamente consigliato di ricalcolare. Questo potrebbe essere un parametro del metodo o un flag impostato da un altro codice che modifica qualsiasi valore dipende dal calcolo di questo metodo:

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

Ora, per questo calcolo banale è ancora più lavoro svolto che salvato, quindi di solito raccomanderei di attenermi alla variabile temp; tuttavia, per calcoli più complessi che vorresti evitare di eseguire più volte e che ti occorrono in più punti del codice, questo è il modo in cui lo faresti.

    
risposta data 23.01.2013 - 20:35
fonte
1

I metodi di supporto hanno un posto, ma devi stare attento a garantire la coerenza dei dati e l'aumento non necessario della portata delle variabili.

Ad esempio, il tuo esempio cita:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

Chiaramente sia _quantity che _itemPrice sono variabili globali (o almeno di livello di classe) e quindi c'è la possibilità che vengano modificate al di fuori di getPrice()

Quindi c'è un potenziale per la prima chiamata a basePrice() per restituire un valore diverso dalla seconda chiamata!

Pertanto, suggerirei che le funzioni di supporto possano essere utili per isolare la matematica complessa, ma in sostituzione delle variabili locali, devi stare attento.

Devi anche evitare reductio ad absurdum - il calcolo di discountFactor deve essere eseguito su un metodo? Quindi il tuo esempio diventa:

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

Il partizionamento oltre un certo livello rende il codice meno leggibile.

    
risposta data 24.01.2013 - 16:59
fonte
0

Se lavori in una lingua con parametri denominati (ObjectiveC, Python, Ruby, ecc.), i vars temporanei sono meno utili.

Tuttavia, nell'esempio basePrice, la query potrebbe richiedere del tempo e potrebbe essere necessario memorizzare il risultato in una variabile temporanea per uso futuro.

Come te, però, io uso variabili temporanee per considerazioni di chiarezza e lunghezza della linea.

Ho anche visto i programmatori fare quanto segue in PHP. È interessante e ottimo per il debug, ma è un po 'strano.

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs
    
risposta data 21.01.2013 - 17:55
fonte
0

La logica alla base di questa raccomandazione è che si desidera essere in grado di utilizzare la stessa precomputazione altrove nella propria applicazione. Vedi Sostituisci temp con query nel catalogo dei modelli di refactoring:

The new method can then be used in other methods

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

Pertanto, nell'esempio host e URI, applicherei questa raccomandazione solo se prevedo di riutilizzare lo stesso URI o la stessa definizione di host.

Se questo è il caso, a causa dello spazio dei nomi, non definirò un metodo globale uri () o host (), ma un nome con più informazioni, come twilio_host () o archive_request_uri ().

Quindi, per il problema della lunghezza della linea, vedo diverse opzioni:

  • Crea una variabile locale, come uri = archive_request_uri() .

Razionale: nel metodo corrente, vuoi che l'URI sia quello menzionato. La definizione URI è ancora ridotta a fattori.

  • Definisci un metodo locale, come uri() { return archive_request_uri() }

Se usi spesso la raccomandazione di Fowler, saprai che il metodo uri () è lo stesso schema.

Se a causa della scelta della lingua è necessario accedere al metodo locale con un 'self.', consiglierei la prima soluzione, per una maggiore espressività (in Python, definirei la funzione uri all'interno del metodo corrente).

    
risposta data 21.01.2013 - 21:50
fonte

Leggi altre domande sui tag