Variáveis ​​temporárias versus requisitos de comprimento de linha

10

Estive lendo a refatoração de Martin Fowler . Geralmente é excelente, mas uma das recomendações de Fowler parece estar causando um pequeno problema.

Fowler recomenda que você substitua variáveis ​​temporárias por uma consulta, portanto, em vez disso:

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

você usa um método auxiliar:

double basePrice() {
    return _quantity * _itemPrice;
}

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

Em geral, concordo, exceto que uma das razões pelas quais uso variáveis ​​temporárias é quando uma linha é muito longa. Por exemplo:

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

Se eu tentasse enfatizar isso, a linha ultrapassaria 80 caracteres.

Como alternativa, acabo com cadeias de código, que não são muito mais fáceis de ler:

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

Quais são algumas estratégias para reconciliar as duas?

Kevin Burke
fonte
10
80 caracteres é cerca de 1/3 de 1 dos meus monitores. Tem certeza de que ainda vale a pena aderir a 80 linhas de caracteres?
jk.
10
sim, ver, por exemplo programmers.stackexchange.com/questions/604/...
Kevin Burke
Porém, seu exemplo $hoste $urié artificial - a menos que o host esteja sendo lido de uma configuração ou outra entrada, eu preferiria que eles estivessem na mesma linha, mesmo que isso ocorra ou se perca.
precisa saber é o seguinte
5
Não precisa ser tão dogmático. O livro é uma lista de técnicas que podem ser usadas quando ajudam, e não um conjunto de regras que você deve aplicar em todos os lugares, sempre. O objetivo é tornar seu código mais sustentável e mais fácil de ler. Se um refator não fizer isso, você não o usará.
Sean McSomething
Embora eu ache que um limite de 80 caracteres seja um pouco excessivo, um limite semelhante (100?) É razoável. Por exemplo, geralmente gosto de programar em monitores orientados a retrato, de modo que as linhas longas podem ser irritantes (pelo menos se forem comuns).
Thomas Eding

Respostas:

16

Como
1. Existem restrições de comprimento de linha para que você possa ver + entender mais código. Eles ainda são válidos.
2. Enfatize o julgamento sobre a convenção cega .
3. Evite variáveis ​​temporárias, a menos que otimize o desempenho .
4. Evite usar recuo profundo para alinhamento nas instruções de várias linhas.
5. Divida as instruções longas em várias linhas ao longo dos limites das ideias :

// 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.

Raciocínio
A principal fonte dos meus problemas (depuração) com variáveis ​​temporárias é que elas tendem a ser mutáveis. Ou seja, assumirei que eles são um valor quando escrevo o código, mas se a função é complexa, algum outro pedaço de código altera seu estado no meio. (Ou o inverso, onde o estado da variável permanece o mesmo, mas o resultado da consulta foi alterado).

Considere manter as consultas, a menos que esteja otimizando o desempenho . Isso mantém qualquer lógica que você usou para calcular esse valor em um único local.

Os exemplos que você deu (Java e ... PHP?) Permitem declarações de várias linhas. Se as linhas ficarem longas, quebre-as. A fonte jquery leva isso a extremos. (A primeira instrução está na linha 69!) Não que eu necessariamente concorde, mas existem outras maneiras de tornar seu código legível do que usar temp vars.

Alguns exemplos
1. Guia de estilo PEP 8 para python (não é o exemplo mais bonito)
2. Paul M Jones no Guia de estilo Pear (meio do argumento da estrada)
3. Comprimento da linha Oracle + convenções de empacotamento (estratégias úteis para manter até 80 caracteres)
4. Práticas Java MDN (enfatiza o julgamento do programador sobre a convenção)

Zachary Yates
fonte
11
A outra parte do problema é que uma variável temporária geralmente sobrevive ao seu valor. Não é um problema em pequenos blocos de escopo, mas em blocos maiores, sim, um grande problema.
Ross Patterson
8
Se você está preocupado com a modificação temporária, coloque uma const nele.
Thomas Eding
3

Penso que o melhor argumento para usar métodos auxiliares em vez de variáveis ​​temporárias é a legibilidade humana. Se você, como humano, tem mais problemas para ler a cadeia do método auxiliar do que o varialbe temporário, não vejo razão para extraí-los.

(Por favor me corrija se eu estiver errado)

mhr
fonte
3

Eu não acho que você precise seguir rigorosamente as diretrizes de 80 caracteres ou que sempre a variável temp local deve ser extraída. Mas longas filas e temps locais devem ser investigados para melhores maneiras de expressar a mesma idéia. Basicamente, eles indicam que uma determinada função ou linha é muito complicada e precisamos decompô-la. Mas precisamos ter cuidado, porque dividir uma tarefa em pedaços de maneira ruim só torna a situação mais complicada. Então, eu devo dividir as coisas em componentes resuable e simples.

Deixe-me ver os exemplos que você postou.

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

Minha observação é que todas as chamadas twilio api começarão com "https://api.twilio.com/2010-04-1/" e, portanto, há uma função reutilizável muito óbvia:

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

Na verdade, acho que o único motivo para gerar uma URL é fazer a solicitação, então eu faria:

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

De fato, muitos dos URLs realmente começam com "Accounts / $ accountSid", então provavelmente extrairia isso também:

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

E se tornarmos o twilio api um objeto que contém o número da conta, poderíamos fazer algo como:

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

