Skip to content

Instantly share code, notes, and snippets.

@ldaniel
Created March 21, 2012 16:52
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ldaniel/2149474 to your computer and use it in GitHub Desktop.
Save ldaniel/2149474 to your computer and use it in GitHub Desktop.
Relação entre testes, design e métricas
Pessoal, gostaria de saber como vocês endereçam as questões que farei após explicar o cenário
abaixo. Pra mim, é muito claro que todas podem ser respondidas com um belo "depende", contudo,
vamos focar as respostas no que atenderia a maioria dos casos (lembrando que esse é um cenário
muito simplório).
-- CENÁRIO -----------------------------------------------------------------------------------------
Um desenvolvedor codificou inicialmente a classe (escrita em C#) no gist "exemplo_antes.cs".
Após aplicar um refactoring, ele chegou no resultado mostrado no gist "exemplo_depois.cs".
Algumas métricas foram coletadas antes e depois do refactoring (vide gist "métricas.txt").
Obs.: Esse código foi tirado daqui ->
http://whileicompile.wordpress.com/2010/09/14/what-is-too-simple-and-small-to-refactor-as-clean-code
-- QUESTÕES ----------------------------------------------------------------------------------------
1) De modo geral, podemos ter como prática que o set de testes deve, no mínimo, ter o mesmo número
de testes que o resultado do indicador CC (cyclomatic complexity)? Por exemplo, se um método tem
5 de CC, ele deve ter no mínimo 5 testes escritos para ele.
Notas: a) estou considerando apenas um assert por método de test.
b) não considerando a eficácia do teste escrito.
2) Membros privados podem ser ignorados nos testes? Devemos garantir os testes pelos membros
públicos observando a cobertura de código dos testes. Isso é suficiente?
Nota: sem entrar no mérito da necessidade ou não de 100% de cobertura. A intenção aqui é
deduzir uma relação entre métricas (total de membros privados/publicos) e testes.
3) Quando, através de métricas, chegamos a conclusão que o código ficou mais complexo, é uma boa
estratégia considerar LoC (lines of code) como indicador para comparar "antes" e "depois"? Que
outras métricas vocês considerariam?
4) Qual a melhor "unidade" para orientarmos a escrita de testes (de unidade, claro): método, classe,
assembly, assunto de negócio ou outra? (estou falando aqui de "Testes de Unidade": qual unidade
você comumente utiliza?)
/* Classe inicial, antes do refatoring */
using System;
namespace CleanCode.Before
{
public class WageCalculator
{
public static float Calculate(float hours, float rate, bool isHourlyWorker)
{
if (hours < 0 || hours > 80)
throw new ArgumentException();
float wages = 0;
if (hours > 40)
{
var overTimeHours = hours - 40;
if (isHourlyWorker)
wages += (overTimeHours * 1.5f) * rate;
else
wages += overTimeHours * rate;
hours -= overTimeHours;
}
wages += hours * rate;
return wages;
}
}
}
/* Classes geradas, após refactoring */
using System;
namespace CleanCode.After
{
public abstract class WageCalculatorBase
{
public float HoursWorked { get; protected set; }
public float HourlyRate { get; protected set; }
public WageCalculatorBase(float hours, float hourlyRate)
{
if (hours < 0 || hours > 80)
throw new
ArgumentOutOfRangeException("Hours must be between 0 and 80.");
HoursWorked = hours;
HourlyRate = hourlyRate;
}
public abstract float Calculate();
}
public class WageCalculatorForContractor : WageCalculatorBase
{
public WageCalculatorForContractor(float hours, float hourlyRate)
: base(hours, hourlyRate)
{
}
public override float Calculate()
{
return HoursWorked * HourlyRate;
}
public static float Calculate(float hours, float hourlyRate)
{
WageCalculatorForContractor payCalc = new
WageCalculatorForContractor(hours, hourlyRate);
return payCalc.Calculate();
}
}
public class WageCalculatorForEmployee : WageCalculatorBase
{
public WageCalculatorForEmployee(float hours, float hourlyRate)
: base(hours, hourlyRate)
{
}
public override float Calculate()
{
if (IsOvertimeRequired)
return CalculateWithOvertime();
return CalculateWithoutOvertime();
}
protected bool IsOvertimeRequired
{
get
{
return HoursWorked > 40;
}
}
protected float CalculateWithoutOvertime()
{
return HoursWorked * HourlyRate;
}
protected float CalculateWithOvertime()
{
float overTimeHours = HoursWorked - 40;
return (overTimeHours * 1.5f + 40) * HourlyRate;
}
public static float Calculate(float hours, float hourlyRate)
{
WageCalculatorForEmployee payCalc = new
WageCalculatorForEmployee(hours, hourlyRate);
return payCalc.Calculate();
}
}
}
Type Member CC LoC
--------------------------------------------------------------------
CleanCode.After........................................[18] [27]
WageCalculatorBase..............................(8) (9)
Calculate() 1 0
HourlyRate.get() 1 1
HourlyRate.set(float) 1 1
HoursWorked.get() 1 1
HoursWorked.set(float) 1 1
WageCalculatorBase(float, float) 3 5
WageCalculatorForContractor.....................(3) (5)
Calculate() 1 2
Calculate(float, float) 1 2
WageCalculatorForContractor(float,float) 1 1
WageCalculatorForEmployee.......................(7) (13)
Calculate() 2 3
Calculate(float, float) 1 2
CalculateWithoutOvertime() 1 2
CalculateWithOvertime() 1 3
IsOvertimeRequired.get() 1 2
WageCalculatorForEmployee(float, float) 1 1
CleanCode.Before........................................[6] [14]
WageCalculator..................................(6) (14)
Calculate(float, float, bool) 5 13
WageCalculator() 1 1
@ldaniel
Copy link
Author

