Skip to content

Instantly share code, notes, and snippets.

@ldaniel
Created March 21, 2012 16:52
Show Gist options
  • 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
@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