Variabili temporanee rispetto ai requisiti di lunghezza della linea


10

Ho letto il refactoring di Martin Fowler . È generalmente eccellente, ma una delle raccomandazioni di Fowler sembra causare qualche problema.

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

si estrae in 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 per il fatto che uso una variabile temporanea quando una linea è troppo lunga. Per esempio:

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

Se avessi provato a spiegarlo, la riga avrebbe superato gli 80 caratteri.

In alternativa, finisco con 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 i due?


10
80 caratteri sono circa 1/3 di 1 dei miei monitor. sei sicuro che valga la pena attenersi a 80 linee di caratteri?
jk.


Il vostro $hoste $uriad esempio è una specie di forzato, anche se - a meno che il padrone di casa era in corso la lettura un'impostazione o altro ingresso, preferisco loro di essere sulla stessa linea, anche se lo fa avvolgere o andare fuori dal bordo.
Izkata,

5
Non c'è bisogno di essere così dogmatici. Il libro è un elenco di tecniche che possono essere utilizzate quando aiutano, non un insieme di regole che devi applicare ovunque, ogni volta. Il punto è rendere il codice più gestibile e più facile da leggere. Se un refattore non lo fa, non lo usi.
Sean McSomething il

Mentre penso che un limite di 80 caratteri sia un po 'eccessivo, un limite simile (100?) È ragionevole. Ad esempio, mi piace spesso programmare su monitor orientati al ritratto, quindi le linee extra lunghe possono essere fastidiose (almeno se sono comuni).
Thomas Eding,

Risposte:


16

Come fare
1. Esistono restrizioni sulla lunghezza delle righe in modo da poter vedere + capire più codice. Sono ancora validi.
2. Enfatizzare il giudizio sulla convenzione cieca .
3. Evitare le variabili temporanee a meno che non si ottimizzi per le prestazioni .
4. Evitare di utilizzare il rientro profondo per l'allineamento nelle istruzioni multilinea.
5. Suddividere le dichiarazioni lunghe in più righe lungo i confini dell'idea :

// 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 ad essere mutabili. Vale a dire, suppongo che siano un valore quando scrivo il codice, ma se la funzione è complessa, qualche altro pezzo di codice cambia il loro stato a metà. (O viceversa, in cui lo stato della variabile rimane lo stesso ma il risultato della query è cambiato).

Prendi in considerazione le query a meno che tu non stia ottimizzando le prestazioni . Ciò mantiene qualsiasi logica che hai usato per calcolare quel valore in un unico posto.

Gli esempi che hai fornito (Java e ... PHP?) Consentono entrambi istruzioni multilinea. Se le linee si allungano, spezzale. La fonte jquery porta questo 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 oltre all'utilizzo di temp vars.