ldaniel commented Apr 11, 2012

@juanplopes

Bom, acho que concordamos então. São ferramentas úteis. Ninguém obriga um desenvolvedor a usar a janelinha quickwatch, mas esperamos um código de qualidade e com o mínimo de bugs.

@mauricioaniche
Copy link

mauricioaniche commented Apr 11, 2012 via email

@ldaniel
Copy link
Author

ldaniel commented Apr 11, 2012

@tucaz não consigo deixar de lembrar das discussões do DNAD em 2008, quando tudo começou. poxa, era exatamente isso aqui. que coisa legal.

... ah saudosismo... =P

@juanplopes
Copy link

@ldaniel

Métricas não são forminhas de bolo, mas estabelecer que elas (muitas vezes sozinhas) são obrigatórias para um bom design é nocivo.

Mas o que eu estava falando, na verdade, são desenvolvedores que não pensam em nada além da receita que seguem para desenvolver uma funcionalidade. Por isso tantas perguntas sobre DDD, repositórios, camadas, etc. no DNA. Por que a grande massa não quer ter que pensar. Quer "comprar" uma modelagem pronta na "prateleira das boas práticas" e usar.

@ldaniel
Copy link
Author

ldaniel commented Apr 11, 2012

@tucaz @mauricioaniche

pois é, eu continuo batendo na tecla de que métricas (as mais comuns, pelo menos) são muito baratas para serem tiradas (custo, tempo, esforço etc). acho que talvez o problema seja um pouco de tudo:

  • zilhões de tecnologias e possibilidades de estudo hoje em dia
  • banalização do skill necessário para um desenvolvedor
  • alta oferta de empregos e poucos profissionais (bons) disponíveis
  • sei lá, tudo pode ser usado como "muleta" hoje em dia...

a impressão que tenho quando olho para o grupo que está reunido aqui respondendo essas questões, é que não representamos os "fodões" da área de TI. caramba, fazer um trabalho bem feito deveria ser a meta básica de qualquer um (independente de contexto, pelo brio mesmo, poxa). no entanto, tenho que concordar com o Tuca quando ele se desanima com o atual mercado.

@ldaniel
Copy link
Author

ldaniel commented Apr 11, 2012

@juanplopes

sim, concordo com tudo.

