Work Effectivetly With Legacy Code - Part 1
Upcoming SlideShare
Loading in...5
×
 

Work Effectivetly With Legacy Code - Part 1

on

  • 2,062 views

Palestra Apresentada por André "Bolha" Bastos na Bluesoft em Dezembro de 2009. Veja o Vídeo da Palestra em http://vimeo.com/8848710. Para mais palestras visite http://blog.bluesoft.com.br

Palestra Apresentada por André "Bolha" Bastos na Bluesoft em Dezembro de 2009. Veja o Vídeo da Palestra em http://vimeo.com/8848710. Para mais palestras visite http://blog.bluesoft.com.br

Statistics

Views

Total Views
2,062
Views on SlideShare
1,987
Embed Views
75

Actions

Likes
2
Downloads
22
Comments
0

6 Embeds 75

http://blog.bluesoft.com.br 38
http://bluesoft.wordpress.com 23
http://infoblogs.com.br 7
http://www.slideshare.net 4
http://www.infoblogs.com.br 2
http://www.plugmasters.com.br 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Work Effectivetly With Legacy Code - Part 1 Work Effectivetly With Legacy Code - Part 1 Presentation Transcript

  • Working effectivetly with legacy code Written by Michael C. Feathers
  • Chapter 1 - Changing Software
  • Etapas de alterações em software costumam ser:  adicionar nova funcionalidade  correção de bugs  melhoria da arquitetura  otimização de uso dos recursos do sistema.
  • A maior diferença entre corrigir bugs e novas funcionalidades é que uma altera o comportamento antigo e novas funcionalidade acresce novo comportamento ao já existente. "Behavior is the most important thing about software. It is what users depend on. Users like it when we add new behavior (provided it is what they really wanted), but if we change or remove behavior they depend on (introduce bugs), they stop trusting us."
  • Segue um exemplo: public class CdPlayer { public void addTrackListing(Track track) { ... } public void replaceTrackListing(String name, Track track) { ... } }
  • Improving design Adotar técnicas que facilitem o entendimento e a manutenabilidade, mantendo o comportamento. Melhorar o desing removendo e alterar o comportamento antigo se chama bug. O oposto disso se chama refactoring.
  • Refactoring Alterações representam muito menos riscos que as reformas, e em geral deve ser o caminho a ser seguido ao se trabalha com código legado. Alterações sem grandes riscos devem ser feitas em pequenas parcelas de código e com cobertura de testes para garantir o comportamento. No entanto, muitas vezes a tentação de alterar um código é grande, mas aprender as técnicas certas são de suma importância.
  • Optimization Tem como objetivo usar menos recursos, aumentar a performance, usar ferramentas que facilitam o trabalho(frameworks). Testes também podem garantir que algumas otimizações mais invasivas não mudem o comportamento do sistema.
  • Área de atuação das alterações Adding a Fixing a Bug Refactoring Optimizing feature Structure Alterações Alterações Alterações Funcionality Alterações New funcionality Alterações Resource usage Alterações
  • Adicionar novas funcionalidades parece-se muito mais com refactoring e otimização do que com correção de bugs Em geral tem de se alterar algumas funcionalidades e até alguns comportamentos, mas deve-se manter muito mais comportamentos do que alterar algum.
  • Chapter 2 - Working with Feedback
  • Em muitas equipes de software existem algumas reações  Edit and pray(Edite e reze)  Cover and modify (Cubra e modifique) Em geral uma equipe de testes escreve um teste contra o código e roda-o contra o código(geralmente a noite)  Covering Software Softwares cobertos por testes que dá um feedback rápido sobre as alterações.  Software Vise Softwares com comportamento fixado por testes.
  • Teste unitários •Testes que verificam o comportamentos de componentes de forma isolada devem testar o comportamento a nível "atômico". Dão feedback rápido do resultado pois rodam rapidamente. • Test Harness: Termo utilizado para definir um sistema sob uma espécie de • “armadura de testes”.
  • Large Tests  Não costumam ser muito eficientes pois costumam ser difícieis de entender e de manter  Dificulta a localização exata do erro pois muitas vezes tem comportamentos aninhados.  São executados de forma lenta, possuem diversas funcionalidades ambíguas.  Difícil de terem boa cobertura porque tende a ter seu comportamento alterado de forma indireta muitas vezes não coberto por um outro teste.
  • O passo a passo de alterações de código legado  Identificação de change points(Locais onde a alteração ocorrerá)  Encontrar os test points  Quebra dependências  Escrever os testes  Fazer as alterações e refatorações necessárias.
  • Chapter 3 - Sensing and separation
  • Sensing: Quebrar dependências quando não se pode acessar determinados valores através de testes. Separation: Quebrar dependências quando não se pode rodar um determinado teste isoladamente.
  • Faking Colaborator e Mock Objects Segue o exemplo abaixo: public classe Sale { private Display display; public Sale(Display display) { this.display = display; } public void scan(String barCode) { ... display.showLine(itemName); ... } } A classe display recebe um código de barras e o exibe em um display de preços.
  • Classes como a anterior são difíceis de testar por possuirem uma interface de usuário. Usa-se então um fake colaborator para simular a saída. public class FakeDisplay implements Display { private String lastLine = ""; public void showLine(String line) { this.lastLine = line; } public String getLastLine() { return lastLine; } }
  • Segue o teste: public class SaleTest { @Test public void displayAnItemShouldShowTheNameAndPrice() { FakeDisplay fakeDisplay = new FakeDisplay(); Sale sale = new Sale(fakeDisplay); sale.scan("152"); assertEquals("Lixo $1,59", display.getLastLine()); } }
  • Mock objects São objetos poderosos que simulam um objeto para abstrair o comportamento dos objetos que não estão sendo testados.
  • Chapter 4 - The Seam Model
  • Seam é um local onde se pode mudar o comportamento de um programa mais sem alterar o local original. A maior vantagem de seam model é que muitas vezes não é necessário alterar nada que mude o comportamento no método original. Exemplo: bool CAsyncSslRec::init() { ... if (!m_bFailureSent) { m_bFailureSent = TRUE; PostReceiveError(SOCKETCALLBACK, SSL_FAILURE); } ... }
  • O método PostReceiveError é um método global pode-se criar uma implementação desse método na própria classe podemos ter uma com o método original e uma com um método vazio, sem comportamento. Com o comportamento original: void CAsyncSslRec::PostReceiveError(UINT type, UINT errorCode) { ::PostReceiveError(type, errorCode); } Comportamento para o teste: void TestingAsyncSslRec:: public CAsyncSslRec { virtual void PostReceiveError(UINT tyoe, UINT errorCode) { //nothing here! } }
  • Vantagem no uso do seam A maior vantagem da técnica Seam é que ao trabalhar com código legado onde muitas vezes quebrar dependências pode ser complicado, esse é uma maneira rápida e com pouca alteração ou nenhuma na classe original sendo a maioria delas na classe(ou classes) para teste.
  • Enabling points São locais onde escolhemos qual comportamento usar. Por exemplo, podemos ter um comportamento padrão para o ambiente e um outro comportamento para testes por exemplo.
  • Preprocessing Seams Algumas linguagens como o C e o C++ ao compilar possuem um estágio de build anterior a compilação final. Pode-se criar macros com operadores condicionais para compilar de forma diferentes usando #if de configurações diferentes entre teste e no ambiente e produção. O enable point são as macros que mudaram as definições antes da compilação.
  • Link Seams Locais onde se podem mudar o caminho de um conjunto de classes no ambiente. Classes com mesmo nome e caminho podem ser testadas fazendo um “switch” através do classpath por exemplo.
  • package fitnesse; ... import fit.Parse; import fit.Fixture; ... public class FitFilter { ... tables = new Parse(input) fixture.doTables(tables); .. tables.print(output); } As classes ft.Parse e fit.Fixture podem ser determinadas no classpath no ambiente de testes que ao rodar usará uma classe fake que possivelmente terá os mesmo métodos para facilitar a escrita de testes.
  • Object Seams É quando usamos uma classe como parâmetro para abstrair um comportamento. Segue o exemplo: public class AnyThing { public void doSomething() { Something something = new EspecificSomething("something here"); something.do(); } }
  • Após pequena refatoração será possível sobrescrever o método doSomething no momento do teste. public classe AnyThing extends OtherThing { public void doSomething(Something something) { ... do(something); ... } protected static void do(Something something) { ... } } Dentre as técnicas apresentadas Object seam é a melhor escolha entre as demais.
  • Chapter 5 - Tools
  • refactoring – A change made to the internal structure of software to make it easier to understand and cheaper to modify without changing existing behavior.
  • Ferramentas de refactoring automático são úteis para refactoring rápidos, mas se atente, use apenas as ferramentas que garantam que o comportamento do código não será alterado.
  • Exemplo de refactoring automático mal sucedido Classe original public class A { private int alpha = 0; private int getValue () { alpha++; return 12; } private void doSomething() { int v = getValue(); int total = 0; for (int n =0; n < 10; n++) { total +=v; } } }
  • após refactoring: public class A { private int alpha = 0; private int getValue () { alpha++; return 12; } private void doSomething() { int total = 0; for (int n =0; n < 10; n++) { total +=getValue(); } } } O método getValue() incrementára 10 vezes o valor original
  • Mock Objects São objetos que simulam o comportamento de outro. Extremamente úteis em código legado, pois esse tipo de código costuma possuir muitas dependências. Sempre que possível quebre algumas dependências quando algum método contiver muitas responsabilidades antes de usar mocks. Existem diversas ferramentas de mock como o jMock, Mockito para java, Mockpp e GoogleMock para C++ .
  • Ferramentas de teste unitário Xunit Uma das primeira ferramentas de teste unitário, escrito originalmente em Smaltalk por Kent Beck e depois portado para o Java por Kent Beck e Erich Gamma. Foi escrito em diversas linguagens. Junit Ferramenta de teste poderosa, rápida e intuitiva para Java No jUnit 3 estendia-se a classe TestCase e dos métodos tinha o prefixo test. No jUnit 4 usa-se a anotation @Test - no teste em si entre outras como @Before @BeforeClass @After @AfterClass para usar recursos e desmontar recursos em comum entre os testes.
  • CpunitLite Ferramenta para criar testes no C++. No entanto mais trabalhosa e bem menos intuitiva que o JUnit por exemplo. NUnit Ferramenta de teste para plataformas .NET e em C#. Muito similar ao JUnit onde a classe e métodos possuem uma “marca” para representarem testes.
  • Ferramentas de teste global Framework for Integrated tests (Fit) Desenvolvido por Ward Cunninghan, basicamente se cria e salva documentos htmls escrita em uma linguagem acessível. Os valores a serem testados são exibidos em uma tabela html. Testes falhos exibem células em vermelho com valor esperado e em verde ao passarem. Mesmo leigos em programação podem escrever um testes com as expectativas dos respectivos valores. Um programador ou designer invocará o método verdadeiro e apontará onde o resultado será mostrado. Outras informações http://fit.c2.com
  • Chapter 6 - I don't have much time and I have to change it
  • Algumas refactorings podem parecer perda de tempo, mas se ela for feita com as técnicas corretas a médio prazo os benefícios já começaram a serem vistos. Lembre-se, mesmo trabalhando com prazos apertados, "Remember, code is your house, and you have live in it." Ao contrário do que possa parecer testes podem fazer com que o trabalho fique mais rápido, pois o feedback é instantâneo e as chances de ajustes ou correções de bugs nesses locais costumam cair drasticamente. Citação do autor: "Boy, we aren't going back to that again." (Rapazes, nós não voltaremos para aquilo novamente). Com prazo apertado, surge dilemas, especialmente quando não se há testes. Gastar um pouco mais agora, ou gastar muito no futuro.
  • Sprout method Usa-se quando o método possui trechos de código que tem responsabilidades totalmente distintas. Muito usada quando há laços for por exemplo e para “aproveitar” o laço mesmo laço coloca-se responsabilidades distintas que dependem dos mesmos valores. Verifica-se quais são as variáveis locais necessárias e passe-as como parâmetros para o novo método.
  • Comente a linha com o trecho onde o novo método será chamado e no novo método retorno o valor de mesmo tipo. Use preferencialmente TDD para criar esse novo método Rode os testes Chame o método novo no método no de origem e remova os comentários deixados nesse método antigo.
  • Vantagens  Em geral a maior vantagem é a legibilidade, geralmente é uma reforma rápida, pois os novos métodos devem ter as mesmas funcionalidades do antigo separadamente.  Novos métodos se tornam testáveis
  • Desvantagens • Como se trata basicamente de uma reforma, soa muitas vezes como desperdício de tempo • O método original nem sempre ficará facilmente testável • Obviamente se perde um pouco de tempo, no entanto, facilidade no entendimento poderá justificar o uso.
  • Sprout class São como os sprout methods, mas a nível de classe. Usada quando um há um mesmo comportamento no sistema diversas vezes. Cria-se uma nova classe com os métodos repetitivos e os invoca no lugar. Passe as variáveis locais como construtor da nova classe. Use comentários antes de remover os métodos originais. Utilize TDD preferencialmente para criar novos métodos. Após a substituição, remova os comentários e código duplicado.
  • Vantagens • Código bem intuitivo aos olhos do programador e a novas funcionalidades ficam em um único lugar • Extremante fácil de testar devido a remoção das repetições e similaridades.
  • Desvantagens • Difícil de determinar, pois pode se tornar algo abstrato e a equipe deve entender bem essa abstração para saber que ela está disponível no sistema. • Generalizar demais uma funcionalidade pode dificultar o uso de funcionalidades muito similares.
  • Wrap method Renomeia-se o antigo método antigo. Cria-se um novo com a mesma assinatura do antigo. O novo método deve invocar o antigo. Coloque as novas funcionalidades no método novo Recomenda-se usar a técnica do Extract method que muitas IDEs possuem e apenas renomear para um nome mais apropriado.
  • public class Employee { ... public void pay() { Money amout = new Money(); ... payDispatcher.pay(this, date, amount); } ... }
  • public class Employee { ... public void dispatchPayment() { Money amout = new Money(); ... payDispatcher.pay(this, date, amount); } public void pay() { logPayment(); dispatchPayment(); } ... }
  • Vantagens O método antigo renomeado não crescerá em tamanho. Usa-se o princípio de conservação de parâmetros(Preserve signatures) evitando-se os erros de compilação e alteração do comportamento.
  • Desvantagens Os métodos em código legado costumam ter nomes e assinaturas ruins, nesse caso é sempre mantido as antigas. Geralmente usa-se essa técnica quando não se possui testes, apenas para evitar que se quebre a antiga funcionalidade. O código ainda se mantém pobre e frágil como no antigo.
  • Wrap class Essa técnica também é chamada de 'decorator pattern'. Recomenda quando se quer adicionar novo comportamento a uma mesma funcionalidade e continuar com comportamento antigo Cria-se uma classe abstrata que estenda e recebe no construtor a classe com as funcionalidades principais. Na classe abstrata sobrescreve-se os métodos da superclasse e invoca-os os métodos de mesmo nome na classe passada no construtor.
  • Classes que necessitarem dessas funcionalidades estenderão a classe decorator e receberão a classe principal no construtor. As classe filhas sempre deverão herdar da classe abstrata e passada no construtor novas classes com as novas funcionalidades e invoca-se o construtor da superclasse.
  • Exemplo: Main class: public class ToolController { public ToolController() { } public void on() { //anything here } public void off() { //anything here } }
  • A classe abstrata: public abstract class ToolControllerDecorator extends ToolController { protected Controller controller; public ToolControllerDecorator(ToolController controller) { this.controller = controller; } public void on() {controller.on();} public void off() {controller.off();} }
  • public class LogController extends ToolControllerDecorator { private Logger logger; public LogController(ToolController controller, Logger logger) { super(controller); this.logger = logger; } public void on() { // anything method in the logger class controller.on(); } ... }
  • Sumário Embora muitas vezes seja um pouco difícil separar as responsabilidades de uma classe, ao utilizar essas técnicas se tornará uma tarefa cada vez mais fácil. Recomendado-se também, que além da melhora visual, comece sempre a testar o novo código para não proliferar códigos sem testes.
  • Chapter 7: it takes forever to make a change
  • Quando é preciso alterar código, inicialmente precisamos entender o que ele faz. Especialmente ao lidar com código no qual não se está muito familiarizado há um sentimento de insegurança
  • Understanding( Entendimento) Na maioria das vezes ao olhar um código saberemos se ele é um código legado ou com boa manutenabilidade. Boa manutenabilidade(well-maintained): Mais segurança e confiança para novas features ou alterações. Legacy code: Desespero, muitas vezes piora-se o código pois não o compreendemos bem.
  • Lag time Tempo que se leva após fazer uma alteração saber qual a resposta o sistema teve a essa alteração. Sem testes, é necessário subir o sistema, e testar como um usuário diversas vezes, e sempre que algo dá errado tudo de novo, isso costuma ser frustrante em muitos casos.
  • A mente humana trabalha melhor quando se tem um feedback rápido de pequenas alterações por vez e isso culminará em um entendimento muito melhor com o todo. Imagine que código sem teste é como o robo Spirit que esteve em Marte. O comando parte da Terra e somente 7 minutos depois a máquina responderá ao comando, e só saberá se ela realmente respondeu 14 minutos depois. No aspecto de tempo de resposta, as linguagens interpretadas costumam levar vantagens perante as compiladas.
  • Breaking Dependencies Quebrar dependências em código legado muitas vezes é um desafio. Detectar interception points antes da alteração. Existem alguns técnicas como PassNull, FakeConnections e getInstance. Ao simular um comportamento evite fazê-las no ambiente de produção e sim no ambiente de testes.
  • Interception point Um simples ponto onde se pode detectar os efeitos de uma simples alteração. Métodos as vezes um tanto confusos quando detectados os interceptions points, facilita o entendimento e o uso de testes, mesmo que indiretamente.
  • Por exemplo: Um método que calcule preço e taxas que variam de estado para estado com essa lógica juntas no mesmo método public class Invoice { ... public Money getValue() { Money total = itemsSum(); if (billing.after(someDate)) { if (originator.getState().equals('NY') { total.add(getLocalShipping() ); //--> Interception point here }else { total.add(getDefaultShipping() ); //--> Interception point here } }else{ total.add( getDefaultShipping() ) ; //--> Interception point here } total.add(getTax()); return total; } ... }
  • Pinch points Um pinch point é uma pequena área de um equema, métodos que ao serem testados podem detectar alterações em vários outros. O termo pinch point muitas vezes é difícil de se entender. Seria como um funil e é justamente nesse "afunilamento" que as bases dos testes serão criadas. Pinch point é o extremo do encapsulamento.
  • Build Dependencies Quebrar o sistema em pacotes com menos classes acelera o processo de building dos testes. Usar interfaces ao invés de classes concretas. Para facilitar isso, muitas IDEs possui as opções Extract implementor and Extract interface. Quebrar diversas classes com responsabilidades distintas, diminui tempo de build dos testes. Isso poderá fazer com que os testes em geral rodem mais rápido.
  • Extract Interface É uma das técnicas mais úteis para se trabalhar com código legado. Consiste em criar uma interface e os métodos que se deseja testar coloque os na interface com a mesma assinatura. Substituir as chamadas que usem a classe concreta pela interface. Outra boa razão de usar interfaces e que em geral interfaces sofrem poucas alterações diminuindo o risco de erros de compilação. Muitas IDEs possuem Extract Interface automático, onde e necessário informar apenas o nome da interface e selecionar os métodos que se deseja implementar.
  • Extract Method Outra técnica bem útil e prática para se trabalhar com código legado. Seleciona-se um trecho de código, cria-se um método e cole o conteúdo antigo nesse método. Passe como parâmetro todas as variáveis locais utilizadas no novo método. Comente o código no método antigo, invoque o método novo e então rode os testes. Remova todas os comentários e códigos duplicados se houver.
  • Inversão de dependência Geralmente quando usamos interfaces ao invés de classes concretas utilizamos o princípio da inversão de dependência. Em geral essas interfaces são passadas no construtor ou setter ao invés de serem construídas dentro da própria classe. Muitas vezes usar muitas interfaces pode soar confuso, pelo menos dois arquivos, mas os benefícios para tempo de rebuild e quebra de dependência valem o preço.
  • Sumário O autor recomenda ler o livro Agile Software Development: Principles, Patterns, and Pratices de Robert C. Martin's. Lá há diversas técnicas além das mostradas nesse livro.
  • Outras técnicas abordadas nesse livro
  • • Notes/Sketching • Listing markup • Scratch Refactoring • Delete Unused Code • Adapt parameter • Encapsule Global References • Extract and Override Call
  • • Extract and Factory Method • Introduce Instance Delagator • Link Substitution • Parameterize Contructor • Parameterize Method • Extract Implementor
  • Michael Feathers Senior Trainer, Mentor and Consultant Michael Feathers is a senior member of Object Mentor team. He provides training, coaching and mentoring services in Agile/XP programming practices, test-driven development, refactoring, object-oriented design, Java, C#, and C++. Michael has over 12 years of experience in developing world-class software solutions. Prior to joining Object Mentor, Michael designed a proprietary programming language and compiler as well as a large multi- platform class library and a framework for instrumentation control.
  • Michael is an active member of the Agile/XP community. As a contribution to this community, he developed and maintains the CPPUnit — an open source C++ port of the JUnit testing framework. He is a member of the ACM and IEEE. He regularly speaks at software conferences around the world and has been the acting chair for the Codefest event at the last three OOPSLA conferences. When Michael isn't engaged with a team, he spends his time investigating new ways of altering design over time in codebases. His key passion is helping teams surmount problems in large legacy code bases and connecting with what makes developing software fun and enriching.