-
-
Save ldaniel/2149474 to your computer and use it in GitHub Desktop.
| 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 |
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
@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é?