@tucaz
Copy link

tucaz commented Apr 11, 2012

@mauricioaniche:

É "impossível" porque o desenvolvedor que ta ali do meu lado todo dia tem dificuldade em compreender como funciona um ORM, javascript básico e até HTML e CSS. Outro dia pedi pra um cara mudar uma classe CSS e ele não sabia como fazer. E isso não é nem aqui no Brasil somente. Esse caso em especifico aconteceu nos EUA então esse problema não é exclusividade nossa.

Imagina falar pra ele que o código dele tem uma complexidade enorme por causa dos caminhos que os IF's geram? Ou pedir pro cara refatorar pra deixar mais coeso? Não é prático.

Se olharmos pro indivíduo dentro do grupo, talvez até seja possível, mas disseminar esse comportamento pra um grupo de desenvolvedores que tem outras motivações é impraticável.

@ldaniel, mais uma vez vou ter que concordar com você que a discussão está interessante. Fazia tempo que eu não via algo assim, onde o pessoal não da carteirada pra fugir do assunto e provar um ponto. Way to go!

@juanplopes
Copy link

@tucaz

Nunca fiz um estudo formal para tentar confirmar isso, mas tenho a sincera impressão que desenvolvedores despreparados assim geram mais prejuízo que valor nos produtos em que desenvolvem.

Não que desenvolvedores iniciantes não possam trabalhar, mas por esse motivo sou a favor de code review ou (para os mais xp-fans) pair programming. É claro que se a proporção de desenvolvedores iniciantes for absurdamente maior que a de experientes, o custo disso fica inviável. E talvez seja irreal na maior parte das empresas com o mercado que temos.

@ldaniel

Curiosamente, sem querer puxar sardinha, eu vejo que a comunidade Java tem muito mais apego às métricas que a .NET. Em .NET você tem dificuldade até em encontrar ferramentas adequadas para tirar essas métricas.

@mauricioaniche
Copy link

mauricioaniche commented Apr 11, 2012 via email

@tucaz
Copy link

tucaz commented Apr 11, 2012

@juanplopes,

compartilho da sua impressão. Tanto é que ultimamente adotei a postura de aceitar mais trabalho (não tanto assim :P) ao invés de contratar gente despreparada que vai gerar mais trabalho do que já temos.

@mauricioaniche,

foi por isso que falei que sou velho chato. Com certeza não é motivo pra desanimar. Essa imagem de desanimado minha é mesmo uma imagem. Apesar dos pesares, ainda tenho energia pra tentar promover um monte de coisa bacana.

Quanto a equipes boas, sei que elas existem. Não em tantos lugares, mas também não estão em extinção. Só não consegui unir o útil ao agradável ainda ($$$). Infelizmente empresas ruins pagam mais, afinal sempre precisam de gente pra resolver problemas, né?

@juanplopes
Copy link

@tucaz

Vem pro Rio. Estamos contratando!

Brincadeira...

Não, é sério. Pode vir. :P

@marcioalthmann
Copy link

Acho que cheguei só um pouco atrasado kkk, mesmo recebendo no twitter o link uma refatoração de um código legado não me permitiu participar :D

@ldaniel interessante a discussão, a única refatoração que gostei foi a do @juanplopes e agora minhas respostas.

1- Sim, no mínimo, quando preciso testar códigos assim geralmente verifico a cobertura de código para garantir que passei por todo lugar.

2 - Essa depende mesmo, testo métodos privados mas ai tenho que concordar com o @juanplopes talvez tenha um probleminha em precisar testar esses caras :)

3 - Cara não sei, acho que LoC não vai ajudar a dizer que ficou mais complexo, muita linha de código simples é melhor que pouca linha de código complexo :D. E ai depende do "feeling" que o @vquaiato disse. Agora pensando... e se além de LoC considerar número de testes? Acho que nesse exemplo o número de testes para o código refatorado acabaria maior do que com o código sem refatorar.

4 - Classe

Mais uma vez parabéns, sensacional a discussão aqui, todos foram geniais

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment