refactoring e code smells: seu código está apodrecendo!

130
Refactoring e Code Smells: Seu código está apodrecendo!

Upload: emanuel-canuto

Post on 29-Nov-2014

222 views

Category:

Software


3 download

DESCRIPTION

Uma apresentação sobre Refactoring e Code Smells, abordando características do software em estado de apodrecimento, identificação de alguns code smells, e refactoring, e focado em melhoria contínua profissional em relação a engenharia de software.

TRANSCRIPT

Refactoring e Code Smells: Seu código está apodrecendo!

Emanuel Canuto

Desenvolvimento há 4UECERock!

PensarMantér a simplicidade

Aprender

@emanuel_canuto

Desenvolvimento de software ágil é f*da!

caso contrário seria músico

CAFÉ!!!

Tava precisando!

https://www.facebook.com/photo.php?fbid=10203189806695222&set=p.10203189806695222&type=1&permPage=1

@handersonbf

Mudar é evoluir!

Fight !!Fight !!Fight !!Fight !!

Quem trabalha ou já trabalhou em algum projeto

Bugado???

Quem já bugou um projeto ???

Pense! Quais foram as experiências ???

RigidezParece simples, mas difícil de mudar

Mudanças em cascata

Dependências entre módulos

Medo de correção de bugs

FragilidadeCode and Fix

Manutenção Adventure

Desenvolvimento fora de controle

Credibilidade começa a cair

Ei mah, tua feature quebrou!

Terminei a feature!!!

ImobilidadeNão é possível reutilizar software

Muitas Dependências

Responsabilidades de mais

Risco alto para reutilizar

“Quem foi o FDP que escreveu isso aqui?”

Refactoring

O que é mais caro?

O que é mais caro?

É melhor pagar sem juros, certo?

Não esqueça, Pague o débito!

Se vc não paga suas contas?

“Refactoring is a disciplined technique for restructuring an

existing body of code, altering its internal structure without

changing its external behavior.”

Martin Fowler

By continuously improving the design of code, we make it easier and easier to work with. This is in

sharp contrast to what typically happens: little refactoring and a great deal of attention paid to

expediently adding new features. If you get into the hygienic habit of refactoring continuously, you'll find that it is easier to extend and maintain code.

Joshua Kerievsky

Não deixe pra depois!

Baby Steps

BAD SMELLSBAD SMELLS

Sintomas de que seu

código está com

problemas.

Sintomas de que seu

código está com

problemas.

Long MethodThe object programs that live best and longest are those with short

methods.

[…]

The key here is not method length but the semantic distance between what the method does and how it does it.

[…]

Long Method[…]

Development environments that allow you to see two methods at once help to eliminate this step, but the real key to making it easy to

understand small methods is good naming.

[…]

public int fechaFolha(String folha) {// imprime a folha adicionando bla bla blatry {

FileReader arq = new FileReader(folha);BufferedReader lerArq = new BufferedReader(arq);String linha = lerArq.readLine();while (linha != null) {

System.out.printf("%s\n", linha);this.linha += linha;linha = lerArq.readLine();

}arq.close();

} catch (IOException e) {System.err.printf("Erro na abertura do arquivo: %s.\n",e.getMessage());

}// verifica se a folha é especialif (linha.contains("ARS0010304")) {

this.folhaEspecial = true;}

...

public int fechaFolha(String folha) { imprimeFolha(folha);ehFolhaEspecial();atualizaDadosDePagamento();

}

public int fechaFolha(String folha) { imprimeFolha(folha);eFolhaEspecial();atualizaDadosDePagamento();

}private void ehFolhaEspecial() {

if (linha.contains("ARS0010304")) {this.folhaEspecial = true;

}}private void imprimeFolha(String folha) {

try {FileReader arq = new FileReader(folha);BufferedReader lerArq = new BufferedReader(arq);String linha = lerArq.readLine();while (linha != null) {

System.out.printf("%s\n", linha);this.linha += linha;linha = lerArq.readLine();

}...

I throw away commented code

Few Short Methods Per Class★ Fácil de testar.★ Fácil para reusar.★ Fácil para modificar.★ Menos bugs são descoberto, estatisticamente em métodos

curtos e classes curtas.★ Equipe de desenvolvimento coda mais rápido, porque há menos

necessidade de refactoring.

Shotgun Surgery

Shotgun Surgery

You whiff this when every time you make a kind of change, you have to make a lot of little changes to a lot of different classes. When the changes are all over the place, they are hard to find, and it's easy to miss an important change.

Feature Envy

Feature EnvyFeature Envy

Feature Envy

A classic smell is a method that seems more interested in a class other than the one it actually is in.

[ . . . ]

The most common focus of the envy is the data.

Feature Envy

Mova o método quando todo o método quer estar claramente em outro lugar, ou…

Extraia o método quando apenas uma parte do método é invejoso, ou…

Extraia a classe se você tem vários métodos invejosos e a funcionalidade não chega a pertencer a outro objeto.

Feature Envy

Long Parameter List

public Object method(int var,int var1,int var2,int var3,int var4,int var5,int var6,int var7) {

...}

Long Parameter List[…]

long parameter lists are hard to understand, because they become inconsistent and difficult to use, and because you are forever

changing them as you need more data.

[…]

Duplicated CodeDuplicated CodeDuplicated CodeDuplicated CodeDuplicated Code

Duplicated Code

Duplicated Code

Duplicated Code

Long MethodLarge Class

Shotgun SurgeryLong Parameter List

Divergent Change

Parallel Inheritance Hierarchies

Switch Statements

Primitive Obsession

Data Clumps

Feature Envy

Incomplete Library ClassAlternative Classes with Different Interfaces

Inappropriate Intimacy

Middle Man

Message Chains

Temporary Field

Speculative Generality

Lazy ClassComments

Refused Bequest

Data ClassBAD SMELLSBAD SMELLS

RefactoringRefactoringRefactoringRefactoringRefactoringRefactoringRefactoringRefactoringRefactoring

Extract MethodMotivation

Extract Method is one of the most common refactorings I do. I look at a method that is too long or look at code that needs a comment to understand its purpose. I then turn that fragment of code into its own method.

I prefer short, well-named methods for several reasons. First, it increases the chances that other methods can use a method when the method is finely grained. Second, it allows the higher-level methods to read more like a series of comments. Overriding also is easier when the methods are finely grained.

It does take a little getting used to if you are used to seeing larger methods. And small methods really work only when you have good names, so you need to pay attention to naming. People sometimes ask me what length I look for in a method. To me length is not the issue. The key is the semantic distance between the method name and the method body. If extracting improves clarity, do it, even if the name is longer than the code you have extracted.

void printOwing() {

Enumeration e = _orders.elements();double outstanding = 0.0;

System.out.println("**************************");System.out.println("***** Customer Owes ******");System.out.println("**************************");

while (e.hasMoreElements()) {Order each = (Order) e.nextElement();outstanding += each.getAmount();

}

System.out.println("name:" + _name);System.out.println("amount" + outstanding);

}

void printOwing() {

Enumeration e = _orders.elements();double outstanding = 0.0;

System.out.println("**************************");System.out.println("***** Customer Owes ******");System.out.println("**************************");

while (e.hasMoreElements()) {Order each = (Order) e.nextElement();outstanding += each.getAmount();

}

System.out.println("name:" + _name);System.out.println("amount" + outstanding);

}

void printOwing() {

Enumeration e = _orders.elements();double outstanding = 0.0;

System.out.println("**************************");System.out.println("***** Customer Owes ******");System.out.println("**************************");

while (e.hasMoreElements()) {Order each = (Order) e.nextElement();outstanding += each.getAmount();

}

System.out.println("name:" + _name);System.out.println("amount" + outstanding);

}

void printBanner() {System.out.println("**************************");System.out.println("***** Customer Owes ******");System.out.println("**************************");

}

void printOwing() {Enumeration e = _orders.elements();double outstanding = 0.0;printBanner();while (e.hasMoreElements()) {

Order each = (Order) e.nextElement();outstanding += each.getAmount();

}System.out.println("name:" + _name);System.out.println("amount" + outstanding);

}

void printOwing() {

Enumeration e = _orders.elements();double outstanding = 0.0;

System.out.println("**************************");System.out.println("***** Customer Owes ******");System.out.println("**************************");

while (e.hasMoreElements()) {Order each = (Order) e.nextElement();outstanding += each.getAmount();

}

System.out.println("name:" + _name);System.out.println("amount" + outstanding);

}

void printDetails(double outstanding) {System.out.println("name:" + _name);System.out.println("amount" + outstanding);

}

void printOwing() {Enumeration e = _orders.elements();double outstanding = 0.0;printBanner();while (e.hasMoreElements()) {

Order each = (Order) e.nextElement();outstanding += each.getAmount();

}printDetails(outstanding);

}

void printOwing() {Enumeration e = _orders.elements();double outstanding = 0.0;printBanner();while (e.hasMoreElements()) {

Order each = (Order) e.nextElement();outstanding += each.getAmount();

}printDetails(outstanding);

}

void printOwing() {Enumeration e = _orders.elements();double outstanding = 0.0;printBanner();while (e.hasMoreElements()) {

Order each = (Order) e.nextElement();outstanding += each.getAmount();

}printDetails(outstanding);

}

double getOutstanding(double outstanding) {Enumeration e = _orders.elements();double result = 0.0;while (e.hasMoreElements()) {

Order each = (Order) e.nextElement();result += each.getAmount();

}return result;

}

void printOwing() {printBanner();double outstanding = getOutstanding();printDetails(outstanding);

}

void printOwing() {printBanner();double outstanding = getOutstanding();printDetails(outstanding);

}

void printBanner() {System.out.println("**************************");System.out.println("***** Customer Owes ******");System.out.println("**************************");

}void printDetails(double outstanding) {

System.out.println("name:" + _name);System.out.println("amount" + outstanding);

}double getOutstanding(double outstanding)...

Replace Temp with QueryMotivation

The problem with temps is that they are temporary and local. Because they can be seen only in the context of the method in which they are used, temps tend to encourage longer methods, because that's the only way you can reach the temp. By replacing the temp with a query method, any method in the class can get at the information. That helps a lot in coming up with cleaner code for the class.

Replace Temp with Query often is a vital step before Extract Method. Local variables make it difficult to extract, so replace as many variables as you can with queries.

The straightforward cases of this refactoring are those in which temps are assigned only to once and those in which the expression that generates the assignment is free of side effects. Other cases are trickier but possible. You may need to use Split Temporary Variable or Separate Query from Modifier first to make things easier. If the temp is used to collect a result (such as summing over a loop), you need to copy some logic into the query method.

... double getPreco() {

int precoBase = _quantidade * _precoDoItem; double fatorDeDesconto; if (precoBase > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

... double getPreco() {

int precoBase = _quantidade * _precoDoItem; double fatorDeDesconto; if (precoBase > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

... double getPreco() {

final int precoBase = _quantidade * _precoDoItem; final double fatorDeDesconto; if (precoBase > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

... double getPreco() {

final int precoBase = precoBase(); final double fatorDeDesconto; if (precoBase > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

... double getPreco() {

final int precoBase = precoBase(); final double fatorDeDesconto; if (precoBase > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}…

private int precoBase() { return _quantidade * _precoDoItem;

}

... double getPreco() {

final int precoBase = precoBase(); final double fatorDeDesconto; if (precoBase > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

... double getPreco() {

final int precoBase = precoBase(); final double fatorDeDesconto; if (precoBase() > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

... double getPreco() {

final double fatorDeDesconto; if (precoBase() > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

... double getPreco() {

final double fatorDeDesconto; if (precoBase() > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

private double fatorDeDesconto() { if (precoBase() > 1000) return 0.95; else return 0.98;

}

... double getPreco() {

final double fatorDeDesconto = fatorDeDesconto(); if (precoBase() > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

... double getPreco() {

final double fatorDeDesconto = fatorDeDesconto(); return precoBase() * fatorDeDesconto;

}...

... double getPreco() {

return precoBase() * fatorDeDesconto();}...

... double getPreco() {

return precoBase() * fatorDeDesconto();}private int precoBase() {

return _quantidade * _precoDoItem;}private double fatorDeDesconto() {

if (precoBase() > 1000) return 0.95; else return 0.98;

}...

... double getPreco() {

int precoBase = _quantidade * _precoDoItem; double fatorDeDesconto; if (precoBase > 1000) fatorDeDesconto = 0.95; else fatorDeDesconto = 0.98; return precoBase * fatorDeDesconto;

}...

double getPreco() { int pb = q * p; double fd; if (pb > 1000) fd = 0.95; else fd = 0.98;return pb * fd;

}

double getPreco() { int pb = q * p; double fd; if (pb > 1000) fd = 0.95; else fd = 0.98;return pb * fd;

}

“Quem foi o FDP que escreveu isso aqui?”

Replace Conditional with PolymorphismMotivation

One of the grandest sounding words in object jargon is polymorphism. The essence of polymorphsim is that it allows you to avoid writing an explicit conditional when you have objects whose behavior varies depending on their types.

As a result you find that switch statements that switch on type codes or if-then-else statements that switch on type strings are much less common in an object-oriented program.

Polymorphism gives you many advantages. The biggest gain occurs when this same set of conditions appears in many places in the program. If you want to add a new type, you have to find and update all the conditionals. But with subclasses you just create a new subclass and provide the appropriate methods. Clients of the class don't need to know about the subclasses, which reduces the dependencies in your system and makes it easier to update.

Replace Conditional with Polymorphism

double getSpeed() { switch (_type) {

case EUROPEAN: return getBaseSpeed();

case AFRICAN: return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;

case NORWEGIAN_BLUE: return (_isNailed) ? 0 : getBaseSpeed(_voltage);

} throw new RuntimeException ("Should be unreachable");

}

Replace Conditional with Polymorphism

Replace Conditional with Polymorphism

public class Employee {private EmployeeType _type;

int payAmount() {switch (getType()) {case EmployeeType.ENGINEER:

return _monthlySalary;case EmployeeType.SALESMAN:

return _monthlySalary + _commission;case EmployeeType.MANAGER:

return _monthlySalary + _bonus;default:

throw new RuntimeException("Incorrect Employee");}

}

int getType() {return _type.getTypeCode();

}

}

public class Employee {private EmployeeType _type;

int payAmount() {switch (getType()) {case EmployeeType.ENGINEER:

return _monthlySalary;case EmployeeType.SALESMAN:

return _monthlySalary + _commission;case EmployeeType.MANAGER:

return _monthlySalary + _bonus;default:

throw new RuntimeException("Incorrect Employee");}

}

int getType() {return _type.getTypeCode();

}

}

public class Employee {private EmployeeType _type;

int payAmount() {switch (getType()) {case EmployeeType.ENGINEER:

return _monthlySalary;case EmployeeType.SALESMAN:

return _monthlySalary + _commission;case EmployeeType.MANAGER:

return _monthlySalary + _bonus;default:

throw new RuntimeException("Incorrect Employee");}

}

int getType() {return _type.getTypeCode();

}

}

public class Employee {private EmployeeType _type;

int payAmount() {switch (getType()) {case EmployeeType.ENGINEER:

return _monthlySalary;case EmployeeType.SALESMAN:

return _monthlySalary + _commission;case EmployeeType.MANAGER:

return _monthlySalary + _bonus;default:

throw new RuntimeException("Incorrect Employee");}

}

int getType() {return _type.getTypeCode();

}

}

public int getMonthlySalary() {return _monthlySalary;

}public int getCommission() {

return _monthlySalary + _commission;}public int getBonus() {

return _monthlySalary + _bonus;}

public class Employee {private EmployeeType _type;

int payAmount() {switch (getType()) {case EmployeeType.ENGINEER:

return _monthlySalary;case EmployeeType.SALESMAN:

return _monthlySalary + _commission;case EmployeeType.MANAGER:

return _monthlySalary + _bonus;default:

throw new RuntimeException("Incorrect Employee");}

}

int getType() {return _type.getTypeCode();

}

}

public class EmployeeType ... int payAmount(Employee emp) {

switch (getTypeCode()) {case ENGINEER:

return emp.getMonthlySalary();case SALESMAN:

return emp.getMonthlySalary() + emp.getCommission();case MANAGER:

return emp.getMonthlySalary() + emp.getBonus();default:

throw new RuntimeException("Incorrect Employee");}

}…

public class EmployeeType ... int payAmount(Employee emp) {

switch (getTypeCode()) {case ENGINEER:

return emp.getMonthlySalary();case SALESMAN:

return emp.getMonthlySalary() + emp.getCommission();case MANAGER:

return emp.getMonthlySalary() + emp.getBonus();default:

throw new RuntimeException("Incorrect Employee");}

}…

public class Employee {…

int payAmount() {switch (getType()) {case EmployeeType.ENGINEER:

return _monthlySalary;case EmployeeType.SALESMAN:

return _monthlySalary + _commission;case EmployeeType.MANAGER:

return _monthlySalary + _bonus;default:

throw new RuntimeException("Incorrect Employee");}

}

}

public class Employee {

… int payAmount() {

return _type.payAmount(this);}

}

public class Salesman extends EmployeeType{

int payAmount(Employee emp) {return emp.getMonthlySalary() + emp.getCommission();

}…

public class Manager extends EmployeeType {

int payAmount(Employee emp) {return emp.getMonthlySalary() + emp.getBonus();

}…

Decompose ConditionalMotivation

One of the most common areas of complexity in a program lies in complex conditional logic. As you write code to test conditions and to do various things depending on various conditions, you quickly end up with a pretty long method. Length of a method is in itself a factor that makes it harder to read, but conditions increase the difficulty. The problem usually lies in the fact that the code, both in the condition checks and in the actions, tells you what happens but can easily obscure why it happens.

As with any large block of code, you can make your intention clearer by decomposing it and replacing chunks of code with a method call named after the intention of that block of code. With conditions you can receive further benefit by doing this for the conditional part and each of the alternatives. This way you highlight the condition and make it clearly what you are branching on. You also highlight the reason for the branching.

if (date.before(SUMMER_START) || date.after(SUMMER_END))

charge = quantity * _winterRate + _winterServiceCharge;else

charge = quantity * _summerRate;

Decompose Conditional

if (date.before(SUMMER_START) || date.after(SUMMER_END))

charge = quantity * _winterRate + _winterServiceCharge;else

charge = quantity * _summerRate;

Decompose Conditional

if (date.before(SUMMER_START) || date.after(SUMMER_END))

charge = quantity * _winterRate + _winterServiceCharge;else

charge = quantity * _summerRate;

Decompose Conditional private boolean notSummer(Date date) {

return date.before (SUMMER_START) || date.after(SUMMER_END);

}

private double summerCharge(int quantity) { return quantity * _summerRate;

}

private double winterCharge(int quantity) { return quantity * _winterRate + _winterServiceCharge; }

if (notSummer(date))

charge = winterCharge(quantity);else

charge = summerCharge(quantity);

Decompose Conditional

private boolean notSummer(Date date) { return date.before (SUMMER_START) || date.after(SUMMER_END); }

private double summerCharge(int quantity) { return quantity * _summerRate;

}

private double winterCharge(int quantity) { return quantity * _winterRate + _winterServiceCharge; }

Decompose Conditional

The code "works", you know it works, but you need to refactor in order to put the code into a form you can understand

and work with in order to extend its functionality.

Tá funcionando então deixa!?

you write tests against the code, knowing that if the test fails, you wrote the test

wrong. Don't change the code until you are confident you have enough tests to let you know if one of your refactorings

broke something.

Tá funcionando então deixa!?

Using refactoring, the application design emerge according to

its needs!

Não tenha medo de mudar, isto só perigoso quando

você não tem testes de unidade automatizados!

Testes de unidade automatizados!

… permita-se ser um profissional melhor…

Use teste automatizados!

@rponte

Projete para suas necessidades atuais, prever

o futuro pode não ser a melhor opção.

Se você não precisa, não faça!

Eu sei fazer isso chapa, você não! Bjus ;***

@OficialMaeDinah

Se você não precisa, não faça!

Precisando de ajuda ???

Finish him !!Finish him !!Finish him !!Finish him !!

http://stateofagile.versionone.com/agile-practices-tools/

http://stateofagile.versionone.com/agile-practices-tools/

http://stateofagile.versionone.com/agile-practices-tools/

Unit Testing

http://stateofagile.versionone.com/agile-practices-tools/

Refactoring

http://stateofagile.versionone.com/agile-practices-tools/

TDD

http://stateofagile.versionone.com/agile-practices-tools/

Collective Code Ownership

Simplicidade

Simples é um adjetivo de dois gêneros e dois números, que descreve uma coisa que não é complicada, que não possui enfeites, ou que é clara, evidente ou natural. Também pode designar uma tarefa fácil de concretizar ou resolver (um problema de simples resolução).

Um projeto simples sempre leva menos tempo para terminar do que um problema complexo. Então, sempre faça a coisa mais simples que poderia funcionar em seguida. Se você encontrar algo que é complexo substituí-lo por algo simples. É sempre mais rápida e barata para substituir código complexo agora, antes de perder muito mais tempo com ela.

Um projeto simples sempre leva menos tempo para terminar do que um problema complexo. Então, sempre faça a coisa mais simples que poderia funcionar em seguida. Se você encontrar algo que é complexo substituí-lo por algo simples. É sempre mais rápida e barata para substituir código complexo agora, antes de perder muito mais tempo com ela.

Um projeto simples sempre leva menos tempo para terminar do que um problema complexo. Então, sempre faça a coisa mais simples que poderia funcionar em seguida. Se você encontrar algo que é complexo substituí-lo por algo simples. É sempre mais rápida e barata para substituir código complexo agora, antes de perder muito mais tempo com ela.

Manter as coisas o mais simples possível, desde que seja possível, nunca adicione funcionalidades antes de serem agendada. Cuidado, porém, manter um design simples é um trabalho difícil.

Manter as coisas o mais simples possível, desde que seja possível, nunca adicione funcionalidades antes de serem agendada. Cuidado, porém, manter um design simples é um trabalho difícil.

Simplicidade: a arte de maximizar a quantidade de trabalho que não precisou ser feito.

The Zen of Python

Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex.

Complex is better than complicated.

Livros

LinksThe principles of OOD ~ http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdfSimplicity ~ http://www.extremeprogramming.org/rules/simple.htmlRefactoring ~ http://refactoring.comRefactoring to Patterns ~ http://goo.gl/k8fGolBad Smells ~ http://sourcemaking.com/refactoring/bad-smells-in-codeCod Smells ~ http://c2.com/cgi/wiki?CodeSmellUsing Good Naming To Detect Bad Code ~ http://c2.com/cgi/wiki?UsingGoodNamingToDetectBadCodeFew Short Methods Per Class ~ http://c2.com/cgi/wiki?FewShortMethodsPerClassRefactoring Legacy Code ~ http://c2.com/cgi/wiki?RefactoringLegacyCode

Obrigado!

Emanuel Canuto

@emanuel_canuto

[email protected]

http://emanuelcanuto.wordpress.com/