Alcuni esempi
1. Guida allo stile PEP 8 per Python (non l'esempio più grazioso)
2. Paul M Jones sulla Guida allo stile Pear (argomento centrale della strada)
3. Lunghezza della linea Oracle + convenzioni di avvolgimento (utili stratagemmi per mantenere 80 caratteri)
4. MDN Java Practices (sottolinea il giudizio del programmatore sulla convenzione)


1
L'altra parte del problema è che una variabile temporanea spesso sopravvive al suo valore. Non è un problema in piccoli blocchi di portata, ma in quelli più grandi, sì, un grosso problema.
Ross Patterson,

8
Se sei preoccupato che il temporaneo venga modificato, inserisci una const.
Thomas Eding,

3

Penso che l'argomento migliore per usare i metodi helper anziché le variabili temporanee sia la leggibilità umana. Se tu, come essere umano, hai più problemi a leggere la catena del metodo helper rispetto alla varialbe temporanea, non vedo alcun motivo per cui dovresti estrarli.

(Per favore correggimi se sbaglio)


3

Non penso che sia necessario seguire rigorosamente le linee guida per 80 caratteri o che sia sempre necessario estrarre la variabile temp locale. Ma lunghe file e temp locali dovrebbero essere studiati per modi migliori di esprimere la stessa idea. Fondamentalmente, indicano che una determinata funzione o linea è troppo complicata e dobbiamo scomporla. Ma dobbiamo stare attenti, perché spezzare un compito in pezzi in un modo cattivo non fa che complicare la situazione. Quindi devo scomporre le cose in componenti resuable e semplici.

Lasciami guardare gli esempi che hai pubblicato.

$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 esiste una funzione riutilizzabile molto ovvia:

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

In effetti, immagino che l'unico motivo per generare un URL sia effettuare la richiesta, quindi farei:

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

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

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

E se trasformiamo il twilio api in un oggetto che contiene il numero di conto, potremmo fare qualcosa del tipo:

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

L'uso di un oggetto $ twilio ha il vantaggio di semplificare i test unitari. 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 penserei a entrambi:

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

o

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

o

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

A seconda di quale sia il linguaggio più riutilizzabile.


1

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

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

Tuttavia, di uguale valore concettuale spesso maggiore è il "riutilizzo del valore". Fowler chiama questi metodi estratti per sostituire le variabili temporanee "query". Bene, cosa è più efficiente; eseguire una query su un database ciascuna delle volte in cui è necessario un determinato valore oppure eseguire una query una volta e archiviare il risultato (presupponendo che il valore sia abbastanza statico da non prevedere che cambi)?

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

Ora, il metodo helper, insieme ad altri codici, potrebbe essere "pigro". Alla prima esecuzione, inizializza una variabile. Tutte le ulteriori chiamate restituirebbero quella variabile fino a quando non viene detto esplicitamente al metodo di ricalcolare. Questo potrebbe essere un parametro del metodo o un flag impostato da un altro codice che modifica qualsiasi valore che il calcolo di questo metodo dipende:

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 banale calcolo, c'è ancora più lavoro svolto che salvato, e quindi in genere consiglierei di attenermi alla variabile temp; tuttavia, per calcoli più complessi che generalmente si desidera evitare di eseguire più volte e che sono necessari in più posizioni nel codice, questo è il modo in cui lo si farebbe.


1

I metodi di supporto hanno un posto, ma devi stare attento a garantire la coerenza dei dati e un aumento non necessario dell'ambito 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 entrambi _quantitye _itemPricesono variabili globali (o almeno a livello di classe) e quindi esiste un potenziale per la loro modifica al di fuori digetPrice()

Pertanto esiste la possibilità che la prima chiamata basePrice()restituisca 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, è necessario fare attenzione.


Devi anche evitare la reductio ad absurdum : il calcolo deve discountFactoressere trasferito a 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.


+1 per rendere il codice meno leggibile. Il partizionamento eccessivo può nascondere il problema aziendale che il codice sorgente sta cercando di risolvere. Potrebbero esserci casi speciali in cui viene applicato un coupon in getPrice (), ma se questo è nascosto in profondità in una catena di chiamate di funzione, anche la regola aziendale viene nascosta.
Reactgular

0

Se ti capita di lavorare in una lingua con parametri denominati (ObjectiveC, Python, Ruby, ecc.), Le variabili temporanee sono meno utili.

Tuttavia, nell'esempio basePrice, l'esecuzione della query potrebbe richiedere del tempo e potrebbe essere necessario archiviare il risultato in una variabile temporanea per un utilizzo futuro.

Come te, però, utilizzo le variabili temp 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

0

La logica alla base di questa raccomandazione è che si desidera poter utilizzare lo stesso pre-calcolo altrove nella propria applicazione. Vedi Sostituisci temp con query nel catalogo dei modelli di refactoring:

Il nuovo metodo può quindi essere utilizzato in altri metodi

    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, nel tuo esempio di host e URI, applicherei questa raccomandazione solo se ho intenzione 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 uri () o host () globale, ma un nome con maggiori informazioni, come twilio_host () o archive_request_uri ().

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

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

Motivazione: Nel metodo corrente, si desidera che l'URI sia quello menzionato. La definizione di URI è ancora fattorizzata.

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

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

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

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.