Usar um objeto $ twilio tem o benefício de facilitar o teste de unidade. Posso atribuir ao objeto um objeto $ twilio diferente que, na verdade, não é chamado de volta para o twilio, que será mais rápido e não fará coisas estranhas ao twilio.

Vamos olhar para o outro

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

Aqui eu pensaria em:

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

ou

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

ou

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

Dependendo de qual é o idioma mais reutilizável.

Winston Ewert
fonte
1

Na verdade, eu discordo do eminente Sr. Fowler sobre isso no caso geral.

A vantagem de extrair um método do código anteriormente embutido é a reutilização do código; o código no método agora é separado de seu uso inicial e agora pode ser usado em outros locais do código sem ser copiado e colado (o que exigiria alterações em vários locais se a lógica geral do código copiado tivesse que mudar) .

Contudo, um valor conceitual igual, geralmente maior, é "reutilização de valor". O Sr. Fowler chama esses métodos extraídos para substituir as variáveis ​​temporárias "consultas". Bem, o que é mais eficiente; consultando um banco de dados a cada uma das várias vezes em que você precisa de um valor específico ou consultando uma vez e armazenando o resultado (assumindo que o valor seja estático o suficiente para que você não espere que ele mude)?

Para quase qualquer cálculo além do relativamente trivial no seu exemplo, na maioria dos idiomas é mais barato armazenar o resultado de um cálculo do que continuar calculando. Portanto, a recomendação geral para recalcular sob demanda é falsa; custa mais tempo do desenvolvedor e mais tempo da CPU e economiza uma quantidade trivial de memória, que na maioria dos sistemas modernos é o recurso mais barato desses três.

Agora, o método auxiliar, em conjunto com outro código, pode ser "preguiçoso". Quando executado pela primeira vez, inicializaria uma variável. Todas as chamadas adicionais retornariam essa variável até que o método fosse instruído explicitamente a recalcular. Pode ser um parâmetro para o método ou um sinalizador definido por outro código que altera qualquer valor que a computação desse método dependa:

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

Agora, para esse cálculo trivial, é ainda mais trabalho executado do que salvo, e, portanto, eu geralmente recomendo manter a variável temp; no entanto, para cálculos mais complexos que você geralmente deseja evitar executar várias vezes e que é necessário em vários locais no código, é dessa maneira que você faria.

KeithS
fonte
1

Os métodos auxiliares têm um lugar, mas você deve ter cuidado ao garantir a consistência dos dados e um aumento desnecessário no escopo das variáveis.

Por exemplo, seu próprio exemplo cita:

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

Claramente tanto _quantitye _itemPricesão variáveis globais (ou pelo nível de classe menos) e, portanto, há um potencial para que sejam fora modificadagetPrice()

Portanto, existe um potencial para a primeira chamada basePrice()retornar um valor diferente da segunda chamada!

Portanto, eu sugeriria que as funções auxiliares podem ser úteis para isolar matemática complexa, mas como uma substituição para variáveis ​​locais, você precisa ter cuidado.


Você também deve evitar reductio ad absurdum - o cálculo de deve discountFactorser derivado de um método? Portanto, seu exemplo se torna:

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

    return basePrice * discountFactor;
}

Particionar além de um certo nível realmente torna o código menos legível.

Andrew
fonte
+1 para tornar o código menos legível. O particionamento excessivo pode ocultar o problema de negócios que o código-fonte está tentando resolver. Pode haver casos especiais em que um cupom é aplicado em getPrice (), mas se isso estiver oculto em uma cadeia de chamadas de função, a regra de negócios também estará oculta.
Reactgular
0

Se você trabalha em uma linguagem com parâmetros nomeados (ObjectiveC, Python, Ruby, etc.), os temp vars são menos úteis.

No entanto, no seu exemplo basePrice, a consulta pode demorar um pouco para ser executada e você pode querer armazenar o resultado em uma variável temp para uso futuro.

Como você, no entanto, uso variáveis ​​temporárias para maior clareza e considerações sobre o comprimento da linha.

Também vi programadores fazerem o seguinte em PHP. É interessante e ótimo para depuração, mas é um pouco estranho.

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs
Dimitry
fonte
0

A lógica por trás desta recomendação é que você deseja poder usar a mesma pré-computação em outro local do seu aplicativo. Consulte Substituir Temp por Consulta no catálogo de padrões de refatoração:

O novo método pode ser usado em outros métodos

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

Portanto, no seu exemplo de host e URI, eu aplicaria essa recomendação apenas se pretender reutilizar o mesmo URI ou definição de host.

Se for esse o caso, devido ao espaço para nome, não definirei um método global uri () ou host (), mas um nome com mais informações, como twilio_host () ou archive_request_uri ().

Em seguida, no problema de comprimento de linha, vejo várias opções:

  • Crie uma variável local, como uri = archive_request_uri().

Fundamentação da petição: No método atual, você deseja que o URI seja o mencionado. A definição de URI ainda é fatorada.

  • Defina um método local, como uri() { return archive_request_uri() }

Se você costuma usar a recomendação de Fowler, saberá que o método uri () é o mesmo padrão.

Se, devido à escolha do idioma, você precisar acessar o método local com um 'self', recomendo a primeira solução, para maior expressividade (em Python, eu definiria a função uri dentro do método atual).

Marc-Emmanuel Coupvent des Gra
fonte